From 00f593040591758f6c68cfcccb6fde6daab18dca Mon Sep 17 00:00:00 2001 From: "Brian R. Murphy" <132495859+brmataptos@users.noreply.github.com> Date: Tue, 2 Jul 2024 20:27:11 -0700 Subject: [PATCH] cleanup move_pr.sh to make it suitable for CI for v2 (#13652) Cleanup of the the move_pr.sh script to allow use in CI: - relocate v2 doc test outputs, use v2 to build for v2 integration tests - env var `MVC_DOCGEN_OUTPUT_DIR` is set by V2 tests to relocate doc outputs for V2 - use nextest -profile smoke-test by default to tolerate rare Heisenbugs by replaying 3x - env var `MOVE_PR_NEXTEST_PROFILE` allows `smoke-test` to be overridden. - a few cleanups - paying attention to env var `MOVE_PR_PROFILE` if set - Add a few compiler-v2 verification tests and update to v1.matched that were left over from #13732 --- .../tests/compiler-v2-doc/account.md | 3371 +++++++++ .../tests/compiler-v2-doc/aggregator.md | 474 ++ .../compiler-v2-doc/aggregator_factory.md | 355 + .../tests/compiler-v2-doc/aggregator_v2.md | 943 +++ .../tests/compiler-v2-doc/aptos_account.md | 1252 ++++ .../tests/compiler-v2-doc/aptos_coin.md | 658 ++ .../tests/compiler-v2-doc/aptos_governance.md | 3136 ++++++++ .../tests/compiler-v2-doc/block.md | 1302 ++++ .../tests/compiler-v2-doc/chain_id.md | 192 + .../tests/compiler-v2-doc/chain_status.md | 339 + .../tests/compiler-v2-doc/code.md | 1248 ++++ .../tests/compiler-v2-doc/coin.md | 4809 ++++++++++++ .../tests/compiler-v2-doc/config_buffer.md | 342 + .../tests/compiler-v2-doc/consensus_config.md | 445 ++ .../tests/compiler-v2-doc/create_signer.md | 133 + .../tests/compiler-v2-doc/delegation_pool.md | 5479 ++++++++++++++ .../dispatchable_fungible_asset.md | 537 ++ .../tests/compiler-v2-doc/dkg.md | 524 ++ .../tests/compiler-v2-doc/event.md | 480 ++ .../tests/compiler-v2-doc/execution_config.md | 252 + .../tests/compiler-v2-doc/function_info.md | 362 + .../tests/compiler-v2-doc/fungible_asset.md | 3683 +++++++++ .../tests/compiler-v2-doc/gas_schedule.md | 658 ++ .../tests/compiler-v2-doc/genesis.md | 1085 +++ .../compiler-v2-doc/governance_proposal.md | 178 + .../tests/compiler-v2-doc/guid.md | 522 ++ .../compiler-v2-doc/jwk_consensus_config.md | 377 + .../tests/compiler-v2-doc/jwks.md | 1675 +++++ .../tests/compiler-v2-doc/keyless_account.md | 800 ++ .../tests/compiler-v2-doc/managed_coin.md | 426 ++ .../tests/compiler-v2-doc/multisig_account.md | 4127 +++++++++++ .../tests/compiler-v2-doc/object.md | 3398 +++++++++ .../compiler-v2-doc/object_code_deployment.md | 373 + .../compiler-v2-doc/optional_aggregator.md | 1158 +++ .../tests/compiler-v2-doc/overview.md | 76 + .../compiler-v2-doc/primary_fungible_store.md | 955 +++ .../tests/compiler-v2-doc/randomness.md | 1315 ++++ .../randomness_api_v0_config.md | 211 + .../compiler-v2-doc/randomness_config.md | 463 ++ .../randomness_config_seqnum.md | 170 + .../tests/compiler-v2-doc/reconfiguration.md | 798 ++ .../compiler-v2-doc/reconfiguration_state.md | 573 ++ .../reconfiguration_with_dkg.md | 251 + .../tests/compiler-v2-doc/resource_account.md | 636 ++ .../tests/compiler-v2-doc/stake.md | 6105 +++++++++++++++ .../tests/compiler-v2-doc/staking_config.md | 1595 ++++ .../tests/compiler-v2-doc/staking_contract.md | 3581 +++++++++ .../tests/compiler-v2-doc/staking_proxy.md | 566 ++ .../tests/compiler-v2-doc/state_storage.md | 456 ++ .../tests/compiler-v2-doc/storage_gas.md | 1636 ++++ .../tests/compiler-v2-doc/system_addresses.md | 594 ++ .../tests/compiler-v2-doc/timestamp.md | 334 + .../compiler-v2-doc/transaction_context.md | 1288 ++++ .../tests/compiler-v2-doc/transaction_fee.md | 1245 ++++ .../compiler-v2-doc/transaction_validation.md | 1033 +++ .../tests/compiler-v2-doc/util.md | 149 + .../validator_consensus_info.md | 205 + .../tests/compiler-v2-doc/version.md | 430 ++ .../tests/compiler-v2-doc/vesting.md | 4528 +++++++++++ .../tests/compiler-v2-doc/voting.md | 2596 +++++++ .../aptos-stdlib/tests/compiler-v2-doc/any.md | 252 + .../tests/compiler-v2-doc/big_vector.md | 1120 +++ .../tests/compiler-v2-doc/bls12381.md | 1661 +++++ .../tests/compiler-v2-doc/bls12381_algebra.md | 631 ++ .../tests/compiler-v2-doc/bn254_algebra.md | 665 ++ .../tests/compiler-v2-doc/capability.md | 735 ++ .../tests/compiler-v2-doc/comparator.md | 387 + .../tests/compiler-v2-doc/copyable_any.md | 230 + .../tests/compiler-v2-doc/crypto_algebra.md | 1755 +++++ .../tests/compiler-v2-doc/debug.md | 237 + .../tests/compiler-v2-doc/ed25519.md | 876 +++ .../tests/compiler-v2-doc/fixed_point64.md | 1333 ++++ .../tests/compiler-v2-doc/from_bcs.md | 363 + .../tests/compiler-v2-doc/hash.md | 623 ++ .../tests/compiler-v2-doc/math128.md | 594 ++ .../tests/compiler-v2-doc/math64.md | 549 ++ .../tests/compiler-v2-doc/math_fixed.md | 299 + .../tests/compiler-v2-doc/math_fixed64.md | 294 + .../tests/compiler-v2-doc/multi_ed25519.md | 1324 ++++ .../tests/compiler-v2-doc/overview.md | 50 + .../tests/compiler-v2-doc/pool_u64.md | 1290 ++++ .../tests/compiler-v2-doc/pool_u64_unbound.md | 1333 ++++ .../tests/compiler-v2-doc/ristretto255.md | 3463 +++++++++ .../ristretto255_bulletproofs.md | 321 + .../compiler-v2-doc/ristretto255_elgamal.md | 707 ++ .../compiler-v2-doc/ristretto255_pedersen.md | 573 ++ .../tests/compiler-v2-doc/secp256k1.md | 432 ++ .../tests/compiler-v2-doc/simple_map.md | 1033 +++ .../tests/compiler-v2-doc/smart_table.md | 1786 +++++ .../tests/compiler-v2-doc/smart_vector.md | 1727 +++++ .../tests/compiler-v2-doc/string_utils.md | 763 ++ .../tests/compiler-v2-doc/table.md | 779 ++ .../compiler-v2-doc/table_with_length.md | 684 ++ .../tests/compiler-v2-doc/type_info.md | 399 + .../tests/compiler-v2-doc/aptos_token.md | 1703 +++++ .../tests/compiler-v2-doc/collection.md | 1702 +++++ .../tests/compiler-v2-doc/overview.md | 22 + .../tests/compiler-v2-doc/property_map.md | 1282 ++++ .../tests/compiler-v2-doc/royalty.md | 401 + .../tests/compiler-v2-doc/token.md | 1649 +++++ .../tests/compiler-v2-doc/overview.md | 22 + .../tests/compiler-v2-doc/property_map.md | 1369 ++++ .../tests/compiler-v2-doc/token.md | 6594 +++++++++++++++++ .../tests/compiler-v2-doc/token_coin_swap.md | 633 ++ .../compiler-v2-doc/token_event_store.md | 1787 +++++ .../tests/compiler-v2-doc/token_transfers.md | 953 +++ .../move-stdlib/tests/compiler-v2-doc/acl.md | 320 + .../move-stdlib/tests/compiler-v2-doc/bcs.md | 59 + .../tests/compiler-v2-doc/bit_vector.md | 553 ++ .../tests/compiler-v2-doc/error.md | 500 ++ .../tests/compiler-v2-doc/features.md | 3803 ++++++++++ .../tests/compiler-v2-doc/fixed_point32.md | 884 +++ .../move-stdlib/tests/compiler-v2-doc/hash.md | 65 + .../tests/compiler-v2-doc/option.md | 1330 ++++ .../tests/compiler-v2-doc/overview.md | 29 + .../tests/compiler-v2-doc/signer.md | 94 + .../tests/compiler-v2-doc/string.md | 552 ++ .../tests/compiler-v2-doc/vector.md | 2033 +++++ aptos-move/framework/src/built_package.rs | 4 +- aptos-move/framework/src/docgen.rs | 7 +- .../move/move-compiler-v2/tests/v1.matched | 5 +- .../move/move-compiler-v2/tests/v1.unmatched | 3 - .../cross_module_valid.verification | 0 .../double_annotation.verification | 0 .../single_module_invalid.verification | 0 .../single_module_valid.verification | 0 .../verify/single_module_invalid.exp | 2 + .../verify/single_module_invalid.move | 19 + third_party/move/scripts/move_pr.sh | 53 +- 129 files changed, 134547 insertions(+), 35 deletions(-) create mode 100644 aptos-move/framework/aptos-framework/tests/compiler-v2-doc/account.md create mode 100644 aptos-move/framework/aptos-framework/tests/compiler-v2-doc/aggregator.md create mode 100644 aptos-move/framework/aptos-framework/tests/compiler-v2-doc/aggregator_factory.md create mode 100644 aptos-move/framework/aptos-framework/tests/compiler-v2-doc/aggregator_v2.md create mode 100644 aptos-move/framework/aptos-framework/tests/compiler-v2-doc/aptos_account.md create mode 100644 aptos-move/framework/aptos-framework/tests/compiler-v2-doc/aptos_coin.md create mode 100644 aptos-move/framework/aptos-framework/tests/compiler-v2-doc/aptos_governance.md create mode 100644 aptos-move/framework/aptos-framework/tests/compiler-v2-doc/block.md create mode 100644 aptos-move/framework/aptos-framework/tests/compiler-v2-doc/chain_id.md create mode 100644 aptos-move/framework/aptos-framework/tests/compiler-v2-doc/chain_status.md create mode 100644 aptos-move/framework/aptos-framework/tests/compiler-v2-doc/code.md create mode 100644 aptos-move/framework/aptos-framework/tests/compiler-v2-doc/coin.md create mode 100644 aptos-move/framework/aptos-framework/tests/compiler-v2-doc/config_buffer.md create mode 100644 aptos-move/framework/aptos-framework/tests/compiler-v2-doc/consensus_config.md create mode 100644 aptos-move/framework/aptos-framework/tests/compiler-v2-doc/create_signer.md create mode 100644 aptos-move/framework/aptos-framework/tests/compiler-v2-doc/delegation_pool.md create mode 100644 aptos-move/framework/aptos-framework/tests/compiler-v2-doc/dispatchable_fungible_asset.md create mode 100644 aptos-move/framework/aptos-framework/tests/compiler-v2-doc/dkg.md create mode 100644 aptos-move/framework/aptos-framework/tests/compiler-v2-doc/event.md create mode 100644 aptos-move/framework/aptos-framework/tests/compiler-v2-doc/execution_config.md create mode 100644 aptos-move/framework/aptos-framework/tests/compiler-v2-doc/function_info.md create mode 100644 aptos-move/framework/aptos-framework/tests/compiler-v2-doc/fungible_asset.md create mode 100644 aptos-move/framework/aptos-framework/tests/compiler-v2-doc/gas_schedule.md create mode 100644 aptos-move/framework/aptos-framework/tests/compiler-v2-doc/genesis.md create mode 100644 aptos-move/framework/aptos-framework/tests/compiler-v2-doc/governance_proposal.md create mode 100644 aptos-move/framework/aptos-framework/tests/compiler-v2-doc/guid.md create mode 100644 aptos-move/framework/aptos-framework/tests/compiler-v2-doc/jwk_consensus_config.md create mode 100644 aptos-move/framework/aptos-framework/tests/compiler-v2-doc/jwks.md create mode 100644 aptos-move/framework/aptos-framework/tests/compiler-v2-doc/keyless_account.md create mode 100644 aptos-move/framework/aptos-framework/tests/compiler-v2-doc/managed_coin.md create mode 100644 aptos-move/framework/aptos-framework/tests/compiler-v2-doc/multisig_account.md create mode 100644 aptos-move/framework/aptos-framework/tests/compiler-v2-doc/object.md create mode 100644 aptos-move/framework/aptos-framework/tests/compiler-v2-doc/object_code_deployment.md create mode 100644 aptos-move/framework/aptos-framework/tests/compiler-v2-doc/optional_aggregator.md create mode 100644 aptos-move/framework/aptos-framework/tests/compiler-v2-doc/overview.md create mode 100644 aptos-move/framework/aptos-framework/tests/compiler-v2-doc/primary_fungible_store.md create mode 100644 aptos-move/framework/aptos-framework/tests/compiler-v2-doc/randomness.md create mode 100644 aptos-move/framework/aptos-framework/tests/compiler-v2-doc/randomness_api_v0_config.md create mode 100644 aptos-move/framework/aptos-framework/tests/compiler-v2-doc/randomness_config.md create mode 100644 aptos-move/framework/aptos-framework/tests/compiler-v2-doc/randomness_config_seqnum.md create mode 100644 aptos-move/framework/aptos-framework/tests/compiler-v2-doc/reconfiguration.md create mode 100644 aptos-move/framework/aptos-framework/tests/compiler-v2-doc/reconfiguration_state.md create mode 100644 aptos-move/framework/aptos-framework/tests/compiler-v2-doc/reconfiguration_with_dkg.md create mode 100644 aptos-move/framework/aptos-framework/tests/compiler-v2-doc/resource_account.md create mode 100644 aptos-move/framework/aptos-framework/tests/compiler-v2-doc/stake.md create mode 100644 aptos-move/framework/aptos-framework/tests/compiler-v2-doc/staking_config.md create mode 100644 aptos-move/framework/aptos-framework/tests/compiler-v2-doc/staking_contract.md create mode 100644 aptos-move/framework/aptos-framework/tests/compiler-v2-doc/staking_proxy.md create mode 100644 aptos-move/framework/aptos-framework/tests/compiler-v2-doc/state_storage.md create mode 100644 aptos-move/framework/aptos-framework/tests/compiler-v2-doc/storage_gas.md create mode 100644 aptos-move/framework/aptos-framework/tests/compiler-v2-doc/system_addresses.md create mode 100644 aptos-move/framework/aptos-framework/tests/compiler-v2-doc/timestamp.md create mode 100644 aptos-move/framework/aptos-framework/tests/compiler-v2-doc/transaction_context.md create mode 100644 aptos-move/framework/aptos-framework/tests/compiler-v2-doc/transaction_fee.md create mode 100644 aptos-move/framework/aptos-framework/tests/compiler-v2-doc/transaction_validation.md create mode 100644 aptos-move/framework/aptos-framework/tests/compiler-v2-doc/util.md create mode 100644 aptos-move/framework/aptos-framework/tests/compiler-v2-doc/validator_consensus_info.md create mode 100644 aptos-move/framework/aptos-framework/tests/compiler-v2-doc/version.md create mode 100644 aptos-move/framework/aptos-framework/tests/compiler-v2-doc/vesting.md create mode 100644 aptos-move/framework/aptos-framework/tests/compiler-v2-doc/voting.md create mode 100644 aptos-move/framework/aptos-stdlib/tests/compiler-v2-doc/any.md create mode 100644 aptos-move/framework/aptos-stdlib/tests/compiler-v2-doc/big_vector.md create mode 100644 aptos-move/framework/aptos-stdlib/tests/compiler-v2-doc/bls12381.md create mode 100644 aptos-move/framework/aptos-stdlib/tests/compiler-v2-doc/bls12381_algebra.md create mode 100644 aptos-move/framework/aptos-stdlib/tests/compiler-v2-doc/bn254_algebra.md create mode 100644 aptos-move/framework/aptos-stdlib/tests/compiler-v2-doc/capability.md create mode 100644 aptos-move/framework/aptos-stdlib/tests/compiler-v2-doc/comparator.md create mode 100644 aptos-move/framework/aptos-stdlib/tests/compiler-v2-doc/copyable_any.md create mode 100644 aptos-move/framework/aptos-stdlib/tests/compiler-v2-doc/crypto_algebra.md create mode 100644 aptos-move/framework/aptos-stdlib/tests/compiler-v2-doc/debug.md create mode 100644 aptos-move/framework/aptos-stdlib/tests/compiler-v2-doc/ed25519.md create mode 100644 aptos-move/framework/aptos-stdlib/tests/compiler-v2-doc/fixed_point64.md create mode 100644 aptos-move/framework/aptos-stdlib/tests/compiler-v2-doc/from_bcs.md create mode 100644 aptos-move/framework/aptos-stdlib/tests/compiler-v2-doc/hash.md create mode 100644 aptos-move/framework/aptos-stdlib/tests/compiler-v2-doc/math128.md create mode 100644 aptos-move/framework/aptos-stdlib/tests/compiler-v2-doc/math64.md create mode 100644 aptos-move/framework/aptos-stdlib/tests/compiler-v2-doc/math_fixed.md create mode 100644 aptos-move/framework/aptos-stdlib/tests/compiler-v2-doc/math_fixed64.md create mode 100644 aptos-move/framework/aptos-stdlib/tests/compiler-v2-doc/multi_ed25519.md create mode 100644 aptos-move/framework/aptos-stdlib/tests/compiler-v2-doc/overview.md create mode 100644 aptos-move/framework/aptos-stdlib/tests/compiler-v2-doc/pool_u64.md create mode 100644 aptos-move/framework/aptos-stdlib/tests/compiler-v2-doc/pool_u64_unbound.md create mode 100644 aptos-move/framework/aptos-stdlib/tests/compiler-v2-doc/ristretto255.md create mode 100644 aptos-move/framework/aptos-stdlib/tests/compiler-v2-doc/ristretto255_bulletproofs.md create mode 100644 aptos-move/framework/aptos-stdlib/tests/compiler-v2-doc/ristretto255_elgamal.md create mode 100644 aptos-move/framework/aptos-stdlib/tests/compiler-v2-doc/ristretto255_pedersen.md create mode 100644 aptos-move/framework/aptos-stdlib/tests/compiler-v2-doc/secp256k1.md create mode 100644 aptos-move/framework/aptos-stdlib/tests/compiler-v2-doc/simple_map.md create mode 100644 aptos-move/framework/aptos-stdlib/tests/compiler-v2-doc/smart_table.md create mode 100644 aptos-move/framework/aptos-stdlib/tests/compiler-v2-doc/smart_vector.md create mode 100644 aptos-move/framework/aptos-stdlib/tests/compiler-v2-doc/string_utils.md create mode 100644 aptos-move/framework/aptos-stdlib/tests/compiler-v2-doc/table.md create mode 100644 aptos-move/framework/aptos-stdlib/tests/compiler-v2-doc/table_with_length.md create mode 100644 aptos-move/framework/aptos-stdlib/tests/compiler-v2-doc/type_info.md create mode 100644 aptos-move/framework/aptos-token-objects/tests/compiler-v2-doc/aptos_token.md create mode 100644 aptos-move/framework/aptos-token-objects/tests/compiler-v2-doc/collection.md create mode 100644 aptos-move/framework/aptos-token-objects/tests/compiler-v2-doc/overview.md create mode 100644 aptos-move/framework/aptos-token-objects/tests/compiler-v2-doc/property_map.md create mode 100644 aptos-move/framework/aptos-token-objects/tests/compiler-v2-doc/royalty.md create mode 100644 aptos-move/framework/aptos-token-objects/tests/compiler-v2-doc/token.md create mode 100644 aptos-move/framework/aptos-token/tests/compiler-v2-doc/overview.md create mode 100644 aptos-move/framework/aptos-token/tests/compiler-v2-doc/property_map.md create mode 100644 aptos-move/framework/aptos-token/tests/compiler-v2-doc/token.md create mode 100644 aptos-move/framework/aptos-token/tests/compiler-v2-doc/token_coin_swap.md create mode 100644 aptos-move/framework/aptos-token/tests/compiler-v2-doc/token_event_store.md create mode 100644 aptos-move/framework/aptos-token/tests/compiler-v2-doc/token_transfers.md create mode 100644 aptos-move/framework/move-stdlib/tests/compiler-v2-doc/acl.md create mode 100644 aptos-move/framework/move-stdlib/tests/compiler-v2-doc/bcs.md create mode 100644 aptos-move/framework/move-stdlib/tests/compiler-v2-doc/bit_vector.md create mode 100644 aptos-move/framework/move-stdlib/tests/compiler-v2-doc/error.md create mode 100644 aptos-move/framework/move-stdlib/tests/compiler-v2-doc/features.md create mode 100644 aptos-move/framework/move-stdlib/tests/compiler-v2-doc/fixed_point32.md create mode 100644 aptos-move/framework/move-stdlib/tests/compiler-v2-doc/hash.md create mode 100644 aptos-move/framework/move-stdlib/tests/compiler-v2-doc/option.md create mode 100644 aptos-move/framework/move-stdlib/tests/compiler-v2-doc/overview.md create mode 100644 aptos-move/framework/move-stdlib/tests/compiler-v2-doc/signer.md create mode 100644 aptos-move/framework/move-stdlib/tests/compiler-v2-doc/string.md create mode 100644 aptos-move/framework/move-stdlib/tests/compiler-v2-doc/vector.md delete mode 100644 third_party/move/move-compiler-v2/tests/verification/cross_module_valid.verification delete mode 100644 third_party/move/move-compiler-v2/tests/verification/double_annotation.verification delete mode 100644 third_party/move/move-compiler-v2/tests/verification/single_module_invalid.verification delete mode 100644 third_party/move/move-compiler-v2/tests/verification/single_module_valid.verification create mode 100644 third_party/move/move-compiler-v2/tests/verification/verify/single_module_invalid.exp create mode 100644 third_party/move/move-compiler-v2/tests/verification/verify/single_module_invalid.move diff --git a/aptos-move/framework/aptos-framework/tests/compiler-v2-doc/account.md b/aptos-move/framework/aptos-framework/tests/compiler-v2-doc/account.md new file mode 100644 index 0000000000000..de6eaf2b2afe6 --- /dev/null +++ b/aptos-move/framework/aptos-framework/tests/compiler-v2-doc/account.md @@ -0,0 +1,3371 @@ + + + +# Module `0x1::account` + + + +- [Struct `KeyRotation`](#0x1_account_KeyRotation) +- [Resource `Account`](#0x1_account_Account) +- [Struct `KeyRotationEvent`](#0x1_account_KeyRotationEvent) +- [Struct `CoinRegisterEvent`](#0x1_account_CoinRegisterEvent) +- [Struct `CapabilityOffer`](#0x1_account_CapabilityOffer) +- [Struct `RotationCapability`](#0x1_account_RotationCapability) +- [Struct `SignerCapability`](#0x1_account_SignerCapability) +- [Resource `OriginatingAddress`](#0x1_account_OriginatingAddress) +- [Struct `RotationProofChallenge`](#0x1_account_RotationProofChallenge) +- [Struct `RotationCapabilityOfferProofChallenge`](#0x1_account_RotationCapabilityOfferProofChallenge) +- [Struct `SignerCapabilityOfferProofChallenge`](#0x1_account_SignerCapabilityOfferProofChallenge) +- [Struct `RotationCapabilityOfferProofChallengeV2`](#0x1_account_RotationCapabilityOfferProofChallengeV2) +- [Struct `SignerCapabilityOfferProofChallengeV2`](#0x1_account_SignerCapabilityOfferProofChallengeV2) +- [Constants](#@Constants_0) +- [Function `initialize`](#0x1_account_initialize) +- [Function `create_account_if_does_not_exist`](#0x1_account_create_account_if_does_not_exist) +- [Function `create_account`](#0x1_account_create_account) +- [Function `create_account_unchecked`](#0x1_account_create_account_unchecked) +- [Function `exists_at`](#0x1_account_exists_at) +- [Function `get_guid_next_creation_num`](#0x1_account_get_guid_next_creation_num) +- [Function `get_sequence_number`](#0x1_account_get_sequence_number) +- [Function `increment_sequence_number`](#0x1_account_increment_sequence_number) +- [Function `get_authentication_key`](#0x1_account_get_authentication_key) +- [Function `rotate_authentication_key_internal`](#0x1_account_rotate_authentication_key_internal) +- [Function `rotate_authentication_key_call`](#0x1_account_rotate_authentication_key_call) +- [Function `rotate_authentication_key`](#0x1_account_rotate_authentication_key) +- [Function `rotate_authentication_key_with_rotation_capability`](#0x1_account_rotate_authentication_key_with_rotation_capability) +- [Function `offer_rotation_capability`](#0x1_account_offer_rotation_capability) +- [Function `is_rotation_capability_offered`](#0x1_account_is_rotation_capability_offered) +- [Function `get_rotation_capability_offer_for`](#0x1_account_get_rotation_capability_offer_for) +- [Function `revoke_rotation_capability`](#0x1_account_revoke_rotation_capability) +- [Function `revoke_any_rotation_capability`](#0x1_account_revoke_any_rotation_capability) +- [Function `offer_signer_capability`](#0x1_account_offer_signer_capability) +- [Function `is_signer_capability_offered`](#0x1_account_is_signer_capability_offered) +- [Function `get_signer_capability_offer_for`](#0x1_account_get_signer_capability_offer_for) +- [Function `revoke_signer_capability`](#0x1_account_revoke_signer_capability) +- [Function `revoke_any_signer_capability`](#0x1_account_revoke_any_signer_capability) +- [Function `create_authorized_signer`](#0x1_account_create_authorized_signer) +- [Function `assert_valid_rotation_proof_signature_and_get_auth_key`](#0x1_account_assert_valid_rotation_proof_signature_and_get_auth_key) +- [Function `update_auth_key_and_originating_address_table`](#0x1_account_update_auth_key_and_originating_address_table) +- [Function `create_resource_address`](#0x1_account_create_resource_address) +- [Function `create_resource_account`](#0x1_account_create_resource_account) +- [Function `create_framework_reserved_account`](#0x1_account_create_framework_reserved_account) +- [Function `create_guid`](#0x1_account_create_guid) +- [Function `new_event_handle`](#0x1_account_new_event_handle) +- [Function `register_coin`](#0x1_account_register_coin) +- [Function `create_signer_with_capability`](#0x1_account_create_signer_with_capability) +- [Function `get_signer_capability_address`](#0x1_account_get_signer_capability_address) +- [Function `verify_signed_message`](#0x1_account_verify_signed_message) +- [Specification](#@Specification_1) + - [High-level Requirements](#high-level-req) + - [Module-level Specification](#module-level-spec) + - [Function `initialize`](#@Specification_1_initialize) + - [Function `create_account_if_does_not_exist`](#@Specification_1_create_account_if_does_not_exist) + - [Function `create_account`](#@Specification_1_create_account) + - [Function `create_account_unchecked`](#@Specification_1_create_account_unchecked) + - [Function `exists_at`](#@Specification_1_exists_at) + - [Function `get_guid_next_creation_num`](#@Specification_1_get_guid_next_creation_num) + - [Function `get_sequence_number`](#@Specification_1_get_sequence_number) + - [Function `increment_sequence_number`](#@Specification_1_increment_sequence_number) + - [Function `get_authentication_key`](#@Specification_1_get_authentication_key) + - [Function `rotate_authentication_key_internal`](#@Specification_1_rotate_authentication_key_internal) + - [Function `rotate_authentication_key_call`](#@Specification_1_rotate_authentication_key_call) + - [Function `rotate_authentication_key`](#@Specification_1_rotate_authentication_key) + - [Function `rotate_authentication_key_with_rotation_capability`](#@Specification_1_rotate_authentication_key_with_rotation_capability) + - [Function `offer_rotation_capability`](#@Specification_1_offer_rotation_capability) + - [Function `is_rotation_capability_offered`](#@Specification_1_is_rotation_capability_offered) + - [Function `get_rotation_capability_offer_for`](#@Specification_1_get_rotation_capability_offer_for) + - [Function `revoke_rotation_capability`](#@Specification_1_revoke_rotation_capability) + - [Function `revoke_any_rotation_capability`](#@Specification_1_revoke_any_rotation_capability) + - [Function `offer_signer_capability`](#@Specification_1_offer_signer_capability) + - [Function `is_signer_capability_offered`](#@Specification_1_is_signer_capability_offered) + - [Function `get_signer_capability_offer_for`](#@Specification_1_get_signer_capability_offer_for) + - [Function `revoke_signer_capability`](#@Specification_1_revoke_signer_capability) + - [Function `revoke_any_signer_capability`](#@Specification_1_revoke_any_signer_capability) + - [Function `create_authorized_signer`](#@Specification_1_create_authorized_signer) + - [Function `assert_valid_rotation_proof_signature_and_get_auth_key`](#@Specification_1_assert_valid_rotation_proof_signature_and_get_auth_key) + - [Function `update_auth_key_and_originating_address_table`](#@Specification_1_update_auth_key_and_originating_address_table) + - [Function `create_resource_address`](#@Specification_1_create_resource_address) + - [Function `create_resource_account`](#@Specification_1_create_resource_account) + - [Function `create_framework_reserved_account`](#@Specification_1_create_framework_reserved_account) + - [Function `create_guid`](#@Specification_1_create_guid) + - [Function `new_event_handle`](#@Specification_1_new_event_handle) + - [Function `register_coin`](#@Specification_1_register_coin) + - [Function `create_signer_with_capability`](#@Specification_1_create_signer_with_capability) + - [Function `verify_signed_message`](#@Specification_1_verify_signed_message) + + +
use 0x1::bcs;
+use 0x1::chain_id;
+use 0x1::create_signer;
+use 0x1::ed25519;
+use 0x1::error;
+use 0x1::event;
+use 0x1::features;
+use 0x1::from_bcs;
+use 0x1::guid;
+use 0x1::hash;
+use 0x1::multi_ed25519;
+use 0x1::option;
+use 0x1::signer;
+use 0x1::system_addresses;
+use 0x1::table;
+use 0x1::type_info;
+use 0x1::vector;
+
+
+
+
+
+
+## Struct `KeyRotation`
+
+
+
+#[event]
+struct KeyRotation has drop, store
+
+
+
+
+account: address
+old_authentication_key: vector<u8>
+new_authentication_key: vector<u8>
+struct Account has store, key
+
+
+
+
+authentication_key: vector<u8>
+sequence_number: u64
+guid_creation_num: u64
+coin_register_events: event::EventHandle<account::CoinRegisterEvent>
+key_rotation_events: event::EventHandle<account::KeyRotationEvent>
+rotation_capability_offer: account::CapabilityOffer<account::RotationCapability>
+signer_capability_offer: account::CapabilityOffer<account::SignerCapability>
+struct KeyRotationEvent has drop, store
+
+
+
+
+old_authentication_key: vector<u8>
+new_authentication_key: vector<u8>
+struct CoinRegisterEvent has drop, store
+
+
+
+
+type_info: type_info::TypeInfo
+struct CapabilityOffer<T> has store
+
+
+
+
+for: option::Option<address>
+struct RotationCapability has drop, store
+
+
+
+
+account: address
+struct SignerCapability has drop, store
+
+
+
+
+account: address
+Account
struct at that address.
+The table in this struct makes it possible to do a reverse lookup: it maps an authentication key, to the address of the account which has that authentication key set.
+
+This mapping is needed when recovering wallets for accounts whose authentication key has been rotated.
+
+For example, imagine a freshly-created wallet with address a
and thus also with authentication key a
, derived from a PK pk_a
with corresponding SK sk_a
.
+It is easy to recover such a wallet given just the secret key sk_a
, since the PK can be derived from the SK, the authentication key can then be derived from the PK, and the address equals the authentication key (since there was no key rotation).
+
+However, if such a wallet rotates its authentication key to b
derived from a different PK pk_b
with SK sk_b
, how would account recovery work?
+The recovered address would no longer be 'a'; it would be b
, which is incorrect.
+This struct solves this problem by mapping the new authentication key b
to the original address a
and thus helping the wallet software during recovery find the correct address.
+
+
+struct OriginatingAddress has key
+
+
+
+
+address_map: table::Table<address, address>
+struct RotationProofChallenge has copy, drop
+
+
+
+
+sequence_number: u64
+originator: address
+current_auth_key: address
+new_public_key: vector<u8>
+RotationCapabilityOfferProofChallengeV2
+
+
+struct RotationCapabilityOfferProofChallenge has drop
+
+
+
+
+sequence_number: u64
+recipient_address: address
+SignerCapabilityOfferProofChallengeV2
+
+
+struct SignerCapabilityOfferProofChallenge has drop
+
+
+
+
+sequence_number: u64
+recipient_address: address
+recipient_address
.
+This V2 struct adds the chain_id
and source_address
to the challenge message, which prevents replaying the challenge message.
+
+
+struct RotationCapabilityOfferProofChallengeV2 has drop
+
+
+
+
+chain_id: u8
+sequence_number: u64
+source_address: address
+recipient_address: address
+struct SignerCapabilityOfferProofChallengeV2 has copy, drop
+
+
+
+
+sequence_number: u64
+source_address: address
+recipient_address: address
+const MAX_U64: u128 = 18446744073709551615;
+
+
+
+
+
+
+Scheme identifier used when hashing an account's address together with a seed to derive the address (not the
+authentication key) of a resource account. This is an abuse of the notion of a scheme identifier which, for now,
+serves to domain separate hashes used to derive resource account addresses from hashes used to derive
+authentication keys. Without such separation, an adversary could create (and get a signer for) a resource account
+whose address matches an existing address of a MultiEd25519 wallet.
+
+
+const DERIVE_RESOURCE_ACCOUNT_SCHEME: u8 = 255;
+
+
+
+
+
+
+Account already exists
+
+
+const EACCOUNT_ALREADY_EXISTS: u64 = 1;
+
+
+
+
+
+
+An attempt to create a resource account on an account that has a committed transaction
+
+
+const EACCOUNT_ALREADY_USED: u64 = 16;
+
+
+
+
+
+
+Account does not exist
+
+
+const EACCOUNT_DOES_NOT_EXIST: u64 = 2;
+
+
+
+
+
+
+Cannot create account because address is reserved
+
+
+const ECANNOT_RESERVED_ADDRESS: u64 = 5;
+
+
+
+
+
+
+Scheme identifier for Ed25519 signatures used to derive authentication keys for Ed25519 public keys.
+
+
+const ED25519_SCHEME: u8 = 0;
+
+
+
+
+
+
+
+
+const EEXCEEDED_MAX_GUID_CREATION_NUM: u64 = 20;
+
+
+
+
+
+
+The caller does not have a valid rotation capability offer from the other account
+
+
+const EINVALID_ACCEPT_ROTATION_CAPABILITY: u64 = 10;
+
+
+
+
+
+
+Abort the transaction if the expected originating address is different from the originating address on-chain
+
+
+const EINVALID_ORIGINATING_ADDRESS: u64 = 13;
+
+
+
+
+
+
+Specified proof of knowledge required to prove ownership of a public key is invalid
+
+
+const EINVALID_PROOF_OF_KNOWLEDGE: u64 = 8;
+
+
+
+
+
+
+Specified scheme required to proceed with the smart contract operation - can only be ED25519_SCHEME(0) OR MULTI_ED25519_SCHEME(1)
+
+
+const EINVALID_SCHEME: u64 = 12;
+
+
+
+
+
+
+The provided authentication key has an invalid length
+
+
+const EMALFORMED_AUTHENTICATION_KEY: u64 = 4;
+
+
+
+
+
+
+The caller does not have a digital-signature-based capability to call this function
+
+
+const ENO_CAPABILITY: u64 = 9;
+
+
+
+
+
+
+
+
+const ENO_SIGNER_CAPABILITY_OFFERED: u64 = 19;
+
+
+
+
+
+
+The specified rotation capablity offer does not exist at the specified offerer address
+
+
+const ENO_SUCH_ROTATION_CAPABILITY_OFFER: u64 = 18;
+
+
+
+
+
+
+The signer capability offer doesn't exist at the given address
+
+
+const ENO_SUCH_SIGNER_CAPABILITY: u64 = 14;
+
+
+
+
+
+
+Address to create is not a valid reserved address for Aptos framework
+
+
+const ENO_VALID_FRAMEWORK_RESERVED_ADDRESS: u64 = 11;
+
+
+
+
+
+
+Offerer address doesn't exist
+
+
+const EOFFERER_ADDRESS_DOES_NOT_EXIST: u64 = 17;
+
+
+
+
+
+
+Transaction exceeded its allocated max gas
+
+
+const EOUT_OF_GAS: u64 = 6;
+
+
+
+
+
+
+An attempt to create a resource account on a claimed account
+
+
+const ERESOURCE_ACCCOUNT_EXISTS: u64 = 15;
+
+
+
+
+
+
+Sequence number exceeds the maximum value for a u64
+
+
+const ESEQUENCE_NUMBER_TOO_BIG: u64 = 3;
+
+
+
+
+
+
+Specified current public key is not correct
+
+
+const EWRONG_CURRENT_PUBLIC_KEY: u64 = 7;
+
+
+
+
+
+
+Explicitly separate the GUID space between Object and Account to prevent accidental overlap.
+
+
+const MAX_GUID_CREATION_NUM: u64 = 1125899906842624;
+
+
+
+
+
+
+Scheme identifier for MultiEd25519 signatures used to derive authentication keys for MultiEd25519 public keys.
+
+
+const MULTI_ED25519_SCHEME: u8 = 1;
+
+
+
+
+
+
+
+
+const ZERO_AUTH_KEY: vector<u8> = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0];
+
+
+
+
+
+
+## Function `initialize`
+
+Only called during genesis to initialize system resources for this module.
+
+
+public(friend) fun initialize(aptos_framework: &signer)
+
+
+
+
+public(friend) fun initialize(aptos_framework: &signer) {
+ system_addresses::assert_aptos_framework(aptos_framework);
+ move_to(aptos_framework, OriginatingAddress {
+ address_map: table::new(),
+ });
+}
+
+
+
+
+public fun create_account_if_does_not_exist(account_address: address)
+
+
+
+
+public fun create_account_if_does_not_exist(account_address: address) {
+ if (!exists<Account>(account_address)) {
+ create_account(account_address);
+ }
+}
+
+
+
+
+Account
resource under new_address
. A signer representing new_address
+is returned. This way, the caller of this function can publish additional resources under
+new_address
.
+
+
+public(friend) fun create_account(new_address: address): signer
+
+
+
+
+public(friend) fun create_account(new_address: address): signer {
+ // there cannot be an Account resource under new_addr already.
+ assert!(!exists<Account>(new_address), error::already_exists(EACCOUNT_ALREADY_EXISTS));
+
+ // NOTE: @core_resources gets created via a `create_account` call, so we do not include it below.
+ assert!(
+ new_address != @vm_reserved && new_address != @aptos_framework && new_address != @aptos_token,
+ error::invalid_argument(ECANNOT_RESERVED_ADDRESS)
+ );
+
+ create_account_unchecked(new_address)
+}
+
+
+
+
+fun create_account_unchecked(new_address: address): signer
+
+
+
+
+fun create_account_unchecked(new_address: address): signer {
+ let new_account = create_signer(new_address);
+ let authentication_key = bcs::to_bytes(&new_address);
+ assert!(
+ vector::length(&authentication_key) == 32,
+ error::invalid_argument(EMALFORMED_AUTHENTICATION_KEY)
+ );
+
+ let guid_creation_num = 0;
+
+ let guid_for_coin = guid::create(new_address, &mut guid_creation_num);
+ let coin_register_events = event::new_event_handle<CoinRegisterEvent>(guid_for_coin);
+
+ let guid_for_rotation = guid::create(new_address, &mut guid_creation_num);
+ let key_rotation_events = event::new_event_handle<KeyRotationEvent>(guid_for_rotation);
+
+ move_to(
+ &new_account,
+ Account {
+ authentication_key,
+ sequence_number: 0,
+ guid_creation_num,
+ coin_register_events,
+ key_rotation_events,
+ rotation_capability_offer: CapabilityOffer { for: option::none() },
+ signer_capability_offer: CapabilityOffer { for: option::none() },
+ }
+ );
+
+ new_account
+}
+
+
+
+
+#[view]
+public fun exists_at(addr: address): bool
+
+
+
+
+public fun exists_at(addr: address): bool {
+ exists<Account>(addr)
+}
+
+
+
+
+#[view]
+public fun get_guid_next_creation_num(addr: address): u64
+
+
+
+
+public fun get_guid_next_creation_num(addr: address): u64 acquires Account {
+ borrow_global<Account>(addr).guid_creation_num
+}
+
+
+
+
+#[view]
+public fun get_sequence_number(addr: address): u64
+
+
+
+
+public fun get_sequence_number(addr: address): u64 acquires Account {
+ borrow_global<Account>(addr).sequence_number
+}
+
+
+
+
+public(friend) fun increment_sequence_number(addr: address)
+
+
+
+
+public(friend) fun increment_sequence_number(addr: address) acquires Account {
+ let sequence_number = &mut borrow_global_mut<Account>(addr).sequence_number;
+
+ assert!(
+ (*sequence_number as u128) < MAX_U64,
+ error::out_of_range(ESEQUENCE_NUMBER_TOO_BIG)
+ );
+
+ *sequence_number = *sequence_number + 1;
+}
+
+
+
+
+#[view]
+public fun get_authentication_key(addr: address): vector<u8>
+
+
+
+
+public fun get_authentication_key(addr: address): vector<u8> acquires Account {
+ borrow_global<Account>(addr).authentication_key
+}
+
+
+
+
+new_auth_key
. This is done in
+many contexts:
+1. During normal key rotation via rotate_authentication_key
or rotate_authentication_key_call
+2. During resource account initialization so that no private key can control the resource account
+3. During multisig_v2 account creation
+
+
+public(friend) fun rotate_authentication_key_internal(account: &signer, new_auth_key: vector<u8>)
+
+
+
+
+public(friend) fun rotate_authentication_key_internal(account: &signer, new_auth_key: vector<u8>) acquires Account {
+ let addr = signer::address_of(account);
+ assert!(exists_at(addr), error::not_found(EACCOUNT_DOES_NOT_EXIST));
+ assert!(
+ vector::length(&new_auth_key) == 32,
+ error::invalid_argument(EMALFORMED_AUTHENTICATION_KEY)
+ );
+ let account_resource = borrow_global_mut<Account>(addr);
+ account_resource.authentication_key = new_auth_key;
+}
+
+
+
+
+OriginatingAddress
table because the new_auth_key
is not "verified": it
+does not come with a proof-of-knowledge of the underlying SK. Nonetheless, we need this functionality due to
+the introduction of non-standard key algorithms, such as passkeys, which cannot produce proofs-of-knowledge in
+the format expected in rotate_authentication_key
.
+
+
+entry fun rotate_authentication_key_call(account: &signer, new_auth_key: vector<u8>)
+
+
+
+
+entry fun rotate_authentication_key_call(account: &signer, new_auth_key: vector<u8>) acquires Account {
+ rotate_authentication_key_internal(account, new_auth_key);
+}
+
+
+
+
+cap_rotate_key
refers to the signature by the account owner's current key on a valid RotationProofChallenge
,
+demonstrating that the user intends to and has the capability to rotate the authentication key of this account;
+- the second signature cap_update_table
refers to the signature by the new key (that the account owner wants to rotate to) on a
+valid RotationProofChallenge
, demonstrating that the user owns the new private key, and has the authority to update the
+OriginatingAddress
map with the new address mapping <new_address, originating_address>
.
+To verify these two signatures, we need their corresponding public key and public key scheme: we use from_scheme
and from_public_key_bytes
+to verify cap_rotate_key
, and to_scheme
and to_public_key_bytes
to verify cap_update_table
.
+A scheme of 0 refers to an Ed25519 key and a scheme of 1 refers to Multi-Ed25519 keys.
+originating address
refers to an account's original/first address.
+
+Here is an example attack if we don't ask for the second signature cap_update_table
:
+Alice has rotated her account addr_a
to new_addr_a
. As a result, the following entry is created, to help Alice when recovering her wallet:
+OriginatingAddress[new_addr_a]
-> addr_a
+Alice has had bad day: her laptop blew up and she needs to reset her account on a new one.
+(Fortunately, she still has her secret key new_sk_a
associated with her new address new_addr_a
, so she can do this.)
+
+But Bob likes to mess with Alice.
+Bob creates an account addr_b
and maliciously rotates it to Alice's new address new_addr_a
. Since we are no longer checking a PoK,
+Bob can easily do this.
+
+Now, the table will be updated to make Alice's new address point to Bob's address: OriginatingAddress[new_addr_a]
-> addr_b
.
+When Alice recovers her account, her wallet will display the attacker's address (Bob's) addr_b
as her address.
+Now Alice will give addr_b
to everyone to pay her, but the money will go to Bob.
+
+Because we ask for a valid cap_update_table
, this kind of attack is not possible. Bob would not have the secret key of Alice's address
+to rotate his address to Alice's address in the first place.
+
+
+public entry fun rotate_authentication_key(account: &signer, from_scheme: u8, from_public_key_bytes: vector<u8>, to_scheme: u8, to_public_key_bytes: vector<u8>, cap_rotate_key: vector<u8>, cap_update_table: vector<u8>)
+
+
+
+
+public entry fun rotate_authentication_key(
+ account: &signer,
+ from_scheme: u8,
+ from_public_key_bytes: vector<u8>,
+ to_scheme: u8,
+ to_public_key_bytes: vector<u8>,
+ cap_rotate_key: vector<u8>,
+ cap_update_table: vector<u8>,
+) acquires Account, OriginatingAddress {
+ let addr = signer::address_of(account);
+ assert!(exists_at(addr), error::not_found(EACCOUNT_DOES_NOT_EXIST));
+ let account_resource = borrow_global_mut<Account>(addr);
+
+ // Verify the given `from_public_key_bytes` matches this account's current authentication key.
+ if (from_scheme == ED25519_SCHEME) {
+ let from_pk = ed25519::new_unvalidated_public_key_from_bytes(from_public_key_bytes);
+ let from_auth_key = ed25519::unvalidated_public_key_to_authentication_key(&from_pk);
+ assert!(
+ account_resource.authentication_key == from_auth_key,
+ error::unauthenticated(EWRONG_CURRENT_PUBLIC_KEY)
+ );
+ } else if (from_scheme == MULTI_ED25519_SCHEME) {
+ let from_pk = multi_ed25519::new_unvalidated_public_key_from_bytes(from_public_key_bytes);
+ let from_auth_key = multi_ed25519::unvalidated_public_key_to_authentication_key(&from_pk);
+ assert!(
+ account_resource.authentication_key == from_auth_key,
+ error::unauthenticated(EWRONG_CURRENT_PUBLIC_KEY)
+ );
+ } else {
+ abort error::invalid_argument(EINVALID_SCHEME)
+ };
+
+ // Construct a valid `RotationProofChallenge` that `cap_rotate_key` and `cap_update_table` will validate against.
+ let curr_auth_key_as_address = from_bcs::to_address(account_resource.authentication_key);
+ let challenge = RotationProofChallenge {
+ sequence_number: account_resource.sequence_number,
+ originator: addr,
+ current_auth_key: curr_auth_key_as_address,
+ new_public_key: to_public_key_bytes,
+ };
+
+ // Assert the challenges signed by the current and new keys are valid
+ assert_valid_rotation_proof_signature_and_get_auth_key(
+ from_scheme,
+ from_public_key_bytes,
+ cap_rotate_key,
+ &challenge
+ );
+ let new_auth_key = assert_valid_rotation_proof_signature_and_get_auth_key(
+ to_scheme,
+ to_public_key_bytes,
+ cap_update_table,
+ &challenge
+ );
+
+ // Update the `OriginatingAddress` table.
+ update_auth_key_and_originating_address_table(addr, account_resource, new_auth_key);
+}
+
+
+
+
+public entry fun rotate_authentication_key_with_rotation_capability(delegate_signer: &signer, rotation_cap_offerer_address: address, new_scheme: u8, new_public_key_bytes: vector<u8>, cap_update_table: vector<u8>)
+
+
+
+
+public entry fun rotate_authentication_key_with_rotation_capability(
+ delegate_signer: &signer,
+ rotation_cap_offerer_address: address,
+ new_scheme: u8,
+ new_public_key_bytes: vector<u8>,
+ cap_update_table: vector<u8>
+) acquires Account, OriginatingAddress {
+ assert!(exists_at(rotation_cap_offerer_address), error::not_found(EOFFERER_ADDRESS_DOES_NOT_EXIST));
+
+ // Check that there exists a rotation capability offer at the offerer's account resource for the delegate.
+ let delegate_address = signer::address_of(delegate_signer);
+ let offerer_account_resource = borrow_global<Account>(rotation_cap_offerer_address);
+ assert!(
+ option::contains(&offerer_account_resource.rotation_capability_offer.for, &delegate_address),
+ error::not_found(ENO_SUCH_ROTATION_CAPABILITY_OFFER)
+ );
+
+ let curr_auth_key = from_bcs::to_address(offerer_account_resource.authentication_key);
+ let challenge = RotationProofChallenge {
+ sequence_number: get_sequence_number(delegate_address),
+ originator: rotation_cap_offerer_address,
+ current_auth_key: curr_auth_key,
+ new_public_key: new_public_key_bytes,
+ };
+
+ // Verifies that the `RotationProofChallenge` from above is signed under the new public key that we are rotating to. l
+ let new_auth_key = assert_valid_rotation_proof_signature_and_get_auth_key(
+ new_scheme,
+ new_public_key_bytes,
+ cap_update_table,
+ &challenge
+ );
+
+ // Update the `OriginatingAddress` table, so we can find the originating address using the new address.
+ let offerer_account_resource = borrow_global_mut<Account>(rotation_cap_offerer_address);
+ update_auth_key_and_originating_address_table(
+ rotation_cap_offerer_address,
+ offerer_account_resource,
+ new_auth_key
+ );
+}
+
+
+
+
+account
to the account at address recipient_address
.
+An account can delegate its rotation capability to only one other address at one time. If the account
+has an existing rotation capability offer, calling this function will update the rotation capability offer with
+the new recipient_address
.
+Here, rotation_capability_sig_bytes
signature indicates that this key rotation is authorized by the account owner,
+and prevents the classic "time-of-check time-of-use" attack.
+For example, users usually rely on what the wallet displays to them as the transaction's outcome. Consider a contract that with 50% probability
+(based on the current timestamp in Move), rotates somebody's key. The wallet might be unlucky and get an outcome where nothing is rotated,
+incorrectly telling the user nothing bad will happen. But when the transaction actually gets executed, the attacker gets lucky and
+the execution path triggers the account key rotation.
+We prevent such attacks by asking for this extra signature authorizing the key rotation.
+
+@param rotation_capability_sig_bytes is the signature by the account owner's key on RotationCapabilityOfferProofChallengeV2
.
+@param account_scheme is the scheme of the account (ed25519 or multi_ed25519).
+@param account_public_key_bytes is the public key of the account owner.
+@param recipient_address is the address of the recipient of the rotation capability - note that if there's an existing rotation capability
+offer, calling this function will replace the previous recipient_address
upon successful verification.
+
+
+public entry fun offer_rotation_capability(account: &signer, rotation_capability_sig_bytes: vector<u8>, account_scheme: u8, account_public_key_bytes: vector<u8>, recipient_address: address)
+
+
+
+
+public entry fun offer_rotation_capability(
+ account: &signer,
+ rotation_capability_sig_bytes: vector<u8>,
+ account_scheme: u8,
+ account_public_key_bytes: vector<u8>,
+ recipient_address: address,
+) acquires Account {
+ let addr = signer::address_of(account);
+ assert!(exists_at(recipient_address), error::not_found(EACCOUNT_DOES_NOT_EXIST));
+
+ // proof that this account intends to delegate its rotation capability to another account
+ let account_resource = borrow_global_mut<Account>(addr);
+ let proof_challenge = RotationCapabilityOfferProofChallengeV2 {
+ chain_id: chain_id::get(),
+ sequence_number: account_resource.sequence_number,
+ source_address: addr,
+ recipient_address,
+ };
+
+ // verify the signature on `RotationCapabilityOfferProofChallengeV2` by the account owner
+ if (account_scheme == ED25519_SCHEME) {
+ let pubkey = ed25519::new_unvalidated_public_key_from_bytes(account_public_key_bytes);
+ let expected_auth_key = ed25519::unvalidated_public_key_to_authentication_key(&pubkey);
+ assert!(
+ account_resource.authentication_key == expected_auth_key,
+ error::invalid_argument(EWRONG_CURRENT_PUBLIC_KEY)
+ );
+
+ let rotation_capability_sig = ed25519::new_signature_from_bytes(rotation_capability_sig_bytes);
+ assert!(
+ ed25519::signature_verify_strict_t(&rotation_capability_sig, &pubkey, proof_challenge),
+ error::invalid_argument(EINVALID_PROOF_OF_KNOWLEDGE)
+ );
+ } else if (account_scheme == MULTI_ED25519_SCHEME) {
+ let pubkey = multi_ed25519::new_unvalidated_public_key_from_bytes(account_public_key_bytes);
+ let expected_auth_key = multi_ed25519::unvalidated_public_key_to_authentication_key(&pubkey);
+ assert!(
+ account_resource.authentication_key == expected_auth_key,
+ error::invalid_argument(EWRONG_CURRENT_PUBLIC_KEY)
+ );
+
+ let rotation_capability_sig = multi_ed25519::new_signature_from_bytes(rotation_capability_sig_bytes);
+ assert!(
+ multi_ed25519::signature_verify_strict_t(&rotation_capability_sig, &pubkey, proof_challenge),
+ error::invalid_argument(EINVALID_PROOF_OF_KNOWLEDGE)
+ );
+ } else {
+ abort error::invalid_argument(EINVALID_SCHEME)
+ };
+
+ // update the existing rotation capability offer or put in a new rotation capability offer for the current account
+ option::swap_or_fill(&mut account_resource.rotation_capability_offer.for, recipient_address);
+}
+
+
+
+
+account_addr
has a rotation capability offer.
+
+
+#[view]
+public fun is_rotation_capability_offered(account_addr: address): bool
+
+
+
+
+public fun is_rotation_capability_offered(account_addr: address): bool acquires Account {
+ let account_resource = borrow_global<Account>(account_addr);
+ option::is_some(&account_resource.rotation_capability_offer.for)
+}
+
+
+
+
+account_addr
.
+
+
+#[view]
+public fun get_rotation_capability_offer_for(account_addr: address): address
+
+
+
+
+public fun get_rotation_capability_offer_for(account_addr: address): address acquires Account {
+ let account_resource = borrow_global<Account>(account_addr);
+ assert!(
+ option::is_some(&account_resource.rotation_capability_offer.for),
+ error::not_found(ENO_SIGNER_CAPABILITY_OFFERED),
+ );
+ *option::borrow(&account_resource.rotation_capability_offer.for)
+}
+
+
+
+
+to_be_revoked_recipient_address
from account
+
+
+public entry fun revoke_rotation_capability(account: &signer, to_be_revoked_address: address)
+
+
+
+
+public entry fun revoke_rotation_capability(account: &signer, to_be_revoked_address: address) acquires Account {
+ assert!(exists_at(to_be_revoked_address), error::not_found(EACCOUNT_DOES_NOT_EXIST));
+ let addr = signer::address_of(account);
+ let account_resource = borrow_global_mut<Account>(addr);
+ assert!(
+ option::contains(&account_resource.rotation_capability_offer.for, &to_be_revoked_address),
+ error::not_found(ENO_SUCH_ROTATION_CAPABILITY_OFFER)
+ );
+ revoke_any_rotation_capability(account);
+}
+
+
+
+
+public entry fun revoke_any_rotation_capability(account: &signer)
+
+
+
+
+public entry fun revoke_any_rotation_capability(account: &signer) acquires Account {
+ let account_resource = borrow_global_mut<Account>(signer::address_of(account));
+ option::extract(&mut account_resource.rotation_capability_offer.for);
+}
+
+
+
+
+account
to the account at address recipient_address
.
+An account can delegate its signer capability to only one other address at one time.
+signer_capability_key_bytes
is the SignerCapabilityOfferProofChallengeV2
signed by the account owner's key
+account_scheme
is the scheme of the account (ed25519 or multi_ed25519).
+account_public_key_bytes
is the public key of the account owner.
+recipient_address
is the address of the recipient of the signer capability - note that if there's an existing
+recipient_address
in the account owner's SignerCapabilityOffer
, this will replace the
+previous recipient_address
upon successful verification (the previous recipient will no longer have access
+to the account owner's signer capability).
+
+
+public entry fun offer_signer_capability(account: &signer, signer_capability_sig_bytes: vector<u8>, account_scheme: u8, account_public_key_bytes: vector<u8>, recipient_address: address)
+
+
+
+
+public entry fun offer_signer_capability(
+ account: &signer,
+ signer_capability_sig_bytes: vector<u8>,
+ account_scheme: u8,
+ account_public_key_bytes: vector<u8>,
+ recipient_address: address
+) acquires Account {
+ let source_address = signer::address_of(account);
+ assert!(exists_at(recipient_address), error::not_found(EACCOUNT_DOES_NOT_EXIST));
+
+ // Proof that this account intends to delegate its signer capability to another account.
+ let proof_challenge = SignerCapabilityOfferProofChallengeV2 {
+ sequence_number: get_sequence_number(source_address),
+ source_address,
+ recipient_address,
+ };
+ verify_signed_message(
+ source_address, account_scheme, account_public_key_bytes, signer_capability_sig_bytes, proof_challenge);
+
+ // Update the existing signer capability offer or put in a new signer capability offer for the recipient.
+ let account_resource = borrow_global_mut<Account>(source_address);
+ option::swap_or_fill(&mut account_resource.signer_capability_offer.for, recipient_address);
+}
+
+
+
+
+account_addr
has a signer capability offer.
+
+
+#[view]
+public fun is_signer_capability_offered(account_addr: address): bool
+
+
+
+
+public fun is_signer_capability_offered(account_addr: address): bool acquires Account {
+ let account_resource = borrow_global<Account>(account_addr);
+ option::is_some(&account_resource.signer_capability_offer.for)
+}
+
+
+
+
+account_addr
.
+
+
+#[view]
+public fun get_signer_capability_offer_for(account_addr: address): address
+
+
+
+
+public fun get_signer_capability_offer_for(account_addr: address): address acquires Account {
+ let account_resource = borrow_global<Account>(account_addr);
+ assert!(
+ option::is_some(&account_resource.signer_capability_offer.for),
+ error::not_found(ENO_SIGNER_CAPABILITY_OFFERED),
+ );
+ *option::borrow(&account_resource.signer_capability_offer.for)
+}
+
+
+
+
+to_be_revoked_address
(i.e., the address that
+has a signer capability offer from account
but will be revoked in this function).
+
+
+public entry fun revoke_signer_capability(account: &signer, to_be_revoked_address: address)
+
+
+
+
+public entry fun revoke_signer_capability(account: &signer, to_be_revoked_address: address) acquires Account {
+ assert!(exists_at(to_be_revoked_address), error::not_found(EACCOUNT_DOES_NOT_EXIST));
+ let addr = signer::address_of(account);
+ let account_resource = borrow_global_mut<Account>(addr);
+ assert!(
+ option::contains(&account_resource.signer_capability_offer.for, &to_be_revoked_address),
+ error::not_found(ENO_SUCH_SIGNER_CAPABILITY)
+ );
+ revoke_any_signer_capability(account);
+}
+
+
+
+
+public entry fun revoke_any_signer_capability(account: &signer)
+
+
+
+
+public entry fun revoke_any_signer_capability(account: &signer) acquires Account {
+ let account_resource = borrow_global_mut<Account>(signer::address_of(account));
+ option::extract(&mut account_resource.signer_capability_offer.for);
+}
+
+
+
+
+account
+at the offerer's address.
+
+
+public fun create_authorized_signer(account: &signer, offerer_address: address): signer
+
+
+
+
+public fun create_authorized_signer(account: &signer, offerer_address: address): signer acquires Account {
+ assert!(exists_at(offerer_address), error::not_found(EOFFERER_ADDRESS_DOES_NOT_EXIST));
+
+ // Check if there's an existing signer capability offer from the offerer.
+ let account_resource = borrow_global<Account>(offerer_address);
+ let addr = signer::address_of(account);
+ assert!(
+ option::contains(&account_resource.signer_capability_offer.for, &addr),
+ error::not_found(ENO_SUCH_SIGNER_CAPABILITY)
+ );
+
+ create_signer(offerer_address)
+}
+
+
+
+
+fun assert_valid_rotation_proof_signature_and_get_auth_key(scheme: u8, public_key_bytes: vector<u8>, signature: vector<u8>, challenge: &account::RotationProofChallenge): vector<u8>
+
+
+
+
+fun assert_valid_rotation_proof_signature_and_get_auth_key(
+ scheme: u8,
+ public_key_bytes: vector<u8>,
+ signature: vector<u8>,
+ challenge: &RotationProofChallenge
+): vector<u8> {
+ if (scheme == ED25519_SCHEME) {
+ let pk = ed25519::new_unvalidated_public_key_from_bytes(public_key_bytes);
+ let sig = ed25519::new_signature_from_bytes(signature);
+ assert!(
+ ed25519::signature_verify_strict_t(&sig, &pk, *challenge),
+ std::error::invalid_argument(EINVALID_PROOF_OF_KNOWLEDGE)
+ );
+ ed25519::unvalidated_public_key_to_authentication_key(&pk)
+ } else if (scheme == MULTI_ED25519_SCHEME) {
+ let pk = multi_ed25519::new_unvalidated_public_key_from_bytes(public_key_bytes);
+ let sig = multi_ed25519::new_signature_from_bytes(signature);
+ assert!(
+ multi_ed25519::signature_verify_strict_t(&sig, &pk, *challenge),
+ std::error::invalid_argument(EINVALID_PROOF_OF_KNOWLEDGE)
+ );
+ multi_ed25519::unvalidated_public_key_to_authentication_key(&pk)
+ } else {
+ abort error::invalid_argument(EINVALID_SCHEME)
+ }
+}
+
+
+
+
+OriginatingAddress
table, so that we can find the originating address using the latest address
+in the event of key recovery.
+
+
+fun update_auth_key_and_originating_address_table(originating_addr: address, account_resource: &mut account::Account, new_auth_key_vector: vector<u8>)
+
+
+
+
+fun update_auth_key_and_originating_address_table(
+ originating_addr: address,
+ account_resource: &mut Account,
+ new_auth_key_vector: vector<u8>,
+) acquires OriginatingAddress {
+ let address_map = &mut borrow_global_mut<OriginatingAddress>(@aptos_framework).address_map;
+ let curr_auth_key = from_bcs::to_address(account_resource.authentication_key);
+
+ // Checks `OriginatingAddress[curr_auth_key]` is either unmapped, or mapped to `originating_address`.
+ // If it's mapped to the originating address, removes that mapping.
+ // Otherwise, abort if it's mapped to a different address.
+ if (table::contains(address_map, curr_auth_key)) {
+ // If account_a with address_a is rotating its keypair from keypair_a to keypair_b, we expect
+ // the address of the account to stay the same, while its keypair updates to keypair_b.
+ // Here, by asserting that we're calling from the account with the originating address, we enforce
+ // the standard of keeping the same address and updating the keypair at the contract level.
+ // Without this assertion, the dapps could also update the account's address to address_b (the address that
+ // is programmatically related to keypaier_b) and update the keypair to keypair_b. This causes problems
+ // for interoperability because different dapps can implement this in different ways.
+ // If the account with address b calls this function with two valid signatures, it will abort at this step,
+ // because address b is not the account's originating address.
+ assert!(
+ originating_addr == table::remove(address_map, curr_auth_key),
+ error::not_found(EINVALID_ORIGINATING_ADDRESS)
+ );
+ };
+
+ // Set `OriginatingAddress[new_auth_key] = originating_address`.
+ let new_auth_key = from_bcs::to_address(new_auth_key_vector);
+ table::add(address_map, new_auth_key, originating_addr);
+
+ if (std::features::module_event_migration_enabled()) {
+ event::emit(KeyRotation {
+ account: originating_addr,
+ old_authentication_key: account_resource.authentication_key,
+ new_authentication_key: new_auth_key_vector,
+ });
+ };
+ event::emit_event<KeyRotationEvent>(
+ &mut account_resource.key_rotation_events,
+ KeyRotationEvent {
+ old_authentication_key: account_resource.authentication_key,
+ new_authentication_key: new_auth_key_vector,
+ }
+ );
+
+ // Update the account resource's authentication key.
+ account_resource.authentication_key = new_auth_key_vector;
+}
+
+
+
+
+public fun create_resource_address(source: &address, seed: vector<u8>): address
+
+
+
+
+public fun create_resource_address(source: &address, seed: vector<u8>): address {
+ let bytes = bcs::to_bytes(source);
+ vector::append(&mut bytes, seed);
+ vector::push_back(&mut bytes, DERIVE_RESOURCE_ACCOUNT_SCHEME);
+ from_bcs::to_address(hash::sha3_256(bytes))
+}
+
+
+
+
+Account::signer_capability_offer::for
to the address of the resource account. While an entity may call
+create_account
to attempt to claim an account ahead of the creation of a resource account, if found Aptos will
+transition ownership of the account over to the resource account. This is done by validating that the account has
+yet to execute any transactions and that the Account::signer_capability_offer::for
is none. The probability of a
+collision where someone has legitimately produced a private key that maps to a resource account address is less
+than (1/2)^(256)
.
+
+
+public fun create_resource_account(source: &signer, seed: vector<u8>): (signer, account::SignerCapability)
+
+
+
+
+public fun create_resource_account(source: &signer, seed: vector<u8>): (signer, SignerCapability) acquires Account {
+ let resource_addr = create_resource_address(&signer::address_of(source), seed);
+ let resource = if (exists_at(resource_addr)) {
+ let account = borrow_global<Account>(resource_addr);
+ assert!(
+ option::is_none(&account.signer_capability_offer.for),
+ error::already_exists(ERESOURCE_ACCCOUNT_EXISTS),
+ );
+ assert!(
+ account.sequence_number == 0,
+ error::invalid_state(EACCOUNT_ALREADY_USED),
+ );
+ create_signer(resource_addr)
+ } else {
+ create_account_unchecked(resource_addr)
+ };
+
+ // By default, only the SignerCapability should have control over the resource account and not the auth key.
+ // If the source account wants direct control via auth key, they would need to explicitly rotate the auth key
+ // of the resource account using the SignerCapability.
+ rotate_authentication_key_internal(&resource, ZERO_AUTH_KEY);
+
+ let account = borrow_global_mut<Account>(resource_addr);
+ account.signer_capability_offer.for = option::some(resource_addr);
+ let signer_cap = SignerCapability { account: resource_addr };
+ (resource, signer_cap)
+}
+
+
+
+
+public(friend) fun create_framework_reserved_account(addr: address): (signer, account::SignerCapability)
+
+
+
+
+public(friend) fun create_framework_reserved_account(addr: address): (signer, SignerCapability) {
+ assert!(
+ addr == @0x1 ||
+ addr == @0x2 ||
+ addr == @0x3 ||
+ addr == @0x4 ||
+ addr == @0x5 ||
+ addr == @0x6 ||
+ addr == @0x7 ||
+ addr == @0x8 ||
+ addr == @0x9 ||
+ addr == @0xa,
+ error::permission_denied(ENO_VALID_FRAMEWORK_RESERVED_ADDRESS),
+ );
+ let signer = create_account_unchecked(addr);
+ let signer_cap = SignerCapability { account: addr };
+ (signer, signer_cap)
+}
+
+
+
+
+public fun create_guid(account_signer: &signer): guid::GUID
+
+
+
+
+public fun create_guid(account_signer: &signer): guid::GUID acquires Account {
+ let addr = signer::address_of(account_signer);
+ let account = borrow_global_mut<Account>(addr);
+ let guid = guid::create(addr, &mut account.guid_creation_num);
+ assert!(
+ account.guid_creation_num < MAX_GUID_CREATION_NUM,
+ error::out_of_range(EEXCEEDED_MAX_GUID_CREATION_NUM),
+ );
+ guid
+}
+
+
+
+
+public fun new_event_handle<T: drop, store>(account: &signer): event::EventHandle<T>
+
+
+
+
+public fun new_event_handle<T: drop + store>(account: &signer): EventHandle<T> acquires Account {
+ event::new_event_handle(create_guid(account))
+}
+
+
+
+
+public(friend) fun register_coin<CoinType>(account_addr: address)
+
+
+
+
+public(friend) fun register_coin<CoinType>(account_addr: address) acquires Account {
+ let account = borrow_global_mut<Account>(account_addr);
+ event::emit_event<CoinRegisterEvent>(
+ &mut account.coin_register_events,
+ CoinRegisterEvent {
+ type_info: type_info::type_of<CoinType>(),
+ },
+ );
+}
+
+
+
+
+public fun create_signer_with_capability(capability: &account::SignerCapability): signer
+
+
+
+
+public fun create_signer_with_capability(capability: &SignerCapability): signer {
+ let addr = &capability.account;
+ create_signer(*addr)
+}
+
+
+
+
+public fun get_signer_capability_address(capability: &account::SignerCapability): address
+
+
+
+
+public fun get_signer_capability_address(capability: &SignerCapability): address {
+ capability.account
+}
+
+
+
+
+public fun verify_signed_message<T: drop>(account: address, account_scheme: u8, account_public_key: vector<u8>, signed_message_bytes: vector<u8>, message: T)
+
+
+
+
+public fun verify_signed_message<T: drop>(
+ account: address,
+ account_scheme: u8,
+ account_public_key: vector<u8>,
+ signed_message_bytes: vector<u8>,
+ message: T,
+) acquires Account {
+ let account_resource = borrow_global_mut<Account>(account);
+ // Verify that the `SignerCapabilityOfferProofChallengeV2` has the right information and is signed by the account owner's key
+ if (account_scheme == ED25519_SCHEME) {
+ let pubkey = ed25519::new_unvalidated_public_key_from_bytes(account_public_key);
+ let expected_auth_key = ed25519::unvalidated_public_key_to_authentication_key(&pubkey);
+ assert!(
+ account_resource.authentication_key == expected_auth_key,
+ error::invalid_argument(EWRONG_CURRENT_PUBLIC_KEY),
+ );
+
+ let signer_capability_sig = ed25519::new_signature_from_bytes(signed_message_bytes);
+ assert!(
+ ed25519::signature_verify_strict_t(&signer_capability_sig, &pubkey, message),
+ error::invalid_argument(EINVALID_PROOF_OF_KNOWLEDGE),
+ );
+ } else if (account_scheme == MULTI_ED25519_SCHEME) {
+ let pubkey = multi_ed25519::new_unvalidated_public_key_from_bytes(account_public_key);
+ let expected_auth_key = multi_ed25519::unvalidated_public_key_to_authentication_key(&pubkey);
+ assert!(
+ account_resource.authentication_key == expected_auth_key,
+ error::invalid_argument(EWRONG_CURRENT_PUBLIC_KEY),
+ );
+
+ let signer_capability_sig = multi_ed25519::new_signature_from_bytes(signed_message_bytes);
+ assert!(
+ multi_ed25519::signature_verify_strict_t(&signer_capability_sig, &pubkey, message),
+ error::invalid_argument(EINVALID_PROOF_OF_KNOWLEDGE),
+ );
+ } else {
+ abort error::invalid_argument(EINVALID_SCHEME)
+ };
+}
+
+
+
+
+No. | Requirement | Criticality | Implementation | Enforcement | +
---|---|---|---|---|
1 | +The initialization of the account module should result in the proper system initialization with valid and consistent resources. | +High | +Initialization of the account module creates a valid address_map table and moves the resources to the OriginatingAddress under the aptos_framework account. | +Audited that the address_map table is created and populated correctly with the expected initial values. | +
2 | +After successfully creating an account, the account resources should initialize with the default data, ensuring the proper initialization of the account state. | +High | +Creating an account via the create_account function validates the state and moves a new account resource under new_address. | +Formally verified via create_account. | +
3 | +Checking the existence of an account under a given address never results in an abort. | +Low | +The exists_at function returns a boolean value indicating the existence of an account under the given address. | +Formally verified by the aborts_if condition. | +
4 | +The account module maintains bounded sequence numbers for all accounts, guaranteeing they remain within the specified limit. | +Medium | +The sequence number of an account may only increase up to MAX_U64 in a succeeding manner. | +Formally verified via increment_sequence_number that it remains within the defined boundary of MAX_U64. | +
5 | +Only the ed25519 and multied25519 signature schemes are permissible. | +Low | +Exclusively perform key rotation using either the ed25519 or multied25519 signature schemes. Currently restricts the offering of rotation/signing capabilities to the ed25519 or multied25519 schemes. | +Formally Verified: rotate_authentication_key, offer_rotation_capability, and offer_signer_capability. Verified that it aborts if the account_scheme is not ED25519_SCHEME and not MULTI_ED25519_SCHEME. Audited that the scheme enums correspond correctly to signature logic. | +
6 | +Exclusively permit the rotation of the authentication key of an account for the account owner or any user who possesses rotation capabilities associated with that account. | +Critical | +In the rotate_authentication_key function, the authentication key derived from the from_public_key_bytes should match the signer's current authentication key. Only the delegate_signer granted the rotation capabilities may invoke the rotate_authentication_key_with_rotation_capability function. | +Formally Verified via rotate_authentication_key and rotate_authentication_key_with_rotation_capability. | +
7 | +Only the owner of an account may offer or revoke the following capabilities: (1) offer_rotation_capability, (2) offer_signer_capability, (3) revoke_rotation_capability, and (4) revoke_signer_capability. | +Critical | +An account resource may only be modified by the owner of the account utilizing: rotation_capability_offer, signer_capability_offer. | +Formally verified via offer_rotation_capability, offer_signer_capability, and revoke_rotation_capability. and revoke_signer_capability. | +
8 | +The capability to create a signer for the account is exclusively reserved for either the account owner or the account that has been granted the signing capabilities. | +Critical | +Signer creation for the account may only be successfully executed by explicitly granting the signing capabilities with the create_authorized_signer function. | +Formally verified via create_authorized_signer. | +
9 | +Rotating the authentication key requires two valid signatures. With the private key of the current authentication key. With the private key of the new authentication key. | +Critical | +The rotate_authentication_key verifies two signatures (current and new) before rotating to the new key. The first signature ensures the user has the intended capability, and the second signature ensures that the user owns the new key. | +Formally verified via rotate_authentication_key and rotate_authentication_key_with_rotation_capability. | +
10 | +The rotation of the authentication key updates the account's authentication key with the newly supplied one. | +High | +The auth_key may only update to the provided new_auth_key after verifying the signature. | +Formally Verified in rotate_authentication_key_internal that the authentication key of an account is modified to the provided authentication key if the signature verification was successful. | +
11 | +The creation number is monotonically increasing. | +Low | +The guid_creation_num in the Account structure is monotonically increasing. | +Formally Verified via guid_creation_num. | +
12 | +The Account resource is persistent. | +Low | +The Account structure assigned to the address should be persistent. | +Audited that the Account structure is persistent. | +
pragma verify = true;
+pragma aborts_if_is_strict;
+
+
+
+
+
+
+### Function `initialize`
+
+
+public(friend) fun initialize(aptos_framework: &signer)
+
+
+
+Only the address @aptos_framework
can call.
+OriginatingAddress does not exist under @aptos_framework
before the call.
+
+
+let aptos_addr = signer::address_of(aptos_framework);
+aborts_if !system_addresses::is_aptos_framework_address(aptos_addr);
+aborts_if exists<OriginatingAddress>(aptos_addr);
+ensures exists<OriginatingAddress>(aptos_addr);
+
+
+
+
+
+
+### Function `create_account_if_does_not_exist`
+
+
+public fun create_account_if_does_not_exist(account_address: address)
+
+
+
+Ensure that the account exists at the end of the call.
+
+
+let authentication_key = bcs::to_bytes(account_address);
+aborts_if !exists<Account>(account_address) && (
+ account_address == @vm_reserved
+ || account_address == @aptos_framework
+ || account_address == @aptos_token
+ || !(len(authentication_key) == 32)
+);
+ensures exists<Account>(account_address);
+
+
+
+
+
+
+### Function `create_account`
+
+
+public(friend) fun create_account(new_address: address): signer
+
+
+
+Check if the bytes of the new address is 32.
+The Account does not exist under the new address before creating the account.
+Limit the new account address is not @vm_reserved / @aptos_framework / @aptos_toke.
+
+
+include CreateAccountAbortsIf {addr: new_address};
+aborts_if new_address == @vm_reserved || new_address == @aptos_framework || new_address == @aptos_token;
+ensures signer::address_of(result) == new_address;
+// This enforces high-level requirement 2:
+ensures exists<Account>(new_address);
+
+
+
+
+
+
+### Function `create_account_unchecked`
+
+
+fun create_account_unchecked(new_address: address): signer
+
+
+
+Check if the bytes of the new address is 32.
+The Account does not exist under the new address before creating the account.
+
+
+include CreateAccountAbortsIf {addr: new_address};
+ensures signer::address_of(result) == new_address;
+ensures exists<Account>(new_address);
+
+
+
+
+
+
+### Function `exists_at`
+
+
+#[view]
+public fun exists_at(addr: address): bool
+
+
+
+
+
+// This enforces high-level requirement 3:
+aborts_if false;
+
+
+
+
+
+
+
+
+schema CreateAccountAbortsIf {
+ addr: address;
+ let authentication_key = bcs::to_bytes(addr);
+ aborts_if len(authentication_key) != 32;
+ aborts_if exists<Account>(addr);
+ ensures len(authentication_key) == 32;
+}
+
+
+
+
+
+
+### Function `get_guid_next_creation_num`
+
+
+#[view]
+public fun get_guid_next_creation_num(addr: address): u64
+
+
+
+
+
+aborts_if !exists<Account>(addr);
+ensures result == global<Account>(addr).guid_creation_num;
+
+
+
+
+
+
+### Function `get_sequence_number`
+
+
+#[view]
+public fun get_sequence_number(addr: address): u64
+
+
+
+
+
+aborts_if !exists<Account>(addr);
+ensures result == global<Account>(addr).sequence_number;
+
+
+
+
+
+
+### Function `increment_sequence_number`
+
+
+public(friend) fun increment_sequence_number(addr: address)
+
+
+
+The Account existed under the address.
+The sequence_number of the Account is up to MAX_U64.
+
+
+let sequence_number = global<Account>(addr).sequence_number;
+aborts_if !exists<Account>(addr);
+// This enforces high-level requirement 4:
+aborts_if sequence_number == MAX_U64;
+modifies global<Account>(addr);
+let post post_sequence_number = global<Account>(addr).sequence_number;
+ensures post_sequence_number == sequence_number + 1;
+
+
+
+
+
+
+### Function `get_authentication_key`
+
+
+#[view]
+public fun get_authentication_key(addr: address): vector<u8>
+
+
+
+
+
+aborts_if !exists<Account>(addr);
+ensures result == global<Account>(addr).authentication_key;
+
+
+
+
+
+
+### Function `rotate_authentication_key_internal`
+
+
+public(friend) fun rotate_authentication_key_internal(account: &signer, new_auth_key: vector<u8>)
+
+
+
+The Account existed under the signer before the call.
+The length of new_auth_key is 32.
+
+
+let addr = signer::address_of(account);
+// This enforces high-level requirement 10:
+let post account_resource = global<Account>(addr);
+aborts_if !exists<Account>(addr);
+aborts_if vector::length(new_auth_key) != 32;
+modifies global<Account>(addr);
+ensures account_resource.authentication_key == new_auth_key;
+
+
+
+
+
+
+### Function `rotate_authentication_key_call`
+
+
+entry fun rotate_authentication_key_call(account: &signer, new_auth_key: vector<u8>)
+
+
+
+
+
+let addr = signer::address_of(account);
+// This enforces high-level requirement 10:
+let post account_resource = global<Account>(addr);
+aborts_if !exists<Account>(addr);
+aborts_if vector::length(new_auth_key) != 32;
+modifies global<Account>(addr);
+ensures account_resource.authentication_key == new_auth_key;
+
+
+
+
+
+
+
+
+fun spec_assert_valid_rotation_proof_signature_and_get_auth_key(scheme: u8, public_key_bytes: vector<u8>, signature: vector<u8>, challenge: RotationProofChallenge): vector<u8>;
+
+
+
+
+
+
+### Function `rotate_authentication_key`
+
+
+public entry fun rotate_authentication_key(account: &signer, from_scheme: u8, from_public_key_bytes: vector<u8>, to_scheme: u8, to_public_key_bytes: vector<u8>, cap_rotate_key: vector<u8>, cap_update_table: vector<u8>)
+
+
+
+The Account existed under the signer
+The authentication scheme is ED25519_SCHEME and MULTI_ED25519_SCHEME
+
+
+let addr = signer::address_of(account);
+let account_resource = global<Account>(addr);
+aborts_if !exists<Account>(addr);
+// This enforces high-level requirement 6:
+include from_scheme == ED25519_SCHEME ==> ed25519::NewUnvalidatedPublicKeyFromBytesAbortsIf { bytes: from_public_key_bytes };
+aborts_if from_scheme == ED25519_SCHEME && ({
+ let expected_auth_key = ed25519::spec_public_key_bytes_to_authentication_key(from_public_key_bytes);
+ account_resource.authentication_key != expected_auth_key
+});
+include from_scheme == MULTI_ED25519_SCHEME ==> multi_ed25519::NewUnvalidatedPublicKeyFromBytesAbortsIf { bytes: from_public_key_bytes };
+aborts_if from_scheme == MULTI_ED25519_SCHEME && ({
+ let from_auth_key = multi_ed25519::spec_public_key_bytes_to_authentication_key(from_public_key_bytes);
+ account_resource.authentication_key != from_auth_key
+});
+// This enforces high-level requirement 5:
+aborts_if from_scheme != ED25519_SCHEME && from_scheme != MULTI_ED25519_SCHEME;
+let curr_auth_key = from_bcs::deserialize<address>(account_resource.authentication_key);
+aborts_if !from_bcs::deserializable<address>(account_resource.authentication_key);
+let challenge = RotationProofChallenge {
+ sequence_number: account_resource.sequence_number,
+ originator: addr,
+ current_auth_key: curr_auth_key,
+ new_public_key: to_public_key_bytes,
+};
+// This enforces high-level requirement 9:
+include AssertValidRotationProofSignatureAndGetAuthKeyAbortsIf {
+ scheme: from_scheme,
+ public_key_bytes: from_public_key_bytes,
+ signature: cap_rotate_key,
+ challenge,
+};
+include AssertValidRotationProofSignatureAndGetAuthKeyAbortsIf {
+ scheme: to_scheme,
+ public_key_bytes: to_public_key_bytes,
+ signature: cap_update_table,
+ challenge,
+};
+let originating_addr = addr;
+let new_auth_key_vector = spec_assert_valid_rotation_proof_signature_and_get_auth_key(to_scheme, to_public_key_bytes, cap_update_table, challenge);
+let address_map = global<OriginatingAddress>(@aptos_framework).address_map;
+let new_auth_key = from_bcs::deserialize<address>(new_auth_key_vector);
+aborts_if !exists<OriginatingAddress>(@aptos_framework);
+aborts_if !from_bcs::deserializable<address>(account_resource.authentication_key);
+aborts_if table::spec_contains(address_map, curr_auth_key) &&
+ table::spec_get(address_map, curr_auth_key) != originating_addr;
+aborts_if !from_bcs::deserializable<address>(new_auth_key_vector);
+aborts_if curr_auth_key != new_auth_key && table::spec_contains(address_map, new_auth_key);
+include UpdateAuthKeyAndOriginatingAddressTableAbortsIf {
+ originating_addr: addr,
+};
+let post auth_key = global<Account>(addr).authentication_key;
+ensures auth_key == new_auth_key_vector;
+
+
+
+
+
+
+### Function `rotate_authentication_key_with_rotation_capability`
+
+
+public entry fun rotate_authentication_key_with_rotation_capability(delegate_signer: &signer, rotation_cap_offerer_address: address, new_scheme: u8, new_public_key_bytes: vector<u8>, cap_update_table: vector<u8>)
+
+
+
+
+
+aborts_if !exists<Account>(rotation_cap_offerer_address);
+let delegate_address = signer::address_of(delegate_signer);
+let offerer_account_resource = global<Account>(rotation_cap_offerer_address);
+aborts_if !from_bcs::deserializable<address>(offerer_account_resource.authentication_key);
+let curr_auth_key = from_bcs::deserialize<address>(offerer_account_resource.authentication_key);
+aborts_if !exists<Account>(delegate_address);
+let challenge = RotationProofChallenge {
+ sequence_number: global<Account>(delegate_address).sequence_number,
+ originator: rotation_cap_offerer_address,
+ current_auth_key: curr_auth_key,
+ new_public_key: new_public_key_bytes,
+};
+// This enforces high-level requirement 6:
+aborts_if !option::spec_contains(offerer_account_resource.rotation_capability_offer.for, delegate_address);
+// This enforces high-level requirement 9:
+include AssertValidRotationProofSignatureAndGetAuthKeyAbortsIf {
+ scheme: new_scheme,
+ public_key_bytes: new_public_key_bytes,
+ signature: cap_update_table,
+ challenge,
+};
+let new_auth_key_vector = spec_assert_valid_rotation_proof_signature_and_get_auth_key(new_scheme, new_public_key_bytes, cap_update_table, challenge);
+let address_map = global<OriginatingAddress>(@aptos_framework).address_map;
+aborts_if !exists<OriginatingAddress>(@aptos_framework);
+aborts_if !from_bcs::deserializable<address>(offerer_account_resource.authentication_key);
+aborts_if table::spec_contains(address_map, curr_auth_key) &&
+ table::spec_get(address_map, curr_auth_key) != rotation_cap_offerer_address;
+aborts_if !from_bcs::deserializable<address>(new_auth_key_vector);
+let new_auth_key = from_bcs::deserialize<address>(new_auth_key_vector);
+aborts_if curr_auth_key != new_auth_key && table::spec_contains(address_map, new_auth_key);
+include UpdateAuthKeyAndOriginatingAddressTableAbortsIf {
+ originating_addr: rotation_cap_offerer_address,
+ account_resource: offerer_account_resource,
+};
+let post auth_key = global<Account>(rotation_cap_offerer_address).authentication_key;
+ensures auth_key == new_auth_key_vector;
+
+
+
+
+
+
+### Function `offer_rotation_capability`
+
+
+public entry fun offer_rotation_capability(account: &signer, rotation_capability_sig_bytes: vector<u8>, account_scheme: u8, account_public_key_bytes: vector<u8>, recipient_address: address)
+
+
+
+
+
+let source_address = signer::address_of(account);
+let account_resource = global<Account>(source_address);
+let proof_challenge = RotationCapabilityOfferProofChallengeV2 {
+ chain_id: global<chain_id::ChainId>(@aptos_framework).id,
+ sequence_number: account_resource.sequence_number,
+ source_address,
+ recipient_address,
+};
+aborts_if !exists<chain_id::ChainId>(@aptos_framework);
+aborts_if !exists<Account>(recipient_address);
+aborts_if !exists<Account>(source_address);
+include account_scheme == ED25519_SCHEME ==> ed25519::NewUnvalidatedPublicKeyFromBytesAbortsIf { bytes: account_public_key_bytes };
+aborts_if account_scheme == ED25519_SCHEME && ({
+ let expected_auth_key = ed25519::spec_public_key_bytes_to_authentication_key(account_public_key_bytes);
+ account_resource.authentication_key != expected_auth_key
+});
+include account_scheme == ED25519_SCHEME ==> ed25519::NewSignatureFromBytesAbortsIf { bytes: rotation_capability_sig_bytes };
+aborts_if account_scheme == ED25519_SCHEME && !ed25519::spec_signature_verify_strict_t(
+ ed25519::Signature { bytes: rotation_capability_sig_bytes },
+ ed25519::UnvalidatedPublicKey { bytes: account_public_key_bytes },
+ proof_challenge
+);
+include account_scheme == MULTI_ED25519_SCHEME ==> multi_ed25519::NewUnvalidatedPublicKeyFromBytesAbortsIf { bytes: account_public_key_bytes };
+aborts_if account_scheme == MULTI_ED25519_SCHEME && ({
+ let expected_auth_key = multi_ed25519::spec_public_key_bytes_to_authentication_key(account_public_key_bytes);
+ account_resource.authentication_key != expected_auth_key
+});
+include account_scheme == MULTI_ED25519_SCHEME ==> multi_ed25519::NewSignatureFromBytesAbortsIf { bytes: rotation_capability_sig_bytes };
+aborts_if account_scheme == MULTI_ED25519_SCHEME && !multi_ed25519::spec_signature_verify_strict_t(
+ multi_ed25519::Signature { bytes: rotation_capability_sig_bytes },
+ multi_ed25519::UnvalidatedPublicKey { bytes: account_public_key_bytes },
+ proof_challenge
+);
+// This enforces high-level requirement 5:
+aborts_if account_scheme != ED25519_SCHEME && account_scheme != MULTI_ED25519_SCHEME;
+// This enforces high-level requirement 7:
+modifies global<Account>(source_address);
+let post offer_for = global<Account>(source_address).rotation_capability_offer.for;
+ensures option::spec_borrow(offer_for) == recipient_address;
+
+
+
+
+
+
+### Function `is_rotation_capability_offered`
+
+
+#[view]
+public fun is_rotation_capability_offered(account_addr: address): bool
+
+
+
+
+
+aborts_if !exists<Account>(account_addr);
+
+
+
+
+
+
+### Function `get_rotation_capability_offer_for`
+
+
+#[view]
+public fun get_rotation_capability_offer_for(account_addr: address): address
+
+
+
+
+
+aborts_if !exists<Account>(account_addr);
+let account_resource = global<Account>(account_addr);
+aborts_if len(account_resource.rotation_capability_offer.for.vec) == 0;
+
+
+
+
+
+
+### Function `revoke_rotation_capability`
+
+
+public entry fun revoke_rotation_capability(account: &signer, to_be_revoked_address: address)
+
+
+
+
+
+aborts_if !exists<Account>(to_be_revoked_address);
+let addr = signer::address_of(account);
+let account_resource = global<Account>(addr);
+aborts_if !exists<Account>(addr);
+aborts_if !option::spec_contains(account_resource.rotation_capability_offer.for,to_be_revoked_address);
+modifies global<Account>(addr);
+ensures exists<Account>(to_be_revoked_address);
+let post offer_for = global<Account>(addr).rotation_capability_offer.for;
+ensures !option::spec_is_some(offer_for);
+
+
+
+
+
+
+### Function `revoke_any_rotation_capability`
+
+
+public entry fun revoke_any_rotation_capability(account: &signer)
+
+
+
+
+
+let addr = signer::address_of(account);
+modifies global<Account>(addr);
+aborts_if !exists<Account>(addr);
+let account_resource = global<Account>(addr);
+// This enforces high-level requirement 7:
+aborts_if !option::is_some(account_resource.rotation_capability_offer.for);
+let post offer_for = global<Account>(addr).rotation_capability_offer.for;
+ensures !option::spec_is_some(offer_for);
+
+
+
+
+
+
+### Function `offer_signer_capability`
+
+
+public entry fun offer_signer_capability(account: &signer, signer_capability_sig_bytes: vector<u8>, account_scheme: u8, account_public_key_bytes: vector<u8>, recipient_address: address)
+
+
+
+The Account existed under the signer.
+The authentication scheme is ED25519_SCHEME and MULTI_ED25519_SCHEME.
+
+
+let source_address = signer::address_of(account);
+let account_resource = global<Account>(source_address);
+let proof_challenge = SignerCapabilityOfferProofChallengeV2 {
+ sequence_number: account_resource.sequence_number,
+ source_address,
+ recipient_address,
+};
+aborts_if !exists<Account>(recipient_address);
+aborts_if !exists<Account>(source_address);
+include account_scheme == ED25519_SCHEME ==> ed25519::NewUnvalidatedPublicKeyFromBytesAbortsIf { bytes: account_public_key_bytes };
+aborts_if account_scheme == ED25519_SCHEME && ({
+ let expected_auth_key = ed25519::spec_public_key_bytes_to_authentication_key(account_public_key_bytes);
+ account_resource.authentication_key != expected_auth_key
+});
+include account_scheme == ED25519_SCHEME ==> ed25519::NewSignatureFromBytesAbortsIf { bytes: signer_capability_sig_bytes };
+aborts_if account_scheme == ED25519_SCHEME && !ed25519::spec_signature_verify_strict_t(
+ ed25519::Signature { bytes: signer_capability_sig_bytes },
+ ed25519::UnvalidatedPublicKey { bytes: account_public_key_bytes },
+ proof_challenge
+);
+include account_scheme == MULTI_ED25519_SCHEME ==> multi_ed25519::NewUnvalidatedPublicKeyFromBytesAbortsIf { bytes: account_public_key_bytes };
+aborts_if account_scheme == MULTI_ED25519_SCHEME && ({
+ let expected_auth_key = multi_ed25519::spec_public_key_bytes_to_authentication_key(account_public_key_bytes);
+ account_resource.authentication_key != expected_auth_key
+});
+include account_scheme == MULTI_ED25519_SCHEME ==> multi_ed25519::NewSignatureFromBytesAbortsIf { bytes: signer_capability_sig_bytes };
+aborts_if account_scheme == MULTI_ED25519_SCHEME && !multi_ed25519::spec_signature_verify_strict_t(
+ multi_ed25519::Signature { bytes: signer_capability_sig_bytes },
+ multi_ed25519::UnvalidatedPublicKey { bytes: account_public_key_bytes },
+ proof_challenge
+);
+// This enforces high-level requirement 5:
+aborts_if account_scheme != ED25519_SCHEME && account_scheme != MULTI_ED25519_SCHEME;
+// This enforces high-level requirement 7:
+modifies global<Account>(source_address);
+let post offer_for = global<Account>(source_address).signer_capability_offer.for;
+ensures option::spec_borrow(offer_for) == recipient_address;
+
+
+
+
+
+
+### Function `is_signer_capability_offered`
+
+
+#[view]
+public fun is_signer_capability_offered(account_addr: address): bool
+
+
+
+
+
+aborts_if !exists<Account>(account_addr);
+
+
+
+
+
+
+### Function `get_signer_capability_offer_for`
+
+
+#[view]
+public fun get_signer_capability_offer_for(account_addr: address): address
+
+
+
+
+
+aborts_if !exists<Account>(account_addr);
+let account_resource = global<Account>(account_addr);
+aborts_if len(account_resource.signer_capability_offer.for.vec) == 0;
+
+
+
+
+
+
+### Function `revoke_signer_capability`
+
+
+public entry fun revoke_signer_capability(account: &signer, to_be_revoked_address: address)
+
+
+
+The Account existed under the signer.
+The value of signer_capability_offer.for of Account resource under the signer is to_be_revoked_address.
+
+
+aborts_if !exists<Account>(to_be_revoked_address);
+let addr = signer::address_of(account);
+let account_resource = global<Account>(addr);
+aborts_if !exists<Account>(addr);
+aborts_if !option::spec_contains(account_resource.signer_capability_offer.for,to_be_revoked_address);
+modifies global<Account>(addr);
+ensures exists<Account>(to_be_revoked_address);
+
+
+
+
+
+
+### Function `revoke_any_signer_capability`
+
+
+public entry fun revoke_any_signer_capability(account: &signer)
+
+
+
+
+
+modifies global<Account>(signer::address_of(account));
+// This enforces high-level requirement 7:
+aborts_if !exists<Account>(signer::address_of(account));
+let account_resource = global<Account>(signer::address_of(account));
+aborts_if !option::is_some(account_resource.signer_capability_offer.for);
+
+
+
+
+
+
+### Function `create_authorized_signer`
+
+
+public fun create_authorized_signer(account: &signer, offerer_address: address): signer
+
+
+
+The Account existed under the signer.
+The value of signer_capability_offer.for of Account resource under the signer is offerer_address.
+
+
+// This enforces high-level requirement 8:
+include AccountContainsAddr{
+ account,
+ address: offerer_address,
+};
+modifies global<Account>(offerer_address);
+ensures exists<Account>(offerer_address);
+ensures signer::address_of(result) == offerer_address;
+
+
+
+
+
+
+
+
+schema AccountContainsAddr {
+ account: signer;
+ address: address;
+ let addr = signer::address_of(account);
+ let account_resource = global<Account>(address);
+ aborts_if !exists<Account>(address);
+ // This enforces high-level requirement 3 of the create_signer module:
+ aborts_if !option::spec_contains(account_resource.signer_capability_offer.for,addr);
+}
+
+
+
+
+
+
+### Function `assert_valid_rotation_proof_signature_and_get_auth_key`
+
+
+fun assert_valid_rotation_proof_signature_and_get_auth_key(scheme: u8, public_key_bytes: vector<u8>, signature: vector<u8>, challenge: &account::RotationProofChallenge): vector<u8>
+
+
+
+
+
+pragma opaque;
+include AssertValidRotationProofSignatureAndGetAuthKeyAbortsIf;
+ensures [abstract] result == spec_assert_valid_rotation_proof_signature_and_get_auth_key(scheme, public_key_bytes, signature, challenge);
+
+
+
+
+
+
+
+
+schema AssertValidRotationProofSignatureAndGetAuthKeyAbortsIf {
+ scheme: u8;
+ public_key_bytes: vector<u8>;
+ signature: vector<u8>;
+ challenge: RotationProofChallenge;
+ include scheme == ED25519_SCHEME ==> ed25519::NewUnvalidatedPublicKeyFromBytesAbortsIf { bytes: public_key_bytes };
+ include scheme == ED25519_SCHEME ==> ed25519::NewSignatureFromBytesAbortsIf { bytes: signature };
+ aborts_if scheme == ED25519_SCHEME && !ed25519::spec_signature_verify_strict_t(
+ ed25519::Signature { bytes: signature },
+ ed25519::UnvalidatedPublicKey { bytes: public_key_bytes },
+ challenge
+ );
+ include scheme == MULTI_ED25519_SCHEME ==> multi_ed25519::NewUnvalidatedPublicKeyFromBytesAbortsIf { bytes: public_key_bytes };
+ include scheme == MULTI_ED25519_SCHEME ==> multi_ed25519::NewSignatureFromBytesAbortsIf { bytes: signature };
+ aborts_if scheme == MULTI_ED25519_SCHEME && !multi_ed25519::spec_signature_verify_strict_t(
+ multi_ed25519::Signature { bytes: signature },
+ multi_ed25519::UnvalidatedPublicKey { bytes: public_key_bytes },
+ challenge
+ );
+ aborts_if scheme != ED25519_SCHEME && scheme != MULTI_ED25519_SCHEME;
+}
+
+
+
+
+
+
+### Function `update_auth_key_and_originating_address_table`
+
+
+fun update_auth_key_and_originating_address_table(originating_addr: address, account_resource: &mut account::Account, new_auth_key_vector: vector<u8>)
+
+
+
+
+
+modifies global<OriginatingAddress>(@aptos_framework);
+include UpdateAuthKeyAndOriginatingAddressTableAbortsIf;
+
+
+
+
+
+
+
+
+schema UpdateAuthKeyAndOriginatingAddressTableAbortsIf {
+ originating_addr: address;
+ account_resource: Account;
+ new_auth_key_vector: vector<u8>;
+ let address_map = global<OriginatingAddress>(@aptos_framework).address_map;
+ let curr_auth_key = from_bcs::deserialize<address>(account_resource.authentication_key);
+ let new_auth_key = from_bcs::deserialize<address>(new_auth_key_vector);
+ aborts_if !exists<OriginatingAddress>(@aptos_framework);
+ aborts_if !from_bcs::deserializable<address>(account_resource.authentication_key);
+ aborts_if table::spec_contains(address_map, curr_auth_key) &&
+ table::spec_get(address_map, curr_auth_key) != originating_addr;
+ aborts_if !from_bcs::deserializable<address>(new_auth_key_vector);
+ aborts_if curr_auth_key != new_auth_key && table::spec_contains(address_map, new_auth_key);
+ ensures table::spec_contains(global<OriginatingAddress>(@aptos_framework).address_map, from_bcs::deserialize<address>(new_auth_key_vector));
+}
+
+
+
+
+
+
+### Function `create_resource_address`
+
+
+public fun create_resource_address(source: &address, seed: vector<u8>): address
+
+
+
+The Account existed under the signer
+The value of signer_capability_offer.for of Account resource under the signer is to_be_revoked_address
+
+
+pragma opaque;
+pragma aborts_if_is_strict = false;
+aborts_if [abstract] false;
+ensures [abstract] result == spec_create_resource_address(source, seed);
+
+
+
+
+
+
+
+
+fun spec_create_resource_address(source: address, seed: vector<u8>): address;
+
+
+
+
+
+
+### Function `create_resource_account`
+
+
+public fun create_resource_account(source: &signer, seed: vector<u8>): (signer, account::SignerCapability)
+
+
+
+
+
+let source_addr = signer::address_of(source);
+let resource_addr = spec_create_resource_address(source_addr, seed);
+aborts_if len(ZERO_AUTH_KEY) != 32;
+include exists_at(resource_addr) ==> CreateResourceAccountAbortsIf;
+include !exists_at(resource_addr) ==> CreateAccountAbortsIf {addr: resource_addr};
+ensures signer::address_of(result_1) == resource_addr;
+let post offer_for = global<Account>(resource_addr).signer_capability_offer.for;
+ensures option::spec_borrow(offer_for) == resource_addr;
+ensures result_2 == SignerCapability { account: resource_addr };
+
+
+
+
+
+
+### Function `create_framework_reserved_account`
+
+
+public(friend) fun create_framework_reserved_account(addr: address): (signer, account::SignerCapability)
+
+
+
+Check if the bytes of the new address is 32.
+The Account does not exist under the new address before creating the account.
+The system reserved addresses is @0x1 / @0x2 / @0x3 / @0x4 / @0x5 / @0x6 / @0x7 / @0x8 / @0x9 / @0xa.
+
+
+aborts_if spec_is_framework_address(addr);
+include CreateAccountAbortsIf {addr};
+ensures signer::address_of(result_1) == addr;
+ensures result_2 == SignerCapability { account: addr };
+
+
+
+
+
+
+
+
+fun spec_is_framework_address(addr: address): bool{
+ addr != @0x1 &&
+ addr != @0x2 &&
+ addr != @0x3 &&
+ addr != @0x4 &&
+ addr != @0x5 &&
+ addr != @0x6 &&
+ addr != @0x7 &&
+ addr != @0x8 &&
+ addr != @0x9 &&
+ addr != @0xa
+}
+
+
+
+
+
+
+### Function `create_guid`
+
+
+public fun create_guid(account_signer: &signer): guid::GUID
+
+
+
+The Account existed under the signer.
+The guid_creation_num of the ccount resource is up to MAX_U64.
+
+
+let addr = signer::address_of(account_signer);
+include NewEventHandleAbortsIf {
+ account: account_signer,
+};
+modifies global<Account>(addr);
+// This enforces high-level requirement 11:
+ensures global<Account>(addr).guid_creation_num == old(global<Account>(addr).guid_creation_num) + 1;
+
+
+
+
+
+
+### Function `new_event_handle`
+
+
+public fun new_event_handle<T: drop, store>(account: &signer): event::EventHandle<T>
+
+
+
+The Account existed under the signer.
+The guid_creation_num of the Account is up to MAX_U64.
+
+
+include NewEventHandleAbortsIf;
+
+
+
+
+
+
+
+
+schema NewEventHandleAbortsIf {
+ account: &signer;
+ let addr = signer::address_of(account);
+ let account = global<Account>(addr);
+ aborts_if !exists<Account>(addr);
+ aborts_if account.guid_creation_num + 1 > MAX_U64;
+ aborts_if account.guid_creation_num + 1 >= MAX_GUID_CREATION_NUM;
+}
+
+
+
+
+
+
+### Function `register_coin`
+
+
+public(friend) fun register_coin<CoinType>(account_addr: address)
+
+
+
+
+
+aborts_if !exists<Account>(account_addr);
+aborts_if !type_info::spec_is_struct<CoinType>();
+modifies global<Account>(account_addr);
+
+
+
+
+
+
+### Function `create_signer_with_capability`
+
+
+public fun create_signer_with_capability(capability: &account::SignerCapability): signer
+
+
+
+
+
+let addr = capability.account;
+ensures signer::address_of(result) == addr;
+
+
+
+
+
+
+
+
+schema CreateResourceAccountAbortsIf {
+ resource_addr: address;
+ let account = global<Account>(resource_addr);
+ aborts_if len(account.signer_capability_offer.for.vec) != 0;
+ aborts_if account.sequence_number != 0;
+}
+
+
+
+
+
+
+### Function `verify_signed_message`
+
+
+public fun verify_signed_message<T: drop>(account: address, account_scheme: u8, account_public_key: vector<u8>, signed_message_bytes: vector<u8>, message: T)
+
+
+
+
+
+pragma aborts_if_is_partial;
+modifies global<Account>(account);
+let account_resource = global<Account>(account);
+aborts_if !exists<Account>(account);
+include account_scheme == ED25519_SCHEME ==> ed25519::NewUnvalidatedPublicKeyFromBytesAbortsIf { bytes: account_public_key };
+aborts_if account_scheme == ED25519_SCHEME && ({
+ let expected_auth_key = ed25519::spec_public_key_bytes_to_authentication_key(account_public_key);
+ account_resource.authentication_key != expected_auth_key
+});
+include account_scheme == MULTI_ED25519_SCHEME ==> multi_ed25519::NewUnvalidatedPublicKeyFromBytesAbortsIf { bytes: account_public_key };
+aborts_if account_scheme == MULTI_ED25519_SCHEME && ({
+ let expected_auth_key = multi_ed25519::spec_public_key_bytes_to_authentication_key(account_public_key);
+ account_resource.authentication_key != expected_auth_key
+});
+include account_scheme == ED25519_SCHEME ==> ed25519::NewSignatureFromBytesAbortsIf { bytes: signed_message_bytes };
+include account_scheme == MULTI_ED25519_SCHEME ==> multi_ed25519::NewSignatureFromBytesAbortsIf { bytes: signed_message_bytes };
+aborts_if account_scheme != ED25519_SCHEME && account_scheme != MULTI_ED25519_SCHEME;
+
+
+
+[move-book]: https://aptos.dev/move/book/SUMMARY
diff --git a/aptos-move/framework/aptos-framework/tests/compiler-v2-doc/aggregator.md b/aptos-move/framework/aptos-framework/tests/compiler-v2-doc/aggregator.md
new file mode 100644
index 0000000000000..4d2bfca013199
--- /dev/null
+++ b/aptos-move/framework/aptos-framework/tests/compiler-v2-doc/aggregator.md
@@ -0,0 +1,474 @@
+
+
+
+# Module `0x1::aggregator`
+
+This module provides an interface for aggregators. Aggregators are similar to
+unsigned integers and support addition and subtraction (aborting on underflow
+or on overflowing a custom upper limit). The difference from integers is that
+aggregators allow to perform both additions and subtractions in parallel across
+multiple transactions, enabling parallel execution. For example, if the first
+transaction is doing add(X, 1)
for aggregator resource X
, and the second
+is doing sub(X,3)
, they can be executed in parallel avoiding a read-modify-write
+dependency.
+However, reading the aggregator value (i.e. calling read(X)
) is an expensive
+operation and should be avoided as much as possible because it reduces the
+parallelism. Moreover, **aggregators can only be created by Aptos Framework (0x1)
+at the moment.**
+
+
+- [Struct `Aggregator`](#0x1_aggregator_Aggregator)
+- [Constants](#@Constants_0)
+- [Function `limit`](#0x1_aggregator_limit)
+- [Function `add`](#0x1_aggregator_add)
+- [Function `sub`](#0x1_aggregator_sub)
+- [Function `read`](#0x1_aggregator_read)
+- [Function `destroy`](#0x1_aggregator_destroy)
+- [Specification](#@Specification_1)
+ - [Struct `Aggregator`](#@Specification_1_Aggregator)
+ - [High-level Requirements](#high-level-req)
+ - [Module-level Specification](#module-level-spec)
+ - [Function `limit`](#@Specification_1_limit)
+ - [Function `add`](#@Specification_1_add)
+ - [Function `sub`](#@Specification_1_sub)
+ - [Function `read`](#@Specification_1_read)
+ - [Function `destroy`](#@Specification_1_destroy)
+
+
+
+
+
+
+
+
+## Struct `Aggregator`
+
+Represents an integer which supports parallel additions and subtractions
+across multiple transactions. See the module description for more details.
+
+
+struct Aggregator has store
+
+
+
+
+handle: address
+key: address
+limit: u128
+const EAGGREGATOR_OVERFLOW: u64 = 1;
+
+
+
+
+
+
+The value of aggregator underflows (goes below zero). Raised by native code.
+
+
+const EAGGREGATOR_UNDERFLOW: u64 = 2;
+
+
+
+
+
+
+Aggregator feature is not supported. Raised by native code.
+
+
+const ENOT_SUPPORTED: u64 = 3;
+
+
+
+
+
+
+## Function `limit`
+
+Returns limit
exceeding which aggregator overflows.
+
+
+public fun limit(aggregator: &aggregator::Aggregator): u128
+
+
+
+
+public fun limit(aggregator: &Aggregator): u128 {
+ aggregator.limit
+}
+
+
+
+
+value
to aggregator. Aborts on overflowing the limit.
+
+
+public fun add(aggregator: &mut aggregator::Aggregator, value: u128)
+
+
+
+
+public native fun add(aggregator: &mut Aggregator, value: u128);
+
+
+
+
+value
from aggregator. Aborts on going below zero.
+
+
+public fun sub(aggregator: &mut aggregator::Aggregator, value: u128)
+
+
+
+
+public native fun sub(aggregator: &mut Aggregator, value: u128);
+
+
+
+
+public fun read(aggregator: &aggregator::Aggregator): u128
+
+
+
+
+public native fun read(aggregator: &Aggregator): u128;
+
+
+
+
+AggregatorFactory
.
+
+
+public fun destroy(aggregator: aggregator::Aggregator)
+
+
+
+
+public native fun destroy(aggregator: Aggregator);
+
+
+
+
+struct Aggregator has store
+
+
+
+
+handle: address
+key: address
+limit: u128
+No. | Requirement | Criticality | Implementation | Enforcement | +
---|---|---|---|---|
1 | +For a given aggregator, it should always be possible to: Return the limit value of the aggregator. Return the current value stored in the aggregator. Destroy an aggregator, removing it from its AggregatorFactory. | +Low | +The following functions should not abort if EventHandle exists: limit(), read(), destroy(). | +Formally verified via read, destroy, and limit. | +
2 | +If the value during addition exceeds the limit, an overflow occurs. | +High | +The native add() function checks the value of the addition to ensure it does not pass the defined limit and results in aggregator overflow. | +Formally verified via add. | +
3 | +Operations over aggregators should be correct. | +High | +The implementation of the add, sub, read and destroy functions is correct. | +The native implementation of the add, sub, read and destroy functions have been manually audited. | +
pragma intrinsic;
+
+
+
+
+
+
+### Function `limit`
+
+
+public fun limit(aggregator: &aggregator::Aggregator): u128
+
+
+
+
+
+pragma opaque;
+// This enforces high-level requirement 1:
+aborts_if false;
+ensures [abstract] result == spec_get_limit(aggregator);
+
+
+
+
+
+
+
+
+native fun spec_read(aggregator: Aggregator): u128;
+
+
+
+
+
+
+
+
+native fun spec_get_limit(a: Aggregator): u128;
+
+
+
+
+
+
+
+
+native fun spec_get_handle(a: Aggregator): u128;
+
+
+
+
+
+
+
+
+native fun spec_get_key(a: Aggregator): u128;
+
+
+
+
+
+
+
+
+native fun spec_aggregator_set_val(a: Aggregator, v: u128): Aggregator;
+
+
+
+
+
+
+
+
+native fun spec_aggregator_get_val(a: Aggregator): u128;
+
+
+
+
+
+
+### Function `add`
+
+
+public fun add(aggregator: &mut aggregator::Aggregator, value: u128)
+
+
+
+
+
+pragma opaque;
+aborts_if spec_aggregator_get_val(aggregator) + value > spec_get_limit(aggregator);
+// This enforces high-level requirement 2:
+aborts_if spec_aggregator_get_val(aggregator) + value > MAX_U128;
+ensures spec_get_limit(aggregator) == spec_get_limit(old(aggregator));
+ensures aggregator == spec_aggregator_set_val(old(aggregator),
+ spec_aggregator_get_val(old(aggregator)) + value);
+
+
+
+
+
+
+### Function `sub`
+
+
+public fun sub(aggregator: &mut aggregator::Aggregator, value: u128)
+
+
+
+
+
+pragma opaque;
+aborts_if spec_aggregator_get_val(aggregator) < value;
+ensures spec_get_limit(aggregator) == spec_get_limit(old(aggregator));
+ensures aggregator == spec_aggregator_set_val(old(aggregator),
+ spec_aggregator_get_val(old(aggregator)) - value);
+
+
+
+
+
+
+### Function `read`
+
+
+public fun read(aggregator: &aggregator::Aggregator): u128
+
+
+
+
+
+pragma opaque;
+// This enforces high-level requirement 1:
+aborts_if false;
+ensures result == spec_read(aggregator);
+ensures result <= spec_get_limit(aggregator);
+
+
+
+
+
+
+### Function `destroy`
+
+
+public fun destroy(aggregator: aggregator::Aggregator)
+
+
+
+
+
+pragma opaque;
+// This enforces high-level requirement 1:
+aborts_if false;
+
+
+
+[move-book]: https://aptos.dev/move/book/SUMMARY
diff --git a/aptos-move/framework/aptos-framework/tests/compiler-v2-doc/aggregator_factory.md b/aptos-move/framework/aptos-framework/tests/compiler-v2-doc/aggregator_factory.md
new file mode 100644
index 0000000000000..7f92cf43addf1
--- /dev/null
+++ b/aptos-move/framework/aptos-framework/tests/compiler-v2-doc/aggregator_factory.md
@@ -0,0 +1,355 @@
+
+
+
+# Module `0x1::aggregator_factory`
+
+This module provides foundations to create aggregators. Currently only
+Aptos Framework (0x1) can create them, so this module helps to wrap
+the constructor of Aggregator
struct so that only a system account
+can initialize one. In the future, this might change and aggregators
+can be enabled for the public.
+
+
+- [Resource `AggregatorFactory`](#0x1_aggregator_factory_AggregatorFactory)
+- [Constants](#@Constants_0)
+- [Function `initialize_aggregator_factory`](#0x1_aggregator_factory_initialize_aggregator_factory)
+- [Function `create_aggregator_internal`](#0x1_aggregator_factory_create_aggregator_internal)
+- [Function `create_aggregator`](#0x1_aggregator_factory_create_aggregator)
+- [Function `new_aggregator`](#0x1_aggregator_factory_new_aggregator)
+- [Specification](#@Specification_1)
+ - [High-level Requirements](#high-level-req)
+ - [Module-level Specification](#module-level-spec)
+ - [Function `initialize_aggregator_factory`](#@Specification_1_initialize_aggregator_factory)
+ - [Function `create_aggregator_internal`](#@Specification_1_create_aggregator_internal)
+ - [Function `create_aggregator`](#@Specification_1_create_aggregator)
+ - [Function `new_aggregator`](#@Specification_1_new_aggregator)
+
+
+use 0x1::aggregator;
+use 0x1::error;
+use 0x1::system_addresses;
+use 0x1::table;
+
+
+
+
+
+
+## Resource `AggregatorFactory`
+
+Creates new aggregators. Used to control the numbers of aggregators in the
+system and who can create them. At the moment, only Aptos Framework (0x1)
+account can.
+
+
+struct AggregatorFactory has key
+
+
+
+
+phantom_table: table::Table<address, u128>
+const EAGGREGATOR_FACTORY_NOT_FOUND: u64 = 1;
+
+
+
+
+
+
+## Function `initialize_aggregator_factory`
+
+Creates a new factory for aggregators. Can only be called during genesis.
+
+
+public(friend) fun initialize_aggregator_factory(aptos_framework: &signer)
+
+
+
+
+public(friend) fun initialize_aggregator_factory(aptos_framework: &signer) {
+ system_addresses::assert_aptos_framework(aptos_framework);
+ let aggregator_factory = AggregatorFactory {
+ phantom_table: table::new()
+ };
+ move_to(aptos_framework, aggregator_factory);
+}
+
+
+
+
+limit
.
+
+
+public(friend) fun create_aggregator_internal(limit: u128): aggregator::Aggregator
+
+
+
+
+public(friend) fun create_aggregator_internal(limit: u128): Aggregator acquires AggregatorFactory {
+ assert!(
+ exists<AggregatorFactory>(@aptos_framework),
+ error::not_found(EAGGREGATOR_FACTORY_NOT_FOUND)
+ );
+
+ let aggregator_factory = borrow_global_mut<AggregatorFactory>(@aptos_framework);
+ new_aggregator(aggregator_factory, limit)
+}
+
+
+
+
+public fun create_aggregator(account: &signer, limit: u128): aggregator::Aggregator
+
+
+
+
+public fun create_aggregator(account: &signer, limit: u128): Aggregator acquires AggregatorFactory {
+ // Only Aptos Framework (0x1) account can call this for now.
+ system_addresses::assert_aptos_framework(account);
+ create_aggregator_internal(limit)
+}
+
+
+
+
+fun new_aggregator(aggregator_factory: &mut aggregator_factory::AggregatorFactory, limit: u128): aggregator::Aggregator
+
+
+
+
+native fun new_aggregator(aggregator_factory: &mut AggregatorFactory, limit: u128): Aggregator;
+
+
+
+
+No. | Requirement | Criticality | Implementation | Enforcement | +
---|---|---|---|---|
1 | +During the module's initialization, it guarantees that the Aptos framework is the caller and that the AggregatorFactory resource will move under the Aptos framework account. | +High | +The initialize function is responsible for establishing the initial state of the module by creating the AggregatorFactory resource, indicating its presence within the module's context. Subsequently, the resource transfers to the Aptos framework account. | +Formally verified via initialize_aggregator_factory. | +
2 | +To create a new aggregator instance, the aggregator factory must already be initialized and exist under the Aptos account. | +High | +The create_aggregator_internal function asserts that AggregatorFactory exists for the Aptos account. | +Formally verified via CreateAggregatorInternalAbortsIf. | +
3 | +Only the Aptos framework address may create an aggregator instance currently. | +Low | +The create_aggregator function ensures that the address calling it is the Aptos framework address. | +Formally verified via create_aggregator. | +
4 | +The creation of new aggregators should be done correctly. | +High | +The native new_aggregator function correctly creates a new aggregator. | +The new_aggregator native function has been manually audited. | +
pragma aborts_if_is_strict;
+
+
+
+
+
+
+### Function `initialize_aggregator_factory`
+
+
+public(friend) fun initialize_aggregator_factory(aptos_framework: &signer)
+
+
+
+Make sure the caller is @aptos_framework.
+AggregatorFactory is not under the caller before creating the resource.
+
+
+let addr = signer::address_of(aptos_framework);
+aborts_if addr != @aptos_framework;
+aborts_if exists<AggregatorFactory>(addr);
+// This enforces high-level requirement 1:
+ensures exists<AggregatorFactory>(addr);
+
+
+
+
+
+
+### Function `create_aggregator_internal`
+
+
+public(friend) fun create_aggregator_internal(limit: u128): aggregator::Aggregator
+
+
+
+
+
+// This enforces high-level requirement 2:
+include CreateAggregatorInternalAbortsIf;
+ensures aggregator::spec_get_limit(result) == limit;
+ensures aggregator::spec_aggregator_get_val(result) == 0;
+
+
+
+
+
+
+
+
+schema CreateAggregatorInternalAbortsIf {
+ aborts_if !exists<AggregatorFactory>(@aptos_framework);
+}
+
+
+
+
+
+
+### Function `create_aggregator`
+
+
+public fun create_aggregator(account: &signer, limit: u128): aggregator::Aggregator
+
+
+
+Make sure the caller is @aptos_framework.
+AggregatorFactory existed under the @aptos_framework when Creating a new aggregator.
+
+
+let addr = signer::address_of(account);
+// This enforces high-level requirement 3:
+aborts_if addr != @aptos_framework;
+aborts_if !exists<AggregatorFactory>(@aptos_framework);
+
+
+
+
+
+
+
+
+native fun spec_new_aggregator(limit: u128): Aggregator;
+
+
+
+
+
+
+### Function `new_aggregator`
+
+
+fun new_aggregator(aggregator_factory: &mut aggregator_factory::AggregatorFactory, limit: u128): aggregator::Aggregator
+
+
+
+
+
+pragma opaque;
+aborts_if false;
+ensures result == spec_new_aggregator(limit);
+ensures aggregator::spec_get_limit(result) == limit;
+
+
+
+[move-book]: https://aptos.dev/move/book/SUMMARY
diff --git a/aptos-move/framework/aptos-framework/tests/compiler-v2-doc/aggregator_v2.md b/aptos-move/framework/aptos-framework/tests/compiler-v2-doc/aggregator_v2.md
new file mode 100644
index 0000000000000..2318d57e73b9a
--- /dev/null
+++ b/aptos-move/framework/aptos-framework/tests/compiler-v2-doc/aggregator_v2.md
@@ -0,0 +1,943 @@
+
+
+
+# Module `0x1::aggregator_v2`
+
+This module provides an interface for aggregators (version 2). Aggregators are
+similar to unsigned integers and support addition and subtraction (aborting on
+underflow or on overflowing a custom upper limit). The difference from integers
+is that aggregators allow to perform both additions and subtractions in parallel
+across multiple transactions, enabling parallel execution. For example, if the
+first transaction is doing try_add(X, 1)
for aggregator X
, and the second is
+doing try_sub(X,3)
, they can be executed in parallel avoiding a read-modify-write
+dependency.
+However, reading the aggregator value (i.e. calling read(X)
) is a resource-intensive
+operation that also reduced parallelism, and should be avoided as much as possible.
+If you need to capture the value, without revealing it, use snapshot function instead,
+which has no parallelism impact.
+
+From parallelism considerations, there are three different levels of effects:
+* enable full parallelism (cannot create conflicts):
+max_value, create_*, snapshot, derive_string_concat
+* enable speculative parallelism (generally parallel via branch prediction)
+try_add, add, try_sub, sub, is_at_least
+* create read/write conflicts, as if you were using a regular field
+read, read_snapshot, read_derived_string
+
+
+- [Struct `Aggregator`](#0x1_aggregator_v2_Aggregator)
+- [Struct `AggregatorSnapshot`](#0x1_aggregator_v2_AggregatorSnapshot)
+- [Struct `DerivedStringSnapshot`](#0x1_aggregator_v2_DerivedStringSnapshot)
+- [Constants](#@Constants_0)
+- [Function `max_value`](#0x1_aggregator_v2_max_value)
+- [Function `create_aggregator`](#0x1_aggregator_v2_create_aggregator)
+- [Function `create_aggregator_with_value`](#0x1_aggregator_v2_create_aggregator_with_value)
+- [Function `create_unbounded_aggregator`](#0x1_aggregator_v2_create_unbounded_aggregator)
+- [Function `create_unbounded_aggregator_with_value`](#0x1_aggregator_v2_create_unbounded_aggregator_with_value)
+- [Function `try_add`](#0x1_aggregator_v2_try_add)
+- [Function `add`](#0x1_aggregator_v2_add)
+- [Function `try_sub`](#0x1_aggregator_v2_try_sub)
+- [Function `sub`](#0x1_aggregator_v2_sub)
+- [Function `is_at_least_impl`](#0x1_aggregator_v2_is_at_least_impl)
+- [Function `is_at_least`](#0x1_aggregator_v2_is_at_least)
+- [Function `read`](#0x1_aggregator_v2_read)
+- [Function `snapshot`](#0x1_aggregator_v2_snapshot)
+- [Function `create_snapshot`](#0x1_aggregator_v2_create_snapshot)
+- [Function `read_snapshot`](#0x1_aggregator_v2_read_snapshot)
+- [Function `read_derived_string`](#0x1_aggregator_v2_read_derived_string)
+- [Function `create_derived_string`](#0x1_aggregator_v2_create_derived_string)
+- [Function `derive_string_concat`](#0x1_aggregator_v2_derive_string_concat)
+- [Function `copy_snapshot`](#0x1_aggregator_v2_copy_snapshot)
+- [Function `string_concat`](#0x1_aggregator_v2_string_concat)
+- [Specification](#@Specification_1)
+ - [Function `create_aggregator`](#@Specification_1_create_aggregator)
+ - [Function `create_unbounded_aggregator`](#@Specification_1_create_unbounded_aggregator)
+ - [Function `try_add`](#@Specification_1_try_add)
+ - [Function `try_sub`](#@Specification_1_try_sub)
+ - [Function `is_at_least_impl`](#@Specification_1_is_at_least_impl)
+ - [Function `read`](#@Specification_1_read)
+ - [Function `snapshot`](#@Specification_1_snapshot)
+ - [Function `create_snapshot`](#@Specification_1_create_snapshot)
+ - [Function `copy_snapshot`](#@Specification_1_copy_snapshot)
+ - [Function `string_concat`](#@Specification_1_string_concat)
+
+
+use 0x1::error;
+use 0x1::features;
+use 0x1::string;
+
+
+
+
+
+
+## Struct `Aggregator`
+
+Represents an integer which supports parallel additions and subtractions
+across multiple transactions. See the module description for more details.
+
+Currently supported types for IntElement are u64 and u128.
+
+
+struct Aggregator<IntElement> has drop, store
+
+
+
+
+value: IntElement
+max_value: IntElement
+struct AggregatorSnapshot<IntElement> has drop, store
+
+
+
+
+value: IntElement
+struct DerivedStringSnapshot has drop, store
+
+
+
+
+value: string::String
+padding: vector<u8>
+const EAGGREGATOR_OVERFLOW: u64 = 1;
+
+
+
+
+
+
+The value of aggregator underflows (goes below zero). Raised by uncoditional sub() call
+
+
+const EAGGREGATOR_UNDERFLOW: u64 = 2;
+
+
+
+
+
+
+The aggregator api v2 feature flag is not enabled.
+
+
+const EAGGREGATOR_API_V2_NOT_ENABLED: u64 = 6;
+
+
+
+
+
+
+The native aggregator function, that is in the move file, is not yet supported.
+and any calls will raise this error.
+
+
+const EAGGREGATOR_FUNCTION_NOT_YET_SUPPORTED: u64 = 9;
+
+
+
+
+
+
+Arguments passed to concat exceed max limit of 256 bytes (for prefix and suffix together).
+
+
+const ECONCAT_STRING_LENGTH_TOO_LARGE: u64 = 8;
+
+
+
+
+
+
+The generic type supplied to the aggregator snapshot is not supported.
+
+
+const EUNSUPPORTED_AGGREGATOR_SNAPSHOT_TYPE: u64 = 5;
+
+
+
+
+
+
+The generic type supplied to the aggregator is not supported.
+
+
+const EUNSUPPORTED_AGGREGATOR_TYPE: u64 = 7;
+
+
+
+
+
+
+## Function `max_value`
+
+Returns max_value
exceeding which aggregator overflows.
+
+
+public fun max_value<IntElement: copy, drop>(aggregator: &aggregator_v2::Aggregator<IntElement>): IntElement
+
+
+
+
+public fun max_value<IntElement: copy + drop>(aggregator: &Aggregator<IntElement>): IntElement {
+ aggregator.max_value
+}
+
+
+
+
+public fun create_aggregator<IntElement: copy, drop>(max_value: IntElement): aggregator_v2::Aggregator<IntElement>
+
+
+
+
+public native fun create_aggregator<IntElement: copy + drop>(max_value: IntElement): Aggregator<IntElement>;
+
+
+
+
+public fun create_aggregator_with_value<IntElement: copy, drop>(start_value: IntElement, max_value: IntElement): aggregator_v2::Aggregator<IntElement>
+
+
+
+
+public fun create_aggregator_with_value<IntElement: copy + drop>(start_value: IntElement, max_value: IntElement): Aggregator<IntElement> {
+ let aggregator = create_aggregator(max_value);
+ add(&mut aggregator, start_value);
+ aggregator
+}
+
+
+
+
+public fun create_unbounded_aggregator<IntElement: copy, drop>(): aggregator_v2::Aggregator<IntElement>
+
+
+
+
+public native fun create_unbounded_aggregator<IntElement: copy + drop>(): Aggregator<IntElement>;
+
+
+
+
+public fun create_unbounded_aggregator_with_value<IntElement: copy, drop>(start_value: IntElement): aggregator_v2::Aggregator<IntElement>
+
+
+
+
+public fun create_unbounded_aggregator_with_value<IntElement: copy + drop>(start_value: IntElement): Aggregator<IntElement> {
+ let aggregator = create_unbounded_aggregator();
+ add(&mut aggregator, start_value);
+ aggregator
+}
+
+
+
+
+value
to aggregator.
+If addition would exceed the max_value, false
is returned, and aggregator value is left unchanged.
+
+Parallelism info: This operation enables speculative parallelism.
+
+
+public fun try_add<IntElement>(aggregator: &mut aggregator_v2::Aggregator<IntElement>, value: IntElement): bool
+
+
+
+
+public native fun try_add<IntElement>(aggregator: &mut Aggregator<IntElement>, value: IntElement): bool;
+
+
+
+
+value
to aggregator, unconditionally.
+If addition would exceed the max_value, EAGGREGATOR_OVERFLOW exception will be thrown.
+
+Parallelism info: This operation enables speculative parallelism.
+
+
+public fun add<IntElement>(aggregator: &mut aggregator_v2::Aggregator<IntElement>, value: IntElement)
+
+
+
+
+public fun add<IntElement>(aggregator: &mut Aggregator<IntElement>, value: IntElement) {
+ assert!(try_add(aggregator, value), error::out_of_range(EAGGREGATOR_OVERFLOW));
+}
+
+
+
+
+value
from aggregator.
+If subtraction would result in a negative value, false
is returned, and aggregator value is left unchanged.
+
+Parallelism info: This operation enables speculative parallelism.
+
+
+public fun try_sub<IntElement>(aggregator: &mut aggregator_v2::Aggregator<IntElement>, value: IntElement): bool
+
+
+
+
+public native fun try_sub<IntElement>(aggregator: &mut Aggregator<IntElement>, value: IntElement): bool;
+
+
+
+
+public fun sub<IntElement>(aggregator: &mut aggregator_v2::Aggregator<IntElement>, value: IntElement)
+
+
+
+
+public fun sub<IntElement>(aggregator: &mut Aggregator<IntElement>, value: IntElement) {
+ assert!(try_sub(aggregator, value), error::out_of_range(EAGGREGATOR_UNDERFLOW));
+}
+
+
+
+
+fun is_at_least_impl<IntElement>(aggregator: &aggregator_v2::Aggregator<IntElement>, min_amount: IntElement): bool
+
+
+
+
+native fun is_at_least_impl<IntElement>(aggregator: &Aggregator<IntElement>, min_amount: IntElement): bool;
+
+
+
+
+min_amount
, false otherwise.
+
+This operation is more efficient and much more parallelization friendly than calling read(agg) > min_amount
.
+Until traits are deployed, is_at_most
/is_equal
utility methods can be derived from this one (assuming +1 doesn't overflow):
+- for is_at_most(agg, max_amount)
, you can do !is_at_least(max_amount + 1)
+- for is_equal(agg, value)
, you can do is_at_least(value) && !is_at_least(value + 1)
+
+Parallelism info: This operation enables speculative parallelism.
+
+
+public fun is_at_least<IntElement>(aggregator: &aggregator_v2::Aggregator<IntElement>, min_amount: IntElement): bool
+
+
+
+
+public fun is_at_least<IntElement>(aggregator: &Aggregator<IntElement>, min_amount: IntElement): bool {
+ assert!(features::aggregator_v2_is_at_least_api_enabled(), EAGGREGATOR_API_V2_NOT_ENABLED);
+ is_at_least_impl(aggregator, min_amount)
+}
+
+
+
+
+public fun read<IntElement>(aggregator: &aggregator_v2::Aggregator<IntElement>): IntElement
+
+
+
+
+public native fun read<IntElement>(aggregator: &Aggregator<IntElement>): IntElement;
+
+
+
+
+public fun snapshot<IntElement>(aggregator: &aggregator_v2::Aggregator<IntElement>): aggregator_v2::AggregatorSnapshot<IntElement>
+
+
+
+
+public native fun snapshot<IntElement>(aggregator: &Aggregator<IntElement>): AggregatorSnapshot<IntElement>;
+
+
+
+
+public fun create_snapshot<IntElement: copy, drop>(value: IntElement): aggregator_v2::AggregatorSnapshot<IntElement>
+
+
+
+
+public native fun create_snapshot<IntElement: copy + drop>(value: IntElement): AggregatorSnapshot<IntElement>;
+
+
+
+
+public fun read_snapshot<IntElement>(snapshot: &aggregator_v2::AggregatorSnapshot<IntElement>): IntElement
+
+
+
+
+public native fun read_snapshot<IntElement>(snapshot: &AggregatorSnapshot<IntElement>): IntElement;
+
+
+
+
+public fun read_derived_string(snapshot: &aggregator_v2::DerivedStringSnapshot): string::String
+
+
+
+
+public native fun read_derived_string(snapshot: &DerivedStringSnapshot): String;
+
+
+
+
+public fun create_derived_string(value: string::String): aggregator_v2::DerivedStringSnapshot
+
+
+
+
+public native fun create_derived_string(value: String): DerivedStringSnapshot;
+
+
+
+
+before
, snapshot
and after
into a single string.
+snapshot passed needs to have integer type - currently supported types are u64 and u128.
+Raises EUNSUPPORTED_AGGREGATOR_SNAPSHOT_TYPE if called with another type.
+If length of prefix and suffix together exceed 256 bytes, ECONCAT_STRING_LENGTH_TOO_LARGE is raised.
+
+Parallelism info: This operation enables parallelism.
+
+
+public fun derive_string_concat<IntElement>(before: string::String, snapshot: &aggregator_v2::AggregatorSnapshot<IntElement>, after: string::String): aggregator_v2::DerivedStringSnapshot
+
+
+
+
+public native fun derive_string_concat<IntElement>(before: String, snapshot: &AggregatorSnapshot<IntElement>, after: String): DerivedStringSnapshot;
+
+
+
+
+#[deprecated]
+public fun copy_snapshot<IntElement: copy, drop>(snapshot: &aggregator_v2::AggregatorSnapshot<IntElement>): aggregator_v2::AggregatorSnapshot<IntElement>
+
+
+
+
+public native fun copy_snapshot<IntElement: copy + drop>(snapshot: &AggregatorSnapshot<IntElement>): AggregatorSnapshot<IntElement>;
+
+
+
+
+#[deprecated]
+public fun string_concat<IntElement>(before: string::String, snapshot: &aggregator_v2::AggregatorSnapshot<IntElement>, after: string::String): aggregator_v2::AggregatorSnapshot<string::String>
+
+
+
+
+public native fun string_concat<IntElement>(before: String, snapshot: &AggregatorSnapshot<IntElement>, after: String): AggregatorSnapshot<String>;
+
+
+
+
+public fun create_aggregator<IntElement: copy, drop>(max_value: IntElement): aggregator_v2::Aggregator<IntElement>
+
+
+
+
+
+pragma opaque;
+
+
+
+
+
+
+### Function `create_unbounded_aggregator`
+
+
+public fun create_unbounded_aggregator<IntElement: copy, drop>(): aggregator_v2::Aggregator<IntElement>
+
+
+
+
+
+pragma opaque;
+
+
+
+
+
+
+### Function `try_add`
+
+
+public fun try_add<IntElement>(aggregator: &mut aggregator_v2::Aggregator<IntElement>, value: IntElement): bool
+
+
+
+
+
+pragma opaque;
+
+
+
+
+
+
+### Function `try_sub`
+
+
+public fun try_sub<IntElement>(aggregator: &mut aggregator_v2::Aggregator<IntElement>, value: IntElement): bool
+
+
+
+
+
+pragma opaque;
+
+
+
+
+
+
+### Function `is_at_least_impl`
+
+
+fun is_at_least_impl<IntElement>(aggregator: &aggregator_v2::Aggregator<IntElement>, min_amount: IntElement): bool
+
+
+
+
+
+pragma opaque;
+
+
+
+
+
+
+### Function `read`
+
+
+public fun read<IntElement>(aggregator: &aggregator_v2::Aggregator<IntElement>): IntElement
+
+
+
+
+
+pragma opaque;
+
+
+
+
+
+
+### Function `snapshot`
+
+
+public fun snapshot<IntElement>(aggregator: &aggregator_v2::Aggregator<IntElement>): aggregator_v2::AggregatorSnapshot<IntElement>
+
+
+
+
+
+pragma opaque;
+
+
+
+
+
+
+### Function `create_snapshot`
+
+
+public fun create_snapshot<IntElement: copy, drop>(value: IntElement): aggregator_v2::AggregatorSnapshot<IntElement>
+
+
+
+
+
+pragma opaque;
+
+
+
+
+
+
+### Function `copy_snapshot`
+
+
+#[deprecated]
+public fun copy_snapshot<IntElement: copy, drop>(snapshot: &aggregator_v2::AggregatorSnapshot<IntElement>): aggregator_v2::AggregatorSnapshot<IntElement>
+
+
+
+
+
+pragma opaque;
+
+
+
+
+
+
+### Function `string_concat`
+
+
+#[deprecated]
+public fun string_concat<IntElement>(before: string::String, snapshot: &aggregator_v2::AggregatorSnapshot<IntElement>, after: string::String): aggregator_v2::AggregatorSnapshot<string::String>
+
+
+
+
+
+pragma opaque;
+
+
+
+[move-book]: https://aptos.dev/move/book/SUMMARY
diff --git a/aptos-move/framework/aptos-framework/tests/compiler-v2-doc/aptos_account.md b/aptos-move/framework/aptos-framework/tests/compiler-v2-doc/aptos_account.md
new file mode 100644
index 0000000000000..3c0bd1d9d9152
--- /dev/null
+++ b/aptos-move/framework/aptos-framework/tests/compiler-v2-doc/aptos_account.md
@@ -0,0 +1,1252 @@
+
+
+
+# Module `0x1::aptos_account`
+
+
+
+- [Resource `DirectTransferConfig`](#0x1_aptos_account_DirectTransferConfig)
+- [Struct `DirectCoinTransferConfigUpdatedEvent`](#0x1_aptos_account_DirectCoinTransferConfigUpdatedEvent)
+- [Struct `DirectCoinTransferConfigUpdated`](#0x1_aptos_account_DirectCoinTransferConfigUpdated)
+- [Constants](#@Constants_0)
+- [Function `create_account`](#0x1_aptos_account_create_account)
+- [Function `batch_transfer`](#0x1_aptos_account_batch_transfer)
+- [Function `transfer`](#0x1_aptos_account_transfer)
+- [Function `batch_transfer_coins`](#0x1_aptos_account_batch_transfer_coins)
+- [Function `transfer_coins`](#0x1_aptos_account_transfer_coins)
+- [Function `deposit_coins`](#0x1_aptos_account_deposit_coins)
+- [Function `assert_account_exists`](#0x1_aptos_account_assert_account_exists)
+- [Function `assert_account_is_registered_for_apt`](#0x1_aptos_account_assert_account_is_registered_for_apt)
+- [Function `set_allow_direct_coin_transfers`](#0x1_aptos_account_set_allow_direct_coin_transfers)
+- [Function `can_receive_direct_coin_transfers`](#0x1_aptos_account_can_receive_direct_coin_transfers)
+- [Function `register_apt`](#0x1_aptos_account_register_apt)
+- [Function `fungible_transfer_only`](#0x1_aptos_account_fungible_transfer_only)
+- [Function `is_fungible_balance_at_least`](#0x1_aptos_account_is_fungible_balance_at_least)
+- [Function `burn_from_fungible_store`](#0x1_aptos_account_burn_from_fungible_store)
+- [Function `ensure_primary_fungible_store_exists`](#0x1_aptos_account_ensure_primary_fungible_store_exists)
+- [Function `primary_fungible_store_address`](#0x1_aptos_account_primary_fungible_store_address)
+- [Specification](#@Specification_1)
+ - [High-level Requirements](#high-level-req)
+ - [Module-level Specification](#module-level-spec)
+ - [Function `create_account`](#@Specification_1_create_account)
+ - [Function `batch_transfer`](#@Specification_1_batch_transfer)
+ - [Function `transfer`](#@Specification_1_transfer)
+ - [Function `batch_transfer_coins`](#@Specification_1_batch_transfer_coins)
+ - [Function `transfer_coins`](#@Specification_1_transfer_coins)
+ - [Function `deposit_coins`](#@Specification_1_deposit_coins)
+ - [Function `assert_account_exists`](#@Specification_1_assert_account_exists)
+ - [Function `assert_account_is_registered_for_apt`](#@Specification_1_assert_account_is_registered_for_apt)
+ - [Function `set_allow_direct_coin_transfers`](#@Specification_1_set_allow_direct_coin_transfers)
+ - [Function `can_receive_direct_coin_transfers`](#@Specification_1_can_receive_direct_coin_transfers)
+ - [Function `register_apt`](#@Specification_1_register_apt)
+ - [Function `fungible_transfer_only`](#@Specification_1_fungible_transfer_only)
+ - [Function `is_fungible_balance_at_least`](#@Specification_1_is_fungible_balance_at_least)
+ - [Function `burn_from_fungible_store`](#@Specification_1_burn_from_fungible_store)
+
+
+use 0x1::account;
+use 0x1::aptos_coin;
+use 0x1::coin;
+use 0x1::create_signer;
+use 0x1::error;
+use 0x1::event;
+use 0x1::features;
+use 0x1::fungible_asset;
+use 0x1::object;
+use 0x1::primary_fungible_store;
+use 0x1::signer;
+
+
+
+
+
+
+## Resource `DirectTransferConfig`
+
+Configuration for whether an account can receive direct transfers of coins that they have not registered.
+
+By default, this is enabled. Users can opt-out by disabling at any time.
+
+
+struct DirectTransferConfig has key
+
+
+
+
+allow_arbitrary_coin_transfers: bool
+update_coin_transfer_events: event::EventHandle<aptos_account::DirectCoinTransferConfigUpdatedEvent>
+struct DirectCoinTransferConfigUpdatedEvent has drop, store
+
+
+
+
+new_allow_direct_transfers: bool
+#[event]
+struct DirectCoinTransferConfigUpdated has drop, store
+
+
+
+
+account: address
+new_allow_direct_transfers: bool
+const EACCOUNT_DOES_NOT_ACCEPT_DIRECT_COIN_TRANSFERS: u64 = 3;
+
+
+
+
+
+
+Account opted out of directly receiving NFT tokens.
+
+
+const EACCOUNT_DOES_NOT_ACCEPT_DIRECT_TOKEN_TRANSFERS: u64 = 4;
+
+
+
+
+
+
+Account does not exist.
+
+
+const EACCOUNT_NOT_FOUND: u64 = 1;
+
+
+
+
+
+
+Account is not registered to receive APT.
+
+
+const EACCOUNT_NOT_REGISTERED_FOR_APT: u64 = 2;
+
+
+
+
+
+
+The lengths of the recipients and amounts lists don't match.
+
+
+const EMISMATCHING_RECIPIENTS_AND_AMOUNTS_LENGTH: u64 = 5;
+
+
+
+
+
+
+## Function `create_account`
+
+Basic account creation methods.
+
+
+public entry fun create_account(auth_key: address)
+
+
+
+
+public entry fun create_account(auth_key: address) {
+ let account_signer = account::create_account(auth_key);
+ register_apt(&account_signer);
+}
+
+
+
+
+public entry fun batch_transfer(source: &signer, recipients: vector<address>, amounts: vector<u64>)
+
+
+
+
+public entry fun batch_transfer(source: &signer, recipients: vector<address>, amounts: vector<u64>) {
+ let recipients_len = vector::length(&recipients);
+ assert!(
+ recipients_len == vector::length(&amounts),
+ error::invalid_argument(EMISMATCHING_RECIPIENTS_AND_AMOUNTS_LENGTH),
+ );
+
+ vector::enumerate_ref(&recipients, |i, to| {
+ let amount = *vector::borrow(&amounts, i);
+ transfer(source, *to, amount);
+ });
+}
+
+
+
+
+public entry fun transfer(source: &signer, to: address, amount: u64)
+
+
+
+
+public entry fun transfer(source: &signer, to: address, amount: u64) {
+ if (!account::exists_at(to)) {
+ create_account(to)
+ };
+
+ if (features::operations_default_to_fa_apt_store_enabled()) {
+ fungible_transfer_only(source, to, amount)
+ } else {
+ // Resource accounts can be created without registering them to receive APT.
+ // This conveniently does the registration if necessary.
+ if (!coin::is_account_registered<AptosCoin>(to)) {
+ coin::register<AptosCoin>(&create_signer(to));
+ };
+ coin::transfer<AptosCoin>(source, to, amount)
+ }
+}
+
+
+
+
+public entry fun batch_transfer_coins<CoinType>(from: &signer, recipients: vector<address>, amounts: vector<u64>)
+
+
+
+
+public entry fun batch_transfer_coins<CoinType>(
+ from: &signer, recipients: vector<address>, amounts: vector<u64>) acquires DirectTransferConfig {
+ let recipients_len = vector::length(&recipients);
+ assert!(
+ recipients_len == vector::length(&amounts),
+ error::invalid_argument(EMISMATCHING_RECIPIENTS_AND_AMOUNTS_LENGTH),
+ );
+
+ vector::enumerate_ref(&recipients, |i, to| {
+ let amount = *vector::borrow(&amounts, i);
+ transfer_coins<CoinType>(from, *to, amount);
+ });
+}
+
+
+
+
+public entry fun transfer_coins<CoinType>(from: &signer, to: address, amount: u64)
+
+
+
+
+public entry fun transfer_coins<CoinType>(from: &signer, to: address, amount: u64) acquires DirectTransferConfig {
+ deposit_coins(to, coin::withdraw<CoinType>(from, amount));
+}
+
+
+
+
+public fun deposit_coins<CoinType>(to: address, coins: coin::Coin<CoinType>)
+
+
+
+
+public fun deposit_coins<CoinType>(to: address, coins: Coin<CoinType>) acquires DirectTransferConfig {
+ if (!account::exists_at(to)) {
+ create_account(to);
+ spec {
+ assert coin::spec_is_account_registered<AptosCoin>(to);
+ assume aptos_std::type_info::type_of<CoinType>() == aptos_std::type_info::type_of<AptosCoin>() ==>
+ coin::spec_is_account_registered<CoinType>(to);
+ };
+ };
+ if (!coin::is_account_registered<CoinType>(to)) {
+ assert!(
+ can_receive_direct_coin_transfers(to),
+ error::permission_denied(EACCOUNT_DOES_NOT_ACCEPT_DIRECT_COIN_TRANSFERS),
+ );
+ coin::register<CoinType>(&create_signer(to));
+ };
+ coin::deposit<CoinType>(to, coins)
+}
+
+
+
+
+public fun assert_account_exists(addr: address)
+
+
+
+
+public fun assert_account_exists(addr: address) {
+ assert!(account::exists_at(addr), error::not_found(EACCOUNT_NOT_FOUND));
+}
+
+
+
+
+public fun assert_account_is_registered_for_apt(addr: address)
+
+
+
+
+public fun assert_account_is_registered_for_apt(addr: address) {
+ assert_account_exists(addr);
+ assert!(coin::is_account_registered<AptosCoin>(addr), error::not_found(EACCOUNT_NOT_REGISTERED_FOR_APT));
+}
+
+
+
+
+account
can receive direct transfers of coins that they have not explicitly registered to receive.
+
+
+public entry fun set_allow_direct_coin_transfers(account: &signer, allow: bool)
+
+
+
+
+public entry fun set_allow_direct_coin_transfers(account: &signer, allow: bool) acquires DirectTransferConfig {
+ let addr = signer::address_of(account);
+ if (exists<DirectTransferConfig>(addr)) {
+ let direct_transfer_config = borrow_global_mut<DirectTransferConfig>(addr);
+ // Short-circuit to avoid emitting an event if direct transfer config is not changing.
+ if (direct_transfer_config.allow_arbitrary_coin_transfers == allow) {
+ return
+ };
+
+ direct_transfer_config.allow_arbitrary_coin_transfers = allow;
+
+ if (std::features::module_event_migration_enabled()) {
+ emit(DirectCoinTransferConfigUpdated { account: addr, new_allow_direct_transfers: allow });
+ };
+ emit_event(
+ &mut direct_transfer_config.update_coin_transfer_events,
+ DirectCoinTransferConfigUpdatedEvent { new_allow_direct_transfers: allow });
+ } else {
+ let direct_transfer_config = DirectTransferConfig {
+ allow_arbitrary_coin_transfers: allow,
+ update_coin_transfer_events: new_event_handle<DirectCoinTransferConfigUpdatedEvent>(account),
+ };
+ if (std::features::module_event_migration_enabled()) {
+ emit(DirectCoinTransferConfigUpdated { account: addr, new_allow_direct_transfers: allow });
+ };
+ emit_event(
+ &mut direct_transfer_config.update_coin_transfer_events,
+ DirectCoinTransferConfigUpdatedEvent { new_allow_direct_transfers: allow });
+ move_to(account, direct_transfer_config);
+ };
+}
+
+
+
+
+account
can receive direct transfers of coins that they have not explicitly registered to
+receive.
+
+By default, this returns true if an account has not explicitly set whether the can receive direct transfers.
+
+
+#[view]
+public fun can_receive_direct_coin_transfers(account: address): bool
+
+
+
+
+public fun can_receive_direct_coin_transfers(account: address): bool acquires DirectTransferConfig {
+ !exists<DirectTransferConfig>(account) ||
+ borrow_global<DirectTransferConfig>(account).allow_arbitrary_coin_transfers
+}
+
+
+
+
+public(friend) fun register_apt(account_signer: &signer)
+
+
+
+
+public(friend) fun register_apt(account_signer: &signer) {
+ if (features::new_accounts_default_to_fa_apt_store_enabled()) {
+ ensure_primary_fungible_store_exists(signer::address_of(account_signer));
+ } else {
+ coin::register<AptosCoin>(account_signer);
+ }
+}
+
+
+
+
+fun fungible_transfer_only(source: &signer, to: address, amount: u64)
+
+
+
+
+fun fungible_transfer_only(
+ source: &signer, to: address, amount: u64
+) {
+ let sender_store = ensure_primary_fungible_store_exists(signer::address_of(source));
+ let recipient_store = ensure_primary_fungible_store_exists(to);
+
+ // use internal APIs, as they skip:
+ // - owner, frozen and dispatchable checks
+ // as APT cannot be frozen or have dispatch, and PFS cannot be transfered
+ // (PFS could potentially be burned. regular transfer would permanently unburn the store.
+ // Ignoring the check here has the equivalent of unburning, transfers, and then burning again)
+ fungible_asset::deposit_internal(recipient_store, fungible_asset::withdraw_internal(sender_store, amount));
+}
+
+
+
+
+public(friend) fun is_fungible_balance_at_least(account: address, amount: u64): bool
+
+
+
+
+public(friend) fun is_fungible_balance_at_least(account: address, amount: u64): bool {
+ let store_addr = primary_fungible_store_address(account);
+ fungible_asset::is_address_balance_at_least(store_addr, amount)
+}
+
+
+
+
+public(friend) fun burn_from_fungible_store(ref: &fungible_asset::BurnRef, account: address, amount: u64)
+
+
+
+
+public(friend) fun burn_from_fungible_store(
+ ref: &BurnRef,
+ account: address,
+ amount: u64,
+) {
+ // Skip burning if amount is zero. This shouldn't error out as it's called as part of transaction fee burning.
+ if (amount != 0) {
+ let store_addr = primary_fungible_store_address(account);
+ fungible_asset::address_burn_from(ref, store_addr, amount);
+ };
+}
+
+
+
+
+fun ensure_primary_fungible_store_exists(owner: address): address
+
+
+
+
+inline fun ensure_primary_fungible_store_exists(owner: address): address {
+ let store_addr = primary_fungible_store_address(owner);
+ if (fungible_asset::store_exists(store_addr)) {
+ store_addr
+ } else {
+ object::object_address(&primary_fungible_store::create_primary_store(owner, object::address_to_object<Metadata>(@aptos_fungible_asset)))
+ }
+}
+
+
+
+
+fun primary_fungible_store_address(account: address): address
+
+
+
+
+inline fun primary_fungible_store_address(account: address): address {
+ object::create_user_derived_object_address(account, @aptos_fungible_asset)
+}
+
+
+
+
+No. | Requirement | Criticality | Implementation | Enforcement | +
---|---|---|---|---|
1 | +During the creation of an Aptos account the following rules should hold: (1) the authentication key should be 32 bytes in length, (2) an Aptos account should not already exist for that authentication key, and (3) the address of the authentication key should not be equal to a reserved address (0x0, 0x1, or 0x3). | +Critical | +The authentication key which is passed in as an argument to create_account should satisfy all necessary conditions. | +Formally verified via CreateAccountAbortsIf. | +
2 | +After creating an Aptos account, the account should become registered to receive AptosCoin. | +Critical | +The create_account function creates a new account for the particular address and registers AptosCoin. | +Formally verified via create_account. | +
3 | +An account may receive a direct transfer of coins they have not registered for if and only if the transfer of arbitrary coins is enabled. By default the option should always set to be enabled for an account. | +Low | +Transfers of a coin to an account that has not yet registered for that coin should abort if and only if the allow_arbitrary_coin_transfers flag is explicitly set to false. | +Formally verified via can_receive_direct_coin_transfers. | +
4 | +Setting direct coin transfers may only occur if and only if a direct transfer config is associated with the provided account address. | +Low | +The set_allow_direct_coin_transfers function ensures the DirectTransferConfig structure exists for the signer. | +Formally verified via set_allow_direct_coin_transfers. | +
5 | +The transfer function should ensure an account is created for the provided destination if one does not exist; then, register AptosCoin for that account if a particular is unregistered before transferring the amount. | +Critical | +The transfer function checks if the recipient account exists. If the account does not exist, the function creates one and registers the account to AptosCoin if not registered. | +Formally verified via transfer. | +
6 | +Creating an account for the provided destination and registering it for that particular CoinType should be the only way to enable depositing coins, provided the account does not already exist. | +Critical | +The deposit_coins function verifies if the recipient account exists. If the account does not exist, the function creates one and ensures that the account becomes registered for the specified CointType. | +Formally verified via deposit_coins. | +
7 | +When performing a batch transfer of Aptos Coin and/or a batch transfer of a custom coin type, it should ensure that the vector containing destination addresses and the vector containing the corresponding amounts are equal in length. | +Low | +The batch_transfer and batch_transfer_coins functions verify that the length of the recipient addresses vector matches the length of the amount vector through an assertion. | +Formally verified via batch_transfer_coins. | +
pragma aborts_if_is_strict;
+
+
+
+
+
+
+### Function `create_account`
+
+
+public entry fun create_account(auth_key: address)
+
+
+
+Check if the bytes of the auth_key is 32.
+The Account does not exist under the auth_key before creating the account.
+Limit the address of auth_key is not @vm_reserved / @aptos_framework / @aptos_toke.
+
+
+// This enforces high-level requirement 1:
+pragma aborts_if_is_partial;
+include CreateAccountAbortsIf;
+ensures exists<account::Account>(auth_key);
+
+
+
+
+
+
+
+
+schema CreateAccountAbortsIf {
+ auth_key: address;
+ aborts_if exists<account::Account>(auth_key);
+ aborts_if length_judgment(auth_key);
+ aborts_if auth_key == @vm_reserved || auth_key == @aptos_framework || auth_key == @aptos_token;
+}
+
+
+
+
+
+
+
+
+fun length_judgment(auth_key: address): bool {
+ use std::bcs;
+
+ let authentication_key = bcs::to_bytes(auth_key);
+ len(authentication_key) != 32
+}
+
+
+
+
+
+
+### Function `batch_transfer`
+
+
+public entry fun batch_transfer(source: &signer, recipients: vector<address>, amounts: vector<u64>)
+
+
+
+
+
+pragma verify = false;
+let account_addr_source = signer::address_of(source);
+let coin_store_source = global<coin::CoinStore<AptosCoin>>(account_addr_source);
+let balance_source = coin_store_source.coin.value;
+requires forall i in 0..len(recipients):
+ recipients[i] != account_addr_source;
+requires exists i in 0..len(recipients):
+ amounts[i] > 0;
+aborts_if len(recipients) != len(amounts);
+aborts_if exists i in 0..len(recipients):
+ !account::exists_at(recipients[i]) && length_judgment(recipients[i]);
+aborts_if exists i in 0..len(recipients):
+ !account::exists_at(recipients[i]) && (recipients[i] == @vm_reserved || recipients[i] == @aptos_framework || recipients[i] == @aptos_token);
+ensures forall i in 0..len(recipients):
+ (!account::exists_at(recipients[i]) ==> !length_judgment(recipients[i])) &&
+ (!account::exists_at(recipients[i]) ==> (recipients[i] != @vm_reserved && recipients[i] != @aptos_framework && recipients[i] != @aptos_token));
+aborts_if exists i in 0..len(recipients):
+ !exists<coin::CoinStore<AptosCoin>>(account_addr_source);
+aborts_if exists i in 0..len(recipients):
+ coin_store_source.frozen;
+aborts_if exists i in 0..len(recipients):
+ global<coin::CoinStore<AptosCoin>>(account_addr_source).coin.value < amounts[i];
+aborts_if exists i in 0..len(recipients):
+ exists<coin::CoinStore<AptosCoin>>(recipients[i]) && global<coin::CoinStore<AptosCoin>>(recipients[i]).frozen;
+aborts_if exists i in 0..len(recipients):
+ account::exists_at(recipients[i]) && !exists<coin::CoinStore<AptosCoin>>(recipients[i]) && global<account::Account>(recipients[i]).guid_creation_num + 2 >= account::MAX_GUID_CREATION_NUM;
+aborts_if exists i in 0..len(recipients):
+ account::exists_at(recipients[i]) && !exists<coin::CoinStore<AptosCoin>>(recipients[i]) && global<account::Account>(recipients[i]).guid_creation_num + 2 > MAX_U64;
+
+
+
+
+
+
+### Function `transfer`
+
+
+public entry fun transfer(source: &signer, to: address, amount: u64)
+
+
+
+
+
+pragma verify = false;
+let account_addr_source = signer::address_of(source);
+requires account_addr_source != to;
+include CreateAccountTransferAbortsIf;
+include GuidAbortsIf<AptosCoin>;
+include WithdrawAbortsIf<AptosCoin>{from: source};
+include TransferEnsures<AptosCoin>;
+aborts_if exists<coin::CoinStore<AptosCoin>>(to) && global<coin::CoinStore<AptosCoin>>(to).frozen;
+// This enforces high-level requirement 5:
+ensures exists<aptos_framework::account::Account>(to);
+ensures exists<coin::CoinStore<AptosCoin>>(to);
+
+
+
+
+
+
+### Function `batch_transfer_coins`
+
+
+public entry fun batch_transfer_coins<CoinType>(from: &signer, recipients: vector<address>, amounts: vector<u64>)
+
+
+
+
+
+pragma verify = false;
+let account_addr_source = signer::address_of(from);
+let coin_store_source = global<coin::CoinStore<CoinType>>(account_addr_source);
+let balance_source = coin_store_source.coin.value;
+requires forall i in 0..len(recipients):
+ recipients[i] != account_addr_source;
+requires exists i in 0..len(recipients):
+ amounts[i] > 0;
+// This enforces high-level requirement 7:
+aborts_if len(recipients) != len(amounts);
+aborts_if exists i in 0..len(recipients):
+ !account::exists_at(recipients[i]) && length_judgment(recipients[i]);
+aborts_if exists i in 0..len(recipients):
+ !account::exists_at(recipients[i]) && (recipients[i] == @vm_reserved || recipients[i] == @aptos_framework || recipients[i] == @aptos_token);
+ensures forall i in 0..len(recipients):
+ (!account::exists_at(recipients[i]) ==> !length_judgment(recipients[i])) &&
+ (!account::exists_at(recipients[i]) ==> (recipients[i] != @vm_reserved && recipients[i] != @aptos_framework && recipients[i] != @aptos_token));
+aborts_if exists i in 0..len(recipients):
+ !exists<coin::CoinStore<CoinType>>(account_addr_source);
+aborts_if exists i in 0..len(recipients):
+ coin_store_source.frozen;
+aborts_if exists i in 0..len(recipients):
+ global<coin::CoinStore<CoinType>>(account_addr_source).coin.value < amounts[i];
+aborts_if exists i in 0..len(recipients):
+ exists<coin::CoinStore<CoinType>>(recipients[i]) && global<coin::CoinStore<CoinType>>(recipients[i]).frozen;
+aborts_if exists i in 0..len(recipients):
+ account::exists_at(recipients[i]) && !exists<coin::CoinStore<CoinType>>(recipients[i]) && global<account::Account>(recipients[i]).guid_creation_num + 2 >= account::MAX_GUID_CREATION_NUM;
+aborts_if exists i in 0..len(recipients):
+ account::exists_at(recipients[i]) && !exists<coin::CoinStore<CoinType>>(recipients[i]) && global<account::Account>(recipients[i]).guid_creation_num + 2 > MAX_U64;
+aborts_if exists i in 0..len(recipients):
+ !coin::spec_is_account_registered<CoinType>(recipients[i]) && !type_info::spec_is_struct<CoinType>();
+
+
+
+
+
+
+### Function `transfer_coins`
+
+
+public entry fun transfer_coins<CoinType>(from: &signer, to: address, amount: u64)
+
+
+
+
+
+pragma verify = false;
+let account_addr_source = signer::address_of(from);
+requires account_addr_source != to;
+include CreateAccountTransferAbortsIf;
+include WithdrawAbortsIf<CoinType>;
+include GuidAbortsIf<CoinType>;
+include RegistCoinAbortsIf<CoinType>;
+include TransferEnsures<CoinType>;
+aborts_if exists<coin::CoinStore<CoinType>>(to) && global<coin::CoinStore<CoinType>>(to).frozen;
+ensures exists<aptos_framework::account::Account>(to);
+ensures exists<aptos_framework::coin::CoinStore<CoinType>>(to);
+
+
+
+
+
+
+### Function `deposit_coins`
+
+
+public fun deposit_coins<CoinType>(to: address, coins: coin::Coin<CoinType>)
+
+
+
+
+
+pragma verify = false;
+include CreateAccountTransferAbortsIf;
+include GuidAbortsIf<CoinType>;
+include RegistCoinAbortsIf<CoinType>;
+let if_exist_coin = exists<coin::CoinStore<CoinType>>(to);
+aborts_if if_exist_coin && global<coin::CoinStore<CoinType>>(to).frozen;
+// This enforces high-level requirement 6:
+ensures exists<aptos_framework::account::Account>(to);
+ensures exists<aptos_framework::coin::CoinStore<CoinType>>(to);
+let coin_store_to = global<coin::CoinStore<CoinType>>(to).coin.value;
+let post post_coin_store_to = global<coin::CoinStore<CoinType>>(to).coin.value;
+ensures if_exist_coin ==> post_coin_store_to == coin_store_to + coins.value;
+
+
+
+
+
+
+### Function `assert_account_exists`
+
+
+public fun assert_account_exists(addr: address)
+
+
+
+
+
+aborts_if !account::exists_at(addr);
+
+
+
+
+
+
+### Function `assert_account_is_registered_for_apt`
+
+
+public fun assert_account_is_registered_for_apt(addr: address)
+
+
+
+Check if the address existed.
+Check if the AptosCoin under the address existed.
+
+
+pragma aborts_if_is_partial;
+aborts_if !account::exists_at(addr);
+aborts_if !coin::spec_is_account_registered<AptosCoin>(addr);
+
+
+
+
+
+
+### Function `set_allow_direct_coin_transfers`
+
+
+public entry fun set_allow_direct_coin_transfers(account: &signer, allow: bool)
+
+
+
+
+
+pragma verify = false;
+
+
+
+
+
+
+### Function `can_receive_direct_coin_transfers`
+
+
+#[view]
+public fun can_receive_direct_coin_transfers(account: address): bool
+
+
+
+
+
+aborts_if false;
+// This enforces high-level requirement 3:
+ensures result == (
+ !exists<DirectTransferConfig>(account) ||
+ global<DirectTransferConfig>(account).allow_arbitrary_coin_transfers
+);
+
+
+
+
+
+
+### Function `register_apt`
+
+
+public(friend) fun register_apt(account_signer: &signer)
+
+
+
+
+
+pragma verify = false;
+
+
+
+
+
+
+### Function `fungible_transfer_only`
+
+
+fun fungible_transfer_only(source: &signer, to: address, amount: u64)
+
+
+
+
+
+pragma verify = false;
+
+
+
+
+
+
+### Function `is_fungible_balance_at_least`
+
+
+public(friend) fun is_fungible_balance_at_least(account: address, amount: u64): bool
+
+
+
+
+
+pragma verify = false;
+
+
+
+
+
+
+### Function `burn_from_fungible_store`
+
+
+public(friend) fun burn_from_fungible_store(ref: &fungible_asset::BurnRef, account: address, amount: u64)
+
+
+
+
+
+pragma verify = false;
+
+
+
+
+
+
+
+
+schema CreateAccountTransferAbortsIf {
+ to: address;
+ aborts_if !account::exists_at(to) && length_judgment(to);
+ aborts_if !account::exists_at(to) && (to == @vm_reserved || to == @aptos_framework || to == @aptos_token);
+}
+
+
+
+
+
+
+
+
+schema WithdrawAbortsIf<CoinType> {
+ from: &signer;
+ amount: u64;
+ let account_addr_source = signer::address_of(from);
+ let coin_store_source = global<coin::CoinStore<CoinType>>(account_addr_source);
+ let balance_source = coin_store_source.coin.value;
+ aborts_if !exists<coin::CoinStore<CoinType>>(account_addr_source);
+ aborts_if coin_store_source.frozen;
+ aborts_if balance_source < amount;
+}
+
+
+
+
+
+
+
+
+schema GuidAbortsIf<CoinType> {
+ to: address;
+ let acc = global<account::Account>(to);
+ aborts_if account::exists_at(to) && !exists<coin::CoinStore<CoinType>>(to) && acc.guid_creation_num + 2 >= account::MAX_GUID_CREATION_NUM;
+ aborts_if account::exists_at(to) && !exists<coin::CoinStore<CoinType>>(to) && acc.guid_creation_num + 2 > MAX_U64;
+}
+
+
+
+
+
+
+
+
+schema RegistCoinAbortsIf<CoinType> {
+ to: address;
+ aborts_if !coin::spec_is_account_registered<CoinType>(to) && !type_info::spec_is_struct<CoinType>();
+ aborts_if exists<aptos_framework::account::Account>(to);
+ aborts_if type_info::type_of<CoinType>() != type_info::type_of<AptosCoin>();
+}
+
+
+
+
+
+
+
+
+schema TransferEnsures<CoinType> {
+ to: address;
+ account_addr_source: address;
+ amount: u64;
+ let if_exist_account = exists<account::Account>(to);
+ let if_exist_coin = exists<coin::CoinStore<CoinType>>(to);
+ let coin_store_to = global<coin::CoinStore<CoinType>>(to);
+ let coin_store_source = global<coin::CoinStore<CoinType>>(account_addr_source);
+ let post p_coin_store_to = global<coin::CoinStore<CoinType>>(to);
+ let post p_coin_store_source = global<coin::CoinStore<CoinType>>(account_addr_source);
+ ensures coin_store_source.coin.value - amount == p_coin_store_source.coin.value;
+ ensures if_exist_account && if_exist_coin ==> coin_store_to.coin.value + amount == p_coin_store_to.coin.value;
+}
+
+
+
+[move-book]: https://aptos.dev/move/book/SUMMARY
diff --git a/aptos-move/framework/aptos-framework/tests/compiler-v2-doc/aptos_coin.md b/aptos-move/framework/aptos-framework/tests/compiler-v2-doc/aptos_coin.md
new file mode 100644
index 0000000000000..5f8c7c73e1502
--- /dev/null
+++ b/aptos-move/framework/aptos-framework/tests/compiler-v2-doc/aptos_coin.md
@@ -0,0 +1,658 @@
+
+
+
+# Module `0x1::aptos_coin`
+
+This module defines a minimal and generic Coin and Balance.
+modified from https://github.com/move-language/move/tree/main/language/documentation/tutorial
+
+
+- [Resource `AptosCoin`](#0x1_aptos_coin_AptosCoin)
+- [Resource `MintCapStore`](#0x1_aptos_coin_MintCapStore)
+- [Struct `DelegatedMintCapability`](#0x1_aptos_coin_DelegatedMintCapability)
+- [Resource `Delegations`](#0x1_aptos_coin_Delegations)
+- [Constants](#@Constants_0)
+- [Function `initialize`](#0x1_aptos_coin_initialize)
+- [Function `has_mint_capability`](#0x1_aptos_coin_has_mint_capability)
+- [Function `destroy_mint_cap`](#0x1_aptos_coin_destroy_mint_cap)
+- [Function `configure_accounts_for_test`](#0x1_aptos_coin_configure_accounts_for_test)
+- [Function `mint`](#0x1_aptos_coin_mint)
+- [Function `delegate_mint_capability`](#0x1_aptos_coin_delegate_mint_capability)
+- [Function `claim_mint_capability`](#0x1_aptos_coin_claim_mint_capability)
+- [Function `find_delegation`](#0x1_aptos_coin_find_delegation)
+- [Specification](#@Specification_1)
+ - [High-level Requirements](#high-level-req)
+ - [Module-level Specification](#module-level-spec)
+ - [Function `initialize`](#@Specification_1_initialize)
+ - [Function `destroy_mint_cap`](#@Specification_1_destroy_mint_cap)
+ - [Function `configure_accounts_for_test`](#@Specification_1_configure_accounts_for_test)
+ - [Function `mint`](#@Specification_1_mint)
+ - [Function `delegate_mint_capability`](#@Specification_1_delegate_mint_capability)
+ - [Function `claim_mint_capability`](#@Specification_1_claim_mint_capability)
+ - [Function `find_delegation`](#@Specification_1_find_delegation)
+
+
+use 0x1::coin;
+use 0x1::error;
+use 0x1::option;
+use 0x1::signer;
+use 0x1::string;
+use 0x1::system_addresses;
+use 0x1::vector;
+
+
+
+
+
+
+## Resource `AptosCoin`
+
+
+
+struct AptosCoin has key
+
+
+
+
+dummy_field: bool
+struct MintCapStore has key
+
+
+
+
+mint_cap: coin::MintCapability<aptos_coin::AptosCoin>
+struct DelegatedMintCapability has store
+
+
+
+
+to: address
+struct Delegations has key
+
+
+
+
+inner: vector<aptos_coin::DelegatedMintCapability>
+const EALREADY_DELEGATED: u64 = 2;
+
+
+
+
+
+
+Cannot find delegation of mint capability to this account
+
+
+const EDELEGATION_NOT_FOUND: u64 = 3;
+
+
+
+
+
+
+Account does not have mint capability
+
+
+const ENO_CAPABILITIES: u64 = 1;
+
+
+
+
+
+
+## Function `initialize`
+
+Can only called during genesis to initialize the Aptos coin.
+
+
+public(friend) fun initialize(aptos_framework: &signer): (coin::BurnCapability<aptos_coin::AptosCoin>, coin::MintCapability<aptos_coin::AptosCoin>)
+
+
+
+
+public(friend) fun initialize(aptos_framework: &signer): (BurnCapability<AptosCoin>, MintCapability<AptosCoin>) {
+ system_addresses::assert_aptos_framework(aptos_framework);
+
+ let (burn_cap, freeze_cap, mint_cap) = coin::initialize_with_parallelizable_supply<AptosCoin>(
+ aptos_framework,
+ string::utf8(b"Aptos Coin"),
+ string::utf8(b"APT"),
+ 8, // decimals
+ true, // monitor_supply
+ );
+
+ // Aptos framework needs mint cap to mint coins to initial validators. This will be revoked once the validators
+ // have been initialized.
+ move_to(aptos_framework, MintCapStore { mint_cap });
+
+ coin::destroy_freeze_cap(freeze_cap);
+ (burn_cap, mint_cap)
+}
+
+
+
+
+public fun has_mint_capability(account: &signer): bool
+
+
+
+
+public fun has_mint_capability(account: &signer): bool {
+ exists<MintCapStore>(signer::address_of(account))
+}
+
+
+
+
+public(friend) fun destroy_mint_cap(aptos_framework: &signer)
+
+
+
+
+public(friend) fun destroy_mint_cap(aptos_framework: &signer) acquires MintCapStore {
+ system_addresses::assert_aptos_framework(aptos_framework);
+ let MintCapStore { mint_cap } = move_from<MintCapStore>(@aptos_framework);
+ coin::destroy_mint_cap(mint_cap);
+}
+
+
+
+
+public(friend) fun configure_accounts_for_test(aptos_framework: &signer, core_resources: &signer, mint_cap: coin::MintCapability<aptos_coin::AptosCoin>)
+
+
+
+
+public(friend) fun configure_accounts_for_test(
+ aptos_framework: &signer,
+ core_resources: &signer,
+ mint_cap: MintCapability<AptosCoin>,
+) {
+ system_addresses::assert_aptos_framework(aptos_framework);
+
+ // Mint the core resource account AptosCoin for gas so it can execute system transactions.
+ let coins = coin::mint<AptosCoin>(
+ 18446744073709551615,
+ &mint_cap,
+ );
+ coin::deposit<AptosCoin>(signer::address_of(core_resources), coins);
+
+ move_to(core_resources, MintCapStore { mint_cap });
+ move_to(core_resources, Delegations { inner: vector::empty() });
+}
+
+
+
+
+public entry fun mint(account: &signer, dst_addr: address, amount: u64)
+
+
+
+
+public entry fun mint(
+ account: &signer,
+ dst_addr: address,
+ amount: u64,
+) acquires MintCapStore {
+ let account_addr = signer::address_of(account);
+
+ assert!(
+ exists<MintCapStore>(account_addr),
+ error::not_found(ENO_CAPABILITIES),
+ );
+
+ let mint_cap = &borrow_global<MintCapStore>(account_addr).mint_cap;
+ let coins_minted = coin::mint<AptosCoin>(amount, mint_cap);
+ coin::deposit<AptosCoin>(dst_addr, coins_minted);
+}
+
+
+
+
+public entry fun delegate_mint_capability(account: signer, to: address)
+
+
+
+
+public entry fun delegate_mint_capability(account: signer, to: address) acquires Delegations {
+ system_addresses::assert_core_resource(&account);
+ let delegations = &mut borrow_global_mut<Delegations>(@core_resources).inner;
+ vector::for_each_ref(delegations, |element| {
+ let element: &DelegatedMintCapability = element;
+ assert!(element.to != to, error::invalid_argument(EALREADY_DELEGATED));
+ });
+ vector::push_back(delegations, DelegatedMintCapability { to });
+}
+
+
+
+
+public entry fun claim_mint_capability(account: &signer)
+
+
+
+
+public entry fun claim_mint_capability(account: &signer) acquires Delegations, MintCapStore {
+ let maybe_index = find_delegation(signer::address_of(account));
+ assert!(option::is_some(&maybe_index), EDELEGATION_NOT_FOUND);
+ let idx = *option::borrow(&maybe_index);
+ let delegations = &mut borrow_global_mut<Delegations>(@core_resources).inner;
+ let DelegatedMintCapability { to: _ } = vector::swap_remove(delegations, idx);
+
+ // Make a copy of mint cap and give it to the specified account.
+ let mint_cap = borrow_global<MintCapStore>(@core_resources).mint_cap;
+ move_to(account, MintCapStore { mint_cap });
+}
+
+
+
+
+fun find_delegation(addr: address): option::Option<u64>
+
+
+
+
+fun find_delegation(addr: address): Option<u64> acquires Delegations {
+ let delegations = &borrow_global<Delegations>(@core_resources).inner;
+ let i = 0;
+ let len = vector::length(delegations);
+ let index = option::none();
+ while (i < len) {
+ let element = vector::borrow(delegations, i);
+ if (element.to == addr) {
+ index = option::some(i);
+ break
+ };
+ i = i + 1;
+ };
+ index
+}
+
+
+
+
+No. | Requirement | Criticality | Implementation | Enforcement | +
---|---|---|---|---|
1 | +The native token, APT, must be initialized during genesis. | +Medium | +The initialize function is only called once, during genesis. | +Formally verified via initialize. | +
2 | +The APT coin may only be created exactly once. | +Medium | +The initialization function may only be called once. | +Enforced through the coin module, which has been audited. | +
4 | +Any type of operation on the APT coin should fail if the user has not registered for the coin. | +Medium | +Coin operations may succeed only on valid user coin registration. | +Enforced through the coin module, which has been audited. | +
pragma verify = true;
+pragma aborts_if_is_strict;
+
+
+
+
+
+
+### Function `initialize`
+
+
+public(friend) fun initialize(aptos_framework: &signer): (coin::BurnCapability<aptos_coin::AptosCoin>, coin::MintCapability<aptos_coin::AptosCoin>)
+
+
+
+
+
+let addr = signer::address_of(aptos_framework);
+aborts_if addr != @aptos_framework;
+aborts_if !string::spec_internal_check_utf8(b"Aptos Coin");
+aborts_if !string::spec_internal_check_utf8(b"APT");
+aborts_if exists<MintCapStore>(addr);
+aborts_if exists<coin::CoinInfo<AptosCoin>>(addr);
+aborts_if !exists<aggregator_factory::AggregatorFactory>(addr);
+// This enforces high-level requirement 1:
+ensures exists<MintCapStore>(addr);
+// This enforces high-level requirement 3:
+ensures global<MintCapStore>(addr).mint_cap == MintCapability<AptosCoin> {};
+ensures exists<coin::CoinInfo<AptosCoin>>(addr);
+ensures result_1 == BurnCapability<AptosCoin> {};
+ensures result_2 == MintCapability<AptosCoin> {};
+
+
+
+
+
+
+### Function `destroy_mint_cap`
+
+
+public(friend) fun destroy_mint_cap(aptos_framework: &signer)
+
+
+
+
+
+let addr = signer::address_of(aptos_framework);
+aborts_if addr != @aptos_framework;
+aborts_if !exists<MintCapStore>(@aptos_framework);
+
+
+
+
+
+
+### Function `configure_accounts_for_test`
+
+
+public(friend) fun configure_accounts_for_test(aptos_framework: &signer, core_resources: &signer, mint_cap: coin::MintCapability<aptos_coin::AptosCoin>)
+
+
+
+
+
+pragma verify = false;
+
+
+
+
+
+
+### Function `mint`
+
+
+public entry fun mint(account: &signer, dst_addr: address, amount: u64)
+
+
+
+
+
+pragma verify = false;
+
+
+
+
+
+
+### Function `delegate_mint_capability`
+
+
+public entry fun delegate_mint_capability(account: signer, to: address)
+
+
+
+
+
+pragma verify = false;
+
+
+
+
+
+
+### Function `claim_mint_capability`
+
+
+public entry fun claim_mint_capability(account: &signer)
+
+
+
+
+
+pragma verify = false;
+
+
+
+
+
+
+### Function `find_delegation`
+
+
+fun find_delegation(addr: address): option::Option<u64>
+
+
+
+
+
+aborts_if !exists<Delegations>(@core_resources);
+
+
+
+
+
+
+
+
+schema ExistsAptosCoin {
+ requires exists<coin::CoinInfo<AptosCoin>>(@aptos_framework);
+}
+
+
+
+[move-book]: https://aptos.dev/move/book/SUMMARY
diff --git a/aptos-move/framework/aptos-framework/tests/compiler-v2-doc/aptos_governance.md b/aptos-move/framework/aptos-framework/tests/compiler-v2-doc/aptos_governance.md
new file mode 100644
index 0000000000000..1a666013b1907
--- /dev/null
+++ b/aptos-move/framework/aptos-framework/tests/compiler-v2-doc/aptos_governance.md
@@ -0,0 +1,3136 @@
+
+
+
+# Module `0x1::aptos_governance`
+
+
+AptosGovernance represents the on-chain governance of the Aptos network. Voting power is calculated based on the
+current epoch's voting power of the proposer or voter's backing stake pool. In addition, for it to count,
+the stake pool's lockup needs to be at least as long as the proposal's duration.
+
+It provides the following flow:
+1. Proposers can create a proposal by calling AptosGovernance::create_proposal. The proposer's backing stake pool
+needs to have the minimum proposer stake required. Off-chain components can subscribe to CreateProposalEvent to
+track proposal creation and proposal ids.
+2. Voters can vote on a proposal. Their voting power is derived from the backing stake pool. A stake pool can vote
+on a proposal multiple times as long as the total voting power of these votes doesn't exceed its total voting power.
+
+
+- [Resource `GovernanceResponsbility`](#0x1_aptos_governance_GovernanceResponsbility)
+- [Resource `GovernanceConfig`](#0x1_aptos_governance_GovernanceConfig)
+- [Struct `RecordKey`](#0x1_aptos_governance_RecordKey)
+- [Resource `VotingRecords`](#0x1_aptos_governance_VotingRecords)
+- [Resource `VotingRecordsV2`](#0x1_aptos_governance_VotingRecordsV2)
+- [Resource `ApprovedExecutionHashes`](#0x1_aptos_governance_ApprovedExecutionHashes)
+- [Resource `GovernanceEvents`](#0x1_aptos_governance_GovernanceEvents)
+- [Struct `CreateProposalEvent`](#0x1_aptos_governance_CreateProposalEvent)
+- [Struct `VoteEvent`](#0x1_aptos_governance_VoteEvent)
+- [Struct `UpdateConfigEvent`](#0x1_aptos_governance_UpdateConfigEvent)
+- [Struct `CreateProposal`](#0x1_aptos_governance_CreateProposal)
+- [Struct `Vote`](#0x1_aptos_governance_Vote)
+- [Struct `UpdateConfig`](#0x1_aptos_governance_UpdateConfig)
+- [Constants](#@Constants_0)
+- [Function `store_signer_cap`](#0x1_aptos_governance_store_signer_cap)
+- [Function `initialize`](#0x1_aptos_governance_initialize)
+- [Function `update_governance_config`](#0x1_aptos_governance_update_governance_config)
+- [Function `initialize_partial_voting`](#0x1_aptos_governance_initialize_partial_voting)
+- [Function `get_voting_duration_secs`](#0x1_aptos_governance_get_voting_duration_secs)
+- [Function `get_min_voting_threshold`](#0x1_aptos_governance_get_min_voting_threshold)
+- [Function `get_required_proposer_stake`](#0x1_aptos_governance_get_required_proposer_stake)
+- [Function `has_entirely_voted`](#0x1_aptos_governance_has_entirely_voted)
+- [Function `get_remaining_voting_power`](#0x1_aptos_governance_get_remaining_voting_power)
+- [Function `create_proposal`](#0x1_aptos_governance_create_proposal)
+- [Function `create_proposal_v2`](#0x1_aptos_governance_create_proposal_v2)
+- [Function `create_proposal_v2_impl`](#0x1_aptos_governance_create_proposal_v2_impl)
+- [Function `batch_vote`](#0x1_aptos_governance_batch_vote)
+- [Function `batch_partial_vote`](#0x1_aptos_governance_batch_partial_vote)
+- [Function `vote`](#0x1_aptos_governance_vote)
+- [Function `partial_vote`](#0x1_aptos_governance_partial_vote)
+- [Function `vote_internal`](#0x1_aptos_governance_vote_internal)
+- [Function `add_approved_script_hash_script`](#0x1_aptos_governance_add_approved_script_hash_script)
+- [Function `add_approved_script_hash`](#0x1_aptos_governance_add_approved_script_hash)
+- [Function `resolve`](#0x1_aptos_governance_resolve)
+- [Function `resolve_multi_step_proposal`](#0x1_aptos_governance_resolve_multi_step_proposal)
+- [Function `remove_approved_hash`](#0x1_aptos_governance_remove_approved_hash)
+- [Function `reconfigure`](#0x1_aptos_governance_reconfigure)
+- [Function `force_end_epoch`](#0x1_aptos_governance_force_end_epoch)
+- [Function `force_end_epoch_test_only`](#0x1_aptos_governance_force_end_epoch_test_only)
+- [Function `toggle_features`](#0x1_aptos_governance_toggle_features)
+- [Function `get_signer_testnet_only`](#0x1_aptos_governance_get_signer_testnet_only)
+- [Function `get_voting_power`](#0x1_aptos_governance_get_voting_power)
+- [Function `get_signer`](#0x1_aptos_governance_get_signer)
+- [Function `create_proposal_metadata`](#0x1_aptos_governance_create_proposal_metadata)
+- [Function `assert_voting_initialization`](#0x1_aptos_governance_assert_voting_initialization)
+- [Specification](#@Specification_1)
+ - [High-level Requirements](#high-level-req)
+ - [Module-level Specification](#module-level-spec)
+ - [Function `store_signer_cap`](#@Specification_1_store_signer_cap)
+ - [Function `initialize`](#@Specification_1_initialize)
+ - [Function `update_governance_config`](#@Specification_1_update_governance_config)
+ - [Function `initialize_partial_voting`](#@Specification_1_initialize_partial_voting)
+ - [Function `get_voting_duration_secs`](#@Specification_1_get_voting_duration_secs)
+ - [Function `get_min_voting_threshold`](#@Specification_1_get_min_voting_threshold)
+ - [Function `get_required_proposer_stake`](#@Specification_1_get_required_proposer_stake)
+ - [Function `has_entirely_voted`](#@Specification_1_has_entirely_voted)
+ - [Function `get_remaining_voting_power`](#@Specification_1_get_remaining_voting_power)
+ - [Function `create_proposal`](#@Specification_1_create_proposal)
+ - [Function `create_proposal_v2`](#@Specification_1_create_proposal_v2)
+ - [Function `create_proposal_v2_impl`](#@Specification_1_create_proposal_v2_impl)
+ - [Function `batch_vote`](#@Specification_1_batch_vote)
+ - [Function `batch_partial_vote`](#@Specification_1_batch_partial_vote)
+ - [Function `vote`](#@Specification_1_vote)
+ - [Function `partial_vote`](#@Specification_1_partial_vote)
+ - [Function `vote_internal`](#@Specification_1_vote_internal)
+ - [Function `add_approved_script_hash_script`](#@Specification_1_add_approved_script_hash_script)
+ - [Function `add_approved_script_hash`](#@Specification_1_add_approved_script_hash)
+ - [Function `resolve`](#@Specification_1_resolve)
+ - [Function `resolve_multi_step_proposal`](#@Specification_1_resolve_multi_step_proposal)
+ - [Function `remove_approved_hash`](#@Specification_1_remove_approved_hash)
+ - [Function `reconfigure`](#@Specification_1_reconfigure)
+ - [Function `force_end_epoch`](#@Specification_1_force_end_epoch)
+ - [Function `force_end_epoch_test_only`](#@Specification_1_force_end_epoch_test_only)
+ - [Function `toggle_features`](#@Specification_1_toggle_features)
+ - [Function `get_signer_testnet_only`](#@Specification_1_get_signer_testnet_only)
+ - [Function `get_voting_power`](#@Specification_1_get_voting_power)
+ - [Function `get_signer`](#@Specification_1_get_signer)
+ - [Function `create_proposal_metadata`](#@Specification_1_create_proposal_metadata)
+ - [Function `assert_voting_initialization`](#@Specification_1_assert_voting_initialization)
+
+
+use 0x1::account;
+use 0x1::aptos_coin;
+use 0x1::coin;
+use 0x1::consensus_config;
+use 0x1::error;
+use 0x1::event;
+use 0x1::features;
+use 0x1::governance_proposal;
+use 0x1::math64;
+use 0x1::option;
+use 0x1::randomness_config;
+use 0x1::reconfiguration_with_dkg;
+use 0x1::signer;
+use 0x1::simple_map;
+use 0x1::smart_table;
+use 0x1::stake;
+use 0x1::staking_config;
+use 0x1::string;
+use 0x1::system_addresses;
+use 0x1::table;
+use 0x1::timestamp;
+use 0x1::vector;
+use 0x1::voting;
+
+
+
+
+
+
+## Resource `GovernanceResponsbility`
+
+Store the SignerCapabilities of accounts under the on-chain governance's control.
+
+
+struct GovernanceResponsbility has key
+
+
+
+
+signer_caps: simple_map::SimpleMap<address, account::SignerCapability>
+struct GovernanceConfig has key
+
+
+
+
+min_voting_threshold: u128
+required_proposer_stake: u64
+voting_duration_secs: u64
+struct RecordKey has copy, drop, store
+
+
+
+
+stake_pool: address
+proposal_id: u64
+struct VotingRecords has key
+
+
+
+
+votes: table::Table<aptos_governance::RecordKey, bool>
+struct VotingRecordsV2 has key
+
+
+
+
+votes: smart_table::SmartTable<aptos_governance::RecordKey, u64>
+struct ApprovedExecutionHashes has key
+
+
+
+
+hashes: simple_map::SimpleMap<u64, vector<u8>>
+struct GovernanceEvents has key
+
+
+
+
+create_proposal_events: event::EventHandle<aptos_governance::CreateProposalEvent>
+update_config_events: event::EventHandle<aptos_governance::UpdateConfigEvent>
+vote_events: event::EventHandle<aptos_governance::VoteEvent>
+struct CreateProposalEvent has drop, store
+
+
+
+
+proposer: address
+stake_pool: address
+proposal_id: u64
+execution_hash: vector<u8>
+proposal_metadata: simple_map::SimpleMap<string::String, vector<u8>>
+struct VoteEvent has drop, store
+
+
+
+
+proposal_id: u64
+voter: address
+stake_pool: address
+num_votes: u64
+should_pass: bool
+struct UpdateConfigEvent has drop, store
+
+
+
+
+min_voting_threshold: u128
+required_proposer_stake: u64
+voting_duration_secs: u64
+#[event]
+struct CreateProposal has drop, store
+
+
+
+
+proposer: address
+stake_pool: address
+proposal_id: u64
+execution_hash: vector<u8>
+proposal_metadata: simple_map::SimpleMap<string::String, vector<u8>>
+#[event]
+struct Vote has drop, store
+
+
+
+
+proposal_id: u64
+voter: address
+stake_pool: address
+num_votes: u64
+should_pass: bool
+#[event]
+struct UpdateConfig has drop, store
+
+
+
+
+min_voting_threshold: u128
+required_proposer_stake: u64
+voting_duration_secs: u64
+const MAX_U64: u64 = 18446744073709551615;
+
+
+
+
+
+
+This matches the same enum const in voting. We have to duplicate it as Move doesn't have support for enums yet.
+
+
+const PROPOSAL_STATE_SUCCEEDED: u64 = 1;
+
+
+
+
+
+
+The specified stake pool has already been used to vote on the same proposal
+
+
+const EALREADY_VOTED: u64 = 4;
+
+
+
+
+
+
+The specified stake pool does not have sufficient stake to create a proposal
+
+
+const EINSUFFICIENT_PROPOSER_STAKE: u64 = 1;
+
+
+
+
+
+
+The specified stake pool does not have long enough remaining lockup to create a proposal or vote
+
+
+const EINSUFFICIENT_STAKE_LOCKUP: u64 = 3;
+
+
+
+
+
+
+Metadata hash cannot be longer than 256 chars
+
+
+const EMETADATA_HASH_TOO_LONG: u64 = 10;
+
+
+
+
+
+
+Metadata location cannot be longer than 256 chars
+
+
+const EMETADATA_LOCATION_TOO_LONG: u64 = 9;
+
+
+
+
+
+
+This account is not the designated voter of the specified stake pool
+
+
+const ENOT_DELEGATED_VOTER: u64 = 2;
+
+
+
+
+
+
+The proposal in the argument is not a partial voting proposal.
+
+
+const ENOT_PARTIAL_VOTING_PROPOSAL: u64 = 14;
+
+
+
+
+
+
+The specified stake pool must be part of the validator set
+
+
+const ENO_VOTING_POWER: u64 = 5;
+
+
+
+
+
+
+Partial voting feature hasn't been properly initialized.
+
+
+const EPARTIAL_VOTING_NOT_INITIALIZED: u64 = 13;
+
+
+
+
+
+
+Proposal is not ready to be resolved. Waiting on time or votes
+
+
+const EPROPOSAL_NOT_RESOLVABLE_YET: u64 = 6;
+
+
+
+
+
+
+The proposal has not been resolved yet
+
+
+const EPROPOSAL_NOT_RESOLVED_YET: u64 = 8;
+
+
+
+
+
+
+Account is not authorized to call this function.
+
+
+const EUNAUTHORIZED: u64 = 11;
+
+
+
+
+
+
+The stake pool is using voting power more than it has.
+
+
+const EVOTING_POWER_OVERFLOW: u64 = 12;
+
+
+
+
+
+
+
+
+const METADATA_HASH_KEY: vector<u8> = [109, 101, 116, 97, 100, 97, 116, 97, 95, 104, 97, 115, 104];
+
+
+
+
+
+
+Proposal metadata attribute keys.
+
+
+const METADATA_LOCATION_KEY: vector<u8> = [109, 101, 116, 97, 100, 97, 116, 97, 95, 108, 111, 99, 97, 116, 105, 111, 110];
+
+
+
+
+
+
+## Function `store_signer_cap`
+
+Can be called during genesis or by the governance itself.
+Stores the signer capability for a given address.
+
+
+public fun store_signer_cap(aptos_framework: &signer, signer_address: address, signer_cap: account::SignerCapability)
+
+
+
+
+public fun store_signer_cap(
+ aptos_framework: &signer,
+ signer_address: address,
+ signer_cap: SignerCapability,
+) acquires GovernanceResponsbility {
+ system_addresses::assert_aptos_framework(aptos_framework);
+ system_addresses::assert_framework_reserved(signer_address);
+
+ if (!exists<GovernanceResponsbility>(@aptos_framework)) {
+ move_to(
+ aptos_framework,
+ GovernanceResponsbility { signer_caps: simple_map::create<address, SignerCapability>() }
+ );
+ };
+
+ let signer_caps = &mut borrow_global_mut<GovernanceResponsbility>(@aptos_framework).signer_caps;
+ simple_map::add(signer_caps, signer_address, signer_cap);
+}
+
+
+
+
+fun initialize(aptos_framework: &signer, min_voting_threshold: u128, required_proposer_stake: u64, voting_duration_secs: u64)
+
+
+
+
+fun initialize(
+ aptos_framework: &signer,
+ min_voting_threshold: u128,
+ required_proposer_stake: u64,
+ voting_duration_secs: u64,
+) {
+ system_addresses::assert_aptos_framework(aptos_framework);
+
+ voting::register<GovernanceProposal>(aptos_framework);
+ move_to(aptos_framework, GovernanceConfig {
+ voting_duration_secs,
+ min_voting_threshold,
+ required_proposer_stake,
+ });
+ move_to(aptos_framework, GovernanceEvents {
+ create_proposal_events: account::new_event_handle<CreateProposalEvent>(aptos_framework),
+ update_config_events: account::new_event_handle<UpdateConfigEvent>(aptos_framework),
+ vote_events: account::new_event_handle<VoteEvent>(aptos_framework),
+ });
+ move_to(aptos_framework, VotingRecords {
+ votes: table::new(),
+ });
+ move_to(aptos_framework, ApprovedExecutionHashes {
+ hashes: simple_map::create<u64, vector<u8>>(),
+ })
+}
+
+
+
+
+public fun update_governance_config(aptos_framework: &signer, min_voting_threshold: u128, required_proposer_stake: u64, voting_duration_secs: u64)
+
+
+
+
+public fun update_governance_config(
+ aptos_framework: &signer,
+ min_voting_threshold: u128,
+ required_proposer_stake: u64,
+ voting_duration_secs: u64,
+) acquires GovernanceConfig, GovernanceEvents {
+ system_addresses::assert_aptos_framework(aptos_framework);
+
+ let governance_config = borrow_global_mut<GovernanceConfig>(@aptos_framework);
+ governance_config.voting_duration_secs = voting_duration_secs;
+ governance_config.min_voting_threshold = min_voting_threshold;
+ governance_config.required_proposer_stake = required_proposer_stake;
+
+ if (std::features::module_event_migration_enabled()) {
+ event::emit(
+ UpdateConfig {
+ min_voting_threshold,
+ required_proposer_stake,
+ voting_duration_secs
+ },
+ )
+ };
+ let events = borrow_global_mut<GovernanceEvents>(@aptos_framework);
+ event::emit_event<UpdateConfigEvent>(
+ &mut events.update_config_events,
+ UpdateConfigEvent {
+ min_voting_threshold,
+ required_proposer_stake,
+ voting_duration_secs
+ },
+ );
+}
+
+
+
+
+public fun initialize_partial_voting(aptos_framework: &signer)
+
+
+
+
+public fun initialize_partial_voting(
+ aptos_framework: &signer,
+) {
+ system_addresses::assert_aptos_framework(aptos_framework);
+
+ move_to(aptos_framework, VotingRecordsV2 {
+ votes: smart_table::new(),
+ });
+}
+
+
+
+
+#[view]
+public fun get_voting_duration_secs(): u64
+
+
+
+
+public fun get_voting_duration_secs(): u64 acquires GovernanceConfig {
+ borrow_global<GovernanceConfig>(@aptos_framework).voting_duration_secs
+}
+
+
+
+
+#[view]
+public fun get_min_voting_threshold(): u128
+
+
+
+
+public fun get_min_voting_threshold(): u128 acquires GovernanceConfig {
+ borrow_global<GovernanceConfig>(@aptos_framework).min_voting_threshold
+}
+
+
+
+
+#[view]
+public fun get_required_proposer_stake(): u64
+
+
+
+
+public fun get_required_proposer_stake(): u64 acquires GovernanceConfig {
+ borrow_global<GovernanceConfig>(@aptos_framework).required_proposer_stake
+}
+
+
+
+
+#[view]
+public fun has_entirely_voted(stake_pool: address, proposal_id: u64): bool
+
+
+
+
+public fun has_entirely_voted(stake_pool: address, proposal_id: u64): bool acquires VotingRecords {
+ let record_key = RecordKey {
+ stake_pool,
+ proposal_id,
+ };
+ // If a stake pool has already voted on a proposal before partial governance voting is enabled,
+ // there is a record in VotingRecords.
+ let voting_records = borrow_global<VotingRecords>(@aptos_framework);
+ table::contains(&voting_records.votes, record_key)
+}
+
+
+
+
+#[view]
+public fun get_remaining_voting_power(stake_pool: address, proposal_id: u64): u64
+
+
+
+
+public fun get_remaining_voting_power(
+ stake_pool: address,
+ proposal_id: u64
+): u64 acquires VotingRecords, VotingRecordsV2 {
+ assert_voting_initialization();
+
+ let proposal_expiration = voting::get_proposal_expiration_secs<GovernanceProposal>(
+ @aptos_framework,
+ proposal_id
+ );
+ let lockup_until = stake::get_lockup_secs(stake_pool);
+ // The voter's stake needs to be locked up at least as long as the proposal's expiration.
+ // Also no one can vote on a expired proposal.
+ if (proposal_expiration > lockup_until || timestamp::now_seconds() > proposal_expiration) {
+ return 0
+ };
+
+ // If a stake pool has already voted on a proposal before partial governance voting is enabled, the stake pool
+ // cannot vote on the proposal even after partial governance voting is enabled.
+ if (has_entirely_voted(stake_pool, proposal_id)) {
+ return 0
+ };
+ let record_key = RecordKey {
+ stake_pool,
+ proposal_id,
+ };
+ let used_voting_power = 0u64;
+ if (features::partial_governance_voting_enabled()) {
+ let voting_records_v2 = borrow_global<VotingRecordsV2>(@aptos_framework);
+ used_voting_power = *smart_table::borrow_with_default(&voting_records_v2.votes, record_key, &0);
+ };
+ get_voting_power(stake_pool) - used_voting_power
+}
+
+
+
+
+stake_pool
.
+@param execution_hash Required. This is the hash of the resolution script. When the proposal is resolved,
+only the exact script with matching hash can be successfully executed.
+
+
+public entry fun create_proposal(proposer: &signer, stake_pool: address, execution_hash: vector<u8>, metadata_location: vector<u8>, metadata_hash: vector<u8>)
+
+
+
+
+public entry fun create_proposal(
+ proposer: &signer,
+ stake_pool: address,
+ execution_hash: vector<u8>,
+ metadata_location: vector<u8>,
+ metadata_hash: vector<u8>,
+) acquires GovernanceConfig, GovernanceEvents {
+ create_proposal_v2(proposer, stake_pool, execution_hash, metadata_location, metadata_hash, false);
+}
+
+
+
+
+stake_pool
.
+@param execution_hash Required. This is the hash of the resolution script. When the proposal is resolved,
+only the exact script with matching hash can be successfully executed.
+
+
+public entry fun create_proposal_v2(proposer: &signer, stake_pool: address, execution_hash: vector<u8>, metadata_location: vector<u8>, metadata_hash: vector<u8>, is_multi_step_proposal: bool)
+
+
+
+
+public entry fun create_proposal_v2(
+ proposer: &signer,
+ stake_pool: address,
+ execution_hash: vector<u8>,
+ metadata_location: vector<u8>,
+ metadata_hash: vector<u8>,
+ is_multi_step_proposal: bool,
+) acquires GovernanceConfig, GovernanceEvents {
+ create_proposal_v2_impl(
+ proposer,
+ stake_pool,
+ execution_hash,
+ metadata_location,
+ metadata_hash,
+ is_multi_step_proposal
+ );
+}
+
+
+
+
+stake_pool
.
+@param execution_hash Required. This is the hash of the resolution script. When the proposal is resolved,
+only the exact script with matching hash can be successfully executed.
+Return proposal_id when a proposal is successfully created.
+
+
+public fun create_proposal_v2_impl(proposer: &signer, stake_pool: address, execution_hash: vector<u8>, metadata_location: vector<u8>, metadata_hash: vector<u8>, is_multi_step_proposal: bool): u64
+
+
+
+
+public fun create_proposal_v2_impl(
+ proposer: &signer,
+ stake_pool: address,
+ execution_hash: vector<u8>,
+ metadata_location: vector<u8>,
+ metadata_hash: vector<u8>,
+ is_multi_step_proposal: bool,
+): u64 acquires GovernanceConfig, GovernanceEvents {
+ let proposer_address = signer::address_of(proposer);
+ assert!(
+ stake::get_delegated_voter(stake_pool) == proposer_address,
+ error::invalid_argument(ENOT_DELEGATED_VOTER)
+ );
+
+ // The proposer's stake needs to be at least the required bond amount.
+ let governance_config = borrow_global<GovernanceConfig>(@aptos_framework);
+ let stake_balance = get_voting_power(stake_pool);
+ assert!(
+ stake_balance >= governance_config.required_proposer_stake,
+ error::invalid_argument(EINSUFFICIENT_PROPOSER_STAKE),
+ );
+
+ // The proposer's stake needs to be locked up at least as long as the proposal's voting period.
+ let current_time = timestamp::now_seconds();
+ let proposal_expiration = current_time + governance_config.voting_duration_secs;
+ assert!(
+ stake::get_lockup_secs(stake_pool) >= proposal_expiration,
+ error::invalid_argument(EINSUFFICIENT_STAKE_LOCKUP),
+ );
+
+ // Create and validate proposal metadata.
+ let proposal_metadata = create_proposal_metadata(metadata_location, metadata_hash);
+
+ // We want to allow early resolution of proposals if more than 50% of the total supply of the network coins
+ // has voted. This doesn't take into subsequent inflation/deflation (rewards are issued every epoch and gas fees
+ // are burnt after every transaction), but inflation/delation is very unlikely to have a major impact on total
+ // supply during the voting period.
+ let total_voting_token_supply = coin::supply<AptosCoin>();
+ let early_resolution_vote_threshold = option::none<u128>();
+ if (option::is_some(&total_voting_token_supply)) {
+ let total_supply = *option::borrow(&total_voting_token_supply);
+ // 50% + 1 to avoid rounding errors.
+ early_resolution_vote_threshold = option::some(total_supply / 2 + 1);
+ };
+
+ let proposal_id = voting::create_proposal_v2(
+ proposer_address,
+ @aptos_framework,
+ governance_proposal::create_proposal(),
+ execution_hash,
+ governance_config.min_voting_threshold,
+ proposal_expiration,
+ early_resolution_vote_threshold,
+ proposal_metadata,
+ is_multi_step_proposal,
+ );
+
+ if (std::features::module_event_migration_enabled()) {
+ event::emit(
+ CreateProposal {
+ proposal_id,
+ proposer: proposer_address,
+ stake_pool,
+ execution_hash,
+ proposal_metadata,
+ },
+ );
+ };
+ let events = borrow_global_mut<GovernanceEvents>(@aptos_framework);
+ event::emit_event<CreateProposalEvent>(
+ &mut events.create_proposal_events,
+ CreateProposalEvent {
+ proposal_id,
+ proposer: proposer_address,
+ stake_pool,
+ execution_hash,
+ proposal_metadata,
+ },
+ );
+ proposal_id
+}
+
+
+
+
+public entry fun batch_vote(voter: &signer, stake_pools: vector<address>, proposal_id: u64, should_pass: bool)
+
+
+
+
+public entry fun batch_vote(
+ voter: &signer,
+ stake_pools: vector<address>,
+ proposal_id: u64,
+ should_pass: bool,
+) acquires ApprovedExecutionHashes, VotingRecords, VotingRecordsV2, GovernanceEvents {
+ vector::for_each(stake_pools, |stake_pool| {
+ vote_internal(voter, stake_pool, proposal_id, MAX_U64, should_pass);
+ });
+}
+
+
+
+
+public entry fun batch_partial_vote(voter: &signer, stake_pools: vector<address>, proposal_id: u64, voting_power: u64, should_pass: bool)
+
+
+
+
+public entry fun batch_partial_vote(
+ voter: &signer,
+ stake_pools: vector<address>,
+ proposal_id: u64,
+ voting_power: u64,
+ should_pass: bool,
+) acquires ApprovedExecutionHashes, VotingRecords, VotingRecordsV2, GovernanceEvents {
+ vector::for_each(stake_pools, |stake_pool| {
+ vote_internal(voter, stake_pool, proposal_id, voting_power, should_pass);
+ });
+}
+
+
+
+
+proposal_id
and all voting power from stake_pool
.
+
+
+public entry fun vote(voter: &signer, stake_pool: address, proposal_id: u64, should_pass: bool)
+
+
+
+
+public entry fun vote(
+ voter: &signer,
+ stake_pool: address,
+ proposal_id: u64,
+ should_pass: bool,
+) acquires ApprovedExecutionHashes, VotingRecords, VotingRecordsV2, GovernanceEvents {
+ vote_internal(voter, stake_pool, proposal_id, MAX_U64, should_pass);
+}
+
+
+
+
+proposal_id
and specified voting power from stake_pool
.
+
+
+public entry fun partial_vote(voter: &signer, stake_pool: address, proposal_id: u64, voting_power: u64, should_pass: bool)
+
+
+
+
+public entry fun partial_vote(
+ voter: &signer,
+ stake_pool: address,
+ proposal_id: u64,
+ voting_power: u64,
+ should_pass: bool,
+) acquires ApprovedExecutionHashes, VotingRecords, VotingRecordsV2, GovernanceEvents {
+ vote_internal(voter, stake_pool, proposal_id, voting_power, should_pass);
+}
+
+
+
+
+proposal_id
and specified voting_power from stake_pool
.
+If voting_power is more than all the left voting power of stake_pool
, use all the left voting power.
+If a stake pool has already voted on a proposal before partial governance voting is enabled, the stake pool
+cannot vote on the proposal even after partial governance voting is enabled.
+
+
+fun vote_internal(voter: &signer, stake_pool: address, proposal_id: u64, voting_power: u64, should_pass: bool)
+
+
+
+
+fun vote_internal(
+ voter: &signer,
+ stake_pool: address,
+ proposal_id: u64,
+ voting_power: u64,
+ should_pass: bool,
+) acquires ApprovedExecutionHashes, VotingRecords, VotingRecordsV2, GovernanceEvents {
+ let voter_address = signer::address_of(voter);
+ assert!(stake::get_delegated_voter(stake_pool) == voter_address, error::invalid_argument(ENOT_DELEGATED_VOTER));
+
+ // The voter's stake needs to be locked up at least as long as the proposal's expiration.
+ let proposal_expiration = voting::get_proposal_expiration_secs<GovernanceProposal>(
+ @aptos_framework,
+ proposal_id
+ );
+ assert!(
+ stake::get_lockup_secs(stake_pool) >= proposal_expiration,
+ error::invalid_argument(EINSUFFICIENT_STAKE_LOCKUP),
+ );
+
+ // If a stake pool has already voted on a proposal before partial governance voting is enabled,
+ // `get_remaining_voting_power` returns 0.
+ let staking_pool_voting_power = get_remaining_voting_power(stake_pool, proposal_id);
+ voting_power = min(voting_power, staking_pool_voting_power);
+
+ // Short-circuit if the voter has no voting power.
+ assert!(voting_power > 0, error::invalid_argument(ENO_VOTING_POWER));
+
+ voting::vote<GovernanceProposal>(
+ &governance_proposal::create_empty_proposal(),
+ @aptos_framework,
+ proposal_id,
+ voting_power,
+ should_pass,
+ );
+
+ let record_key = RecordKey {
+ stake_pool,
+ proposal_id,
+ };
+ if (features::partial_governance_voting_enabled()) {
+ let voting_records_v2 = borrow_global_mut<VotingRecordsV2>(@aptos_framework);
+ let used_voting_power = smart_table::borrow_mut_with_default(&mut voting_records_v2.votes, record_key, 0);
+ // This calculation should never overflow because the used voting cannot exceed the total voting power of this stake pool.
+ *used_voting_power = *used_voting_power + voting_power;
+ } else {
+ let voting_records = borrow_global_mut<VotingRecords>(@aptos_framework);
+ assert!(
+ !table::contains(&voting_records.votes, record_key),
+ error::invalid_argument(EALREADY_VOTED));
+ table::add(&mut voting_records.votes, record_key, true);
+ };
+
+ if (std::features::module_event_migration_enabled()) {
+ event::emit(
+ Vote {
+ proposal_id,
+ voter: voter_address,
+ stake_pool,
+ num_votes: voting_power,
+ should_pass,
+ },
+ );
+ };
+ let events = borrow_global_mut<GovernanceEvents>(@aptos_framework);
+ event::emit_event<VoteEvent>(
+ &mut events.vote_events,
+ VoteEvent {
+ proposal_id,
+ voter: voter_address,
+ stake_pool,
+ num_votes: voting_power,
+ should_pass,
+ },
+ );
+
+ let proposal_state = voting::get_proposal_state<GovernanceProposal>(@aptos_framework, proposal_id);
+ if (proposal_state == PROPOSAL_STATE_SUCCEEDED) {
+ add_approved_script_hash(proposal_id);
+ }
+}
+
+
+
+
+public entry fun add_approved_script_hash_script(proposal_id: u64)
+
+
+
+
+public entry fun add_approved_script_hash_script(proposal_id: u64) acquires ApprovedExecutionHashes {
+ add_approved_script_hash(proposal_id)
+}
+
+
+
+
+public fun add_approved_script_hash(proposal_id: u64)
+
+
+
+
+public fun add_approved_script_hash(proposal_id: u64) acquires ApprovedExecutionHashes {
+ let approved_hashes = borrow_global_mut<ApprovedExecutionHashes>(@aptos_framework);
+
+ // Ensure the proposal can be resolved.
+ let proposal_state = voting::get_proposal_state<GovernanceProposal>(@aptos_framework, proposal_id);
+ assert!(proposal_state == PROPOSAL_STATE_SUCCEEDED, error::invalid_argument(EPROPOSAL_NOT_RESOLVABLE_YET));
+
+ let execution_hash = voting::get_execution_hash<GovernanceProposal>(@aptos_framework, proposal_id);
+
+ // If this is a multi-step proposal, the proposal id will already exist in the ApprovedExecutionHashes map.
+ // We will update execution hash in ApprovedExecutionHashes to be the next_execution_hash.
+ if (simple_map::contains_key(&approved_hashes.hashes, &proposal_id)) {
+ let current_execution_hash = simple_map::borrow_mut(&mut approved_hashes.hashes, &proposal_id);
+ *current_execution_hash = execution_hash;
+ } else {
+ simple_map::add(&mut approved_hashes.hashes, proposal_id, execution_hash);
+ }
+}
+
+
+
+
+public fun resolve(proposal_id: u64, signer_address: address): signer
+
+
+
+
+public fun resolve(
+ proposal_id: u64,
+ signer_address: address
+): signer acquires ApprovedExecutionHashes, GovernanceResponsbility {
+ voting::resolve<GovernanceProposal>(@aptos_framework, proposal_id);
+ remove_approved_hash(proposal_id);
+ get_signer(signer_address)
+}
+
+
+
+
+public fun resolve_multi_step_proposal(proposal_id: u64, signer_address: address, next_execution_hash: vector<u8>): signer
+
+
+
+
+public fun resolve_multi_step_proposal(
+ proposal_id: u64,
+ signer_address: address,
+ next_execution_hash: vector<u8>
+): signer acquires GovernanceResponsbility, ApprovedExecutionHashes {
+ voting::resolve_proposal_v2<GovernanceProposal>(@aptos_framework, proposal_id, next_execution_hash);
+ // If the current step is the last step of this multi-step proposal,
+ // we will remove the execution hash from the ApprovedExecutionHashes map.
+ if (vector::length(&next_execution_hash) == 0) {
+ remove_approved_hash(proposal_id);
+ } else {
+ // If the current step is not the last step of this proposal,
+ // we replace the current execution hash with the next execution hash
+ // in the ApprovedExecutionHashes map.
+ add_approved_script_hash(proposal_id)
+ };
+ get_signer(signer_address)
+}
+
+
+
+
+public fun remove_approved_hash(proposal_id: u64)
+
+
+
+
+public fun remove_approved_hash(proposal_id: u64) acquires ApprovedExecutionHashes {
+ assert!(
+ voting::is_resolved<GovernanceProposal>(@aptos_framework, proposal_id),
+ error::invalid_argument(EPROPOSAL_NOT_RESOLVED_YET),
+ );
+
+ let approved_hashes = &mut borrow_global_mut<ApprovedExecutionHashes>(@aptos_framework).hashes;
+ if (simple_map::contains_key(approved_hashes, &proposal_id)) {
+ simple_map::remove(approved_hashes, &proposal_id);
+ };
+}
+
+
+
+
+RECONFIGURE_WITH_DKG
is disabled, it finishes immediately.
+- At the end of the calling transaction, we will be in a new epoch.
+- If feature RECONFIGURE_WITH_DKG
is enabled, it starts DKG, and the new epoch will start in a block prologue after DKG finishes.
+
+This behavior affects when an update of an on-chain config (e.g. ConsensusConfig
, Features
) takes effect,
+since such updates are applied whenever we enter an new epoch.
+
+
+public entry fun reconfigure(aptos_framework: &signer)
+
+
+
+
+public entry fun reconfigure(aptos_framework: &signer) {
+ system_addresses::assert_aptos_framework(aptos_framework);
+ if (consensus_config::validator_txn_enabled() && randomness_config::enabled()) {
+ reconfiguration_with_dkg::try_start();
+ } else {
+ reconfiguration_with_dkg::finish(aptos_framework);
+ }
+}
+
+
+
+
+RECONFIGURE_WITH_DKG
is enabled and we are in the middle of a DKG,
+stop waiting for DKG and enter the new epoch without randomness.
+
+WARNING: currently only used by tests. In most cases you should use reconfigure()
instead.
+TODO: migrate these tests to be aware of async reconfiguration.
+
+
+public entry fun force_end_epoch(aptos_framework: &signer)
+
+
+
+
+public entry fun force_end_epoch(aptos_framework: &signer) {
+ system_addresses::assert_aptos_framework(aptos_framework);
+ reconfiguration_with_dkg::finish(aptos_framework);
+}
+
+
+
+
+force_end_epoch()
equivalent but only called in testnet,
+where the core resources account exists and has been granted power to mint Aptos coins.
+
+
+public entry fun force_end_epoch_test_only(aptos_framework: &signer)
+
+
+
+
+public entry fun force_end_epoch_test_only(aptos_framework: &signer) acquires GovernanceResponsbility {
+ let core_signer = get_signer_testnet_only(aptos_framework, @0x1);
+ system_addresses::assert_aptos_framework(&core_signer);
+ reconfiguration_with_dkg::finish(&core_signer);
+}
+
+
+
+
+public fun toggle_features(aptos_framework: &signer, enable: vector<u64>, disable: vector<u64>)
+
+
+
+
+public fun toggle_features(aptos_framework: &signer, enable: vector<u64>, disable: vector<u64>) {
+ system_addresses::assert_aptos_framework(aptos_framework);
+ features::change_feature_flags_for_next_epoch(aptos_framework, enable, disable);
+ reconfigure(aptos_framework);
+}
+
+
+
+
+public fun get_signer_testnet_only(core_resources: &signer, signer_address: address): signer
+
+
+
+
+public fun get_signer_testnet_only(
+ core_resources: &signer, signer_address: address): signer acquires GovernanceResponsbility {
+ system_addresses::assert_core_resource(core_resources);
+ // Core resources account only has mint capability in tests/testnets.
+ assert!(aptos_coin::has_mint_capability(core_resources), error::unauthenticated(EUNAUTHORIZED));
+ get_signer(signer_address)
+}
+
+
+
+
+#[view]
+public fun get_voting_power(pool_address: address): u64
+
+
+
+
+public fun get_voting_power(pool_address: address): u64 {
+ let allow_validator_set_change = staking_config::get_allow_validator_set_change(&staking_config::get());
+ if (allow_validator_set_change) {
+ let (active, _, pending_active, pending_inactive) = stake::get_stake(pool_address);
+ // We calculate the voting power as total non-inactive stakes of the pool. Even if the validator is not in the
+ // active validator set, as long as they have a lockup (separately checked in create_proposal and voting), their
+ // stake would still count in their voting power for governance proposals.
+ active + pending_active + pending_inactive
+ } else {
+ stake::get_current_epoch_voting_power(pool_address)
+ }
+}
+
+
+
+
+fun get_signer(signer_address: address): signer
+
+
+
+
+fun get_signer(signer_address: address): signer acquires GovernanceResponsbility {
+ let governance_responsibility = borrow_global<GovernanceResponsbility>(@aptos_framework);
+ let signer_cap = simple_map::borrow(&governance_responsibility.signer_caps, &signer_address);
+ create_signer_with_capability(signer_cap)
+}
+
+
+
+
+fun create_proposal_metadata(metadata_location: vector<u8>, metadata_hash: vector<u8>): simple_map::SimpleMap<string::String, vector<u8>>
+
+
+
+
+fun create_proposal_metadata(
+ metadata_location: vector<u8>,
+ metadata_hash: vector<u8>
+): SimpleMap<String, vector<u8>> {
+ assert!(string::length(&utf8(metadata_location)) <= 256, error::invalid_argument(EMETADATA_LOCATION_TOO_LONG));
+ assert!(string::length(&utf8(metadata_hash)) <= 256, error::invalid_argument(EMETADATA_HASH_TOO_LONG));
+
+ let metadata = simple_map::create<String, vector<u8>>();
+ simple_map::add(&mut metadata, utf8(METADATA_LOCATION_KEY), metadata_location);
+ simple_map::add(&mut metadata, utf8(METADATA_HASH_KEY), metadata_hash);
+ metadata
+}
+
+
+
+
+fun assert_voting_initialization()
+
+
+
+
+fun assert_voting_initialization() {
+ if (features::partial_governance_voting_enabled()) {
+ assert!(exists<VotingRecordsV2>(@aptos_framework), error::invalid_state(EPARTIAL_VOTING_NOT_INITIALIZED));
+ };
+}
+
+
+
+
+No. | Requirement | Criticality | Implementation | Enforcement | +
---|---|---|---|---|
1 | +The create proposal function calls create proposal v2. | +Low | +The create_proposal function internally calls create_proposal_v2. | +This is manually audited to ensure create_proposal_v2 is called in create_proposal. | +
2 | +The proposer must have a stake equal to or greater than the required bond amount. | +High | +The create_proposal_v2 function verifies that the stake balance equals or exceeds the required proposer stake amount. | +Formally verified in CreateProposalAbortsIf. | +
3 | +The Approved execution hashes resources that exist when the vote function is called. | +Low | +The Vote function acquires the Approved execution hashes resources. | +Formally verified in VoteAbortIf. | +
4 | +The execution script hash of a successful governance proposal is added to the approved list if the proposal can be resolved. | +Medium | +The add_approved_script_hash function asserts that proposal_state == PROPOSAL_STATE_SUCCEEDED. | +Formally verified in AddApprovedScriptHash. | +
pragma verify = true;
+pragma aborts_if_is_strict;
+
+
+
+
+
+
+### Function `store_signer_cap`
+
+
+public fun store_signer_cap(aptos_framework: &signer, signer_address: address, signer_cap: account::SignerCapability)
+
+
+
+
+
+aborts_if !system_addresses::is_aptos_framework_address(signer::address_of(aptos_framework));
+aborts_if !system_addresses::is_framework_reserved_address(signer_address);
+let signer_caps = global<GovernanceResponsbility>(@aptos_framework).signer_caps;
+aborts_if exists<GovernanceResponsbility>(@aptos_framework) &&
+ simple_map::spec_contains_key(signer_caps, signer_address);
+ensures exists<GovernanceResponsbility>(@aptos_framework);
+let post post_signer_caps = global<GovernanceResponsbility>(@aptos_framework).signer_caps;
+ensures simple_map::spec_contains_key(post_signer_caps, signer_address);
+
+
+
+
+
+
+### Function `initialize`
+
+
+fun initialize(aptos_framework: &signer, min_voting_threshold: u128, required_proposer_stake: u64, voting_duration_secs: u64)
+
+
+
+Signer address must be @aptos_framework.
+The signer does not allow these resources (GovernanceProposal, GovernanceConfig, GovernanceEvents, VotingRecords, ApprovedExecutionHashes) to exist.
+The signer must have an Account.
+Limit addition overflow.
+
+
+let addr = signer::address_of(aptos_framework);
+let register_account = global<account::Account>(addr);
+aborts_if exists<voting::VotingForum<GovernanceProposal>>(addr);
+aborts_if !exists<account::Account>(addr);
+aborts_if register_account.guid_creation_num + 7 > MAX_U64;
+aborts_if register_account.guid_creation_num + 7 >= account::MAX_GUID_CREATION_NUM;
+aborts_if !type_info::spec_is_struct<GovernanceProposal>();
+include InitializeAbortIf;
+ensures exists<voting::VotingForum<governance_proposal::GovernanceProposal>>(addr);
+ensures exists<GovernanceConfig>(addr);
+ensures exists<GovernanceEvents>(addr);
+ensures exists<VotingRecords>(addr);
+ensures exists<ApprovedExecutionHashes>(addr);
+
+
+
+
+
+
+### Function `update_governance_config`
+
+
+public fun update_governance_config(aptos_framework: &signer, min_voting_threshold: u128, required_proposer_stake: u64, voting_duration_secs: u64)
+
+
+
+Signer address must be @aptos_framework.
+Address @aptos_framework must exist GovernanceConfig and GovernanceEvents.
+
+
+let addr = signer::address_of(aptos_framework);
+let governance_config = global<GovernanceConfig>(@aptos_framework);
+let post new_governance_config = global<GovernanceConfig>(@aptos_framework);
+aborts_if addr != @aptos_framework;
+aborts_if !exists<GovernanceConfig>(@aptos_framework);
+aborts_if !exists<GovernanceEvents>(@aptos_framework);
+modifies global<GovernanceConfig>(addr);
+ensures new_governance_config.voting_duration_secs == voting_duration_secs;
+ensures new_governance_config.min_voting_threshold == min_voting_threshold;
+ensures new_governance_config.required_proposer_stake == required_proposer_stake;
+
+
+
+
+
+
+### Function `initialize_partial_voting`
+
+
+public fun initialize_partial_voting(aptos_framework: &signer)
+
+
+
+Signer address must be @aptos_framework.
+Abort if structs have already been created.
+
+
+let addr = signer::address_of(aptos_framework);
+aborts_if addr != @aptos_framework;
+aborts_if exists<VotingRecordsV2>(@aptos_framework);
+ensures exists<VotingRecordsV2>(@aptos_framework);
+
+
+
+
+
+
+
+
+schema InitializeAbortIf {
+ aptos_framework: &signer;
+ min_voting_threshold: u128;
+ required_proposer_stake: u64;
+ voting_duration_secs: u64;
+ let addr = signer::address_of(aptos_framework);
+ let account = global<account::Account>(addr);
+ aborts_if addr != @aptos_framework;
+ aborts_if exists<voting::VotingForum<governance_proposal::GovernanceProposal>>(addr);
+ aborts_if exists<GovernanceConfig>(addr);
+ aborts_if exists<GovernanceEvents>(addr);
+ aborts_if exists<VotingRecords>(addr);
+ aborts_if exists<ApprovedExecutionHashes>(addr);
+ aborts_if !exists<account::Account>(addr);
+}
+
+
+
+
+
+
+### Function `get_voting_duration_secs`
+
+
+#[view]
+public fun get_voting_duration_secs(): u64
+
+
+
+
+
+include AbortsIfNotGovernanceConfig;
+
+
+
+
+
+
+### Function `get_min_voting_threshold`
+
+
+#[view]
+public fun get_min_voting_threshold(): u128
+
+
+
+
+
+include AbortsIfNotGovernanceConfig;
+
+
+
+
+
+
+### Function `get_required_proposer_stake`
+
+
+#[view]
+public fun get_required_proposer_stake(): u64
+
+
+
+
+
+include AbortsIfNotGovernanceConfig;
+
+
+
+
+
+
+
+
+schema AbortsIfNotGovernanceConfig {
+ aborts_if !exists<GovernanceConfig>(@aptos_framework);
+}
+
+
+
+
+
+
+### Function `has_entirely_voted`
+
+
+#[view]
+public fun has_entirely_voted(stake_pool: address, proposal_id: u64): bool
+
+
+
+
+
+aborts_if !exists<VotingRecords>(@aptos_framework);
+
+
+
+
+
+
+### Function `get_remaining_voting_power`
+
+
+#[view]
+public fun get_remaining_voting_power(stake_pool: address, proposal_id: u64): u64
+
+
+
+
+
+aborts_if features::spec_partial_governance_voting_enabled() && !exists<VotingRecordsV2>(@aptos_framework);
+include voting::AbortsIfNotContainProposalID<GovernanceProposal> {
+ voting_forum_address: @aptos_framework
+};
+aborts_if !exists<stake::StakePool>(stake_pool);
+aborts_if spec_proposal_expiration <= locked_until && !exists<timestamp::CurrentTimeMicroseconds>(@aptos_framework);
+let spec_proposal_expiration = voting::spec_get_proposal_expiration_secs<GovernanceProposal>(@aptos_framework, proposal_id);
+let locked_until = global<stake::StakePool>(stake_pool).locked_until_secs;
+let remain_zero_1_cond = (spec_proposal_expiration > locked_until || timestamp::spec_now_seconds() > spec_proposal_expiration);
+ensures remain_zero_1_cond ==> result == 0;
+let record_key = RecordKey {
+ stake_pool,
+ proposal_id,
+};
+let entirely_voted = spec_has_entirely_voted(stake_pool, proposal_id, record_key);
+aborts_if !remain_zero_1_cond && !exists<VotingRecords>(@aptos_framework);
+include !remain_zero_1_cond && !entirely_voted ==> GetVotingPowerAbortsIf {
+ pool_address: stake_pool
+};
+let staking_config = global<staking_config::StakingConfig>(@aptos_framework);
+let voting_power = spec_get_voting_power(stake_pool, staking_config);
+let voting_records_v2 = borrow_global<VotingRecordsV2>(@aptos_framework);
+let used_voting_power = if (smart_table::spec_contains(voting_records_v2.votes, record_key)) {
+ smart_table::spec_get(voting_records_v2.votes, record_key)
+} else {
+ 0
+};
+aborts_if !remain_zero_1_cond && !entirely_voted && features::spec_partial_governance_voting_enabled() &&
+ used_voting_power > 0 && voting_power < used_voting_power;
+ensures result == spec_get_remaining_voting_power(stake_pool, proposal_id);
+
+
+
+
+
+
+
+
+fun spec_get_remaining_voting_power(stake_pool: address, proposal_id: u64): u64 {
+ let spec_proposal_expiration = voting::spec_get_proposal_expiration_secs<GovernanceProposal>(@aptos_framework, proposal_id);
+ let locked_until = global<stake::StakePool>(stake_pool).locked_until_secs;
+ let remain_zero_1_cond = (spec_proposal_expiration > locked_until || timestamp::spec_now_seconds() > spec_proposal_expiration);
+ let staking_config = global<staking_config::StakingConfig>(@aptos_framework);
+ let voting_records_v2 = borrow_global<VotingRecordsV2>(@aptos_framework);
+ let record_key = RecordKey {
+ stake_pool,
+ proposal_id,
+ };
+ let entirely_voted = spec_has_entirely_voted(stake_pool, proposal_id, record_key);
+ let voting_power = spec_get_voting_power(stake_pool, staking_config);
+ let used_voting_power = if (smart_table::spec_contains(voting_records_v2.votes, record_key)) {
+ smart_table::spec_get(voting_records_v2.votes, record_key)
+ } else {
+ 0
+ };
+ if (remain_zero_1_cond) {
+ 0
+ } else if (entirely_voted) {
+ 0
+ } else if (!features::spec_partial_governance_voting_enabled()) {
+ voting_power
+ } else {
+ voting_power - used_voting_power
+ }
+}
+
+
+
+
+
+
+
+
+fun spec_has_entirely_voted(stake_pool: address, proposal_id: u64, record_key: RecordKey): bool {
+ let voting_records = global<VotingRecords>(@aptos_framework);
+ table::spec_contains(voting_records.votes, record_key)
+}
+
+
+
+
+
+
+
+
+schema GetVotingPowerAbortsIf {
+ pool_address: address;
+ let staking_config = global<staking_config::StakingConfig>(@aptos_framework);
+ aborts_if !exists<staking_config::StakingConfig>(@aptos_framework);
+ let allow_validator_set_change = staking_config.allow_validator_set_change;
+ let stake_pool_res = global<stake::StakePool>(pool_address);
+ aborts_if allow_validator_set_change && (stake_pool_res.active.value + stake_pool_res.pending_active.value + stake_pool_res.pending_inactive.value) > MAX_U64;
+ aborts_if !exists<stake::StakePool>(pool_address);
+ aborts_if !allow_validator_set_change && !exists<stake::ValidatorSet>(@aptos_framework);
+ aborts_if !allow_validator_set_change && stake::spec_is_current_epoch_validator(pool_address) && stake_pool_res.active.value + stake_pool_res.pending_inactive.value > MAX_U64;
+}
+
+
+
+
+
+
+### Function `create_proposal`
+
+
+public entry fun create_proposal(proposer: &signer, stake_pool: address, execution_hash: vector<u8>, metadata_location: vector<u8>, metadata_hash: vector<u8>)
+
+
+
+The same as spec of create_proposal_v2()
.
+
+
+pragma verify_duration_estimate = 60;
+requires chain_status::is_operating();
+include CreateProposalAbortsIf;
+
+
+
+
+
+
+### Function `create_proposal_v2`
+
+
+public entry fun create_proposal_v2(proposer: &signer, stake_pool: address, execution_hash: vector<u8>, metadata_location: vector<u8>, metadata_hash: vector<u8>, is_multi_step_proposal: bool)
+
+
+
+
+
+pragma verify_duration_estimate = 60;
+requires chain_status::is_operating();
+include CreateProposalAbortsIf;
+
+
+
+
+
+
+### Function `create_proposal_v2_impl`
+
+
+public fun create_proposal_v2_impl(proposer: &signer, stake_pool: address, execution_hash: vector<u8>, metadata_location: vector<u8>, metadata_hash: vector<u8>, is_multi_step_proposal: bool): u64
+
+
+
+
+
+pragma verify_duration_estimate = 60;
+requires chain_status::is_operating();
+include CreateProposalAbortsIf;
+
+
+
+
+
+
+### Function `batch_vote`
+
+
+public entry fun batch_vote(voter: &signer, stake_pools: vector<address>, proposal_id: u64, should_pass: bool)
+
+
+
+
+
+pragma verify = false;
+
+
+
+
+
+
+### Function `batch_partial_vote`
+
+
+public entry fun batch_partial_vote(voter: &signer, stake_pools: vector<address>, proposal_id: u64, voting_power: u64, should_pass: bool)
+
+
+
+
+
+pragma verify = false;
+
+
+
+
+
+
+### Function `vote`
+
+
+public entry fun vote(voter: &signer, stake_pool: address, proposal_id: u64, should_pass: bool)
+
+
+
+stake_pool must exist StakePool.
+The delegated voter under the resource StakePool of the stake_pool must be the voter address.
+Address @aptos_framework must exist VotingRecords and GovernanceProposal.
+
+
+pragma verify_duration_estimate = 60;
+requires chain_status::is_operating();
+include VoteAbortIf {
+ voting_power: MAX_U64
+};
+
+
+
+
+
+
+### Function `partial_vote`
+
+
+public entry fun partial_vote(voter: &signer, stake_pool: address, proposal_id: u64, voting_power: u64, should_pass: bool)
+
+
+
+stake_pool must exist StakePool.
+The delegated voter under the resource StakePool of the stake_pool must be the voter address.
+Address @aptos_framework must exist VotingRecords and GovernanceProposal.
+Address @aptos_framework must exist VotingRecordsV2 if partial_governance_voting flag is enabled.
+
+
+pragma verify_duration_estimate = 60;
+requires chain_status::is_operating();
+include VoteAbortIf;
+
+
+
+
+
+
+### Function `vote_internal`
+
+
+fun vote_internal(voter: &signer, stake_pool: address, proposal_id: u64, voting_power: u64, should_pass: bool)
+
+
+
+stake_pool must exist StakePool.
+The delegated voter under the resource StakePool of the stake_pool must be the voter address.
+Address @aptos_framework must exist VotingRecords and GovernanceProposal.
+Address @aptos_framework must exist VotingRecordsV2 if partial_governance_voting flag is enabled.
+
+
+pragma verify_duration_estimate = 60;
+requires chain_status::is_operating();
+include VoteAbortIf;
+
+
+
+
+
+
+
+
+schema VoteAbortIf {
+ voter: &signer;
+ stake_pool: address;
+ proposal_id: u64;
+ should_pass: bool;
+ voting_power: u64;
+ include VotingGetDelegatedVoterAbortsIf { sign: voter };
+ aborts_if spec_proposal_expiration <= locked_until && !exists<timestamp::CurrentTimeMicroseconds>(@aptos_framework);
+ let spec_proposal_expiration = voting::spec_get_proposal_expiration_secs<GovernanceProposal>(@aptos_framework, proposal_id);
+ let locked_until = global<stake::StakePool>(stake_pool).locked_until_secs;
+ let remain_zero_1_cond = (spec_proposal_expiration > locked_until || timestamp::spec_now_seconds() > spec_proposal_expiration);
+ let record_key = RecordKey {
+ stake_pool,
+ proposal_id,
+ };
+ let entirely_voted = spec_has_entirely_voted(stake_pool, proposal_id, record_key);
+ aborts_if !remain_zero_1_cond && !exists<VotingRecords>(@aptos_framework);
+ include !remain_zero_1_cond && !entirely_voted ==> GetVotingPowerAbortsIf {
+ pool_address: stake_pool
+ };
+ let staking_config = global<staking_config::StakingConfig>(@aptos_framework);
+ let spec_voting_power = spec_get_voting_power(stake_pool, staking_config);
+ let voting_records_v2 = borrow_global<VotingRecordsV2>(@aptos_framework);
+ let used_voting_power = if (smart_table::spec_contains(voting_records_v2.votes, record_key)) {
+ smart_table::spec_get(voting_records_v2.votes, record_key)
+ } else {
+ 0
+ };
+ aborts_if !remain_zero_1_cond && !entirely_voted && features::spec_partial_governance_voting_enabled() &&
+ used_voting_power > 0 && spec_voting_power < used_voting_power;
+ let remaining_power = spec_get_remaining_voting_power(stake_pool, proposal_id);
+ let real_voting_power = min(voting_power, remaining_power);
+ aborts_if !(real_voting_power > 0);
+ aborts_if !exists<VotingRecords>(@aptos_framework);
+ let voting_records = global<VotingRecords>(@aptos_framework);
+ let allow_validator_set_change = global<staking_config::StakingConfig>(@aptos_framework).allow_validator_set_change;
+ let stake_pool_res = global<stake::StakePool>(stake_pool);
+ aborts_if !exists<voting::VotingForum<GovernanceProposal>>(@aptos_framework);
+ let voting_forum = global<voting::VotingForum<GovernanceProposal>>(@aptos_framework);
+ let proposal = table::spec_get(voting_forum.proposals, proposal_id);
+ aborts_if !table::spec_contains(voting_forum.proposals, proposal_id);
+ let proposal_expiration = proposal.expiration_secs;
+ let locked_until_secs = global<stake::StakePool>(stake_pool).locked_until_secs;
+ aborts_if proposal_expiration > locked_until_secs;
+ aborts_if timestamp::now_seconds() > proposal_expiration;
+ aborts_if proposal.is_resolved;
+ aborts_if !string::spec_internal_check_utf8(voting::IS_MULTI_STEP_PROPOSAL_IN_EXECUTION_KEY);
+ let execution_key = utf8(voting::IS_MULTI_STEP_PROPOSAL_IN_EXECUTION_KEY);
+ aborts_if simple_map::spec_contains_key(proposal.metadata, execution_key) &&
+ simple_map::spec_get(proposal.metadata, execution_key) != std::bcs::to_bytes(false);
+ aborts_if
+ if (should_pass) { proposal.yes_votes + real_voting_power > MAX_U128 } else { proposal.no_votes + real_voting_power > MAX_U128 };
+ let post post_voting_forum = global<voting::VotingForum<GovernanceProposal>>(@aptos_framework);
+ let post post_proposal = table::spec_get(post_voting_forum.proposals, proposal_id);
+ aborts_if !string::spec_internal_check_utf8(voting::RESOLVABLE_TIME_METADATA_KEY);
+ let key = utf8(voting::RESOLVABLE_TIME_METADATA_KEY);
+ ensures simple_map::spec_contains_key(post_proposal.metadata, key);
+ ensures simple_map::spec_get(post_proposal.metadata, key) == std::bcs::to_bytes(timestamp::now_seconds());
+ aborts_if features::spec_partial_governance_voting_enabled() && used_voting_power + real_voting_power > MAX_U64;
+ aborts_if !features::spec_partial_governance_voting_enabled() && table::spec_contains(voting_records.votes, record_key);
+ aborts_if !exists<GovernanceEvents>(@aptos_framework);
+ let early_resolution_threshold = option::spec_borrow(proposal.early_resolution_vote_threshold);
+ let is_voting_period_over = timestamp::spec_now_seconds() > proposal_expiration;
+ let new_proposal_yes_votes_0 = proposal.yes_votes + real_voting_power;
+ let can_be_resolved_early_0 = option::spec_is_some(proposal.early_resolution_vote_threshold) &&
+ (new_proposal_yes_votes_0 >= early_resolution_threshold ||
+ proposal.no_votes >= early_resolution_threshold);
+ let is_voting_closed_0 = is_voting_period_over || can_be_resolved_early_0;
+ let proposal_state_successed_0 = is_voting_closed_0 && new_proposal_yes_votes_0 > proposal.no_votes &&
+ new_proposal_yes_votes_0 + proposal.no_votes >= proposal.min_vote_threshold;
+ let new_proposal_no_votes_0 = proposal.no_votes + real_voting_power;
+ let can_be_resolved_early_1 = option::spec_is_some(proposal.early_resolution_vote_threshold) &&
+ (proposal.yes_votes >= early_resolution_threshold ||
+ new_proposal_no_votes_0 >= early_resolution_threshold);
+ let is_voting_closed_1 = is_voting_period_over || can_be_resolved_early_1;
+ let proposal_state_successed_1 = is_voting_closed_1 && proposal.yes_votes > new_proposal_no_votes_0 &&
+ proposal.yes_votes + new_proposal_no_votes_0 >= proposal.min_vote_threshold;
+ let new_proposal_yes_votes_1 = proposal.yes_votes + real_voting_power;
+ let can_be_resolved_early_2 = option::spec_is_some(proposal.early_resolution_vote_threshold) &&
+ (new_proposal_yes_votes_1 >= early_resolution_threshold ||
+ proposal.no_votes >= early_resolution_threshold);
+ let is_voting_closed_2 = is_voting_period_over || can_be_resolved_early_2;
+ let proposal_state_successed_2 = is_voting_closed_2 && new_proposal_yes_votes_1 > proposal.no_votes &&
+ new_proposal_yes_votes_1 + proposal.no_votes >= proposal.min_vote_threshold;
+ let new_proposal_no_votes_1 = proposal.no_votes + real_voting_power;
+ let can_be_resolved_early_3 = option::spec_is_some(proposal.early_resolution_vote_threshold) &&
+ (proposal.yes_votes >= early_resolution_threshold ||
+ new_proposal_no_votes_1 >= early_resolution_threshold);
+ let is_voting_closed_3 = is_voting_period_over || can_be_resolved_early_3;
+ let proposal_state_successed_3 = is_voting_closed_3 && proposal.yes_votes > new_proposal_no_votes_1 &&
+ proposal.yes_votes + new_proposal_no_votes_1 >= proposal.min_vote_threshold;
+ let post can_be_resolved_early = option::spec_is_some(proposal.early_resolution_vote_threshold) &&
+ (post_proposal.yes_votes >= early_resolution_threshold ||
+ post_proposal.no_votes >= early_resolution_threshold);
+ let post is_voting_closed = is_voting_period_over || can_be_resolved_early;
+ let post proposal_state_successed = is_voting_closed && post_proposal.yes_votes > post_proposal.no_votes &&
+ post_proposal.yes_votes + post_proposal.no_votes >= proposal.min_vote_threshold;
+ let execution_hash = proposal.execution_hash;
+ let post post_approved_hashes = global<ApprovedExecutionHashes>(@aptos_framework);
+ // This enforces high-level requirement 3:
+ aborts_if
+ if (should_pass) {
+ proposal_state_successed_0 && !exists<ApprovedExecutionHashes>(@aptos_framework)
+ } else {
+ proposal_state_successed_1 && !exists<ApprovedExecutionHashes>(@aptos_framework)
+ };
+ aborts_if
+ if (should_pass) {
+ proposal_state_successed_2 && !exists<ApprovedExecutionHashes>(@aptos_framework)
+ } else {
+ proposal_state_successed_3 && !exists<ApprovedExecutionHashes>(@aptos_framework)
+ };
+ ensures proposal_state_successed ==> simple_map::spec_contains_key(post_approved_hashes.hashes, proposal_id) &&
+ simple_map::spec_get(post_approved_hashes.hashes, proposal_id) == execution_hash;
+ aborts_if features::spec_partial_governance_voting_enabled() && !exists<VotingRecordsV2>(@aptos_framework);
+}
+
+
+
+
+
+
+### Function `add_approved_script_hash_script`
+
+
+public entry fun add_approved_script_hash_script(proposal_id: u64)
+
+
+
+
+
+requires chain_status::is_operating();
+include AddApprovedScriptHash;
+
+
+
+
+
+
+
+
+schema AddApprovedScriptHash {
+ proposal_id: u64;
+ aborts_if !exists<ApprovedExecutionHashes>(@aptos_framework);
+ aborts_if !exists<voting::VotingForum<GovernanceProposal>>(@aptos_framework);
+ let voting_forum = global<voting::VotingForum<GovernanceProposal>>(@aptos_framework);
+ let proposal = table::spec_get(voting_forum.proposals, proposal_id);
+ aborts_if !table::spec_contains(voting_forum.proposals, proposal_id);
+ let early_resolution_threshold = option::spec_borrow(proposal.early_resolution_vote_threshold);
+ aborts_if timestamp::now_seconds() <= proposal.expiration_secs &&
+ (option::spec_is_none(proposal.early_resolution_vote_threshold) ||
+ proposal.yes_votes < early_resolution_threshold && proposal.no_votes < early_resolution_threshold);
+ aborts_if (timestamp::now_seconds() > proposal.expiration_secs ||
+ option::spec_is_some(proposal.early_resolution_vote_threshold) && (proposal.yes_votes >= early_resolution_threshold ||
+ proposal.no_votes >= early_resolution_threshold)) &&
+ (proposal.yes_votes <= proposal.no_votes || proposal.yes_votes + proposal.no_votes < proposal.min_vote_threshold);
+ let post post_approved_hashes = global<ApprovedExecutionHashes>(@aptos_framework);
+ // This enforces high-level requirement 4:
+ ensures simple_map::spec_contains_key(post_approved_hashes.hashes, proposal_id) &&
+ simple_map::spec_get(post_approved_hashes.hashes, proposal_id) == proposal.execution_hash;
+}
+
+
+
+
+
+
+### Function `add_approved_script_hash`
+
+
+public fun add_approved_script_hash(proposal_id: u64)
+
+
+
+
+
+requires chain_status::is_operating();
+include AddApprovedScriptHash;
+
+
+
+
+
+
+### Function `resolve`
+
+
+public fun resolve(proposal_id: u64, signer_address: address): signer
+
+
+
+Address @aptos_framework must exist ApprovedExecutionHashes and GovernanceProposal and GovernanceResponsbility.
+
+
+requires chain_status::is_operating();
+include VotingIsProposalResolvableAbortsif;
+let voting_forum = global<voting::VotingForum<GovernanceProposal>>(@aptos_framework);
+let proposal = table::spec_get(voting_forum.proposals, proposal_id);
+let multi_step_key = utf8(voting::IS_MULTI_STEP_PROPOSAL_KEY);
+let has_multi_step_key = simple_map::spec_contains_key(proposal.metadata, multi_step_key);
+let is_multi_step_proposal = aptos_std::from_bcs::deserialize<bool>(simple_map::spec_get(proposal.metadata, multi_step_key));
+aborts_if has_multi_step_key && !aptos_std::from_bcs::deserializable<bool>(simple_map::spec_get(proposal.metadata, multi_step_key));
+aborts_if !string::spec_internal_check_utf8(voting::IS_MULTI_STEP_PROPOSAL_KEY);
+aborts_if has_multi_step_key && is_multi_step_proposal;
+let post post_voting_forum = global<voting::VotingForum<GovernanceProposal>>(@aptos_framework);
+let post post_proposal = table::spec_get(post_voting_forum.proposals, proposal_id);
+ensures post_proposal.is_resolved == true && post_proposal.resolution_time_secs == timestamp::now_seconds();
+aborts_if option::spec_is_none(proposal.execution_content);
+aborts_if !exists<ApprovedExecutionHashes>(@aptos_framework);
+let post post_approved_hashes = global<ApprovedExecutionHashes>(@aptos_framework).hashes;
+ensures !simple_map::spec_contains_key(post_approved_hashes, proposal_id);
+include GetSignerAbortsIf;
+let governance_responsibility = global<GovernanceResponsbility>(@aptos_framework);
+let signer_cap = simple_map::spec_get(governance_responsibility.signer_caps, signer_address);
+let addr = signer_cap.account;
+ensures signer::address_of(result) == addr;
+
+
+
+
+
+
+### Function `resolve_multi_step_proposal`
+
+
+public fun resolve_multi_step_proposal(proposal_id: u64, signer_address: address, next_execution_hash: vector<u8>): signer
+
+
+
+
+
+requires chain_status::is_operating();
+pragma verify_duration_estimate = 120;
+include VotingIsProposalResolvableAbortsif;
+let voting_forum = global<voting::VotingForum<GovernanceProposal>>(@aptos_framework);
+let proposal = table::spec_get(voting_forum.proposals, proposal_id);
+let post post_voting_forum = global<voting::VotingForum<GovernanceProposal>>(@aptos_framework);
+let post post_proposal = table::spec_get(post_voting_forum.proposals, proposal_id);
+aborts_if !string::spec_internal_check_utf8(voting::IS_MULTI_STEP_PROPOSAL_IN_EXECUTION_KEY);
+let multi_step_in_execution_key = utf8(voting::IS_MULTI_STEP_PROPOSAL_IN_EXECUTION_KEY);
+let post is_multi_step_proposal_in_execution_value = simple_map::spec_get(post_proposal.metadata, multi_step_in_execution_key);
+aborts_if !string::spec_internal_check_utf8(voting::IS_MULTI_STEP_PROPOSAL_KEY);
+let multi_step_key = utf8(voting::IS_MULTI_STEP_PROPOSAL_KEY);
+aborts_if simple_map::spec_contains_key(proposal.metadata, multi_step_key) &&
+ !aptos_std::from_bcs::deserializable<bool>(simple_map::spec_get(proposal.metadata, multi_step_key));
+let is_multi_step = simple_map::spec_contains_key(proposal.metadata, multi_step_key) &&
+ aptos_std::from_bcs::deserialize<bool>(simple_map::spec_get(proposal.metadata, multi_step_key));
+let next_execution_hash_is_empty = len(next_execution_hash) == 0;
+aborts_if !is_multi_step && !next_execution_hash_is_empty;
+aborts_if next_execution_hash_is_empty && is_multi_step && !simple_map::spec_contains_key(proposal.metadata, multi_step_in_execution_key);
+ensures next_execution_hash_is_empty ==> post_proposal.is_resolved == true && post_proposal.resolution_time_secs == timestamp::spec_now_seconds() &&
+ if (is_multi_step) {
+ is_multi_step_proposal_in_execution_value == std::bcs::serialize(false)
+ } else {
+ simple_map::spec_contains_key(proposal.metadata, multi_step_in_execution_key) ==>
+ is_multi_step_proposal_in_execution_value == std::bcs::serialize(true)
+ };
+ensures !next_execution_hash_is_empty ==> post_proposal.execution_hash == next_execution_hash;
+aborts_if !exists<ApprovedExecutionHashes>(@aptos_framework);
+let post post_approved_hashes = global<ApprovedExecutionHashes>(@aptos_framework).hashes;
+ensures next_execution_hash_is_empty ==> !simple_map::spec_contains_key(post_approved_hashes, proposal_id);
+ensures !next_execution_hash_is_empty ==>
+ simple_map::spec_get(post_approved_hashes, proposal_id) == next_execution_hash;
+include GetSignerAbortsIf;
+let governance_responsibility = global<GovernanceResponsbility>(@aptos_framework);
+let signer_cap = simple_map::spec_get(governance_responsibility.signer_caps, signer_address);
+let addr = signer_cap.account;
+ensures signer::address_of(result) == addr;
+
+
+
+
+
+
+
+
+schema VotingIsProposalResolvableAbortsif {
+ proposal_id: u64;
+ aborts_if !exists<voting::VotingForum<GovernanceProposal>>(@aptos_framework);
+ let voting_forum = global<voting::VotingForum<GovernanceProposal>>(@aptos_framework);
+ let proposal = table::spec_get(voting_forum.proposals, proposal_id);
+ aborts_if !table::spec_contains(voting_forum.proposals, proposal_id);
+ let early_resolution_threshold = option::spec_borrow(proposal.early_resolution_vote_threshold);
+ let voting_period_over = timestamp::now_seconds() > proposal.expiration_secs;
+ let be_resolved_early = option::spec_is_some(proposal.early_resolution_vote_threshold) &&
+ (proposal.yes_votes >= early_resolution_threshold ||
+ proposal.no_votes >= early_resolution_threshold);
+ let voting_closed = voting_period_over || be_resolved_early;
+ aborts_if voting_closed && (proposal.yes_votes <= proposal.no_votes || proposal.yes_votes + proposal.no_votes < proposal.min_vote_threshold);
+ aborts_if !voting_closed;
+ aborts_if proposal.is_resolved;
+ aborts_if !string::spec_internal_check_utf8(voting::RESOLVABLE_TIME_METADATA_KEY);
+ aborts_if !simple_map::spec_contains_key(proposal.metadata, utf8(voting::RESOLVABLE_TIME_METADATA_KEY));
+ let resolvable_time = aptos_std::from_bcs::deserialize<u64>(simple_map::spec_get(proposal.metadata, utf8(voting::RESOLVABLE_TIME_METADATA_KEY)));
+ aborts_if !aptos_std::from_bcs::deserializable<u64>(simple_map::spec_get(proposal.metadata, utf8(voting::RESOLVABLE_TIME_METADATA_KEY)));
+ aborts_if timestamp::now_seconds() <= resolvable_time;
+ aborts_if aptos_framework::transaction_context::spec_get_script_hash() != proposal.execution_hash;
+}
+
+
+
+
+
+
+### Function `remove_approved_hash`
+
+
+public fun remove_approved_hash(proposal_id: u64)
+
+
+
+Address @aptos_framework must exist ApprovedExecutionHashes and GovernanceProposal.
+
+
+aborts_if !exists<voting::VotingForum<GovernanceProposal>>(@aptos_framework);
+aborts_if !exists<ApprovedExecutionHashes>(@aptos_framework);
+let voting_forum = global<voting::VotingForum<GovernanceProposal>>(@aptos_framework);
+aborts_if !table::spec_contains(voting_forum.proposals, proposal_id);
+aborts_if !exists<voting::VotingForum<GovernanceProposal>>(@aptos_framework);
+let proposal = table::spec_get(voting_forum.proposals, proposal_id);
+aborts_if !proposal.is_resolved;
+let post approved_hashes = global<ApprovedExecutionHashes>(@aptos_framework).hashes;
+ensures !simple_map::spec_contains_key(approved_hashes, proposal_id);
+
+
+
+
+
+
+### Function `reconfigure`
+
+
+public entry fun reconfigure(aptos_framework: &signer)
+
+
+
+
+
+pragma verify = false;
+aborts_if !system_addresses::is_aptos_framework_address(signer::address_of(aptos_framework));
+include reconfiguration_with_dkg::FinishRequirement {
+ framework: aptos_framework
+};
+include stake::GetReconfigStartTimeRequirement;
+include transaction_fee::RequiresCollectedFeesPerValueLeqBlockAptosSupply;
+requires chain_status::is_operating();
+requires exists<stake::ValidatorFees>(@aptos_framework);
+requires exists<CoinInfo<AptosCoin>>(@aptos_framework);
+requires exists<staking_config::StakingRewardsConfig>(@aptos_framework);
+include staking_config::StakingRewardsConfigRequirement;
+
+
+
+
+
+
+### Function `force_end_epoch`
+
+
+public entry fun force_end_epoch(aptos_framework: &signer)
+
+
+
+
+
+pragma verify = false;
+let address = signer::address_of(aptos_framework);
+include reconfiguration_with_dkg::FinishRequirement {
+ framework: aptos_framework
+};
+
+
+
+
+
+
+
+
+schema VotingInitializationAbortIfs {
+ aborts_if features::spec_partial_governance_voting_enabled() && !exists<VotingRecordsV2>(@aptos_framework);
+}
+
+
+
+
+
+
+### Function `force_end_epoch_test_only`
+
+
+public entry fun force_end_epoch_test_only(aptos_framework: &signer)
+
+
+
+
+
+pragma verify = false;
+
+
+
+
+
+
+### Function `toggle_features`
+
+
+public fun toggle_features(aptos_framework: &signer, enable: vector<u64>, disable: vector<u64>)
+
+
+
+Signer address must be @aptos_framework.
+Address @aptos_framework must exist GovernanceConfig and GovernanceEvents.
+
+
+pragma verify = false;
+let addr = signer::address_of(aptos_framework);
+aborts_if addr != @aptos_framework;
+include reconfiguration_with_dkg::FinishRequirement {
+ framework: aptos_framework
+};
+include stake::GetReconfigStartTimeRequirement;
+include transaction_fee::RequiresCollectedFeesPerValueLeqBlockAptosSupply;
+requires chain_status::is_operating();
+requires exists<stake::ValidatorFees>(@aptos_framework);
+requires exists<CoinInfo<AptosCoin>>(@aptos_framework);
+requires exists<staking_config::StakingRewardsConfig>(@aptos_framework);
+include staking_config::StakingRewardsConfigRequirement;
+
+
+
+
+
+
+### Function `get_signer_testnet_only`
+
+
+public fun get_signer_testnet_only(core_resources: &signer, signer_address: address): signer
+
+
+
+Signer address must be @core_resources.
+signer must exist in MintCapStore.
+Address @aptos_framework must exist GovernanceResponsbility.
+
+
+aborts_if signer::address_of(core_resources) != @core_resources;
+aborts_if !exists<aptos_coin::MintCapStore>(signer::address_of(core_resources));
+include GetSignerAbortsIf;
+
+
+
+
+
+
+### Function `get_voting_power`
+
+
+#[view]
+public fun get_voting_power(pool_address: address): u64
+
+
+
+Address @aptos_framework must exist StakingConfig.
+limit addition overflow.
+pool_address must exist in StakePool.
+
+
+include GetVotingPowerAbortsIf;
+let staking_config = global<staking_config::StakingConfig>(@aptos_framework);
+let allow_validator_set_change = staking_config.allow_validator_set_change;
+let stake_pool_res = global<stake::StakePool>(pool_address);
+ensures allow_validator_set_change ==> result == stake_pool_res.active.value + stake_pool_res.pending_active.value + stake_pool_res.pending_inactive.value;
+ensures !allow_validator_set_change ==> if (stake::spec_is_current_epoch_validator(pool_address)) {
+ result == stake_pool_res.active.value + stake_pool_res.pending_inactive.value
+} else {
+ result == 0
+};
+ensures result == spec_get_voting_power(pool_address, staking_config);
+
+
+
+
+
+
+
+
+fun spec_get_voting_power(pool_address: address, staking_config: staking_config::StakingConfig): u64 {
+ let allow_validator_set_change = staking_config.allow_validator_set_change;
+ let stake_pool_res = global<stake::StakePool>(pool_address);
+ if (allow_validator_set_change) {
+ stake_pool_res.active.value + stake_pool_res.pending_active.value + stake_pool_res.pending_inactive.value
+ } else if (!allow_validator_set_change && (stake::spec_is_current_epoch_validator(pool_address))) {
+ stake_pool_res.active.value + stake_pool_res.pending_inactive.value
+ } else {
+ 0
+ }
+}
+
+
+
+
+
+
+### Function `get_signer`
+
+
+fun get_signer(signer_address: address): signer
+
+
+
+
+
+include GetSignerAbortsIf;
+
+
+
+
+
+
+
+
+schema GetSignerAbortsIf {
+ signer_address: address;
+ aborts_if !exists<GovernanceResponsbility>(@aptos_framework);
+ let cap_map = global<GovernanceResponsbility>(@aptos_framework).signer_caps;
+ aborts_if !simple_map::spec_contains_key(cap_map, signer_address);
+}
+
+
+
+
+
+
+### Function `create_proposal_metadata`
+
+
+fun create_proposal_metadata(metadata_location: vector<u8>, metadata_hash: vector<u8>): simple_map::SimpleMap<string::String, vector<u8>>
+
+
+
+
+
+include CreateProposalMetadataAbortsIf;
+
+
+
+
+
+
+
+
+schema CreateProposalMetadataAbortsIf {
+ metadata_location: vector<u8>;
+ metadata_hash: vector<u8>;
+ aborts_if string::length(utf8(metadata_location)) > 256;
+ aborts_if string::length(utf8(metadata_hash)) > 256;
+ aborts_if !string::spec_internal_check_utf8(metadata_location);
+ aborts_if !string::spec_internal_check_utf8(metadata_hash);
+ aborts_if !string::spec_internal_check_utf8(METADATA_LOCATION_KEY);
+ aborts_if !string::spec_internal_check_utf8(METADATA_HASH_KEY);
+}
+
+
+
+
+
+
+### Function `assert_voting_initialization`
+
+
+fun assert_voting_initialization()
+
+
+
+
+
+include VotingInitializationAbortIfs;
+
+
+
+[move-book]: https://aptos.dev/move/book/SUMMARY
diff --git a/aptos-move/framework/aptos-framework/tests/compiler-v2-doc/block.md b/aptos-move/framework/aptos-framework/tests/compiler-v2-doc/block.md
new file mode 100644
index 0000000000000..5e56a8eac0dc7
--- /dev/null
+++ b/aptos-move/framework/aptos-framework/tests/compiler-v2-doc/block.md
@@ -0,0 +1,1302 @@
+
+
+
+# Module `0x1::block`
+
+This module defines a struct storing the metadata of the block and new block events.
+
+
+- [Resource `BlockResource`](#0x1_block_BlockResource)
+- [Resource `CommitHistory`](#0x1_block_CommitHistory)
+- [Struct `NewBlockEvent`](#0x1_block_NewBlockEvent)
+- [Struct `UpdateEpochIntervalEvent`](#0x1_block_UpdateEpochIntervalEvent)
+- [Struct `NewBlock`](#0x1_block_NewBlock)
+- [Struct `UpdateEpochInterval`](#0x1_block_UpdateEpochInterval)
+- [Constants](#@Constants_0)
+- [Function `initialize`](#0x1_block_initialize)
+- [Function `initialize_commit_history`](#0x1_block_initialize_commit_history)
+- [Function `update_epoch_interval_microsecs`](#0x1_block_update_epoch_interval_microsecs)
+- [Function `get_epoch_interval_secs`](#0x1_block_get_epoch_interval_secs)
+- [Function `block_prologue_common`](#0x1_block_block_prologue_common)
+- [Function `block_prologue`](#0x1_block_block_prologue)
+- [Function `block_prologue_ext`](#0x1_block_block_prologue_ext)
+- [Function `get_current_block_height`](#0x1_block_get_current_block_height)
+- [Function `emit_new_block_event`](#0x1_block_emit_new_block_event)
+- [Function `emit_genesis_block_event`](#0x1_block_emit_genesis_block_event)
+- [Function `emit_writeset_block_event`](#0x1_block_emit_writeset_block_event)
+- [Specification](#@Specification_1)
+ - [High-level Requirements](#high-level-req)
+ - [Module-level Specification](#module-level-spec)
+ - [Resource `BlockResource`](#@Specification_1_BlockResource)
+ - [Resource `CommitHistory`](#@Specification_1_CommitHistory)
+ - [Function `initialize`](#@Specification_1_initialize)
+ - [Function `update_epoch_interval_microsecs`](#@Specification_1_update_epoch_interval_microsecs)
+ - [Function `get_epoch_interval_secs`](#@Specification_1_get_epoch_interval_secs)
+ - [Function `block_prologue_common`](#@Specification_1_block_prologue_common)
+ - [Function `block_prologue`](#@Specification_1_block_prologue)
+ - [Function `block_prologue_ext`](#@Specification_1_block_prologue_ext)
+ - [Function `get_current_block_height`](#@Specification_1_get_current_block_height)
+ - [Function `emit_new_block_event`](#@Specification_1_emit_new_block_event)
+ - [Function `emit_genesis_block_event`](#@Specification_1_emit_genesis_block_event)
+ - [Function `emit_writeset_block_event`](#@Specification_1_emit_writeset_block_event)
+
+
+use 0x1::account;
+use 0x1::error;
+use 0x1::event;
+use 0x1::features;
+use 0x1::option;
+use 0x1::randomness;
+use 0x1::reconfiguration;
+use 0x1::reconfiguration_with_dkg;
+use 0x1::stake;
+use 0x1::state_storage;
+use 0x1::system_addresses;
+use 0x1::table_with_length;
+use 0x1::timestamp;
+use 0x1::transaction_fee;
+
+
+
+
+
+
+## Resource `BlockResource`
+
+Should be in-sync with BlockResource rust struct in new_block.rs
+
+
+struct BlockResource has key
+
+
+
+
+height: u64
+epoch_interval: u64
+new_block_events: event::EventHandle<block::NewBlockEvent>
+update_epoch_interval_events: event::EventHandle<block::UpdateEpochIntervalEvent>
+struct CommitHistory has key
+
+
+
+
+max_capacity: u32
+next_idx: u32
+table: table_with_length::TableWithLength<u32, block::NewBlockEvent>
+struct NewBlockEvent has copy, drop, store
+
+
+
+
+hash: address
+epoch: u64
+round: u64
+height: u64
+previous_block_votes_bitvec: vector<u8>
+proposer: address
+failed_proposer_indices: vector<u64>
+time_microseconds: u64
+struct UpdateEpochIntervalEvent has drop, store
+
+
+
+
+old_epoch_interval: u64
+new_epoch_interval: u64
+#[event]
+struct NewBlock has drop, store
+
+
+
+
+hash: address
+epoch: u64
+round: u64
+height: u64
+previous_block_votes_bitvec: vector<u8>
+proposer: address
+failed_proposer_indices: vector<u64>
+time_microseconds: u64
+#[event]
+struct UpdateEpochInterval has drop, store
+
+
+
+
+old_epoch_interval: u64
+new_epoch_interval: u64
+const MAX_U64: u64 = 18446744073709551615;
+
+
+
+
+
+
+An invalid proposer was provided. Expected the proposer to be the VM or an active validator.
+
+
+const EINVALID_PROPOSER: u64 = 2;
+
+
+
+
+
+
+The number of new block events does not equal the current block height.
+
+
+const ENUM_NEW_BLOCK_EVENTS_DOES_NOT_MATCH_BLOCK_HEIGHT: u64 = 1;
+
+
+
+
+
+
+Epoch interval cannot be 0.
+
+
+const EZERO_EPOCH_INTERVAL: u64 = 3;
+
+
+
+
+
+
+The maximum capacity of the commit history cannot be 0.
+
+
+const EZERO_MAX_CAPACITY: u64 = 3;
+
+
+
+
+
+
+## Function `initialize`
+
+This can only be called during Genesis.
+
+
+public(friend) fun initialize(aptos_framework: &signer, epoch_interval_microsecs: u64)
+
+
+
+
+public(friend) fun initialize(aptos_framework: &signer, epoch_interval_microsecs: u64) {
+ system_addresses::assert_aptos_framework(aptos_framework);
+ assert!(epoch_interval_microsecs > 0, error::invalid_argument(EZERO_EPOCH_INTERVAL));
+
+ move_to<CommitHistory>(aptos_framework, CommitHistory {
+ max_capacity: 2000,
+ next_idx: 0,
+ table: table_with_length::new(),
+ });
+
+ move_to<BlockResource>(
+ aptos_framework,
+ BlockResource {
+ height: 0,
+ epoch_interval: epoch_interval_microsecs,
+ new_block_events: account::new_event_handle<NewBlockEvent>(aptos_framework),
+ update_epoch_interval_events: account::new_event_handle<UpdateEpochIntervalEvent>(aptos_framework),
+ }
+ );
+}
+
+
+
+
+public fun initialize_commit_history(fx: &signer, max_capacity: u32)
+
+
+
+
+public fun initialize_commit_history(fx: &signer, max_capacity: u32) {
+ assert!(max_capacity > 0, error::invalid_argument(EZERO_MAX_CAPACITY));
+ move_to<CommitHistory>(fx, CommitHistory {
+ max_capacity,
+ next_idx: 0,
+ table: table_with_length::new(),
+ });
+}
+
+
+
+
+public fun update_epoch_interval_microsecs(aptos_framework: &signer, new_epoch_interval: u64)
+
+
+
+
+public fun update_epoch_interval_microsecs(
+ aptos_framework: &signer,
+ new_epoch_interval: u64,
+) acquires BlockResource {
+ system_addresses::assert_aptos_framework(aptos_framework);
+ assert!(new_epoch_interval > 0, error::invalid_argument(EZERO_EPOCH_INTERVAL));
+
+ let block_resource = borrow_global_mut<BlockResource>(@aptos_framework);
+ let old_epoch_interval = block_resource.epoch_interval;
+ block_resource.epoch_interval = new_epoch_interval;
+
+ if (std::features::module_event_migration_enabled()) {
+ event::emit(
+ UpdateEpochInterval { old_epoch_interval, new_epoch_interval },
+ );
+ };
+ event::emit_event<UpdateEpochIntervalEvent>(
+ &mut block_resource.update_epoch_interval_events,
+ UpdateEpochIntervalEvent { old_epoch_interval, new_epoch_interval },
+ );
+}
+
+
+
+
+#[view]
+public fun get_epoch_interval_secs(): u64
+
+
+
+
+public fun get_epoch_interval_secs(): u64 acquires BlockResource {
+ borrow_global<BlockResource>(@aptos_framework).epoch_interval / 1000000
+}
+
+
+
+
+fun block_prologue_common(vm: &signer, hash: address, epoch: u64, round: u64, proposer: address, failed_proposer_indices: vector<u64>, previous_block_votes_bitvec: vector<u8>, timestamp: u64): u64
+
+
+
+
+fun block_prologue_common(
+ vm: &signer,
+ hash: address,
+ epoch: u64,
+ round: u64,
+ proposer: address,
+ failed_proposer_indices: vector<u64>,
+ previous_block_votes_bitvec: vector<u8>,
+ timestamp: u64
+): u64 acquires BlockResource, CommitHistory {
+ // Operational constraint: can only be invoked by the VM.
+ system_addresses::assert_vm(vm);
+
+ // Blocks can only be produced by a valid proposer or by the VM itself for Nil blocks (no user txs).
+ assert!(
+ proposer == @vm_reserved || stake::is_current_epoch_validator(proposer),
+ error::permission_denied(EINVALID_PROPOSER),
+ );
+
+ let proposer_index = option::none();
+ if (proposer != @vm_reserved) {
+ proposer_index = option::some(stake::get_validator_index(proposer));
+ };
+
+ let block_metadata_ref = borrow_global_mut<BlockResource>(@aptos_framework);
+ block_metadata_ref.height = event::counter(&block_metadata_ref.new_block_events);
+
+ // Emit both event v1 and v2 for compatibility. Eventually only module events will be kept.
+ let new_block_event = NewBlockEvent {
+ hash,
+ epoch,
+ round,
+ height: block_metadata_ref.height,
+ previous_block_votes_bitvec,
+ proposer,
+ failed_proposer_indices,
+ time_microseconds: timestamp,
+ };
+ let new_block_event_v2 = NewBlock {
+ hash,
+ epoch,
+ round,
+ height: block_metadata_ref.height,
+ previous_block_votes_bitvec,
+ proposer,
+ failed_proposer_indices,
+ time_microseconds: timestamp,
+ };
+ emit_new_block_event(vm, &mut block_metadata_ref.new_block_events, new_block_event, new_block_event_v2);
+
+ if (features::collect_and_distribute_gas_fees()) {
+ // Assign the fees collected from the previous block to the previous block proposer.
+ // If for any reason the fees cannot be assigned, this function burns the collected coins.
+ transaction_fee::process_collected_fees();
+ // Set the proposer of this block as the receiver of the fees, so that the fees for this
+ // block are assigned to the right account.
+ transaction_fee::register_proposer_for_fee_collection(proposer);
+ };
+
+ // Performance scores have to be updated before the epoch transition as the transaction that triggers the
+ // transition is the last block in the previous epoch.
+ stake::update_performance_statistics(proposer_index, failed_proposer_indices);
+ state_storage::on_new_block(reconfiguration::current_epoch());
+
+ block_metadata_ref.epoch_interval
+}
+
+
+
+
+fun block_prologue(vm: signer, hash: address, epoch: u64, round: u64, proposer: address, failed_proposer_indices: vector<u64>, previous_block_votes_bitvec: vector<u8>, timestamp: u64)
+
+
+
+
+fun block_prologue(
+ vm: signer,
+ hash: address,
+ epoch: u64,
+ round: u64,
+ proposer: address,
+ failed_proposer_indices: vector<u64>,
+ previous_block_votes_bitvec: vector<u8>,
+ timestamp: u64
+) acquires BlockResource, CommitHistory {
+ let epoch_interval = block_prologue_common(&vm, hash, epoch, round, proposer, failed_proposer_indices, previous_block_votes_bitvec, timestamp);
+ randomness::on_new_block(&vm, epoch, round, option::none());
+ if (timestamp - reconfiguration::last_reconfiguration_time() >= epoch_interval) {
+ reconfiguration::reconfigure();
+ };
+}
+
+
+
+
+block_prologue()
but trigger reconfiguration with DKG after epoch timed out.
+
+
+fun block_prologue_ext(vm: signer, hash: address, epoch: u64, round: u64, proposer: address, failed_proposer_indices: vector<u64>, previous_block_votes_bitvec: vector<u8>, timestamp: u64, randomness_seed: option::Option<vector<u8>>)
+
+
+
+
+fun block_prologue_ext(
+ vm: signer,
+ hash: address,
+ epoch: u64,
+ round: u64,
+ proposer: address,
+ failed_proposer_indices: vector<u64>,
+ previous_block_votes_bitvec: vector<u8>,
+ timestamp: u64,
+ randomness_seed: Option<vector<u8>>,
+) acquires BlockResource, CommitHistory {
+ let epoch_interval = block_prologue_common(
+ &vm,
+ hash,
+ epoch,
+ round,
+ proposer,
+ failed_proposer_indices,
+ previous_block_votes_bitvec,
+ timestamp
+ );
+ randomness::on_new_block(&vm, epoch, round, randomness_seed);
+
+ if (timestamp - reconfiguration::last_reconfiguration_time() >= epoch_interval) {
+ reconfiguration_with_dkg::try_start();
+ };
+}
+
+
+
+
+#[view]
+public fun get_current_block_height(): u64
+
+
+
+
+public fun get_current_block_height(): u64 acquires BlockResource {
+ borrow_global<BlockResource>(@aptos_framework).height
+}
+
+
+
+
+fun emit_new_block_event(vm: &signer, event_handle: &mut event::EventHandle<block::NewBlockEvent>, new_block_event: block::NewBlockEvent, new_block_event_v2: block::NewBlock)
+
+
+
+
+fun emit_new_block_event(
+ vm: &signer,
+ event_handle: &mut EventHandle<NewBlockEvent>,
+ new_block_event: NewBlockEvent,
+ new_block_event_v2: NewBlock
+) acquires CommitHistory {
+ if (exists<CommitHistory>(@aptos_framework)) {
+ let commit_history_ref = borrow_global_mut<CommitHistory>(@aptos_framework);
+ let idx = commit_history_ref.next_idx;
+ if (table_with_length::contains(&commit_history_ref.table, idx)) {
+ table_with_length::remove(&mut commit_history_ref.table, idx);
+ };
+ table_with_length::add(&mut commit_history_ref.table, idx, copy new_block_event);
+ spec {
+ assume idx + 1 <= MAX_U32;
+ };
+ commit_history_ref.next_idx = (idx + 1) % commit_history_ref.max_capacity;
+ };
+ timestamp::update_global_time(vm, new_block_event.proposer, new_block_event.time_microseconds);
+ assert!(
+ event::counter(event_handle) == new_block_event.height,
+ error::invalid_argument(ENUM_NEW_BLOCK_EVENTS_DOES_NOT_MATCH_BLOCK_HEIGHT),
+ );
+ if (std::features::module_event_migration_enabled()) {
+ event::emit(new_block_event_v2);
+ };
+ event::emit_event<NewBlockEvent>(event_handle, new_block_event);
+}
+
+
+
+
+NewBlockEvent
event. This function will be invoked by genesis directly to generate the very first
+reconfiguration event.
+
+
+fun emit_genesis_block_event(vm: signer)
+
+
+
+
+fun emit_genesis_block_event(vm: signer) acquires BlockResource, CommitHistory {
+ let block_metadata_ref = borrow_global_mut<BlockResource>(@aptos_framework);
+ let genesis_id = @0x0;
+ emit_new_block_event(
+ &vm,
+ &mut block_metadata_ref.new_block_events,
+ NewBlockEvent {
+ hash: genesis_id,
+ epoch: 0,
+ round: 0,
+ height: 0,
+ previous_block_votes_bitvec: vector::empty(),
+ proposer: @vm_reserved,
+ failed_proposer_indices: vector::empty(),
+ time_microseconds: 0,
+ },
+ NewBlock {
+ hash: genesis_id,
+ epoch: 0,
+ round: 0,
+ height: 0,
+ previous_block_votes_bitvec: vector::empty(),
+ proposer: @vm_reserved,
+ failed_proposer_indices: vector::empty(),
+ time_microseconds: 0,
+ }
+ );
+}
+
+
+
+
+NewBlockEvent
event. This function will be invoked by write set script directly to generate the
+new block event for WriteSetPayload.
+
+
+public fun emit_writeset_block_event(vm_signer: &signer, fake_block_hash: address)
+
+
+
+
+public fun emit_writeset_block_event(vm_signer: &signer, fake_block_hash: address) acquires BlockResource, CommitHistory {
+ system_addresses::assert_vm(vm_signer);
+ let block_metadata_ref = borrow_global_mut<BlockResource>(@aptos_framework);
+ block_metadata_ref.height = event::counter(&block_metadata_ref.new_block_events);
+
+ emit_new_block_event(
+ vm_signer,
+ &mut block_metadata_ref.new_block_events,
+ NewBlockEvent {
+ hash: fake_block_hash,
+ epoch: reconfiguration::current_epoch(),
+ round: MAX_U64,
+ height: block_metadata_ref.height,
+ previous_block_votes_bitvec: vector::empty(),
+ proposer: @vm_reserved,
+ failed_proposer_indices: vector::empty(),
+ time_microseconds: timestamp::now_microseconds(),
+ },
+ NewBlock {
+ hash: fake_block_hash,
+ epoch: reconfiguration::current_epoch(),
+ round: MAX_U64,
+ height: block_metadata_ref.height,
+ previous_block_votes_bitvec: vector::empty(),
+ proposer: @vm_reserved,
+ failed_proposer_indices: vector::empty(),
+ time_microseconds: timestamp::now_microseconds(),
+ }
+ );
+}
+
+
+
+
+No. | Requirement | Criticality | Implementation | Enforcement | +
---|---|---|---|---|
1 | +During the module's initialization, it guarantees that the BlockResource resource moves under the Aptos framework account with initial values. | +High | +The initialize function is responsible for setting up the initial state of the module, ensuring that the following conditions are met (1) the BlockResource resource is created, indicating its existence within the module's context, and moved under the Aptos framework account, (2) the block height is set to zero during initialization, and (3) the epoch interval is greater than zero. | +Formally Verified via Initialize. | +
2 | +Only the Aptos framework address may execute the following functionalities: (1) initialize BlockResource, and (2) update the epoch interval. | +Critical | +The initialize and update_epoch_interval_microsecs functions ensure that only aptos_framework can call them. | +Formally Verified via Initialize and update_epoch_interval_microsecs. | +
3 | +When updating the epoch interval, its value must be greater than zero and BlockResource must exist. | +High | +The update_epoch_interval_microsecs function asserts that new_epoch_interval is greater than zero and updates BlockResource's state. | +Formally verified via UpdateEpochIntervalMicrosecs and epoch_interval. | +
4 | +Only a valid proposer or the virtual machine is authorized to produce blocks. | +Critical | +During the execution of the block_prologue function, the validity of the proposer address is verified when setting the metadata for the current block. | +Formally Verified via block_prologue. | +
5 | +While emitting a new block event, the number of them is equal to the current block height. | +Medium | +The emit_new_block_event function asserts that the number of new block events equals the current block height. | +Formally Verified via emit_new_block_event. | +
invariant [suspendable] chain_status::is_operating() ==> exists<BlockResource>(@aptos_framework);
+invariant [suspendable] chain_status::is_operating() ==> exists<CommitHistory>(@aptos_framework);
+
+
+
+
+
+
+### Resource `BlockResource`
+
+
+struct BlockResource has key
+
+
+
+
+height: u64
+epoch_interval: u64
+new_block_events: event::EventHandle<block::NewBlockEvent>
+update_epoch_interval_events: event::EventHandle<block::UpdateEpochIntervalEvent>
+// This enforces high-level requirement 3:
+invariant epoch_interval > 0;
+
+
+
+
+
+
+### Resource `CommitHistory`
+
+
+struct CommitHistory has key
+
+
+
+
+max_capacity: u32
+next_idx: u32
+table: table_with_length::TableWithLength<u32, block::NewBlockEvent>
+invariant max_capacity > 0;
+
+
+
+
+
+
+### Function `initialize`
+
+
+public(friend) fun initialize(aptos_framework: &signer, epoch_interval_microsecs: u64)
+
+
+
+The caller is aptos_framework.
+The new_epoch_interval must be greater than 0.
+The BlockResource is not under the caller before initializing.
+The Account is not under the caller until the BlockResource is created for the caller.
+Make sure The BlockResource under the caller existed after initializing.
+The number of new events created does not exceed MAX_U64.
+
+
+// This enforces high-level requirement 1:
+include Initialize;
+include NewEventHandle;
+let addr = signer::address_of(aptos_framework);
+let account = global<account::Account>(addr);
+aborts_if account.guid_creation_num + 2 >= account::MAX_GUID_CREATION_NUM;
+
+
+
+
+
+
+### Function `update_epoch_interval_microsecs`
+
+
+public fun update_epoch_interval_microsecs(aptos_framework: &signer, new_epoch_interval: u64)
+
+
+
+The caller is @aptos_framework.
+The new_epoch_interval must be greater than 0.
+The BlockResource existed under the @aptos_framework.
+
+
+// This enforces high-level requirement 3:
+include UpdateEpochIntervalMicrosecs;
+
+
+
+
+
+
+
+
+schema UpdateEpochIntervalMicrosecs {
+ aptos_framework: signer;
+ new_epoch_interval: u64;
+ let addr = signer::address_of(aptos_framework);
+ // This enforces high-level requirement 2:
+ aborts_if addr != @aptos_framework;
+ aborts_if new_epoch_interval == 0;
+ aborts_if !exists<BlockResource>(addr);
+ let post block_resource = global<BlockResource>(addr);
+ ensures block_resource.epoch_interval == new_epoch_interval;
+}
+
+
+
+
+
+
+### Function `get_epoch_interval_secs`
+
+
+#[view]
+public fun get_epoch_interval_secs(): u64
+
+
+
+
+
+aborts_if !exists<BlockResource>(@aptos_framework);
+
+
+
+
+
+
+### Function `block_prologue_common`
+
+
+fun block_prologue_common(vm: &signer, hash: address, epoch: u64, round: u64, proposer: address, failed_proposer_indices: vector<u64>, previous_block_votes_bitvec: vector<u8>, timestamp: u64): u64
+
+
+
+
+
+pragma verify_duration_estimate = 1000;
+include BlockRequirement;
+aborts_if false;
+
+
+
+
+
+
+### Function `block_prologue`
+
+
+fun block_prologue(vm: signer, hash: address, epoch: u64, round: u64, proposer: address, failed_proposer_indices: vector<u64>, previous_block_votes_bitvec: vector<u8>, timestamp: u64)
+
+
+
+
+
+pragma verify_duration_estimate = 1000;
+requires timestamp >= reconfiguration::last_reconfiguration_time();
+include BlockRequirement;
+aborts_if false;
+
+
+
+
+
+
+### Function `block_prologue_ext`
+
+
+fun block_prologue_ext(vm: signer, hash: address, epoch: u64, round: u64, proposer: address, failed_proposer_indices: vector<u64>, previous_block_votes_bitvec: vector<u8>, timestamp: u64, randomness_seed: option::Option<vector<u8>>)
+
+
+
+
+
+pragma verify_duration_estimate = 1000;
+requires timestamp >= reconfiguration::last_reconfiguration_time();
+include BlockRequirement;
+include stake::ResourceRequirement;
+include stake::GetReconfigStartTimeRequirement;
+aborts_if false;
+
+
+
+
+
+
+### Function `get_current_block_height`
+
+
+#[view]
+public fun get_current_block_height(): u64
+
+
+
+
+
+aborts_if !exists<BlockResource>(@aptos_framework);
+
+
+
+
+
+
+### Function `emit_new_block_event`
+
+
+fun emit_new_block_event(vm: &signer, event_handle: &mut event::EventHandle<block::NewBlockEvent>, new_block_event: block::NewBlockEvent, new_block_event_v2: block::NewBlock)
+
+
+
+
+
+let proposer = new_block_event.proposer;
+let timestamp = new_block_event.time_microseconds;
+requires chain_status::is_operating();
+requires system_addresses::is_vm(vm);
+requires (proposer == @vm_reserved) ==> (timestamp::spec_now_microseconds() == timestamp);
+requires (proposer != @vm_reserved) ==> (timestamp::spec_now_microseconds() < timestamp);
+// This enforces high-level requirement 5:
+requires event::counter(event_handle) == new_block_event.height;
+aborts_if false;
+
+
+
+
+
+
+### Function `emit_genesis_block_event`
+
+
+fun emit_genesis_block_event(vm: signer)
+
+
+
+
+
+requires chain_status::is_operating();
+requires system_addresses::is_vm(vm);
+requires event::counter(global<BlockResource>(@aptos_framework).new_block_events) == 0;
+requires (timestamp::spec_now_microseconds() == 0);
+aborts_if false;
+
+
+
+
+
+
+### Function `emit_writeset_block_event`
+
+
+public fun emit_writeset_block_event(vm_signer: &signer, fake_block_hash: address)
+
+
+
+The caller is @vm_reserved.
+The BlockResource existed under the @aptos_framework.
+The Configuration existed under the @aptos_framework.
+The CurrentTimeMicroseconds existed under the @aptos_framework.
+
+
+requires chain_status::is_operating();
+include EmitWritesetBlockEvent;
+
+
+
+
+
+
+
+
+schema EmitWritesetBlockEvent {
+ vm_signer: signer;
+ let addr = signer::address_of(vm_signer);
+ aborts_if addr != @vm_reserved;
+ aborts_if !exists<BlockResource>(@aptos_framework);
+ aborts_if !exists<reconfiguration::Configuration>(@aptos_framework);
+ aborts_if !exists<timestamp::CurrentTimeMicroseconds>(@aptos_framework);
+}
+
+
+
+[move-book]: https://aptos.dev/move/book/SUMMARY
diff --git a/aptos-move/framework/aptos-framework/tests/compiler-v2-doc/chain_id.md b/aptos-move/framework/aptos-framework/tests/compiler-v2-doc/chain_id.md
new file mode 100644
index 0000000000000..7a40b807b0418
--- /dev/null
+++ b/aptos-move/framework/aptos-framework/tests/compiler-v2-doc/chain_id.md
@@ -0,0 +1,192 @@
+
+
+
+# Module `0x1::chain_id`
+
+The chain id distinguishes between different chains (e.g., testnet and the main network).
+One important role is to prevent transactions intended for one chain from being executed on another.
+This code provides a container for storing a chain id and functions to initialize and get it.
+
+
+- [Resource `ChainId`](#0x1_chain_id_ChainId)
+- [Function `initialize`](#0x1_chain_id_initialize)
+- [Function `get`](#0x1_chain_id_get)
+- [Specification](#@Specification_0)
+ - [High-level Requirements](#high-level-req)
+ - [Module-level Specification](#module-level-spec)
+ - [Function `initialize`](#@Specification_0_initialize)
+ - [Function `get`](#@Specification_0_get)
+
+
+use 0x1::system_addresses;
+
+
+
+
+
+
+## Resource `ChainId`
+
+
+
+struct ChainId has key
+
+
+
+
+id: u8
+id
of this instance under the SystemAddresses address
+
+
+public(friend) fun initialize(aptos_framework: &signer, id: u8)
+
+
+
+
+public(friend) fun initialize(aptos_framework: &signer, id: u8) {
+ system_addresses::assert_aptos_framework(aptos_framework);
+ move_to(aptos_framework, ChainId { id })
+}
+
+
+
+
+#[view]
+public fun get(): u8
+
+
+
+
+public fun get(): u8 acquires ChainId {
+ borrow_global<ChainId>(@aptos_framework).id
+}
+
+
+
+
+No. | Requirement | Criticality | Implementation | Enforcement | +
---|---|---|---|---|
1 | +During genesis, the ChainId resource should be created and moved under the Aptos framework account with the specified chain id. | +Medium | +The chain_id::initialize function is responsible for generating the ChainId resource and then storing it under the aptos_framework account. | +Formally verified via initialize. | +
2 | +The chain id can only be fetched if the chain id resource exists under the Aptos framework account. | +Low | +The chain_id::get function fetches the chain id by borrowing the ChainId resource from the aptos_framework account. | +Formally verified via get. | +
pragma verify = true;
+pragma aborts_if_is_strict;
+
+
+
+
+
+
+### Function `initialize`
+
+
+public(friend) fun initialize(aptos_framework: &signer, id: u8)
+
+
+
+
+
+let addr = signer::address_of(aptos_framework);
+aborts_if addr != @aptos_framework;
+aborts_if exists<ChainId>(@aptos_framework);
+// This enforces high-level requirement 1:
+ensures exists<ChainId>(addr);
+ensures global<ChainId>(addr).id == id;
+
+
+
+
+
+
+### Function `get`
+
+
+#[view]
+public fun get(): u8
+
+
+
+
+
+// This enforces high-level requirement 2:
+aborts_if !exists<ChainId>(@aptos_framework);
+
+
+
+[move-book]: https://aptos.dev/move/book/SUMMARY
diff --git a/aptos-move/framework/aptos-framework/tests/compiler-v2-doc/chain_status.md b/aptos-move/framework/aptos-framework/tests/compiler-v2-doc/chain_status.md
new file mode 100644
index 0000000000000..a8230b3f81b99
--- /dev/null
+++ b/aptos-move/framework/aptos-framework/tests/compiler-v2-doc/chain_status.md
@@ -0,0 +1,339 @@
+
+
+
+# Module `0x1::chain_status`
+
+This module code to assert that it is running in genesis (Self::assert_genesis
) or after
+genesis (Self::assert_operating
). These are essentially distinct states of the system. Specifically,
+if Self::assert_operating
succeeds, assumptions about invariants over the global state can be made
+which reflect that the system has been successfully initialized.
+
+
+- [Resource `GenesisEndMarker`](#0x1_chain_status_GenesisEndMarker)
+- [Constants](#@Constants_0)
+- [Function `set_genesis_end`](#0x1_chain_status_set_genesis_end)
+- [Function `is_genesis`](#0x1_chain_status_is_genesis)
+- [Function `is_operating`](#0x1_chain_status_is_operating)
+- [Function `assert_operating`](#0x1_chain_status_assert_operating)
+- [Function `assert_genesis`](#0x1_chain_status_assert_genesis)
+- [Specification](#@Specification_1)
+ - [High-level Requirements](#high-level-req)
+ - [Module-level Specification](#module-level-spec)
+ - [Function `set_genesis_end`](#@Specification_1_set_genesis_end)
+ - [Function `assert_operating`](#@Specification_1_assert_operating)
+ - [Function `assert_genesis`](#@Specification_1_assert_genesis)
+
+
+use 0x1::error;
+use 0x1::system_addresses;
+
+
+
+
+
+
+## Resource `GenesisEndMarker`
+
+Marker to publish at the end of genesis.
+
+
+struct GenesisEndMarker has key
+
+
+
+
+dummy_field: bool
+const ENOT_GENESIS: u64 = 2;
+
+
+
+
+
+
+The blockchain is not in the operating status.
+
+
+const ENOT_OPERATING: u64 = 1;
+
+
+
+
+
+
+## Function `set_genesis_end`
+
+Marks that genesis has finished.
+
+
+public(friend) fun set_genesis_end(aptos_framework: &signer)
+
+
+
+
+public(friend) fun set_genesis_end(aptos_framework: &signer) {
+ system_addresses::assert_aptos_framework(aptos_framework);
+ move_to(aptos_framework, GenesisEndMarker {});
+}
+
+
+
+
+#[view]
+public fun is_genesis(): bool
+
+
+
+
+public fun is_genesis(): bool {
+ !exists<GenesisEndMarker>(@aptos_framework)
+}
+
+
+
+
+!is_genesis()
and is provided for convenience.
+Testing is_operating()
is more frequent than is_genesis()
.
+
+
+#[view]
+public fun is_operating(): bool
+
+
+
+
+public fun is_operating(): bool {
+ exists<GenesisEndMarker>(@aptos_framework)
+}
+
+
+
+
+public fun assert_operating()
+
+
+
+
+public fun assert_operating() {
+ assert!(is_operating(), error::invalid_state(ENOT_OPERATING));
+}
+
+
+
+
+public fun assert_genesis()
+
+
+
+
+public fun assert_genesis() {
+ assert!(is_genesis(), error::invalid_state(ENOT_OPERATING));
+}
+
+
+
+
+No. | Requirement | Criticality | Implementation | Enforcement | +
---|---|---|---|---|
1 | +The end of genesis mark should persist throughout the entire life of the chain. | +Medium | +The Aptos framework account should never drop the GenesisEndMarker resource. | +Audited that GenesisEndMarker is published at the end of genesis and never removed. Formally verified via set_genesis_end that GenesisEndMarker is published. | +
2 | +The status of the chain should never be genesis and operating at the same time. | +Low | +The status of the chain is determined by the GenesisEndMarker resource. | +Formally verified via global invariant. | +
3 | +The status of the chain should only be changed once, from genesis to operating. | +Low | +Attempting to assign a resource type more than once will abort. | +Formally verified via set_genesis_end. | +
pragma verify = true;
+pragma aborts_if_is_strict;
+// This enforces high-level requirement 2:
+invariant is_genesis() == !is_operating();
+
+
+
+
+
+
+### Function `set_genesis_end`
+
+
+public(friend) fun set_genesis_end(aptos_framework: &signer)
+
+
+
+
+
+pragma verify = true;
+pragma delegate_invariants_to_caller;
+let addr = signer::address_of(aptos_framework);
+aborts_if addr != @aptos_framework;
+// This enforces high-level requirement 3:
+aborts_if exists<GenesisEndMarker>(@aptos_framework);
+// This enforces high-level requirement 1:
+ensures global<GenesisEndMarker>(@aptos_framework) == GenesisEndMarker {};
+
+
+
+
+
+
+
+
+schema RequiresIsOperating {
+ requires is_operating();
+}
+
+
+
+
+
+
+### Function `assert_operating`
+
+
+public fun assert_operating()
+
+
+
+
+
+aborts_if !is_operating();
+
+
+
+
+
+
+### Function `assert_genesis`
+
+
+public fun assert_genesis()
+
+
+
+
+
+aborts_if !is_genesis();
+
+
+
+[move-book]: https://aptos.dev/move/book/SUMMARY
diff --git a/aptos-move/framework/aptos-framework/tests/compiler-v2-doc/code.md b/aptos-move/framework/aptos-framework/tests/compiler-v2-doc/code.md
new file mode 100644
index 0000000000000..b3adf637e2702
--- /dev/null
+++ b/aptos-move/framework/aptos-framework/tests/compiler-v2-doc/code.md
@@ -0,0 +1,1248 @@
+
+
+
+# Module `0x1::code`
+
+This module supports functionality related to code management.
+
+
+- [Resource `PackageRegistry`](#0x1_code_PackageRegistry)
+- [Struct `PackageMetadata`](#0x1_code_PackageMetadata)
+- [Struct `PackageDep`](#0x1_code_PackageDep)
+- [Struct `ModuleMetadata`](#0x1_code_ModuleMetadata)
+- [Struct `UpgradePolicy`](#0x1_code_UpgradePolicy)
+- [Struct `PublishPackage`](#0x1_code_PublishPackage)
+- [Struct `AllowedDep`](#0x1_code_AllowedDep)
+- [Constants](#@Constants_0)
+- [Function `upgrade_policy_arbitrary`](#0x1_code_upgrade_policy_arbitrary)
+- [Function `upgrade_policy_compat`](#0x1_code_upgrade_policy_compat)
+- [Function `upgrade_policy_immutable`](#0x1_code_upgrade_policy_immutable)
+- [Function `can_change_upgrade_policy_to`](#0x1_code_can_change_upgrade_policy_to)
+- [Function `initialize`](#0x1_code_initialize)
+- [Function `publish_package`](#0x1_code_publish_package)
+- [Function `freeze_code_object`](#0x1_code_freeze_code_object)
+- [Function `publish_package_txn`](#0x1_code_publish_package_txn)
+- [Function `check_upgradability`](#0x1_code_check_upgradability)
+- [Function `check_coexistence`](#0x1_code_check_coexistence)
+- [Function `check_dependencies`](#0x1_code_check_dependencies)
+- [Function `is_policy_exempted_address`](#0x1_code_is_policy_exempted_address)
+- [Function `get_module_names`](#0x1_code_get_module_names)
+- [Function `request_publish`](#0x1_code_request_publish)
+- [Function `request_publish_with_allowed_deps`](#0x1_code_request_publish_with_allowed_deps)
+- [Specification](#@Specification_1)
+ - [High-level Requirements](#high-level-req)
+ - [Module-level Specification](#module-level-spec)
+ - [Function `initialize`](#@Specification_1_initialize)
+ - [Function `publish_package`](#@Specification_1_publish_package)
+ - [Function `freeze_code_object`](#@Specification_1_freeze_code_object)
+ - [Function `publish_package_txn`](#@Specification_1_publish_package_txn)
+ - [Function `check_upgradability`](#@Specification_1_check_upgradability)
+ - [Function `check_coexistence`](#@Specification_1_check_coexistence)
+ - [Function `check_dependencies`](#@Specification_1_check_dependencies)
+ - [Function `get_module_names`](#@Specification_1_get_module_names)
+ - [Function `request_publish`](#@Specification_1_request_publish)
+ - [Function `request_publish_with_allowed_deps`](#@Specification_1_request_publish_with_allowed_deps)
+
+
+use 0x1::copyable_any;
+use 0x1::error;
+use 0x1::event;
+use 0x1::features;
+use 0x1::object;
+use 0x1::option;
+use 0x1::signer;
+use 0x1::string;
+use 0x1::system_addresses;
+use 0x1::util;
+use 0x1::vector;
+
+
+
+
+
+
+## Resource `PackageRegistry`
+
+The package registry at the given address.
+
+
+struct PackageRegistry has drop, store, key
+
+
+
+
+packages: vector<code::PackageMetadata>
+struct PackageMetadata has drop, store
+
+
+
+
+name: string::String
+upgrade_policy: code::UpgradePolicy
+upgrade_number: u64
+source_digest: string::String
+manifest: vector<u8>
+modules: vector<code::ModuleMetadata>
+deps: vector<code::PackageDep>
+extension: option::Option<copyable_any::Any>
+struct PackageDep has copy, drop, store
+
+
+
+
+account: address
+package_name: string::String
+struct ModuleMetadata has drop, store
+
+
+
+
+name: string::String
+source: vector<u8>
+source_map: vector<u8>
+extension: option::Option<copyable_any::Any>
+struct UpgradePolicy has copy, drop, store
+
+
+
+
+policy: u8
+#[event]
+struct PublishPackage has drop, store
+
+
+
+
+code_address: address
+is_upgrade: bool
+struct AllowedDep has drop
+
+
+
+
+account: address
+module_name: string::String
+code_object
does not exist.
+
+
+const ECODE_OBJECT_DOES_NOT_EXIST: u64 = 10;
+
+
+
+
+
+
+A dependency to an arbitrary
package must be on the same address.
+
+
+const EDEP_ARBITRARY_NOT_SAME_ADDRESS: u64 = 7;
+
+
+
+
+
+
+A dependency cannot have a weaker upgrade policy.
+
+
+const EDEP_WEAKER_POLICY: u64 = 6;
+
+
+
+
+
+
+Creating a package with incompatible upgrade policy is disabled.
+
+
+const EINCOMPATIBLE_POLICY_DISABLED: u64 = 8;
+
+
+
+
+
+
+Cannot delete a module that was published in the same package
+
+
+const EMODULE_MISSING: u64 = 4;
+
+
+
+
+
+
+Package contains duplicate module names with existing modules publised in other packages on this address
+
+
+const EMODULE_NAME_CLASH: u64 = 1;
+
+
+
+
+
+
+Not the owner of the package registry.
+
+
+const ENOT_PACKAGE_OWNER: u64 = 9;
+
+
+
+
+
+
+Dependency could not be resolved to any published package.
+
+
+const EPACKAGE_DEP_MISSING: u64 = 5;
+
+
+
+
+
+
+Cannot upgrade an immutable package
+
+
+const EUPGRADE_IMMUTABLE: u64 = 2;
+
+
+
+
+
+
+Cannot downgrade a package's upgradability policy
+
+
+const EUPGRADE_WEAKER_POLICY: u64 = 3;
+
+
+
+
+
+
+## Function `upgrade_policy_arbitrary`
+
+Whether unconditional code upgrade with no compatibility check is allowed. This
+publication mode should only be used for modules which aren't shared with user others.
+The developer is responsible for not breaking memory layout of any resources he already
+stored on chain.
+
+
+public fun upgrade_policy_arbitrary(): code::UpgradePolicy
+
+
+
+
+public fun upgrade_policy_arbitrary(): UpgradePolicy {
+ UpgradePolicy { policy: 0 }
+}
+
+
+
+
+public fun upgrade_policy_compat(): code::UpgradePolicy
+
+
+
+
+public fun upgrade_policy_compat(): UpgradePolicy {
+ UpgradePolicy { policy: 1 }
+}
+
+
+
+
+public fun upgrade_policy_immutable(): code::UpgradePolicy
+
+
+
+
+public fun upgrade_policy_immutable(): UpgradePolicy {
+ UpgradePolicy { policy: 2 }
+}
+
+
+
+
+public fun can_change_upgrade_policy_to(from: code::UpgradePolicy, to: code::UpgradePolicy): bool
+
+
+
+
+public fun can_change_upgrade_policy_to(from: UpgradePolicy, to: UpgradePolicy): bool {
+ from.policy <= to.policy
+}
+
+
+
+
+fun initialize(aptos_framework: &signer, package_owner: &signer, metadata: code::PackageMetadata)
+
+
+
+
+fun initialize(aptos_framework: &signer, package_owner: &signer, metadata: PackageMetadata)
+acquires PackageRegistry {
+ system_addresses::assert_aptos_framework(aptos_framework);
+ let addr = signer::address_of(package_owner);
+ if (!exists<PackageRegistry>(addr)) {
+ move_to(package_owner, PackageRegistry { packages: vector[metadata] })
+ } else {
+ vector::push_back(&mut borrow_global_mut<PackageRegistry>(addr).packages, metadata)
+ }
+}
+
+
+
+
+public fun publish_package(owner: &signer, pack: code::PackageMetadata, code: vector<vector<u8>>)
+
+
+
+
+public fun publish_package(owner: &signer, pack: PackageMetadata, code: vector<vector<u8>>) acquires PackageRegistry {
+ // Disallow incompatible upgrade mode. Governance can decide later if this should be reconsidered.
+ assert!(
+ pack.upgrade_policy.policy > upgrade_policy_arbitrary().policy,
+ error::invalid_argument(EINCOMPATIBLE_POLICY_DISABLED),
+ );
+
+ let addr = signer::address_of(owner);
+ if (!exists<PackageRegistry>(addr)) {
+ move_to(owner, PackageRegistry { packages: vector::empty() })
+ };
+
+ // Checks for valid dependencies to other packages
+ let allowed_deps = check_dependencies(addr, &pack);
+
+ // Check package against conflicts
+ // To avoid prover compiler error on spec
+ // the package need to be an immutable variable
+ let module_names = get_module_names(&pack);
+ let package_immutable = &borrow_global<PackageRegistry>(addr).packages;
+ let len = vector::length(package_immutable);
+ let index = len;
+ let upgrade_number = 0;
+ vector::enumerate_ref(package_immutable
+ , |i, old| {
+ let old: &PackageMetadata = old;
+ if (old.name == pack.name) {
+ upgrade_number = old.upgrade_number + 1;
+ check_upgradability(old, &pack, &module_names);
+ index = i;
+ } else {
+ check_coexistence(old, &module_names)
+ };
+ });
+
+ // Assign the upgrade counter.
+ pack.upgrade_number = upgrade_number;
+
+ let packages = &mut borrow_global_mut<PackageRegistry>(addr).packages;
+ // Update registry
+ let policy = pack.upgrade_policy;
+ if (index < len) {
+ *vector::borrow_mut(packages, index) = pack
+ } else {
+ vector::push_back(packages, pack)
+ };
+
+ event::emit(PublishPackage {
+ code_address: addr,
+ is_upgrade: upgrade_number > 0
+ });
+
+ // Request publish
+ if (features::code_dependency_check_enabled())
+ request_publish_with_allowed_deps(addr, module_names, allowed_deps, code, policy.policy)
+ else
+ // The new `request_publish_with_allowed_deps` has not yet rolled out, so call downwards
+ // compatible code.
+ request_publish(addr, module_names, code, policy.policy)
+}
+
+
+
+
+public fun freeze_code_object(publisher: &signer, code_object: object::Object<code::PackageRegistry>)
+
+
+
+
+public fun freeze_code_object(publisher: &signer, code_object: Object<PackageRegistry>) acquires PackageRegistry {
+ let code_object_addr = object::object_address(&code_object);
+ assert!(exists<PackageRegistry>(code_object_addr), error::not_found(ECODE_OBJECT_DOES_NOT_EXIST));
+ assert!(
+ object::is_owner(code_object, signer::address_of(publisher)),
+ error::permission_denied(ENOT_PACKAGE_OWNER)
+ );
+
+ let registry = borrow_global_mut<PackageRegistry>(code_object_addr);
+ vector::for_each_mut<PackageMetadata>(&mut registry.packages, |pack| {
+ let package: &mut PackageMetadata = pack;
+ package.upgrade_policy = upgrade_policy_immutable();
+ });
+}
+
+
+
+
+publish_package
but as an entry function which can be called as a transaction. Because
+of current restrictions for txn parameters, the metadata needs to be passed in serialized form.
+
+
+public entry fun publish_package_txn(owner: &signer, metadata_serialized: vector<u8>, code: vector<vector<u8>>)
+
+
+
+
+public entry fun publish_package_txn(owner: &signer, metadata_serialized: vector<u8>, code: vector<vector<u8>>)
+acquires PackageRegistry {
+ publish_package(owner, util::from_bytes<PackageMetadata>(metadata_serialized), code)
+}
+
+
+
+
+fun check_upgradability(old_pack: &code::PackageMetadata, new_pack: &code::PackageMetadata, new_modules: &vector<string::String>)
+
+
+
+
+fun check_upgradability(
+ old_pack: &PackageMetadata, new_pack: &PackageMetadata, new_modules: &vector<String>) {
+ assert!(old_pack.upgrade_policy.policy < upgrade_policy_immutable().policy,
+ error::invalid_argument(EUPGRADE_IMMUTABLE));
+ assert!(can_change_upgrade_policy_to(old_pack.upgrade_policy, new_pack.upgrade_policy),
+ error::invalid_argument(EUPGRADE_WEAKER_POLICY));
+ let old_modules = get_module_names(old_pack);
+
+ vector::for_each_ref(&old_modules, |old_module| {
+ assert!(
+ vector::contains(new_modules, old_module),
+ EMODULE_MISSING
+ );
+ });
+}
+
+
+
+
+fun check_coexistence(old_pack: &code::PackageMetadata, new_modules: &vector<string::String>)
+
+
+
+
+fun check_coexistence(old_pack: &PackageMetadata, new_modules: &vector<String>) {
+ // The modules introduced by each package must not overlap with `names`.
+ vector::for_each_ref(&old_pack.modules, |old_mod| {
+ let old_mod: &ModuleMetadata = old_mod;
+ let j = 0;
+ while (j < vector::length(new_modules)) {
+ let name = vector::borrow(new_modules, j);
+ assert!(&old_mod.name != name, error::already_exists(EMODULE_NAME_CLASH));
+ j = j + 1;
+ };
+ });
+}
+
+
+
+
+fun check_dependencies(publish_address: address, pack: &code::PackageMetadata): vector<code::AllowedDep>
+
+
+
+
+fun check_dependencies(publish_address: address, pack: &PackageMetadata): vector<AllowedDep>
+acquires PackageRegistry {
+ let allowed_module_deps = vector::empty();
+ let deps = &pack.deps;
+ vector::for_each_ref(deps, |dep| {
+ let dep: &PackageDep = dep;
+ assert!(exists<PackageRegistry>(dep.account), error::not_found(EPACKAGE_DEP_MISSING));
+ if (is_policy_exempted_address(dep.account)) {
+ // Allow all modules from this address, by using "" as a wildcard in the AllowedDep
+ let account: address = dep.account;
+ let module_name = string::utf8(b"");
+ vector::push_back(&mut allowed_module_deps, AllowedDep { account, module_name });
+ } else {
+ let registry = borrow_global<PackageRegistry>(dep.account);
+ let found = vector::any(®istry.packages, |dep_pack| {
+ let dep_pack: &PackageMetadata = dep_pack;
+ if (dep_pack.name == dep.package_name) {
+ // Check policy
+ assert!(
+ dep_pack.upgrade_policy.policy >= pack.upgrade_policy.policy,
+ error::invalid_argument(EDEP_WEAKER_POLICY)
+ );
+ if (dep_pack.upgrade_policy == upgrade_policy_arbitrary()) {
+ assert!(
+ dep.account == publish_address,
+ error::invalid_argument(EDEP_ARBITRARY_NOT_SAME_ADDRESS)
+ )
+ };
+ // Add allowed deps
+ let account = dep.account;
+ let k = 0;
+ let r = vector::length(&dep_pack.modules);
+ while (k < r) {
+ let module_name = vector::borrow(&dep_pack.modules, k).name;
+ vector::push_back(&mut allowed_module_deps, AllowedDep { account, module_name });
+ k = k + 1;
+ };
+ true
+ } else {
+ false
+ }
+ });
+ assert!(found, error::not_found(EPACKAGE_DEP_MISSING));
+ };
+ });
+ allowed_module_deps
+}
+
+
+
+
+compatible
.
+
+
+fun is_policy_exempted_address(addr: address): bool
+
+
+
+
+fun is_policy_exempted_address(addr: address): bool {
+ addr == @1 || addr == @2 || addr == @3 || addr == @4 || addr == @5 ||
+ addr == @6 || addr == @7 || addr == @8 || addr == @9 || addr == @10
+}
+
+
+
+
+fun get_module_names(pack: &code::PackageMetadata): vector<string::String>
+
+
+
+
+fun get_module_names(pack: &PackageMetadata): vector<String> {
+ let module_names = vector::empty();
+ vector::for_each_ref(&pack.modules, |pack_module| {
+ let pack_module: &ModuleMetadata = pack_module;
+ vector::push_back(&mut module_names, pack_module.name);
+ });
+ module_names
+}
+
+
+
+
+fun request_publish(owner: address, expected_modules: vector<string::String>, bundle: vector<vector<u8>>, policy: u8)
+
+
+
+
+native fun request_publish(
+ owner: address,
+ expected_modules: vector<String>,
+ bundle: vector<vector<u8>>,
+ policy: u8
+);
+
+
+
+
+fun request_publish_with_allowed_deps(owner: address, expected_modules: vector<string::String>, allowed_deps: vector<code::AllowedDep>, bundle: vector<vector<u8>>, policy: u8)
+
+
+
+
+native fun request_publish_with_allowed_deps(
+ owner: address,
+ expected_modules: vector<String>,
+ allowed_deps: vector<AllowedDep>,
+ bundle: vector<vector<u8>>,
+ policy: u8
+);
+
+
+
+
+No. | Requirement | Criticality | Implementation | Enforcement | +
---|---|---|---|---|
1 | +Updating a package should fail if the user is not the owner of it. | +Critical | +The publish_package function may only be able to update the package if the signer is the actual owner of the package. | +The Aptos upgrade native functions have been manually audited. | +
2 | +The arbitrary upgrade policy should never be used. | +Critical | +There should never be a pass of an arbitrary upgrade policy to the request_publish native function. | +Manually audited that it aborts if package.upgrade_policy.policy == 0. | +
3 | +Should perform accurate compatibility checks when the policy indicates compatibility, ensuring it meets the required conditions. | +Critical | +Specifies if it should perform compatibility checks for upgrades. The check only passes if a new module has (a) the same public functions, and (b) for existing resources, no layout change. | +The Move upgradability patterns have been manually audited. | +
4 | +Package upgrades should abide by policy change rules. In particular, The new upgrade policy must be equal to or stricter when compared to the old one. The original upgrade policy must not be immutable. The new package must contain all modules contained in the old package. | +Medium | +A package may only be updated using the publish_package function when the check_upgradability function returns true. | +This is audited by a manual review of the check_upgradability patterns. | +
5 | +The upgrade policy of a package must not exceed the strictness level imposed by its dependencies. | +Medium | +The upgrade_policy of a package may only be less than its dependencies throughout the upgrades. In addition, the native code properly restricts the use of dependencies outside the passed-in metadata. | +This has been manually audited. | +
6 | +The extension for package metadata is currently unused. | +Medium | +The extension field in PackageMetadata should be unused. | +Data invariant on the extension field has been manually audited. | +
7 | +The upgrade number of a package increases incrementally in a monotonic manner with each subsequent upgrade. | +Low | +On each upgrade of a particular package, the publish_package function updates the upgrade_number for that package. | +Post condition on upgrade_number has been manually audited. | +
pragma verify = true;
+pragma aborts_if_is_strict;
+
+
+
+
+
+
+### Function `initialize`
+
+
+fun initialize(aptos_framework: &signer, package_owner: &signer, metadata: code::PackageMetadata)
+
+
+
+
+
+let aptos_addr = signer::address_of(aptos_framework);
+let owner_addr = signer::address_of(package_owner);
+aborts_if !system_addresses::is_aptos_framework_address(aptos_addr);
+ensures exists<PackageRegistry>(owner_addr);
+
+
+
+
+
+
+### Function `publish_package`
+
+
+public fun publish_package(owner: &signer, pack: code::PackageMetadata, code: vector<vector<u8>>)
+
+
+
+
+
+pragma aborts_if_is_partial;
+let addr = signer::address_of(owner);
+modifies global<PackageRegistry>(addr);
+aborts_if pack.upgrade_policy.policy <= upgrade_policy_arbitrary().policy;
+
+
+
+
+
+
+### Function `freeze_code_object`
+
+
+public fun freeze_code_object(publisher: &signer, code_object: object::Object<code::PackageRegistry>)
+
+
+
+
+
+pragma aborts_if_is_partial;
+let code_object_addr = code_object.inner;
+aborts_if !exists<object::ObjectCore>(code_object_addr);
+aborts_if !exists<PackageRegistry>(code_object_addr);
+aborts_if !object::is_owner(code_object, signer::address_of(publisher));
+modifies global<PackageRegistry>(code_object_addr);
+
+
+
+
+
+
+### Function `publish_package_txn`
+
+
+public entry fun publish_package_txn(owner: &signer, metadata_serialized: vector<u8>, code: vector<vector<u8>>)
+
+
+
+
+
+pragma verify = false;
+
+
+
+
+
+
+### Function `check_upgradability`
+
+
+fun check_upgradability(old_pack: &code::PackageMetadata, new_pack: &code::PackageMetadata, new_modules: &vector<string::String>)
+
+
+
+
+
+pragma aborts_if_is_partial;
+aborts_if old_pack.upgrade_policy.policy >= upgrade_policy_immutable().policy;
+aborts_if !can_change_upgrade_policy_to(old_pack.upgrade_policy, new_pack.upgrade_policy);
+
+
+
+
+
+
+### Function `check_coexistence`
+
+
+fun check_coexistence(old_pack: &code::PackageMetadata, new_modules: &vector<string::String>)
+
+
+
+
+
+pragma verify = false;
+
+
+
+
+
+
+### Function `check_dependencies`
+
+
+fun check_dependencies(publish_address: address, pack: &code::PackageMetadata): vector<code::AllowedDep>
+
+
+
+
+
+pragma verify = false;
+
+
+
+
+
+
+### Function `get_module_names`
+
+
+fun get_module_names(pack: &code::PackageMetadata): vector<string::String>
+
+
+
+
+
+pragma opaque;
+aborts_if [abstract] false;
+ensures [abstract] len(result) == len(pack.modules);
+ensures [abstract] forall i in 0..len(result): result[i] == pack.modules[i].name;
+
+
+
+
+
+
+### Function `request_publish`
+
+
+fun request_publish(owner: address, expected_modules: vector<string::String>, bundle: vector<vector<u8>>, policy: u8)
+
+
+
+
+
+pragma opaque;
+
+
+
+
+
+
+### Function `request_publish_with_allowed_deps`
+
+
+fun request_publish_with_allowed_deps(owner: address, expected_modules: vector<string::String>, allowed_deps: vector<code::AllowedDep>, bundle: vector<vector<u8>>, policy: u8)
+
+
+
+
+
+pragma opaque;
+
+
+
+[move-book]: https://aptos.dev/move/book/SUMMARY
diff --git a/aptos-move/framework/aptos-framework/tests/compiler-v2-doc/coin.md b/aptos-move/framework/aptos-framework/tests/compiler-v2-doc/coin.md
new file mode 100644
index 0000000000000..146093ce9e8d6
--- /dev/null
+++ b/aptos-move/framework/aptos-framework/tests/compiler-v2-doc/coin.md
@@ -0,0 +1,4809 @@
+
+
+
+# Module `0x1::coin`
+
+This module provides the foundation for typesafe Coins.
+
+
+- [Struct `Coin`](#0x1_coin_Coin)
+- [Struct `AggregatableCoin`](#0x1_coin_AggregatableCoin)
+- [Resource `CoinStore`](#0x1_coin_CoinStore)
+- [Resource `SupplyConfig`](#0x1_coin_SupplyConfig)
+- [Resource `CoinInfo`](#0x1_coin_CoinInfo)
+- [Struct `CoinDeposit`](#0x1_coin_CoinDeposit)
+- [Struct `CoinWithdraw`](#0x1_coin_CoinWithdraw)
+- [Struct `Deposit`](#0x1_coin_Deposit)
+- [Struct `Withdraw`](#0x1_coin_Withdraw)
+- [Struct `DepositEvent`](#0x1_coin_DepositEvent)
+- [Struct `WithdrawEvent`](#0x1_coin_WithdrawEvent)
+- [Struct `CoinEventHandleDeletion`](#0x1_coin_CoinEventHandleDeletion)
+- [Struct `PairCreation`](#0x1_coin_PairCreation)
+- [Resource `MigrationFlag`](#0x1_coin_MigrationFlag)
+- [Struct `MintCapability`](#0x1_coin_MintCapability)
+- [Struct `FreezeCapability`](#0x1_coin_FreezeCapability)
+- [Struct `BurnCapability`](#0x1_coin_BurnCapability)
+- [Resource `CoinConversionMap`](#0x1_coin_CoinConversionMap)
+- [Resource `PairedCoinType`](#0x1_coin_PairedCoinType)
+- [Resource `PairedFungibleAssetRefs`](#0x1_coin_PairedFungibleAssetRefs)
+- [Struct `MintRefReceipt`](#0x1_coin_MintRefReceipt)
+- [Struct `TransferRefReceipt`](#0x1_coin_TransferRefReceipt)
+- [Struct `BurnRefReceipt`](#0x1_coin_BurnRefReceipt)
+- [Resource `Ghost$supply`](#0x1_coin_Ghost$supply)
+- [Resource `Ghost$aggregate_supply`](#0x1_coin_Ghost$aggregate_supply)
+- [Constants](#@Constants_0)
+- [Function `paired_metadata`](#0x1_coin_paired_metadata)
+- [Function `create_coin_conversion_map`](#0x1_coin_create_coin_conversion_map)
+- [Function `create_pairing`](#0x1_coin_create_pairing)
+- [Function `is_apt`](#0x1_coin_is_apt)
+- [Function `create_and_return_paired_metadata_if_not_exist`](#0x1_coin_create_and_return_paired_metadata_if_not_exist)
+- [Function `ensure_paired_metadata`](#0x1_coin_ensure_paired_metadata)
+- [Function `paired_coin`](#0x1_coin_paired_coin)
+- [Function `coin_to_fungible_asset`](#0x1_coin_coin_to_fungible_asset)
+- [Function `fungible_asset_to_coin`](#0x1_coin_fungible_asset_to_coin)
+- [Function `assert_paired_metadata_exists`](#0x1_coin_assert_paired_metadata_exists)
+- [Function `paired_mint_ref_exists`](#0x1_coin_paired_mint_ref_exists)
+- [Function `get_paired_mint_ref`](#0x1_coin_get_paired_mint_ref)
+- [Function `return_paired_mint_ref`](#0x1_coin_return_paired_mint_ref)
+- [Function `paired_transfer_ref_exists`](#0x1_coin_paired_transfer_ref_exists)
+- [Function `get_paired_transfer_ref`](#0x1_coin_get_paired_transfer_ref)
+- [Function `return_paired_transfer_ref`](#0x1_coin_return_paired_transfer_ref)
+- [Function `paired_burn_ref_exists`](#0x1_coin_paired_burn_ref_exists)
+- [Function `get_paired_burn_ref`](#0x1_coin_get_paired_burn_ref)
+- [Function `convert_and_take_paired_burn_ref`](#0x1_coin_convert_and_take_paired_burn_ref)
+- [Function `return_paired_burn_ref`](#0x1_coin_return_paired_burn_ref)
+- [Function `borrow_paired_burn_ref`](#0x1_coin_borrow_paired_burn_ref)
+- [Function `initialize_supply_config`](#0x1_coin_initialize_supply_config)
+- [Function `allow_supply_upgrades`](#0x1_coin_allow_supply_upgrades)
+- [Function `initialize_aggregatable_coin`](#0x1_coin_initialize_aggregatable_coin)
+- [Function `is_aggregatable_coin_zero`](#0x1_coin_is_aggregatable_coin_zero)
+- [Function `drain_aggregatable_coin`](#0x1_coin_drain_aggregatable_coin)
+- [Function `merge_aggregatable_coin`](#0x1_coin_merge_aggregatable_coin)
+- [Function `collect_into_aggregatable_coin`](#0x1_coin_collect_into_aggregatable_coin)
+- [Function `calculate_amount_to_withdraw`](#0x1_coin_calculate_amount_to_withdraw)
+- [Function `maybe_convert_to_fungible_store`](#0x1_coin_maybe_convert_to_fungible_store)
+- [Function `migrate_to_fungible_store`](#0x1_coin_migrate_to_fungible_store)
+- [Function `coin_address`](#0x1_coin_coin_address)
+- [Function `balance`](#0x1_coin_balance)
+- [Function `is_balance_at_least`](#0x1_coin_is_balance_at_least)
+- [Function `coin_balance`](#0x1_coin_coin_balance)
+- [Function `is_coin_initialized`](#0x1_coin_is_coin_initialized)
+- [Function `is_coin_store_frozen`](#0x1_coin_is_coin_store_frozen)
+- [Function `is_account_registered`](#0x1_coin_is_account_registered)
+- [Function `name`](#0x1_coin_name)
+- [Function `symbol`](#0x1_coin_symbol)
+- [Function `decimals`](#0x1_coin_decimals)
+- [Function `supply`](#0x1_coin_supply)
+- [Function `coin_supply`](#0x1_coin_coin_supply)
+- [Function `burn`](#0x1_coin_burn)
+- [Function `burn_from`](#0x1_coin_burn_from)
+- [Function `deposit`](#0x1_coin_deposit)
+- [Function `migrated_primary_fungible_store_exists`](#0x1_coin_migrated_primary_fungible_store_exists)
+- [Function `force_deposit`](#0x1_coin_force_deposit)
+- [Function `destroy_zero`](#0x1_coin_destroy_zero)
+- [Function `extract`](#0x1_coin_extract)
+- [Function `extract_all`](#0x1_coin_extract_all)
+- [Function `freeze_coin_store`](#0x1_coin_freeze_coin_store)
+- [Function `unfreeze_coin_store`](#0x1_coin_unfreeze_coin_store)
+- [Function `upgrade_supply`](#0x1_coin_upgrade_supply)
+- [Function `initialize`](#0x1_coin_initialize)
+- [Function `initialize_with_parallelizable_supply`](#0x1_coin_initialize_with_parallelizable_supply)
+- [Function `initialize_internal`](#0x1_coin_initialize_internal)
+- [Function `merge`](#0x1_coin_merge)
+- [Function `mint`](#0x1_coin_mint)
+- [Function `register`](#0x1_coin_register)
+- [Function `transfer`](#0x1_coin_transfer)
+- [Function `value`](#0x1_coin_value)
+- [Function `withdraw`](#0x1_coin_withdraw)
+- [Function `zero`](#0x1_coin_zero)
+- [Function `destroy_freeze_cap`](#0x1_coin_destroy_freeze_cap)
+- [Function `destroy_mint_cap`](#0x1_coin_destroy_mint_cap)
+- [Function `destroy_burn_cap`](#0x1_coin_destroy_burn_cap)
+- [Function `mint_internal`](#0x1_coin_mint_internal)
+- [Function `burn_internal`](#0x1_coin_burn_internal)
+- [Specification](#@Specification_1)
+ - [High-level Requirements](#high-level-req)
+ - [Module-level Specification](#module-level-spec)
+ - [Struct `AggregatableCoin`](#@Specification_1_AggregatableCoin)
+ - [Function `coin_to_fungible_asset`](#@Specification_1_coin_to_fungible_asset)
+ - [Function `fungible_asset_to_coin`](#@Specification_1_fungible_asset_to_coin)
+ - [Function `initialize_supply_config`](#@Specification_1_initialize_supply_config)
+ - [Function `allow_supply_upgrades`](#@Specification_1_allow_supply_upgrades)
+ - [Function `initialize_aggregatable_coin`](#@Specification_1_initialize_aggregatable_coin)
+ - [Function `is_aggregatable_coin_zero`](#@Specification_1_is_aggregatable_coin_zero)
+ - [Function `drain_aggregatable_coin`](#@Specification_1_drain_aggregatable_coin)
+ - [Function `merge_aggregatable_coin`](#@Specification_1_merge_aggregatable_coin)
+ - [Function `collect_into_aggregatable_coin`](#@Specification_1_collect_into_aggregatable_coin)
+ - [Function `maybe_convert_to_fungible_store`](#@Specification_1_maybe_convert_to_fungible_store)
+ - [Function `coin_address`](#@Specification_1_coin_address)
+ - [Function `balance`](#@Specification_1_balance)
+ - [Function `is_coin_initialized`](#@Specification_1_is_coin_initialized)
+ - [Function `is_account_registered`](#@Specification_1_is_account_registered)
+ - [Function `name`](#@Specification_1_name)
+ - [Function `symbol`](#@Specification_1_symbol)
+ - [Function `decimals`](#@Specification_1_decimals)
+ - [Function `supply`](#@Specification_1_supply)
+ - [Function `coin_supply`](#@Specification_1_coin_supply)
+ - [Function `burn`](#@Specification_1_burn)
+ - [Function `burn_from`](#@Specification_1_burn_from)
+ - [Function `deposit`](#@Specification_1_deposit)
+ - [Function `force_deposit`](#@Specification_1_force_deposit)
+ - [Function `destroy_zero`](#@Specification_1_destroy_zero)
+ - [Function `extract`](#@Specification_1_extract)
+ - [Function `extract_all`](#@Specification_1_extract_all)
+ - [Function `freeze_coin_store`](#@Specification_1_freeze_coin_store)
+ - [Function `unfreeze_coin_store`](#@Specification_1_unfreeze_coin_store)
+ - [Function `upgrade_supply`](#@Specification_1_upgrade_supply)
+ - [Function `initialize`](#@Specification_1_initialize)
+ - [Function `initialize_with_parallelizable_supply`](#@Specification_1_initialize_with_parallelizable_supply)
+ - [Function `initialize_internal`](#@Specification_1_initialize_internal)
+ - [Function `merge`](#@Specification_1_merge)
+ - [Function `mint`](#@Specification_1_mint)
+ - [Function `register`](#@Specification_1_register)
+ - [Function `transfer`](#@Specification_1_transfer)
+ - [Function `withdraw`](#@Specification_1_withdraw)
+ - [Function `mint_internal`](#@Specification_1_mint_internal)
+ - [Function `burn_internal`](#@Specification_1_burn_internal)
+
+
+use 0x1::account;
+use 0x1::aggregator;
+use 0x1::aggregator_factory;
+use 0x1::create_signer;
+use 0x1::error;
+use 0x1::event;
+use 0x1::features;
+use 0x1::fungible_asset;
+use 0x1::guid;
+use 0x1::object;
+use 0x1::option;
+use 0x1::optional_aggregator;
+use 0x1::primary_fungible_store;
+use 0x1::signer;
+use 0x1::string;
+use 0x1::system_addresses;
+use 0x1::table;
+use 0x1::type_info;
+
+
+
+
+
+
+## Struct `Coin`
+
+Core data structures
+Main structure representing a coin/token in an account's custody.
+
+
+struct Coin<CoinType> has store
+
+
+
+
+value: u64
+struct AggregatableCoin<CoinType> has store
+
+
+
+
+value: aggregator::Aggregator
+struct CoinStore<CoinType> has key
+
+
+
+
+coin: coin::Coin<CoinType>
+frozen: bool
+deposit_events: event::EventHandle<coin::DepositEvent>
+withdraw_events: event::EventHandle<coin::WithdrawEvent>
+struct SupplyConfig has key
+
+
+
+
+allow_upgrades: bool
+struct CoinInfo<CoinType> has key
+
+
+
+
+name: string::String
+symbol: string::String
+decimals: u8
+decimals
equals 2
, a balance of 505
coins should
+ be displayed to a user as 5.05
(505 / 10 ** 2
).
+supply: option::Option<optional_aggregator::OptionalAggregator>
+#[event]
+struct CoinDeposit has drop, store
+
+
+
+
+coin_type: string::String
+account: address
+amount: u64
+#[event]
+struct CoinWithdraw has drop, store
+
+
+
+
+coin_type: string::String
+account: address
+amount: u64
+#[event]
+#[deprecated]
+struct Deposit<CoinType> has drop, store
+
+
+
+
+account: address
+amount: u64
+#[event]
+#[deprecated]
+struct Withdraw<CoinType> has drop, store
+
+
+
+
+account: address
+amount: u64
+struct DepositEvent has drop, store
+
+
+
+
+amount: u64
+struct WithdrawEvent has drop, store
+
+
+
+
+amount: u64
+#[event]
+struct CoinEventHandleDeletion has drop, store
+
+
+
+
+event_handle_creation_address: address
+deleted_deposit_event_handle_creation_number: u64
+deleted_withdraw_event_handle_creation_number: u64
+#[event]
+struct PairCreation has drop, store
+
+
+
+
+coin_type: type_info::TypeInfo
+fungible_asset_metadata_address: address
+#[resource_group_member(#[group = 0x1::object::ObjectGroup])]
+struct MigrationFlag has key
+
+
+
+
+dummy_field: bool
+struct MintCapability<CoinType> has copy, store
+
+
+
+
+dummy_field: bool
+struct FreezeCapability<CoinType> has copy, store
+
+
+
+
+dummy_field: bool
+struct BurnCapability<CoinType> has copy, store
+
+
+
+
+dummy_field: bool
+struct CoinConversionMap has key
+
+
+
+
+coin_to_fungible_asset_map: table::Table<type_info::TypeInfo, object::Object<fungible_asset::Metadata>>
+#[resource_group_member(#[group = 0x1::object::ObjectGroup])]
+struct PairedCoinType has key
+
+
+
+
+type: type_info::TypeInfo
+#[resource_group_member(#[group = 0x1::object::ObjectGroup])]
+struct PairedFungibleAssetRefs has key
+
+
+
+
+mint_ref_opt: option::Option<fungible_asset::MintRef>
+transfer_ref_opt: option::Option<fungible_asset::TransferRef>
+burn_ref_opt: option::Option<fungible_asset::BurnRef>
+struct MintRefReceipt
+
+
+
+
+metadata: object::Object<fungible_asset::Metadata>
+struct TransferRefReceipt
+
+
+
+
+metadata: object::Object<fungible_asset::Metadata>
+struct BurnRefReceipt
+
+
+
+
+metadata: object::Object<fungible_asset::Metadata>
+struct Ghost$supply<CoinType> has copy, drop, store, key
+
+
+
+
+v: num
+struct Ghost$aggregate_supply<CoinType> has copy, drop, store, key
+
+
+
+
+v: num
+const MAX_U64: u128 = 18446744073709551615;
+
+
+
+
+
+
+Maximum possible coin supply.
+
+
+const MAX_U128: u128 = 340282366920938463463374607431768211455;
+
+
+
+
+
+
+Not enough coins to complete transaction
+
+
+const EINSUFFICIENT_BALANCE: u64 = 6;
+
+
+
+
+
+
+The value of aggregatable coin used for transaction fees redistribution does not fit in u64.
+
+
+const EAGGREGATABLE_COIN_VALUE_TOO_LARGE: u64 = 14;
+
+
+
+
+
+
+APT pairing is not eanbled yet.
+
+
+const EAPT_PAIRING_IS_NOT_ENABLED: u64 = 28;
+
+
+
+
+
+
+The BurnRef does not exist.
+
+
+const EBURN_REF_NOT_FOUND: u64 = 25;
+
+
+
+
+
+
+The BurnRefReceipt does not match the BurnRef to be returned.
+
+
+const EBURN_REF_RECEIPT_MISMATCH: u64 = 24;
+
+
+
+
+
+
+The coin converison map is not created yet.
+
+
+const ECOIN_CONVERSION_MAP_NOT_FOUND: u64 = 27;
+
+
+
+
+
+
+Address of account which is used to initialize a coin CoinType
doesn't match the deployer of module
+
+
+const ECOIN_INFO_ADDRESS_MISMATCH: u64 = 1;
+
+
+
+
+
+
+CoinType
is already initialized as a coin
+
+
+const ECOIN_INFO_ALREADY_PUBLISHED: u64 = 2;
+
+
+
+
+
+
+CoinType
hasn't been initialized as a coin
+
+
+const ECOIN_INFO_NOT_PUBLISHED: u64 = 3;
+
+
+
+
+
+
+Name of the coin is too long
+
+
+const ECOIN_NAME_TOO_LONG: u64 = 12;
+
+
+
+
+
+
+Deprecated. Account already has CoinStore
registered for CoinType
+
+
+const ECOIN_STORE_ALREADY_PUBLISHED: u64 = 4;
+
+
+
+
+
+
+Account hasn't registered CoinStore
for CoinType
+
+
+const ECOIN_STORE_NOT_PUBLISHED: u64 = 5;
+
+
+
+
+
+
+Cannot upgrade the total supply of coins to different implementation.
+
+
+const ECOIN_SUPPLY_UPGRADE_NOT_SUPPORTED: u64 = 11;
+
+
+
+
+
+
+Symbol of the coin is too long
+
+
+const ECOIN_SYMBOL_TOO_LONG: u64 = 13;
+
+
+
+
+
+
+The feature of migration from coin to fungible asset is not enabled.
+
+
+const ECOIN_TO_FUNGIBLE_ASSET_FEATURE_NOT_ENABLED: u64 = 18;
+
+
+
+
+
+
+The coin type from the map does not match the calling function type argument.
+
+
+const ECOIN_TYPE_MISMATCH: u64 = 17;
+
+
+
+
+
+
+Cannot destroy non-zero coins
+
+
+const EDESTRUCTION_OF_NONZERO_TOKEN: u64 = 7;
+
+
+
+
+
+
+CoinStore is frozen. Coins cannot be deposited or withdrawn
+
+
+const EFROZEN: u64 = 10;
+
+
+
+
+
+
+The migration process from coin to fungible asset is not enabled yet.
+
+
+const EMIGRATION_FRAMEWORK_NOT_ENABLED: u64 = 26;
+
+
+
+
+
+
+The MintRef does not exist.
+
+
+const EMINT_REF_NOT_FOUND: u64 = 21;
+
+
+
+
+
+
+The MintRefReceipt does not match the MintRef to be returned.
+
+
+const EMINT_REF_RECEIPT_MISMATCH: u64 = 20;
+
+
+
+
+
+
+Error regarding paired coin type of the fungible asset metadata.
+
+
+const EPAIRED_COIN: u64 = 15;
+
+
+
+
+
+
+Error regarding paired fungible asset metadata of a coin type.
+
+
+const EPAIRED_FUNGIBLE_ASSET: u64 = 16;
+
+
+
+
+
+
+PairedFungibleAssetRefs resource does not exist.
+
+
+const EPAIRED_FUNGIBLE_ASSET_REFS_NOT_FOUND: u64 = 19;
+
+
+
+
+
+
+The TransferRef does not exist.
+
+
+const ETRANSFER_REF_NOT_FOUND: u64 = 23;
+
+
+
+
+
+
+The TransferRefReceipt does not match the TransferRef to be returned.
+
+
+const ETRANSFER_REF_RECEIPT_MISMATCH: u64 = 22;
+
+
+
+
+
+
+
+
+const MAX_COIN_NAME_LENGTH: u64 = 32;
+
+
+
+
+
+
+
+
+const MAX_COIN_SYMBOL_LENGTH: u64 = 10;
+
+
+
+
+
+
+## Function `paired_metadata`
+
+Get the paired fungible asset metadata object of a coin type. If not exist, return option::none().
+
+
+#[view]
+public fun paired_metadata<CoinType>(): option::Option<object::Object<fungible_asset::Metadata>>
+
+
+
+
+public fun paired_metadata<CoinType>(): Option<Object<Metadata>> acquires CoinConversionMap {
+ if (exists<CoinConversionMap>(@aptos_framework) && features::coin_to_fungible_asset_migration_feature_enabled(
+ )) {
+ let map = &borrow_global<CoinConversionMap>(@aptos_framework).coin_to_fungible_asset_map;
+ let type = type_info::type_of<CoinType>();
+ if (table::contains(map, type)) {
+ return option::some(*table::borrow(map, type))
+ }
+ };
+ option::none()
+}
+
+
+
+
+public entry fun create_coin_conversion_map(aptos_framework: &signer)
+
+
+
+
+public entry fun create_coin_conversion_map(aptos_framework: &signer) {
+ system_addresses::assert_aptos_framework(aptos_framework);
+ if (!exists<CoinConversionMap>(@aptos_framework)) {
+ move_to(aptos_framework, CoinConversionMap {
+ coin_to_fungible_asset_map: table::new(),
+ })
+ };
+}
+
+
+
+
+AptosCoin
.
+
+
+public entry fun create_pairing<CoinType>(aptos_framework: &signer)
+
+
+
+
+public entry fun create_pairing<CoinType>(
+ aptos_framework: &signer
+) acquires CoinConversionMap, CoinInfo {
+ system_addresses::assert_aptos_framework(aptos_framework);
+ create_and_return_paired_metadata_if_not_exist<CoinType>(true);
+}
+
+
+
+
+fun is_apt<CoinType>(): bool
+
+
+
+
+inline fun is_apt<CoinType>(): bool {
+ type_info::type_name<CoinType>() == string::utf8(b"0x1::aptos_coin::AptosCoin")
+}
+
+
+
+
+fun create_and_return_paired_metadata_if_not_exist<CoinType>(allow_apt_creation: bool): object::Object<fungible_asset::Metadata>
+
+
+
+
+inline fun create_and_return_paired_metadata_if_not_exist<CoinType>(allow_apt_creation: bool): Object<Metadata> {
+ assert!(
+ features::coin_to_fungible_asset_migration_feature_enabled(),
+ error::invalid_state(EMIGRATION_FRAMEWORK_NOT_ENABLED)
+ );
+ assert!(exists<CoinConversionMap>(@aptos_framework), error::not_found(ECOIN_CONVERSION_MAP_NOT_FOUND));
+ let map = borrow_global_mut<CoinConversionMap>(@aptos_framework);
+ let type = type_info::type_of<CoinType>();
+ if (!table::contains(&map.coin_to_fungible_asset_map, type)) {
+ let is_apt = is_apt<CoinType>();
+ assert!(!is_apt || allow_apt_creation, error::invalid_state(EAPT_PAIRING_IS_NOT_ENABLED));
+ let metadata_object_cref =
+ if (is_apt) {
+ object::create_sticky_object_at_address(@aptos_framework, @aptos_fungible_asset)
+ } else {
+ object::create_named_object(
+ &create_signer::create_signer(@aptos_fungible_asset),
+ *string::bytes(&type_info::type_name<CoinType>())
+ )
+ };
+ primary_fungible_store::create_primary_store_enabled_fungible_asset(
+ &metadata_object_cref,
+ option::map(coin_supply<CoinType>(), |_| MAX_U128),
+ name<CoinType>(),
+ symbol<CoinType>(),
+ decimals<CoinType>(),
+ string::utf8(b""),
+ string::utf8(b""),
+ );
+
+ let metadata_object_signer = &object::generate_signer(&metadata_object_cref);
+ let type = type_info::type_of<CoinType>();
+ move_to(metadata_object_signer, PairedCoinType { type });
+ let metadata_obj = object::object_from_constructor_ref(&metadata_object_cref);
+
+ table::add(&mut map.coin_to_fungible_asset_map, type, metadata_obj);
+ event::emit(PairCreation {
+ coin_type: type,
+ fungible_asset_metadata_address: object_address(&metadata_obj)
+ });
+
+ // Generates all three refs
+ let mint_ref = fungible_asset::generate_mint_ref(&metadata_object_cref);
+ let transfer_ref = fungible_asset::generate_transfer_ref(&metadata_object_cref);
+ let burn_ref = fungible_asset::generate_burn_ref(&metadata_object_cref);
+ move_to(metadata_object_signer,
+ PairedFungibleAssetRefs {
+ mint_ref_opt: option::some(mint_ref),
+ transfer_ref_opt: option::some(transfer_ref),
+ burn_ref_opt: option::some(burn_ref),
+ }
+ );
+ };
+ *table::borrow(&map.coin_to_fungible_asset_map, type)
+}
+
+
+
+
+public(friend) fun ensure_paired_metadata<CoinType>(): object::Object<fungible_asset::Metadata>
+
+
+
+
+public(friend) fun ensure_paired_metadata<CoinType>(): Object<Metadata> acquires CoinConversionMap, CoinInfo {
+ create_and_return_paired_metadata_if_not_exist<CoinType>(false)
+}
+
+
+
+
+#[view]
+public fun paired_coin(metadata: object::Object<fungible_asset::Metadata>): option::Option<type_info::TypeInfo>
+
+
+
+
+public fun paired_coin(metadata: Object<Metadata>): Option<TypeInfo> acquires PairedCoinType {
+ let metadata_addr = object::object_address(&metadata);
+ if (exists<PairedCoinType>(metadata_addr)) {
+ option::some(borrow_global<PairedCoinType>(metadata_addr).type)
+ } else {
+ option::none()
+ }
+}
+
+
+
+
+public fun coin_to_fungible_asset<CoinType>(coin: coin::Coin<CoinType>): fungible_asset::FungibleAsset
+
+
+
+
+public fun coin_to_fungible_asset<CoinType>(
+ coin: Coin<CoinType>
+): FungibleAsset acquires CoinConversionMap, CoinInfo {
+ let metadata = ensure_paired_metadata<CoinType>();
+ let amount = burn_internal(coin);
+ fungible_asset::mint_internal(metadata, amount)
+}
+
+
+
+
+fun fungible_asset_to_coin<CoinType>(fungible_asset: fungible_asset::FungibleAsset): coin::Coin<CoinType>
+
+
+
+
+fun fungible_asset_to_coin<CoinType>(
+ fungible_asset: FungibleAsset
+): Coin<CoinType> acquires CoinInfo, PairedCoinType {
+ let metadata_addr = object::object_address(&fungible_asset::metadata_from_asset(&fungible_asset));
+ assert!(
+ object::object_exists<PairedCoinType>(metadata_addr),
+ error::not_found(EPAIRED_COIN)
+ );
+ let coin_type_info = borrow_global<PairedCoinType>(metadata_addr).type;
+ assert!(coin_type_info == type_info::type_of<CoinType>(), error::invalid_argument(ECOIN_TYPE_MISMATCH));
+ let amount = fungible_asset::burn_internal(fungible_asset);
+ mint_internal<CoinType>(amount)
+}
+
+
+
+
+fun assert_paired_metadata_exists<CoinType>(): object::Object<fungible_asset::Metadata>
+
+
+
+
+inline fun assert_paired_metadata_exists<CoinType>(): Object<Metadata> {
+ let metadata_opt = paired_metadata<CoinType>();
+ assert!(option::is_some(&metadata_opt), error::not_found(EPAIRED_FUNGIBLE_ASSET));
+ option::destroy_some(metadata_opt)
+}
+
+
+
+
+MintRef
has not been taken.
+
+
+#[view]
+public fun paired_mint_ref_exists<CoinType>(): bool
+
+
+
+
+public fun paired_mint_ref_exists<CoinType>(): bool acquires CoinConversionMap, PairedFungibleAssetRefs {
+ let metadata = assert_paired_metadata_exists<CoinType>();
+ let metadata_addr = object_address(&metadata);
+ assert!(exists<PairedFungibleAssetRefs>(metadata_addr), error::internal(EPAIRED_FUNGIBLE_ASSET_REFS_NOT_FOUND));
+ option::is_some(&borrow_global<PairedFungibleAssetRefs>(metadata_addr).mint_ref_opt)
+}
+
+
+
+
+MintRef
of paired fungible asset of a coin type from MintCapability
.
+
+
+public fun get_paired_mint_ref<CoinType>(_: &coin::MintCapability<CoinType>): (fungible_asset::MintRef, coin::MintRefReceipt)
+
+
+
+
+public fun get_paired_mint_ref<CoinType>(
+ _: &MintCapability<CoinType>
+): (MintRef, MintRefReceipt) acquires CoinConversionMap, PairedFungibleAssetRefs {
+ let metadata = assert_paired_metadata_exists<CoinType>();
+ let metadata_addr = object_address(&metadata);
+ assert!(exists<PairedFungibleAssetRefs>(metadata_addr), error::internal(EPAIRED_FUNGIBLE_ASSET_REFS_NOT_FOUND));
+ let mint_ref_opt = &mut borrow_global_mut<PairedFungibleAssetRefs>(metadata_addr).mint_ref_opt;
+ assert!(option::is_some(mint_ref_opt), error::not_found(EMINT_REF_NOT_FOUND));
+ (option::extract(mint_ref_opt), MintRefReceipt { metadata })
+}
+
+
+
+
+MintRef
with the hot potato receipt.
+
+
+public fun return_paired_mint_ref(mint_ref: fungible_asset::MintRef, receipt: coin::MintRefReceipt)
+
+
+
+
+public fun return_paired_mint_ref(mint_ref: MintRef, receipt: MintRefReceipt) acquires PairedFungibleAssetRefs {
+ let MintRefReceipt { metadata } = receipt;
+ assert!(
+ fungible_asset::mint_ref_metadata(&mint_ref) == metadata,
+ error::invalid_argument(EMINT_REF_RECEIPT_MISMATCH)
+ );
+ let metadata_addr = object_address(&metadata);
+ let mint_ref_opt = &mut borrow_global_mut<PairedFungibleAssetRefs>(metadata_addr).mint_ref_opt;
+ option::fill(mint_ref_opt, mint_ref);
+}
+
+
+
+
+TransferRef
still exists.
+
+
+#[view]
+public fun paired_transfer_ref_exists<CoinType>(): bool
+
+
+
+
+public fun paired_transfer_ref_exists<CoinType>(): bool acquires CoinConversionMap, PairedFungibleAssetRefs {
+ let metadata = assert_paired_metadata_exists<CoinType>();
+ let metadata_addr = object_address(&metadata);
+ assert!(exists<PairedFungibleAssetRefs>(metadata_addr), error::internal(EPAIRED_FUNGIBLE_ASSET_REFS_NOT_FOUND));
+ option::is_some(&borrow_global<PairedFungibleAssetRefs>(metadata_addr).transfer_ref_opt)
+}
+
+
+
+
+FreezeCapability
.
+
+
+public fun get_paired_transfer_ref<CoinType>(_: &coin::FreezeCapability<CoinType>): (fungible_asset::TransferRef, coin::TransferRefReceipt)
+
+
+
+
+public fun get_paired_transfer_ref<CoinType>(
+ _: &FreezeCapability<CoinType>
+): (TransferRef, TransferRefReceipt) acquires CoinConversionMap, PairedFungibleAssetRefs {
+ let metadata = assert_paired_metadata_exists<CoinType>();
+ let metadata_addr = object_address(&metadata);
+ assert!(exists<PairedFungibleAssetRefs>(metadata_addr), error::internal(EPAIRED_FUNGIBLE_ASSET_REFS_NOT_FOUND));
+ let transfer_ref_opt = &mut borrow_global_mut<PairedFungibleAssetRefs>(metadata_addr).transfer_ref_opt;
+ assert!(option::is_some(transfer_ref_opt), error::not_found(ETRANSFER_REF_NOT_FOUND));
+ (option::extract(transfer_ref_opt), TransferRefReceipt { metadata })
+}
+
+
+
+
+TransferRef
with the hot potato receipt.
+
+
+public fun return_paired_transfer_ref(transfer_ref: fungible_asset::TransferRef, receipt: coin::TransferRefReceipt)
+
+
+
+
+public fun return_paired_transfer_ref(
+ transfer_ref: TransferRef,
+ receipt: TransferRefReceipt
+) acquires PairedFungibleAssetRefs {
+ let TransferRefReceipt { metadata } = receipt;
+ assert!(
+ fungible_asset::transfer_ref_metadata(&transfer_ref) == metadata,
+ error::invalid_argument(ETRANSFER_REF_RECEIPT_MISMATCH)
+ );
+ let metadata_addr = object_address(&metadata);
+ let transfer_ref_opt = &mut borrow_global_mut<PairedFungibleAssetRefs>(metadata_addr).transfer_ref_opt;
+ option::fill(transfer_ref_opt, transfer_ref);
+}
+
+
+
+
+BurnRef
has not been taken.
+
+
+#[view]
+public fun paired_burn_ref_exists<CoinType>(): bool
+
+
+
+
+public fun paired_burn_ref_exists<CoinType>(): bool acquires CoinConversionMap, PairedFungibleAssetRefs {
+ let metadata = assert_paired_metadata_exists<CoinType>();
+ let metadata_addr = object_address(&metadata);
+ assert!(exists<PairedFungibleAssetRefs>(metadata_addr), error::internal(EPAIRED_FUNGIBLE_ASSET_REFS_NOT_FOUND));
+ option::is_some(&borrow_global<PairedFungibleAssetRefs>(metadata_addr).burn_ref_opt)
+}
+
+
+
+
+BurnRef
of paired fungible asset of a coin type from BurnCapability
.
+
+
+public fun get_paired_burn_ref<CoinType>(_: &coin::BurnCapability<CoinType>): (fungible_asset::BurnRef, coin::BurnRefReceipt)
+
+
+
+
+public fun get_paired_burn_ref<CoinType>(
+ _: &BurnCapability<CoinType>
+): (BurnRef, BurnRefReceipt) acquires CoinConversionMap, PairedFungibleAssetRefs {
+ let metadata = assert_paired_metadata_exists<CoinType>();
+ let metadata_addr = object_address(&metadata);
+ assert!(exists<PairedFungibleAssetRefs>(metadata_addr), error::internal(EPAIRED_FUNGIBLE_ASSET_REFS_NOT_FOUND));
+ let burn_ref_opt = &mut borrow_global_mut<PairedFungibleAssetRefs>(metadata_addr).burn_ref_opt;
+ assert!(option::is_some(burn_ref_opt), error::not_found(EBURN_REF_NOT_FOUND));
+ (option::extract(burn_ref_opt), BurnRefReceipt { metadata })
+}
+
+
+
+
+public fun convert_and_take_paired_burn_ref<CoinType>(burn_cap: coin::BurnCapability<CoinType>): fungible_asset::BurnRef
+
+
+
+
+public fun convert_and_take_paired_burn_ref<CoinType>(
+ burn_cap: BurnCapability<CoinType>
+): BurnRef acquires CoinConversionMap, PairedFungibleAssetRefs {
+ destroy_burn_cap(burn_cap);
+ let metadata = assert_paired_metadata_exists<CoinType>();
+ let metadata_addr = object_address(&metadata);
+ assert!(exists<PairedFungibleAssetRefs>(metadata_addr), error::internal(EPAIRED_FUNGIBLE_ASSET_REFS_NOT_FOUND));
+ let burn_ref_opt = &mut borrow_global_mut<PairedFungibleAssetRefs>(metadata_addr).burn_ref_opt;
+ assert!(option::is_some(burn_ref_opt), error::not_found(EBURN_REF_NOT_FOUND));
+ option::extract(burn_ref_opt)
+}
+
+
+
+
+BurnRef
with the hot potato receipt.
+
+
+public fun return_paired_burn_ref(burn_ref: fungible_asset::BurnRef, receipt: coin::BurnRefReceipt)
+
+
+
+
+public fun return_paired_burn_ref(
+ burn_ref: BurnRef,
+ receipt: BurnRefReceipt
+) acquires PairedFungibleAssetRefs {
+ let BurnRefReceipt { metadata } = receipt;
+ assert!(
+ fungible_asset::burn_ref_metadata(&burn_ref) == metadata,
+ error::invalid_argument(EBURN_REF_RECEIPT_MISMATCH)
+ );
+ let metadata_addr = object_address(&metadata);
+ let burn_ref_opt = &mut borrow_global_mut<PairedFungibleAssetRefs>(metadata_addr).burn_ref_opt;
+ option::fill(burn_ref_opt, burn_ref);
+}
+
+
+
+
+fun borrow_paired_burn_ref<CoinType>(_: &coin::BurnCapability<CoinType>): &fungible_asset::BurnRef
+
+
+
+
+inline fun borrow_paired_burn_ref<CoinType>(
+ _: &BurnCapability<CoinType>
+): &BurnRef acquires CoinConversionMap, PairedFungibleAssetRefs {
+ let metadata = assert_paired_metadata_exists<CoinType>();
+ let metadata_addr = object_address(&metadata);
+ assert!(exists<PairedFungibleAssetRefs>(metadata_addr), error::internal(EPAIRED_FUNGIBLE_ASSET_REFS_NOT_FOUND));
+ let burn_ref_opt = &mut borrow_global_mut<PairedFungibleAssetRefs>(metadata_addr).burn_ref_opt;
+ assert!(option::is_some(burn_ref_opt), error::not_found(EBURN_REF_NOT_FOUND));
+ option::borrow(burn_ref_opt)
+}
+
+
+
+
+public(friend) fun initialize_supply_config(aptos_framework: &signer)
+
+
+
+
+public(friend) fun initialize_supply_config(aptos_framework: &signer) {
+ system_addresses::assert_aptos_framework(aptos_framework);
+ move_to(aptos_framework, SupplyConfig { allow_upgrades: false });
+}
+
+
+
+
+public fun allow_supply_upgrades(aptos_framework: &signer, allowed: bool)
+
+
+
+
+public fun allow_supply_upgrades(aptos_framework: &signer, allowed: bool) acquires SupplyConfig {
+ system_addresses::assert_aptos_framework(aptos_framework);
+ let allow_upgrades = &mut borrow_global_mut<SupplyConfig>(@aptos_framework).allow_upgrades;
+ *allow_upgrades = allowed;
+}
+
+
+
+
+limit
. Note that this function can
+only be called by Aptos Framework (0x1) account for now because of create_aggregator
.
+
+
+public(friend) fun initialize_aggregatable_coin<CoinType>(aptos_framework: &signer): coin::AggregatableCoin<CoinType>
+
+
+
+
+public(friend) fun initialize_aggregatable_coin<CoinType>(aptos_framework: &signer): AggregatableCoin<CoinType> {
+ let aggregator = aggregator_factory::create_aggregator(aptos_framework, MAX_U64);
+ AggregatableCoin<CoinType> {
+ value: aggregator,
+ }
+}
+
+
+
+
+public(friend) fun is_aggregatable_coin_zero<CoinType>(coin: &coin::AggregatableCoin<CoinType>): bool
+
+
+
+
+public(friend) fun is_aggregatable_coin_zero<CoinType>(coin: &AggregatableCoin<CoinType>): bool {
+ let amount = aggregator::read(&coin.value);
+ amount == 0
+}
+
+
+
+
+public(friend) fun drain_aggregatable_coin<CoinType>(coin: &mut coin::AggregatableCoin<CoinType>): coin::Coin<CoinType>
+
+
+
+
+public(friend) fun drain_aggregatable_coin<CoinType>(coin: &mut AggregatableCoin<CoinType>): Coin<CoinType> {
+ spec {
+ // TODO: The data invariant is not properly assumed from CollectedFeesPerBlock.
+ assume aggregator::spec_get_limit(coin.value) == MAX_U64;
+ };
+ let amount = aggregator::read(&coin.value);
+ assert!(amount <= MAX_U64, error::out_of_range(EAGGREGATABLE_COIN_VALUE_TOO_LARGE));
+ spec {
+ update aggregate_supply<CoinType> = aggregate_supply<CoinType> - amount;
+ };
+ aggregator::sub(&mut coin.value, amount);
+ spec {
+ update supply<CoinType> = supply<CoinType> + amount;
+ };
+ Coin<CoinType> {
+ value: (amount as u64),
+ }
+}
+
+
+
+
+coin
into aggregatable coin (dst_coin
).
+
+
+public(friend) fun merge_aggregatable_coin<CoinType>(dst_coin: &mut coin::AggregatableCoin<CoinType>, coin: coin::Coin<CoinType>)
+
+
+
+
+public(friend) fun merge_aggregatable_coin<CoinType>(
+ dst_coin: &mut AggregatableCoin<CoinType>,
+ coin: Coin<CoinType>
+) {
+ spec {
+ update supply<CoinType> = supply<CoinType> - coin.value;
+ };
+ let Coin { value } = coin;
+ let amount = (value as u128);
+ spec {
+ update aggregate_supply<CoinType> = aggregate_supply<CoinType> + amount;
+ };
+ aggregator::add(&mut dst_coin.value, amount);
+}
+
+
+
+
+public(friend) fun collect_into_aggregatable_coin<CoinType>(account_addr: address, amount: u64, dst_coin: &mut coin::AggregatableCoin<CoinType>)
+
+
+
+
+public(friend) fun collect_into_aggregatable_coin<CoinType>(
+ account_addr: address,
+ amount: u64,
+ dst_coin: &mut AggregatableCoin<CoinType>,
+) acquires CoinStore, CoinConversionMap, CoinInfo, PairedCoinType {
+ // Skip collecting if amount is zero.
+ if (amount == 0) {
+ return
+ };
+
+ let (coin_amount_to_collect, fa_amount_to_collect) = calculate_amount_to_withdraw<CoinType>(
+ account_addr,
+ amount
+ );
+ let coin = if (coin_amount_to_collect > 0) {
+ let coin_store = borrow_global_mut<CoinStore<CoinType>>(account_addr);
+ extract(&mut coin_store.coin, coin_amount_to_collect)
+ } else {
+ zero()
+ };
+ if (fa_amount_to_collect > 0) {
+ let store_addr = primary_fungible_store::primary_store_address(
+ account_addr,
+ option::destroy_some(paired_metadata<CoinType>())
+ );
+ let fa = fungible_asset::withdraw_internal(store_addr, fa_amount_to_collect);
+ merge(&mut coin, fungible_asset_to_coin<CoinType>(fa));
+ };
+ merge_aggregatable_coin(dst_coin, coin);
+}
+
+
+
+
+fun calculate_amount_to_withdraw<CoinType>(account_addr: address, amount: u64): (u64, u64)
+
+
+
+
+inline fun calculate_amount_to_withdraw<CoinType>(
+ account_addr: address,
+ amount: u64
+): (u64, u64) {
+ let coin_balance = coin_balance<CoinType>(account_addr);
+ if (coin_balance >= amount) {
+ (amount, 0)
+ } else {
+ let metadata = paired_metadata<CoinType>();
+ if (option::is_some(&metadata) && primary_fungible_store::primary_store_exists(
+ account_addr,
+ option::destroy_some(metadata)
+ ))
+ (coin_balance, amount - coin_balance)
+ else
+ abort error::invalid_argument(EINSUFFICIENT_BALANCE)
+ }
+}
+
+
+
+
+fun maybe_convert_to_fungible_store<CoinType>(account: address)
+
+
+
+
+fun maybe_convert_to_fungible_store<CoinType>(account: address) acquires CoinStore, CoinConversionMap, CoinInfo {
+ if (!features::coin_to_fungible_asset_migration_feature_enabled()) {
+ abort error::unavailable(ECOIN_TO_FUNGIBLE_ASSET_FEATURE_NOT_ENABLED)
+ };
+ assert!(is_coin_initialized<CoinType>(), error::invalid_argument(ECOIN_INFO_NOT_PUBLISHED));
+
+ let metadata = ensure_paired_metadata<CoinType>();
+ let store = primary_fungible_store::ensure_primary_store_exists(account, metadata);
+ let store_address = object::object_address(&store);
+ if (exists<CoinStore<CoinType>>(account)) {
+ let CoinStore<CoinType> { coin, frozen, deposit_events, withdraw_events } = move_from<CoinStore<CoinType>>(
+ account
+ );
+ event::emit(
+ CoinEventHandleDeletion {
+ event_handle_creation_address: guid::creator_address(
+ event::guid(&deposit_events)
+ ),
+ deleted_deposit_event_handle_creation_number: guid::creation_num(event::guid(&deposit_events)),
+ deleted_withdraw_event_handle_creation_number: guid::creation_num(event::guid(&withdraw_events))
+ }
+ );
+ event::destroy_handle(deposit_events);
+ event::destroy_handle(withdraw_events);
+ if (coin.value == 0) {
+ destroy_zero(coin);
+ } else {
+ fungible_asset::deposit(store, coin_to_fungible_asset(coin));
+ };
+ // Note:
+ // It is possible the primary fungible store may already exist before this function call.
+ // In this case, if the account owns a frozen CoinStore and an unfrozen primary fungible store, this
+ // function would convert and deposit the rest coin into the primary store and freeze it to make the
+ // `frozen` semantic as consistent as possible.
+ if (frozen != fungible_asset::is_frozen(store)) {
+ fungible_asset::set_frozen_flag_internal(store, frozen);
+ }
+ };
+ if (!exists<MigrationFlag>(store_address)) {
+ move_to(&create_signer::create_signer(store_address), MigrationFlag {});
+ }
+}
+
+
+
+
+CoinType
if not yet.
+
+
+public entry fun migrate_to_fungible_store<CoinType>(account: &signer)
+
+
+
+
+public entry fun migrate_to_fungible_store<CoinType>(
+ account: &signer
+) acquires CoinStore, CoinConversionMap, CoinInfo {
+ maybe_convert_to_fungible_store<CoinType>(signer::address_of(account));
+}
+
+
+
+
+fun coin_address<CoinType>(): address
+
+
+
+
+fun coin_address<CoinType>(): address {
+ let type_info = type_info::type_of<CoinType>();
+ type_info::account_address(&type_info)
+}
+
+
+
+
+owner
for provided CoinType
and its paired FA if exists.
+
+
+#[view]
+public fun balance<CoinType>(owner: address): u64
+
+
+
+
+public fun balance<CoinType>(owner: address): u64 acquires CoinConversionMap, CoinStore {
+ let paired_metadata = paired_metadata<CoinType>();
+ coin_balance<CoinType>(owner) + if (option::is_some(&paired_metadata)) {
+ primary_fungible_store::balance(
+ owner,
+ option::extract(&mut paired_metadata)
+ )
+ } else { 0 }
+}
+
+
+
+
+owner
for provided CoinType
and its paired FA is >= amount
.
+
+
+#[view]
+public fun is_balance_at_least<CoinType>(owner: address, amount: u64): bool
+
+
+
+
+public fun is_balance_at_least<CoinType>(owner: address, amount: u64): bool acquires CoinConversionMap, CoinStore {
+ let coin_balance = coin_balance<CoinType>(owner);
+ if (coin_balance >= amount) {
+ return true
+ };
+
+ let paired_metadata = paired_metadata<CoinType>();
+ let left_amount = amount - coin_balance;
+ if (option::is_some(&paired_metadata)) {
+ primary_fungible_store::is_balance_at_least(
+ owner,
+ option::extract(&mut paired_metadata),
+ left_amount
+ )
+ } else { false }
+}
+
+
+
+
+fun coin_balance<CoinType>(owner: address): u64
+
+
+
+
+inline fun coin_balance<CoinType>(owner: address): u64 {
+ if (exists<CoinStore<CoinType>>(owner)) {
+ borrow_global<CoinStore<CoinType>>(owner).coin.value
+ } else {
+ 0
+ }
+}
+
+
+
+
+true
if the type CoinType
is an initialized coin.
+
+
+#[view]
+public fun is_coin_initialized<CoinType>(): bool
+
+
+
+
+public fun is_coin_initialized<CoinType>(): bool {
+ exists<CoinInfo<CoinType>>(coin_address<CoinType>())
+}
+
+
+
+
+true
is account_addr has frozen the CoinStore or if it's not registered at all
+
+
+#[view]
+public fun is_coin_store_frozen<CoinType>(account_addr: address): bool
+
+
+
+
+public fun is_coin_store_frozen<CoinType>(
+ account_addr: address
+): bool acquires CoinStore, CoinConversionMap {
+ if (!is_account_registered<CoinType>(account_addr)) {
+ return true
+ };
+
+ let coin_store = borrow_global<CoinStore<CoinType>>(account_addr);
+ coin_store.frozen
+}
+
+
+
+
+true
if account_addr
is registered to receive CoinType
.
+
+
+#[view]
+public fun is_account_registered<CoinType>(account_addr: address): bool
+
+
+
+
+public fun is_account_registered<CoinType>(account_addr: address): bool acquires CoinConversionMap {
+ assert!(is_coin_initialized<CoinType>(), error::invalid_argument(ECOIN_INFO_NOT_PUBLISHED));
+ if (exists<CoinStore<CoinType>>(account_addr)) {
+ true
+ } else {
+ let paired_metadata_opt = paired_metadata<CoinType>();
+ (option::is_some(
+ &paired_metadata_opt
+ ) && migrated_primary_fungible_store_exists(account_addr, option::destroy_some(paired_metadata_opt)))
+ }
+}
+
+
+
+
+#[view]
+public fun name<CoinType>(): string::String
+
+
+
+
+public fun name<CoinType>(): string::String acquires CoinInfo {
+ borrow_global<CoinInfo<CoinType>>(coin_address<CoinType>()).name
+}
+
+
+
+
+#[view]
+public fun symbol<CoinType>(): string::String
+
+
+
+
+public fun symbol<CoinType>(): string::String acquires CoinInfo {
+ borrow_global<CoinInfo<CoinType>>(coin_address<CoinType>()).symbol
+}
+
+
+
+
+decimals
equals 2
, a balance of 505
coins should
+be displayed to a user as 5.05
(505 / 10 ** 2
).
+
+
+#[view]
+public fun decimals<CoinType>(): u8
+
+
+
+
+public fun decimals<CoinType>(): u8 acquires CoinInfo {
+ borrow_global<CoinInfo<CoinType>>(coin_address<CoinType>()).decimals
+}
+
+
+
+
+#[view]
+public fun supply<CoinType>(): option::Option<u128>
+
+
+
+
+public fun supply<CoinType>(): Option<u128> acquires CoinInfo, CoinConversionMap {
+ let coin_supply = coin_supply<CoinType>();
+ let metadata = paired_metadata<CoinType>();
+ if (option::is_some(&metadata)) {
+ let fungible_asset_supply = fungible_asset::supply(option::extract(&mut metadata));
+ if (option::is_some(&coin_supply)) {
+ let supply = option::borrow_mut(&mut coin_supply);
+ *supply = *supply + option::destroy_some(fungible_asset_supply);
+ };
+ };
+ coin_supply
+}
+
+
+
+
+#[view]
+public fun coin_supply<CoinType>(): option::Option<u128>
+
+
+
+
+public fun coin_supply<CoinType>(): Option<u128> acquires CoinInfo {
+ let maybe_supply = &borrow_global<CoinInfo<CoinType>>(coin_address<CoinType>()).supply;
+ if (option::is_some(maybe_supply)) {
+ // We do track supply, in this case read from optional aggregator.
+ let supply = option::borrow(maybe_supply);
+ let value = optional_aggregator::read(supply);
+ option::some(value)
+ } else {
+ option::none()
+ }
+}
+
+
+
+
+coin
with capability.
+The capability _cap
should be passed as a reference to BurnCapability<CoinType>
.
+
+
+public fun burn<CoinType>(coin: coin::Coin<CoinType>, _cap: &coin::BurnCapability<CoinType>)
+
+
+
+
+public fun burn<CoinType>(coin: Coin<CoinType>, _cap: &BurnCapability<CoinType>) acquires CoinInfo {
+ burn_internal(coin);
+}
+
+
+
+
+coin
from the specified account
with capability.
+The capability burn_cap
should be passed as a reference to BurnCapability<CoinType>
.
+This function shouldn't fail as it's called as part of transaction fee burning.
+
+Note: This bypasses CoinStore::frozen -- coins within a frozen CoinStore can be burned.
+
+
+public fun burn_from<CoinType>(account_addr: address, amount: u64, burn_cap: &coin::BurnCapability<CoinType>)
+
+
+
+
+public fun burn_from<CoinType>(
+ account_addr: address,
+ amount: u64,
+ burn_cap: &BurnCapability<CoinType>,
+) acquires CoinInfo, CoinStore, CoinConversionMap, PairedFungibleAssetRefs {
+ // Skip burning if amount is zero. This shouldn't error out as it's called as part of transaction fee burning.
+ if (amount == 0) {
+ return
+ };
+
+ let (coin_amount_to_burn, fa_amount_to_burn) = calculate_amount_to_withdraw<CoinType>(
+ account_addr,
+ amount
+ );
+ if (coin_amount_to_burn > 0) {
+ let coin_store = borrow_global_mut<CoinStore<CoinType>>(account_addr);
+ let coin_to_burn = extract(&mut coin_store.coin, coin_amount_to_burn);
+ burn(coin_to_burn, burn_cap);
+ };
+ if (fa_amount_to_burn > 0) {
+ fungible_asset::burn_from(
+ borrow_paired_burn_ref(burn_cap),
+ primary_fungible_store::primary_store(account_addr, option::destroy_some(paired_metadata<CoinType>())),
+ fa_amount_to_burn
+ );
+ };
+}
+
+
+
+
+public fun deposit<CoinType>(account_addr: address, coin: coin::Coin<CoinType>)
+
+
+
+
+public fun deposit<CoinType>(
+ account_addr: address,
+ coin: Coin<CoinType>
+) acquires CoinStore, CoinConversionMap, CoinInfo {
+ if (exists<CoinStore<CoinType>>(account_addr)) {
+ let coin_store = borrow_global_mut<CoinStore<CoinType>>(account_addr);
+ assert!(
+ !coin_store.frozen,
+ error::permission_denied(EFROZEN),
+ );
+ if (std::features::module_event_migration_enabled()) {
+ event::emit(
+ CoinDeposit { coin_type: type_name<CoinType>(), account: account_addr, amount: coin.value }
+ );
+ };
+ event::emit_event<DepositEvent>(
+ &mut coin_store.deposit_events,
+ DepositEvent { amount: coin.value },
+ );
+ merge(&mut coin_store.coin, coin);
+ } else {
+ let metadata = paired_metadata<CoinType>();
+ if (option::is_some(&metadata) && migrated_primary_fungible_store_exists(
+ account_addr,
+ option::destroy_some(metadata)
+ )) {
+ primary_fungible_store::deposit(account_addr, coin_to_fungible_asset(coin));
+ } else {
+ abort error::not_found(ECOIN_STORE_NOT_PUBLISHED)
+ };
+ }
+}
+
+
+
+
+fun migrated_primary_fungible_store_exists(account_address: address, metadata: object::Object<fungible_asset::Metadata>): bool
+
+
+
+
+inline fun migrated_primary_fungible_store_exists(
+ account_address: address,
+ metadata: Object<Metadata>
+): bool {
+ let primary_store_address = primary_fungible_store::primary_store_address<Metadata>(account_address, metadata);
+ fungible_asset::store_exists(primary_store_address) && (
+ // migration flag is needed, until we start defaulting new accounts to APT PFS
+ features::new_accounts_default_to_fa_apt_store_enabled() || exists<MigrationFlag>(primary_store_address)
+ )
+}
+
+
+
+
+public(friend) fun force_deposit<CoinType>(account_addr: address, coin: coin::Coin<CoinType>)
+
+
+
+
+public(friend) fun force_deposit<CoinType>(
+ account_addr: address,
+ coin: Coin<CoinType>
+) acquires CoinStore, CoinConversionMap, CoinInfo {
+ if (exists<CoinStore<CoinType>>(account_addr)) {
+ let coin_store = borrow_global_mut<CoinStore<CoinType>>(account_addr);
+ merge(&mut coin_store.coin, coin);
+ } else {
+ let metadata = paired_metadata<CoinType>();
+ if (option::is_some(&metadata) && migrated_primary_fungible_store_exists(
+ account_addr,
+ option::destroy_some(metadata)
+ )) {
+ let fa = coin_to_fungible_asset(coin);
+ let metadata = fungible_asset::asset_metadata(&fa);
+ let store = primary_fungible_store::primary_store(account_addr, metadata);
+ fungible_asset::deposit_internal(object::object_address(&store), fa);
+ } else {
+ abort error::not_found(ECOIN_STORE_NOT_PUBLISHED)
+ }
+ }
+}
+
+
+
+
+value
in the passed-in token
is non-zero
+so it is impossible to "burn" any non-zero amount of Coin
without having
+a BurnCapability
for the specific CoinType
.
+
+
+public fun destroy_zero<CoinType>(zero_coin: coin::Coin<CoinType>)
+
+
+
+
+public fun destroy_zero<CoinType>(zero_coin: Coin<CoinType>) {
+ spec {
+ update supply<CoinType> = supply<CoinType> - zero_coin.value;
+ };
+ let Coin { value } = zero_coin;
+ assert!(value == 0, error::invalid_argument(EDESTRUCTION_OF_NONZERO_TOKEN))
+}
+
+
+
+
+amount
from the passed-in coin
, where the original token is modified in place.
+
+
+public fun extract<CoinType>(coin: &mut coin::Coin<CoinType>, amount: u64): coin::Coin<CoinType>
+
+
+
+
+public fun extract<CoinType>(coin: &mut Coin<CoinType>, amount: u64): Coin<CoinType> {
+ assert!(coin.value >= amount, error::invalid_argument(EINSUFFICIENT_BALANCE));
+ spec {
+ update supply<CoinType> = supply<CoinType> - amount;
+ };
+ coin.value = coin.value - amount;
+ spec {
+ update supply<CoinType> = supply<CoinType> + amount;
+ };
+ Coin { value: amount }
+}
+
+
+
+
+coin
, where the original token is modified in place.
+
+
+public fun extract_all<CoinType>(coin: &mut coin::Coin<CoinType>): coin::Coin<CoinType>
+
+
+
+
+public fun extract_all<CoinType>(coin: &mut Coin<CoinType>): Coin<CoinType> {
+ let total_value = coin.value;
+ spec {
+ update supply<CoinType> = supply<CoinType> - coin.value;
+ };
+ coin.value = 0;
+ spec {
+ update supply<CoinType> = supply<CoinType> + total_value;
+ };
+ Coin { value: total_value }
+}
+
+
+
+
+#[legacy_entry_fun]
+public entry fun freeze_coin_store<CoinType>(account_addr: address, _freeze_cap: &coin::FreezeCapability<CoinType>)
+
+
+
+
+public entry fun freeze_coin_store<CoinType>(
+ account_addr: address,
+ _freeze_cap: &FreezeCapability<CoinType>,
+) acquires CoinStore {
+ let coin_store = borrow_global_mut<CoinStore<CoinType>>(account_addr);
+ coin_store.frozen = true;
+}
+
+
+
+
+#[legacy_entry_fun]
+public entry fun unfreeze_coin_store<CoinType>(account_addr: address, _freeze_cap: &coin::FreezeCapability<CoinType>)
+
+
+
+
+public entry fun unfreeze_coin_store<CoinType>(
+ account_addr: address,
+ _freeze_cap: &FreezeCapability<CoinType>,
+) acquires CoinStore {
+ let coin_store = borrow_global_mut<CoinStore<CoinType>>(account_addr);
+ coin_store.frozen = false;
+}
+
+
+
+
+public entry fun upgrade_supply<CoinType>(account: &signer)
+
+
+
+
+public entry fun upgrade_supply<CoinType>(account: &signer) acquires CoinInfo, SupplyConfig {
+ let account_addr = signer::address_of(account);
+
+ // Only coin creators can upgrade total supply.
+ assert!(
+ coin_address<CoinType>() == account_addr,
+ error::invalid_argument(ECOIN_INFO_ADDRESS_MISMATCH),
+ );
+
+ // Can only succeed once on-chain governance agreed on the upgrade.
+ assert!(
+ borrow_global_mut<SupplyConfig>(@aptos_framework).allow_upgrades,
+ error::permission_denied(ECOIN_SUPPLY_UPGRADE_NOT_SUPPORTED)
+ );
+
+ let maybe_supply = &mut borrow_global_mut<CoinInfo<CoinType>>(account_addr).supply;
+ if (option::is_some(maybe_supply)) {
+ let supply = option::borrow_mut(maybe_supply);
+
+ // If supply is tracked and the current implementation uses an integer - upgrade.
+ if (!optional_aggregator::is_parallelizable(supply)) {
+ optional_aggregator::switch(supply);
+ }
+ }
+}
+
+
+
+
+CoinType
and returns minting/freezing/burning capabilities.
+The given signer also becomes the account hosting the information about the coin
+(name, supply, etc.). Supply is initialized as non-parallelizable integer.
+
+
+public fun initialize<CoinType>(account: &signer, name: string::String, symbol: string::String, decimals: u8, monitor_supply: bool): (coin::BurnCapability<CoinType>, coin::FreezeCapability<CoinType>, coin::MintCapability<CoinType>)
+
+
+
+
+public fun initialize<CoinType>(
+ account: &signer,
+ name: string::String,
+ symbol: string::String,
+ decimals: u8,
+ monitor_supply: bool,
+): (BurnCapability<CoinType>, FreezeCapability<CoinType>, MintCapability<CoinType>) {
+ initialize_internal(account, name, symbol, decimals, monitor_supply, false)
+}
+
+
+
+
+initialize
but supply can be initialized to parallelizable aggregator.
+
+
+public(friend) fun initialize_with_parallelizable_supply<CoinType>(account: &signer, name: string::String, symbol: string::String, decimals: u8, monitor_supply: bool): (coin::BurnCapability<CoinType>, coin::FreezeCapability<CoinType>, coin::MintCapability<CoinType>)
+
+
+
+
+public(friend) fun initialize_with_parallelizable_supply<CoinType>(
+ account: &signer,
+ name: string::String,
+ symbol: string::String,
+ decimals: u8,
+ monitor_supply: bool,
+): (BurnCapability<CoinType>, FreezeCapability<CoinType>, MintCapability<CoinType>) {
+ system_addresses::assert_aptos_framework(account);
+ initialize_internal(account, name, symbol, decimals, monitor_supply, true)
+}
+
+
+
+
+fun initialize_internal<CoinType>(account: &signer, name: string::String, symbol: string::String, decimals: u8, monitor_supply: bool, parallelizable: bool): (coin::BurnCapability<CoinType>, coin::FreezeCapability<CoinType>, coin::MintCapability<CoinType>)
+
+
+
+
+fun initialize_internal<CoinType>(
+ account: &signer,
+ name: string::String,
+ symbol: string::String,
+ decimals: u8,
+ monitor_supply: bool,
+ parallelizable: bool,
+): (BurnCapability<CoinType>, FreezeCapability<CoinType>, MintCapability<CoinType>) {
+ let account_addr = signer::address_of(account);
+
+ assert!(
+ coin_address<CoinType>() == account_addr,
+ error::invalid_argument(ECOIN_INFO_ADDRESS_MISMATCH),
+ );
+
+ assert!(
+ !exists<CoinInfo<CoinType>>(account_addr),
+ error::already_exists(ECOIN_INFO_ALREADY_PUBLISHED),
+ );
+
+ assert!(string::length(&name) <= MAX_COIN_NAME_LENGTH, error::invalid_argument(ECOIN_NAME_TOO_LONG));
+ assert!(string::length(&symbol) <= MAX_COIN_SYMBOL_LENGTH, error::invalid_argument(ECOIN_SYMBOL_TOO_LONG));
+
+ let coin_info = CoinInfo<CoinType> {
+ name,
+ symbol,
+ decimals,
+ supply: if (monitor_supply) {
+ option::some(
+ optional_aggregator::new(MAX_U128, parallelizable)
+ )
+ } else { option::none() },
+ };
+ move_to(account, coin_info);
+
+ (BurnCapability<CoinType> {}, FreezeCapability<CoinType> {}, MintCapability<CoinType> {})
+}
+
+
+
+
+dst_coin
will have a value equal
+to the sum of the two tokens (dst_coin
and source_coin
).
+
+
+public fun merge<CoinType>(dst_coin: &mut coin::Coin<CoinType>, source_coin: coin::Coin<CoinType>)
+
+
+
+
+public fun merge<CoinType>(dst_coin: &mut Coin<CoinType>, source_coin: Coin<CoinType>) {
+ spec {
+ assume dst_coin.value + source_coin.value <= MAX_U64;
+ };
+ spec {
+ update supply<CoinType> = supply<CoinType> - source_coin.value;
+ };
+ let Coin { value } = source_coin;
+ spec {
+ update supply<CoinType> = supply<CoinType> + value;
+ };
+ dst_coin.value = dst_coin.value + value;
+}
+
+
+
+
+Coin
with capability.
+The capability _cap
should be passed as reference to MintCapability<CoinType>
.
+Returns minted Coin
.
+
+
+public fun mint<CoinType>(amount: u64, _cap: &coin::MintCapability<CoinType>): coin::Coin<CoinType>
+
+
+
+
+public fun mint<CoinType>(
+ amount: u64,
+ _cap: &MintCapability<CoinType>,
+): Coin<CoinType> acquires CoinInfo {
+ mint_internal<CoinType>(amount)
+}
+
+
+
+
+public fun register<CoinType>(account: &signer)
+
+
+
+
+public fun register<CoinType>(account: &signer) acquires CoinConversionMap {
+ let account_addr = signer::address_of(account);
+ // Short-circuit and do nothing if account is already registered for CoinType.
+ if (is_account_registered<CoinType>(account_addr)) {
+ return
+ };
+
+ account::register_coin<CoinType>(account_addr);
+ let coin_store = CoinStore<CoinType> {
+ coin: Coin { value: 0 },
+ frozen: false,
+ deposit_events: account::new_event_handle<DepositEvent>(account),
+ withdraw_events: account::new_event_handle<WithdrawEvent>(account),
+ };
+ move_to(account, coin_store);
+}
+
+
+
+
+amount
of coins CoinType
from from
to to
.
+
+
+public entry fun transfer<CoinType>(from: &signer, to: address, amount: u64)
+
+
+
+
+public entry fun transfer<CoinType>(
+ from: &signer,
+ to: address,
+ amount: u64,
+) acquires CoinStore, CoinConversionMap, CoinInfo, PairedCoinType {
+ let coin = withdraw<CoinType>(from, amount);
+ deposit(to, coin);
+}
+
+
+
+
+value
passed in coin
.
+
+
+public fun value<CoinType>(coin: &coin::Coin<CoinType>): u64
+
+
+
+
+public fun value<CoinType>(coin: &Coin<CoinType>): u64 {
+ coin.value
+}
+
+
+
+
+amount
of coin CoinType
from the signing account.
+
+
+public fun withdraw<CoinType>(account: &signer, amount: u64): coin::Coin<CoinType>
+
+
+
+
+public fun withdraw<CoinType>(
+ account: &signer,
+ amount: u64,
+): Coin<CoinType> acquires CoinStore, CoinConversionMap, CoinInfo, PairedCoinType {
+ let account_addr = signer::address_of(account);
+
+ let (coin_amount_to_withdraw, fa_amount_to_withdraw) = calculate_amount_to_withdraw<CoinType>(
+ account_addr,
+ amount
+ );
+ let withdrawn_coin = if (coin_amount_to_withdraw > 0) {
+ let coin_store = borrow_global_mut<CoinStore<CoinType>>(account_addr);
+ assert!(
+ !coin_store.frozen,
+ error::permission_denied(EFROZEN),
+ );
+ if (std::features::module_event_migration_enabled()) {
+ event::emit(
+ CoinWithdraw {
+ coin_type: type_name<CoinType>(), account: account_addr, amount: coin_amount_to_withdraw
+ }
+ );
+ };
+ event::emit_event<WithdrawEvent>(
+ &mut coin_store.withdraw_events,
+ WithdrawEvent { amount: coin_amount_to_withdraw },
+ );
+ extract(&mut coin_store.coin, coin_amount_to_withdraw)
+ } else {
+ zero()
+ };
+ if (fa_amount_to_withdraw > 0) {
+ let fa = primary_fungible_store::withdraw(
+ account,
+ option::destroy_some(paired_metadata<CoinType>()),
+ fa_amount_to_withdraw
+ );
+ merge(&mut withdrawn_coin, fungible_asset_to_coin(fa));
+ };
+ withdrawn_coin
+}
+
+
+
+
+Coin<CoinType>
with a value of 0
.
+
+
+public fun zero<CoinType>(): coin::Coin<CoinType>
+
+
+
+
+public fun zero<CoinType>(): Coin<CoinType> {
+ spec {
+ update supply<CoinType> = supply<CoinType> + 0;
+ };
+ Coin<CoinType> {
+ value: 0
+ }
+}
+
+
+
+
+public fun destroy_freeze_cap<CoinType>(freeze_cap: coin::FreezeCapability<CoinType>)
+
+
+
+
+public fun destroy_freeze_cap<CoinType>(freeze_cap: FreezeCapability<CoinType>) {
+ let FreezeCapability<CoinType> {} = freeze_cap;
+}
+
+
+
+
+public fun destroy_mint_cap<CoinType>(mint_cap: coin::MintCapability<CoinType>)
+
+
+
+
+public fun destroy_mint_cap<CoinType>(mint_cap: MintCapability<CoinType>) {
+ let MintCapability<CoinType> {} = mint_cap;
+}
+
+
+
+
+public fun destroy_burn_cap<CoinType>(burn_cap: coin::BurnCapability<CoinType>)
+
+
+
+
+public fun destroy_burn_cap<CoinType>(burn_cap: BurnCapability<CoinType>) {
+ let BurnCapability<CoinType> {} = burn_cap;
+}
+
+
+
+
+fun mint_internal<CoinType>(amount: u64): coin::Coin<CoinType>
+
+
+
+
+fun mint_internal<CoinType>(amount: u64): Coin<CoinType> acquires CoinInfo {
+ if (amount == 0) {
+ return Coin<CoinType> {
+ value: 0
+ }
+ };
+
+ let maybe_supply = &mut borrow_global_mut<CoinInfo<CoinType>>(coin_address<CoinType>()).supply;
+ if (option::is_some(maybe_supply)) {
+ let supply = option::borrow_mut(maybe_supply);
+ spec {
+ use aptos_framework::optional_aggregator;
+ use aptos_framework::aggregator;
+ assume optional_aggregator::is_parallelizable(supply) ==> (aggregator::spec_aggregator_get_val(
+ option::borrow(supply.aggregator)
+ )
+ + amount <= aggregator::spec_get_limit(option::borrow(supply.aggregator)));
+ assume !optional_aggregator::is_parallelizable(supply) ==>
+ (option::borrow(supply.integer).value + amount <= option::borrow(supply.integer).limit);
+ };
+ optional_aggregator::add(supply, (amount as u128));
+ };
+ spec {
+ update supply<CoinType> = supply<CoinType> + amount;
+ };
+ Coin<CoinType> { value: amount }
+}
+
+
+
+
+fun burn_internal<CoinType>(coin: coin::Coin<CoinType>): u64
+
+
+
+
+fun burn_internal<CoinType>(coin: Coin<CoinType>): u64 acquires CoinInfo {
+ spec {
+ update supply<CoinType> = supply<CoinType> - coin.value;
+ };
+ let Coin { value: amount } = coin;
+ if (amount != 0) {
+ let maybe_supply = &mut borrow_global_mut<CoinInfo<CoinType>>(coin_address<CoinType>()).supply;
+ if (option::is_some(maybe_supply)) {
+ let supply = option::borrow_mut(maybe_supply);
+ optional_aggregator::sub(supply, (amount as u128));
+ };
+ };
+ amount
+}
+
+
+
+
+No. | Requirement | Criticality | Implementation | Enforcement | +
---|---|---|---|---|
1 | +Only the owner of a coin may mint, burn or freeze coins. | +Critical | +Acquiring capabilities for a particular CoinType may only occur if the caller has a signer for the module declaring that type. The initialize function returns these capabilities to the caller. | +Formally Verified via upgrade_supply and initialize. | +
2 | +Each coin may only be created exactly once. | +Medium | +The initialization function may only be called once. | +Formally Verified via initialize. | +
3 | +The merging of coins may only be done on coins of the same type. | +Critical | +The merge function is limited to merging coins of the same type only. | +Formally Verified via merge. | +
4 | +The supply of a coin is only affected by burn and mint operations. | +High | +Only mint and burn operations on a coin alter the total supply of coins. | +Formally Verified via TotalSupplyNoChange. | +
5 | +Users may register an account for a coin multiple times idempotently. | +Medium | +The register function should work idempotently. Importantly, it should not abort if the coin is already registered. | +Formally verified via aborts_if on register. | +
6 | +Coin operations should fail if the user has not registered for the coin. | +Medium | +Coin operations may succeed only on valid user coin registration. | +Formally Verified via balance, burn_from, freeze, unfreeze, transfer and withdraw. | +
7 | +It should always be possible to (1) determine if a coin exists, and (2) determine if a user registered an account with a particular coin. If a coin exists, it should always be possible to request the following information of the coin: (1) Name, (2) Symbol, and (3) Supply. | +Low | +The following functions should never abort: (1) is_coin_initialized, and (2) is_account_registered. The following functions should not abort if the coin exists: (1) name, (2) symbol, and (3) supply. | +Formally Verified in corresponding functions: is_coin_initialized, is_account_registered, name, symbol and supply. | +
8 | +Coin operations should fail if the user's CoinStore is frozen. | +Medium | +If the CoinStore of an address is frozen, coin operations are disallowed. | +Formally Verified via withdraw, transfer and deposit. | +
9 | +Utilizing AggregatableCoins does not violate other critical invariants, such as (4). | +High | +Utilizing AggregatableCoin does not change the real-supply of any token. | +Formally Verified via TotalSupplyNoChange. | +
pragma verify = true;
+
+global supply<CoinType>: num;
+
+global aggregate_supply<CoinType>: num;
+apply TotalSupplyTracked<CoinType> to *<CoinType> except
+initialize, initialize_internal, initialize_with_parallelizable_supply;
+
+
+
+
+
+
+
+
+fun spec_fun_supply_tracked<CoinType>(val: u64, supply: Option<OptionalAggregator>): bool {
+ option::spec_is_some(supply) ==> val == optional_aggregator::optional_aggregator_value
+ (option::spec_borrow(supply))
+}
+
+
+
+
+
+
+
+
+schema TotalSupplyTracked<CoinType> {
+ ensures old(spec_fun_supply_tracked<CoinType>(supply<CoinType> + aggregate_supply<CoinType>,
+ global<CoinInfo<CoinType>>(type_info::type_of<CoinType>().account_address).supply)) ==>
+ spec_fun_supply_tracked<CoinType>(supply<CoinType> + aggregate_supply<CoinType>,
+ global<CoinInfo<CoinType>>(type_info::type_of<CoinType>().account_address).supply);
+}
+
+
+
+
+
+
+
+
+fun spec_fun_supply_no_change<CoinType>(old_supply: Option<OptionalAggregator>,
+ supply: Option<OptionalAggregator>): bool {
+ option::spec_is_some(old_supply) ==> optional_aggregator::optional_aggregator_value
+ (option::spec_borrow(old_supply)) == optional_aggregator::optional_aggregator_value
+ (option::spec_borrow(supply))
+}
+
+
+
+
+
+
+
+
+schema TotalSupplyNoChange<CoinType> {
+ let old_supply = global<CoinInfo<CoinType>>(type_info::type_of<CoinType>().account_address).supply;
+ let post supply = global<CoinInfo<CoinType>>(type_info::type_of<CoinType>().account_address).supply;
+ ensures spec_fun_supply_no_change<CoinType>(old_supply, supply);
+}
+
+
+
+
+
+
+
+
+fun spec_is_account_registered<CoinType>(account_addr: address): bool {
+ let paired_metadata_opt = spec_paired_metadata<CoinType>();
+ exists<CoinStore<CoinType>>(account_addr) || (option::spec_is_some(
+ paired_metadata_opt
+ ) && primary_fungible_store::spec_primary_store_exists(account_addr, option::spec_borrow(paired_metadata_opt)))
+}
+
+
+
+
+
+
+
+
+schema CoinSubAbortsIf<CoinType> {
+ amount: u64;
+ let addr = type_info::type_of<CoinType>().account_address;
+ let maybe_supply = global<CoinInfo<CoinType>>(addr).supply;
+ include (option::is_some(
+ maybe_supply
+ )) ==> optional_aggregator::SubAbortsIf { optional_aggregator: option::borrow(maybe_supply), value: amount };
+}
+
+
+
+
+
+
+
+
+schema CoinAddAbortsIf<CoinType> {
+ amount: u64;
+ let addr = type_info::type_of<CoinType>().account_address;
+ let maybe_supply = global<CoinInfo<CoinType>>(addr).supply;
+ include (option::is_some(
+ maybe_supply
+ )) ==> optional_aggregator::AddAbortsIf { optional_aggregator: option::borrow(maybe_supply), value: amount };
+}
+
+
+
+
+
+
+
+
+schema AbortsIfNotExistCoinInfo<CoinType> {
+ let addr = type_info::type_of<CoinType>().account_address;
+ aborts_if !exists<CoinInfo<CoinType>>(addr);
+}
+
+
+
+
+
+
+### Struct `AggregatableCoin`
+
+
+struct AggregatableCoin<CoinType> has store
+
+
+
+
+value: aggregator::Aggregator
+invariant aggregator::spec_get_limit(value) == MAX_U64;
+
+
+
+
+
+
+### Function `coin_to_fungible_asset`
+
+
+public fun coin_to_fungible_asset<CoinType>(coin: coin::Coin<CoinType>): fungible_asset::FungibleAsset
+
+
+
+
+
+pragma verify = false;
+let addr = type_info::type_of<CoinType>().account_address;
+modifies global<CoinInfo<CoinType>>(addr);
+
+
+
+
+
+
+### Function `fungible_asset_to_coin`
+
+
+fun fungible_asset_to_coin<CoinType>(fungible_asset: fungible_asset::FungibleAsset): coin::Coin<CoinType>
+
+
+
+
+
+pragma verify = false;
+
+
+
+
+
+
+### Function `initialize_supply_config`
+
+
+public(friend) fun initialize_supply_config(aptos_framework: &signer)
+
+
+
+Can only be initialized once.
+Can only be published by reserved addresses.
+
+
+let aptos_addr = signer::address_of(aptos_framework);
+aborts_if !system_addresses::is_aptos_framework_address(aptos_addr);
+aborts_if exists<SupplyConfig>(aptos_addr);
+ensures !global<SupplyConfig>(aptos_addr).allow_upgrades;
+ensures exists<SupplyConfig>(aptos_addr);
+
+
+
+
+
+
+### Function `allow_supply_upgrades`
+
+
+public fun allow_supply_upgrades(aptos_framework: &signer, allowed: bool)
+
+
+
+Can only be updated by @aptos_framework
.
+
+
+modifies global<SupplyConfig>(@aptos_framework);
+let aptos_addr = signer::address_of(aptos_framework);
+aborts_if !system_addresses::is_aptos_framework_address(aptos_addr);
+aborts_if !exists<SupplyConfig>(aptos_addr);
+let post allow_upgrades_post = global<SupplyConfig>(@aptos_framework);
+ensures allow_upgrades_post.allow_upgrades == allowed;
+
+
+
+
+
+
+### Function `initialize_aggregatable_coin`
+
+
+public(friend) fun initialize_aggregatable_coin<CoinType>(aptos_framework: &signer): coin::AggregatableCoin<CoinType>
+
+
+
+
+
+include system_addresses::AbortsIfNotAptosFramework { account: aptos_framework };
+include aggregator_factory::CreateAggregatorInternalAbortsIf;
+
+
+
+
+
+
+### Function `is_aggregatable_coin_zero`
+
+
+public(friend) fun is_aggregatable_coin_zero<CoinType>(coin: &coin::AggregatableCoin<CoinType>): bool
+
+
+
+
+
+aborts_if false;
+ensures result == (aggregator::spec_read(coin.value) == 0);
+
+
+
+
+
+
+### Function `drain_aggregatable_coin`
+
+
+public(friend) fun drain_aggregatable_coin<CoinType>(coin: &mut coin::AggregatableCoin<CoinType>): coin::Coin<CoinType>
+
+
+
+
+
+aborts_if aggregator::spec_read(coin.value) > MAX_U64;
+ensures result.value == aggregator::spec_aggregator_get_val(old(coin).value);
+
+
+
+
+
+
+### Function `merge_aggregatable_coin`
+
+
+public(friend) fun merge_aggregatable_coin<CoinType>(dst_coin: &mut coin::AggregatableCoin<CoinType>, coin: coin::Coin<CoinType>)
+
+
+
+
+
+let aggr = dst_coin.value;
+let post p_aggr = dst_coin.value;
+aborts_if aggregator::spec_aggregator_get_val(aggr)
+ + coin.value > aggregator::spec_get_limit(aggr);
+aborts_if aggregator::spec_aggregator_get_val(aggr)
+ + coin.value > MAX_U128;
+ensures aggregator::spec_aggregator_get_val(aggr) + coin.value == aggregator::spec_aggregator_get_val(p_aggr);
+
+
+
+
+
+
+### Function `collect_into_aggregatable_coin`
+
+
+public(friend) fun collect_into_aggregatable_coin<CoinType>(account_addr: address, amount: u64, dst_coin: &mut coin::AggregatableCoin<CoinType>)
+
+
+
+
+
+pragma verify = false;
+let aggr = dst_coin.value;
+let post p_aggr = dst_coin.value;
+let coin_store = global<CoinStore<CoinType>>(account_addr);
+let post p_coin_store = global<CoinStore<CoinType>>(account_addr);
+aborts_if amount > 0 && !exists<CoinStore<CoinType>>(account_addr);
+aborts_if amount > 0 && coin_store.coin.value < amount;
+aborts_if amount > 0 && aggregator::spec_aggregator_get_val(aggr)
+ + amount > aggregator::spec_get_limit(aggr);
+aborts_if amount > 0 && aggregator::spec_aggregator_get_val(aggr)
+ + amount > MAX_U128;
+ensures aggregator::spec_aggregator_get_val(aggr) + amount == aggregator::spec_aggregator_get_val(p_aggr);
+ensures coin_store.coin.value - amount == p_coin_store.coin.value;
+
+
+
+
+
+
+### Function `maybe_convert_to_fungible_store`
+
+
+fun maybe_convert_to_fungible_store<CoinType>(account: address)
+
+
+
+
+
+pragma verify = false;
+modifies global<CoinInfo<CoinType>>(account);
+modifies global<CoinStore<CoinType>>(account);
+
+
+
+
+
+
+
+
+schema DepositAbortsIf<CoinType> {
+ account_addr: address;
+ let coin_store = global<CoinStore<CoinType>>(account_addr);
+ aborts_if !exists<CoinStore<CoinType>>(account_addr);
+ aborts_if coin_store.frozen;
+}
+
+
+
+
+
+
+### Function `coin_address`
+
+
+fun coin_address<CoinType>(): address
+
+
+
+Get address by reflection.
+
+
+pragma opaque;
+aborts_if [abstract] false;
+ensures [abstract] result == type_info::type_of<CoinType>().account_address;
+
+
+
+
+
+
+### Function `balance`
+
+
+#[view]
+public fun balance<CoinType>(owner: address): u64
+
+
+
+
+
+pragma verify = false;
+aborts_if !exists<CoinStore<CoinType>>(owner);
+ensures result == global<CoinStore<CoinType>>(owner).coin.value;
+
+
+
+
+
+
+### Function `is_coin_initialized`
+
+
+#[view]
+public fun is_coin_initialized<CoinType>(): bool
+
+
+
+
+
+// This enforces high-level requirement 7:
+aborts_if false;
+
+
+
+
+
+
+### Function `is_account_registered`
+
+
+#[view]
+public fun is_account_registered<CoinType>(account_addr: address): bool
+
+
+
+
+
+pragma aborts_if_is_partial;
+aborts_if false;
+
+
+
+
+
+
+
+
+fun get_coin_supply_opt<CoinType>(): Option<OptionalAggregator> {
+ global<CoinInfo<CoinType>>(type_info::type_of<CoinType>().account_address).supply
+}
+
+
+
+
+
+
+
+
+fun spec_paired_metadata<CoinType>(): Option<Object<Metadata>> {
+ if (exists<CoinConversionMap>(@aptos_framework)) {
+ let map = global<CoinConversionMap>(@aptos_framework).coin_to_fungible_asset_map;
+ if (table::spec_contains(map, type_info::type_of<CoinType>())) {
+ let metadata = table::spec_get(map, type_info::type_of<CoinType>());
+ option::spec_some(metadata)
+ } else {
+ option::spec_none()
+ }
+ } else {
+ option::spec_none()
+ }
+}
+
+
+
+
+
+
+### Function `name`
+
+
+#[view]
+public fun name<CoinType>(): string::String
+
+
+
+
+
+// This enforces high-level requirement 7:
+include AbortsIfNotExistCoinInfo<CoinType>;
+
+
+
+
+
+
+### Function `symbol`
+
+
+#[view]
+public fun symbol<CoinType>(): string::String
+
+
+
+
+
+// This enforces high-level requirement 7:
+include AbortsIfNotExistCoinInfo<CoinType>;
+
+
+
+
+
+
+### Function `decimals`
+
+
+#[view]
+public fun decimals<CoinType>(): u8
+
+
+
+
+
+include AbortsIfNotExistCoinInfo<CoinType>;
+
+
+
+
+
+
+### Function `supply`
+
+
+#[view]
+public fun supply<CoinType>(): option::Option<u128>
+
+
+
+
+
+pragma verify = false;
+
+
+
+
+
+
+### Function `coin_supply`
+
+
+#[view]
+public fun coin_supply<CoinType>(): option::Option<u128>
+
+
+
+
+
+let coin_addr = type_info::type_of<CoinType>().account_address;
+// This enforces high-level requirement 7:
+aborts_if !exists<CoinInfo<CoinType>>(coin_addr);
+let maybe_supply = global<CoinInfo<CoinType>>(coin_addr).supply;
+let supply = option::spec_borrow(maybe_supply);
+let value = optional_aggregator::optional_aggregator_value(supply);
+ensures if (option::spec_is_some(maybe_supply)) {
+ result == option::spec_some(value)
+} else {
+ option::spec_is_none(result)
+};
+
+
+
+
+
+
+### Function `burn`
+
+
+public fun burn<CoinType>(coin: coin::Coin<CoinType>, _cap: &coin::BurnCapability<CoinType>)
+
+
+
+
+
+pragma verify = false;
+let addr = type_info::type_of<CoinType>().account_address;
+modifies global<CoinInfo<CoinType>>(addr);
+include AbortsIfNotExistCoinInfo<CoinType>;
+aborts_if coin.value == 0;
+include CoinSubAbortsIf<CoinType> { amount: coin.value };
+ensures supply<CoinType> == old(supply<CoinType>) - coin.value;
+
+
+
+
+
+
+### Function `burn_from`
+
+
+public fun burn_from<CoinType>(account_addr: address, amount: u64, burn_cap: &coin::BurnCapability<CoinType>)
+
+
+
+
+
+pragma verify = false;
+let addr = type_info::type_of<CoinType>().account_address;
+let coin_store = global<CoinStore<CoinType>>(account_addr);
+let post post_coin_store = global<CoinStore<CoinType>>(account_addr);
+modifies global<CoinInfo<CoinType>>(addr);
+modifies global<CoinStore<CoinType>>(account_addr);
+// This enforces high-level requirement 6:
+aborts_if amount != 0 && !exists<CoinInfo<CoinType>>(addr);
+aborts_if amount != 0 && !exists<CoinStore<CoinType>>(account_addr);
+aborts_if coin_store.coin.value < amount;
+let maybe_supply = global<CoinInfo<CoinType>>(addr).supply;
+let supply_aggr = option::spec_borrow(maybe_supply);
+let value = optional_aggregator::optional_aggregator_value(supply_aggr);
+let post post_maybe_supply = global<CoinInfo<CoinType>>(addr).supply;
+let post post_supply = option::spec_borrow(post_maybe_supply);
+let post post_value = optional_aggregator::optional_aggregator_value(post_supply);
+aborts_if option::spec_is_some(maybe_supply) && value < amount;
+ensures post_coin_store.coin.value == coin_store.coin.value - amount;
+// This enforces high-level requirement 5 of the managed_coin module:
+ensures if (option::spec_is_some(maybe_supply)) {
+ post_value == value - amount
+} else {
+ option::spec_is_none(post_maybe_supply)
+};
+ensures supply<CoinType> == old(supply<CoinType>) - amount;
+
+
+
+
+
+
+### Function `deposit`
+
+
+public fun deposit<CoinType>(account_addr: address, coin: coin::Coin<CoinType>)
+
+
+
+account_addr
is not frozen.
+
+
+pragma verify = false;
+modifies global<CoinInfo<CoinType>>(account_addr);
+// This enforces high-level requirement 8:
+include DepositAbortsIf<CoinType>;
+ensures global<CoinStore<CoinType>>(account_addr).coin.value == old(
+ global<CoinStore<CoinType>>(account_addr)
+).coin.value + coin.value;
+
+
+
+
+
+
+### Function `force_deposit`
+
+
+public(friend) fun force_deposit<CoinType>(account_addr: address, coin: coin::Coin<CoinType>)
+
+
+
+
+
+pragma verify = false;
+modifies global<CoinStore<CoinType>>(account_addr);
+aborts_if !exists<CoinStore<CoinType>>(account_addr);
+ensures global<CoinStore<CoinType>>(account_addr).coin.value == old(
+ global<CoinStore<CoinType>>(account_addr)
+).coin.value + coin.value;
+
+
+
+
+
+
+### Function `destroy_zero`
+
+
+public fun destroy_zero<CoinType>(zero_coin: coin::Coin<CoinType>)
+
+
+
+The value of zero_coin
must be 0.
+
+
+aborts_if zero_coin.value > 0;
+
+
+
+
+
+
+### Function `extract`
+
+
+public fun extract<CoinType>(coin: &mut coin::Coin<CoinType>, amount: u64): coin::Coin<CoinType>
+
+
+
+
+
+aborts_if coin.value < amount;
+ensures result.value == amount;
+ensures coin.value == old(coin.value) - amount;
+
+
+
+
+
+
+### Function `extract_all`
+
+
+public fun extract_all<CoinType>(coin: &mut coin::Coin<CoinType>): coin::Coin<CoinType>
+
+
+
+
+
+ensures result.value == old(coin).value;
+ensures coin.value == 0;
+
+
+
+
+
+
+### Function `freeze_coin_store`
+
+
+#[legacy_entry_fun]
+public entry fun freeze_coin_store<CoinType>(account_addr: address, _freeze_cap: &coin::FreezeCapability<CoinType>)
+
+
+
+
+
+pragma verify = false;
+modifies global<CoinStore<CoinType>>(account_addr);
+// This enforces high-level requirement 6:
+aborts_if !exists<CoinStore<CoinType>>(account_addr);
+let post coin_store = global<CoinStore<CoinType>>(account_addr);
+ensures coin_store.frozen;
+
+
+
+
+
+
+### Function `unfreeze_coin_store`
+
+
+#[legacy_entry_fun]
+public entry fun unfreeze_coin_store<CoinType>(account_addr: address, _freeze_cap: &coin::FreezeCapability<CoinType>)
+
+
+
+
+
+pragma verify = false;
+modifies global<CoinStore<CoinType>>(account_addr);
+// This enforces high-level requirement 6:
+aborts_if !exists<CoinStore<CoinType>>(account_addr);
+let post coin_store = global<CoinStore<CoinType>>(account_addr);
+ensures !coin_store.frozen;
+
+
+
+
+
+
+### Function `upgrade_supply`
+
+
+public entry fun upgrade_supply<CoinType>(account: &signer)
+
+
+
+The creator of CoinType
must be @aptos_framework
.
+SupplyConfig
allow upgrade.
+
+
+let account_addr = signer::address_of(account);
+let coin_address = type_info::type_of<CoinType>().account_address;
+aborts_if coin_address != account_addr;
+aborts_if !exists<SupplyConfig>(@aptos_framework);
+// This enforces high-level requirement 1:
+aborts_if !exists<CoinInfo<CoinType>>(account_addr);
+let supply_config = global<SupplyConfig>(@aptos_framework);
+aborts_if !supply_config.allow_upgrades;
+modifies global<CoinInfo<CoinType>>(account_addr);
+let maybe_supply = global<CoinInfo<CoinType>>(account_addr).supply;
+let supply = option::spec_borrow(maybe_supply);
+let value = optional_aggregator::optional_aggregator_value(supply);
+let post post_maybe_supply = global<CoinInfo<CoinType>>(account_addr).supply;
+let post post_supply = option::spec_borrow(post_maybe_supply);
+let post post_value = optional_aggregator::optional_aggregator_value(post_supply);
+let supply_no_parallel = option::spec_is_some(maybe_supply) &&
+ !optional_aggregator::is_parallelizable(supply);
+aborts_if supply_no_parallel && !exists<aggregator_factory::AggregatorFactory>(@aptos_framework);
+ensures supply_no_parallel ==>
+ optional_aggregator::is_parallelizable(post_supply) && post_value == value;
+
+
+
+
+
+
+### Function `initialize`
+
+
+public fun initialize<CoinType>(account: &signer, name: string::String, symbol: string::String, decimals: u8, monitor_supply: bool): (coin::BurnCapability<CoinType>, coin::FreezeCapability<CoinType>, coin::MintCapability<CoinType>)
+
+
+
+
+
+let account_addr = signer::address_of(account);
+// This enforces high-level requirement 1:
+aborts_if type_info::type_of<CoinType>().account_address != account_addr;
+// This enforces high-level requirement 2:
+aborts_if exists<CoinInfo<CoinType>>(account_addr);
+aborts_if string::length(name) > MAX_COIN_NAME_LENGTH;
+aborts_if string::length(symbol) > MAX_COIN_SYMBOL_LENGTH;
+
+
+
+
+
+
+### Function `initialize_with_parallelizable_supply`
+
+
+public(friend) fun initialize_with_parallelizable_supply<CoinType>(account: &signer, name: string::String, symbol: string::String, decimals: u8, monitor_supply: bool): (coin::BurnCapability<CoinType>, coin::FreezeCapability<CoinType>, coin::MintCapability<CoinType>)
+
+
+
+
+
+let addr = signer::address_of(account);
+aborts_if addr != @aptos_framework;
+aborts_if monitor_supply && !exists<aggregator_factory::AggregatorFactory>(@aptos_framework);
+include InitializeInternalSchema<CoinType> {
+ name: name.bytes,
+ symbol: symbol.bytes
+};
+ensures exists<CoinInfo<CoinType>>(addr);
+
+
+
+
+
+
+### Function `initialize_internal`
+
+
+fun initialize_internal<CoinType>(account: &signer, name: string::String, symbol: string::String, decimals: u8, monitor_supply: bool, parallelizable: bool): (coin::BurnCapability<CoinType>, coin::FreezeCapability<CoinType>, coin::MintCapability<CoinType>)
+
+
+
+
+
+include InitializeInternalSchema<CoinType> {
+ name: name.bytes,
+ symbol: symbol.bytes
+};
+let account_addr = signer::address_of(account);
+let post coin_info = global<CoinInfo<CoinType>>(account_addr);
+let post supply = option::spec_borrow(coin_info.supply);
+let post value = optional_aggregator::optional_aggregator_value(supply);
+let post limit = optional_aggregator::optional_aggregator_limit(supply);
+modifies global<CoinInfo<CoinType>>(account_addr);
+aborts_if monitor_supply && parallelizable
+ && !exists<aggregator_factory::AggregatorFactory>(@aptos_framework);
+// This enforces high-level requirement 2 of the managed_coin module:
+ensures exists<CoinInfo<CoinType>>(account_addr)
+ && coin_info.name == name
+ && coin_info.symbol == symbol
+ && coin_info.decimals == decimals;
+ensures if (monitor_supply) {
+ value == 0 && limit == MAX_U128
+ && (parallelizable == optional_aggregator::is_parallelizable(supply))
+} else {
+ option::spec_is_none(coin_info.supply)
+};
+ensures result_1 == BurnCapability<CoinType> {};
+ensures result_2 == FreezeCapability<CoinType> {};
+ensures result_3 == MintCapability<CoinType> {};
+
+
+
+
+
+
+### Function `merge`
+
+
+public fun merge<CoinType>(dst_coin: &mut coin::Coin<CoinType>, source_coin: coin::Coin<CoinType>)
+
+
+
+
+
+// This enforces high-level requirement 3:
+ensures dst_coin.value == old(dst_coin.value) + source_coin.value;
+
+
+
+
+
+
+### Function `mint`
+
+
+public fun mint<CoinType>(amount: u64, _cap: &coin::MintCapability<CoinType>): coin::Coin<CoinType>
+
+
+
+
+
+let addr = type_info::type_of<CoinType>().account_address;
+modifies global<CoinInfo<CoinType>>(addr);
+
+
+
+
+
+
+### Function `register`
+
+
+public fun register<CoinType>(account: &signer)
+
+
+
+An account can only be registered once.
+Updating Account.guid_creation_num
will not overflow.
+
+
+pragma verify = false;
+
+
+
+
+
+
+### Function `transfer`
+
+
+public entry fun transfer<CoinType>(from: &signer, to: address, amount: u64)
+
+
+
+from
and to
account not frozen.
+from
and to
not the same address.
+from
account sufficient balance.
+
+
+pragma verify = false;
+let account_addr_from = signer::address_of(from);
+let coin_store_from = global<CoinStore<CoinType>>(account_addr_from);
+let post coin_store_post_from = global<CoinStore<CoinType>>(account_addr_from);
+let coin_store_to = global<CoinStore<CoinType>>(to);
+let post coin_store_post_to = global<CoinStore<CoinType>>(to);
+// This enforces high-level requirement 6:
+aborts_if !exists<CoinStore<CoinType>>(account_addr_from);
+aborts_if !exists<CoinStore<CoinType>>(to);
+// This enforces high-level requirement 8:
+aborts_if coin_store_from.frozen;
+aborts_if coin_store_to.frozen;
+aborts_if coin_store_from.coin.value < amount;
+ensures account_addr_from != to ==> coin_store_post_from.coin.value ==
+ coin_store_from.coin.value - amount;
+ensures account_addr_from != to ==> coin_store_post_to.coin.value == coin_store_to.coin.value + amount;
+ensures account_addr_from == to ==> coin_store_post_from.coin.value == coin_store_from.coin.value;
+
+
+
+
+
+
+### Function `withdraw`
+
+
+public fun withdraw<CoinType>(account: &signer, amount: u64): coin::Coin<CoinType>
+
+
+
+Account is not frozen and sufficient balance.
+
+
+pragma verify = false;
+include WithdrawAbortsIf<CoinType>;
+modifies global<CoinStore<CoinType>>(account_addr);
+let account_addr = signer::address_of(account);
+let coin_store = global<CoinStore<CoinType>>(account_addr);
+let balance = coin_store.coin.value;
+let post coin_post = global<CoinStore<CoinType>>(account_addr).coin.value;
+ensures coin_post == balance - amount;
+ensures result == Coin<CoinType> { value: amount };
+
+
+
+
+
+
+
+
+schema WithdrawAbortsIf<CoinType> {
+ account: &signer;
+ amount: u64;
+ let account_addr = signer::address_of(account);
+ let coin_store = global<CoinStore<CoinType>>(account_addr);
+ let balance = coin_store.coin.value;
+ // This enforces high-level requirement 6:
+ aborts_if !exists<CoinStore<CoinType>>(account_addr);
+ // This enforces high-level requirement 8:
+ aborts_if coin_store.frozen;
+ aborts_if balance < amount;
+}
+
+
+
+
+
+
+### Function `mint_internal`
+
+
+fun mint_internal<CoinType>(amount: u64): coin::Coin<CoinType>
+
+
+
+
+
+let addr = type_info::type_of<CoinType>().account_address;
+modifies global<CoinInfo<CoinType>>(addr);
+aborts_if (amount != 0) && !exists<CoinInfo<CoinType>>(addr);
+ensures supply<CoinType> == old(supply<CoinType>) + amount;
+ensures result.value == amount;
+
+
+
+
+
+
+### Function `burn_internal`
+
+
+fun burn_internal<CoinType>(coin: coin::Coin<CoinType>): u64
+
+
+
+
+
+pragma verify = false;
+let addr = type_info::type_of<CoinType>().account_address;
+modifies global<CoinInfo<CoinType>>(addr);
+
+
+
+[move-book]: https://aptos.dev/move/book/SUMMARY
diff --git a/aptos-move/framework/aptos-framework/tests/compiler-v2-doc/config_buffer.md b/aptos-move/framework/aptos-framework/tests/compiler-v2-doc/config_buffer.md
new file mode 100644
index 0000000000000..714bc07f1e101
--- /dev/null
+++ b/aptos-move/framework/aptos-framework/tests/compiler-v2-doc/config_buffer.md
@@ -0,0 +1,342 @@
+
+
+
+# Module `0x1::config_buffer`
+
+This wrapper helps store an on-chain config for the next epoch.
+
+Once reconfigure with DKG is introduced, every on-chain config C
should do the following.
+- Support async update when DKG is enabled. This is typically done by 3 steps below.
+- Implement C::set_for_next_epoch()
using upsert()
function in this module.
+- Implement C::on_new_epoch()
using extract()
function in this module.
+- Update 0x1::reconfiguration_with_dkg::finish()
to call C::on_new_epoch()
.
+- Support sychronous update when DKG is disabled.
+This is typically done by implementing C::set()
to update the config resource directly.
+
+NOTE: on-chain config 0x1::state::ValidatorSet
implemented its own buffer.
+
+
+- [Resource `PendingConfigs`](#0x1_config_buffer_PendingConfigs)
+- [Constants](#@Constants_0)
+- [Function `initialize`](#0x1_config_buffer_initialize)
+- [Function `does_exist`](#0x1_config_buffer_does_exist)
+- [Function `upsert`](#0x1_config_buffer_upsert)
+- [Function `extract`](#0x1_config_buffer_extract)
+- [Specification](#@Specification_1)
+ - [Function `does_exist`](#@Specification_1_does_exist)
+ - [Function `upsert`](#@Specification_1_upsert)
+ - [Function `extract`](#@Specification_1_extract)
+
+
+use 0x1::any;
+use 0x1::option;
+use 0x1::simple_map;
+use 0x1::string;
+use 0x1::system_addresses;
+use 0x1::type_info;
+
+
+
+
+
+
+## Resource `PendingConfigs`
+
+
+
+struct PendingConfigs has key
+
+
+
+
+configs: simple_map::SimpleMap<string::String, any::Any>
+const ESTD_SIGNER_NEEDED: u64 = 1;
+
+
+
+
+
+
+## Function `initialize`
+
+
+
+public fun initialize(aptos_framework: &signer)
+
+
+
+
+public fun initialize(aptos_framework: &signer) {
+ system_addresses::assert_aptos_framework(aptos_framework);
+ if (!exists<PendingConfigs>(@aptos_framework)) {
+ move_to(aptos_framework, PendingConfigs {
+ configs: simple_map::new(),
+ })
+ }
+}
+
+
+
+
+T
.
+
+
+public fun does_exist<T: store>(): bool
+
+
+
+
+public fun does_exist<T: store>(): bool acquires PendingConfigs {
+ if (exists<PendingConfigs>(@aptos_framework)) {
+ let config = borrow_global<PendingConfigs>(@aptos_framework);
+ simple_map::contains_key(&config.configs, &type_info::type_name<T>())
+ } else {
+ false
+ }
+}
+
+
+
+
+X::set_for_next_epoch()
where X is an on-chain config.
+
+
+public(friend) fun upsert<T: drop, store>(config: T)
+
+
+
+
+public(friend) fun upsert<T: drop + store>(config: T) acquires PendingConfigs {
+ let configs = borrow_global_mut<PendingConfigs>(@aptos_framework);
+ let key = type_info::type_name<T>();
+ let value = any::pack(config);
+ simple_map::upsert(&mut configs.configs, key, value);
+}
+
+
+
+
+T
out (buffer cleared). Abort if the buffer is empty.
+Should only be used at the end of a reconfiguration.
+
+Typically used in X::on_new_epoch()
where X is an on-chaon config.
+
+
+public fun extract<T: store>(): T
+
+
+
+
+public fun extract<T: store>(): T acquires PendingConfigs {
+ let configs = borrow_global_mut<PendingConfigs>(@aptos_framework);
+ let key = type_info::type_name<T>();
+ let (_, value_packed) = simple_map::remove(&mut configs.configs, &key);
+ any::unpack(value_packed)
+}
+
+
+
+
+pragma verify = true;
+
+
+
+
+
+
+### Function `does_exist`
+
+
+public fun does_exist<T: store>(): bool
+
+
+
+
+
+aborts_if false;
+let type_name = type_info::type_name<T>();
+ensures result == spec_fun_does_exist<T>(type_name);
+
+
+
+
+
+
+
+
+fun spec_fun_does_exist<T: store>(type_name: String): bool {
+ if (exists<PendingConfigs>(@aptos_framework)) {
+ let config = global<PendingConfigs>(@aptos_framework);
+ simple_map::spec_contains_key(config.configs, type_name)
+ } else {
+ false
+ }
+}
+
+
+
+
+
+
+### Function `upsert`
+
+
+public(friend) fun upsert<T: drop, store>(config: T)
+
+
+
+
+
+aborts_if !exists<PendingConfigs>(@aptos_framework);
+
+
+
+
+
+
+### Function `extract`
+
+
+public fun extract<T: store>(): T
+
+
+
+
+
+aborts_if !exists<PendingConfigs>(@aptos_framework);
+include ExtractAbortsIf<T>;
+
+
+
+
+
+
+
+
+schema ExtractAbortsIf<T> {
+ let configs = global<PendingConfigs>(@aptos_framework);
+ let key = type_info::type_name<T>();
+ aborts_if !simple_map::spec_contains_key(configs.configs, key);
+ include any::UnpackAbortsIf<T> {
+ x: simple_map::spec_get(configs.configs, key)
+ };
+}
+
+
+
+
+
+
+
+
+schema SetForNextEpochAbortsIf {
+ account: &signer;
+ config: vector<u8>;
+ let account_addr = std::signer::address_of(account);
+ aborts_if account_addr != @aptos_framework;
+ aborts_if len(config) == 0;
+ aborts_if !exists<PendingConfigs>(@aptos_framework);
+}
+
+
+
+
+
+
+
+
+schema OnNewEpochAbortsIf<T> {
+ let type_name = type_info::type_name<T>();
+ let configs = global<PendingConfigs>(@aptos_framework);
+ include spec_fun_does_exist<T>(type_name) ==> any::UnpackAbortsIf<T> {
+ x: simple_map::spec_get(configs.configs, type_name)
+ };
+}
+
+
+
+
+
+
+
+
+schema OnNewEpochRequirement<T> {
+ let type_name = type_info::type_name<T>();
+ let configs = global<PendingConfigs>(@aptos_framework);
+ include spec_fun_does_exist<T>(type_name) ==> any::UnpackRequirement<T> {
+ x: simple_map::spec_get(configs.configs, type_name)
+ };
+}
+
+
+
+[move-book]: https://aptos.dev/move/book/SUMMARY
diff --git a/aptos-move/framework/aptos-framework/tests/compiler-v2-doc/consensus_config.md b/aptos-move/framework/aptos-framework/tests/compiler-v2-doc/consensus_config.md
new file mode 100644
index 0000000000000..d8f4179edd006
--- /dev/null
+++ b/aptos-move/framework/aptos-framework/tests/compiler-v2-doc/consensus_config.md
@@ -0,0 +1,445 @@
+
+
+
+# Module `0x1::consensus_config`
+
+Maintains the consensus config for the blockchain. The config is stored in a
+Reconfiguration, and may be updated by root.
+
+
+- [Resource `ConsensusConfig`](#0x1_consensus_config_ConsensusConfig)
+- [Constants](#@Constants_0)
+- [Function `initialize`](#0x1_consensus_config_initialize)
+- [Function `set`](#0x1_consensus_config_set)
+- [Function `set_for_next_epoch`](#0x1_consensus_config_set_for_next_epoch)
+- [Function `on_new_epoch`](#0x1_consensus_config_on_new_epoch)
+- [Function `validator_txn_enabled`](#0x1_consensus_config_validator_txn_enabled)
+- [Function `validator_txn_enabled_internal`](#0x1_consensus_config_validator_txn_enabled_internal)
+- [Specification](#@Specification_1)
+ - [High-level Requirements](#high-level-req)
+ - [Module-level Specification](#module-level-spec)
+ - [Function `initialize`](#@Specification_1_initialize)
+ - [Function `set`](#@Specification_1_set)
+ - [Function `set_for_next_epoch`](#@Specification_1_set_for_next_epoch)
+ - [Function `on_new_epoch`](#@Specification_1_on_new_epoch)
+ - [Function `validator_txn_enabled`](#@Specification_1_validator_txn_enabled)
+ - [Function `validator_txn_enabled_internal`](#@Specification_1_validator_txn_enabled_internal)
+
+
+use 0x1::chain_status;
+use 0x1::config_buffer;
+use 0x1::error;
+use 0x1::reconfiguration;
+use 0x1::system_addresses;
+
+
+
+
+
+
+## Resource `ConsensusConfig`
+
+
+
+struct ConsensusConfig has drop, store, key
+
+
+
+
+config: vector<u8>
+const EINVALID_CONFIG: u64 = 1;
+
+
+
+
+
+
+## Function `initialize`
+
+Publishes the ConsensusConfig config.
+
+
+public(friend) fun initialize(aptos_framework: &signer, config: vector<u8>)
+
+
+
+
+public(friend) fun initialize(aptos_framework: &signer, config: vector<u8>) {
+ system_addresses::assert_aptos_framework(aptos_framework);
+ assert!(vector::length(&config) > 0, error::invalid_argument(EINVALID_CONFIG));
+ move_to(aptos_framework, ConsensusConfig { config });
+}
+
+
+
+
+set_for_next_epoch()
.
+
+WARNING: calling this while randomness is enabled will trigger a new epoch without randomness!
+
+TODO: update all the tests that reference this function, then disable this function.
+
+
+public fun set(account: &signer, config: vector<u8>)
+
+
+
+
+public fun set(account: &signer, config: vector<u8>) acquires ConsensusConfig {
+ system_addresses::assert_aptos_framework(account);
+ chain_status::assert_genesis();
+ assert!(vector::length(&config) > 0, error::invalid_argument(EINVALID_CONFIG));
+
+ let config_ref = &mut borrow_global_mut<ConsensusConfig>(@aptos_framework).config;
+ *config_ref = config;
+
+ // Need to trigger reconfiguration so validator nodes can sync on the updated configs.
+ reconfiguration::reconfigure();
+}
+
+
+
+
+public fun set_for_next_epoch(account: &signer, config: vector<u8>)
+
+
+
+
+public fun set_for_next_epoch(account: &signer, config: vector<u8>) {
+ system_addresses::assert_aptos_framework(account);
+ assert!(vector::length(&config) > 0, error::invalid_argument(EINVALID_CONFIG));
+ std::config_buffer::upsert<ConsensusConfig>(ConsensusConfig {config});
+}
+
+
+
+
+ConsensusConfig
, if there is any.
+
+
+public(friend) fun on_new_epoch(framework: &signer)
+
+
+
+
+public(friend) fun on_new_epoch(framework: &signer) acquires ConsensusConfig {
+ system_addresses::assert_aptos_framework(framework);
+ if (config_buffer::does_exist<ConsensusConfig>()) {
+ let new_config = config_buffer::extract<ConsensusConfig>();
+ if (exists<ConsensusConfig>(@aptos_framework)) {
+ *borrow_global_mut<ConsensusConfig>(@aptos_framework) = new_config;
+ } else {
+ move_to(framework, new_config);
+ };
+ }
+}
+
+
+
+
+public fun validator_txn_enabled(): bool
+
+
+
+
+public fun validator_txn_enabled(): bool acquires ConsensusConfig {
+ let config_bytes = borrow_global<ConsensusConfig>(@aptos_framework).config;
+ validator_txn_enabled_internal(config_bytes)
+}
+
+
+
+
+fun validator_txn_enabled_internal(config_bytes: vector<u8>): bool
+
+
+
+
+native fun validator_txn_enabled_internal(config_bytes: vector<u8>): bool;
+
+
+
+
+No. | Requirement | Criticality | Implementation | Enforcement | +
---|---|---|---|---|
1 | +During genesis, the Aptos framework account should be assigned the consensus config resource. | +Medium | +The consensus_config::initialize function calls the assert_aptos_framework function to ensure that the signer is the aptos_framework and then assigns the ConsensusConfig resource to it. | +Formally verified via initialize. | +
2 | +Only aptos framework account is allowed to update the consensus configuration. | +Medium | +The consensus_config::set function ensures that the signer is aptos_framework. | +Formally verified via set. | +
3 | +Only a valid configuration can be used during initialization and update. | +Medium | +Both the initialize and set functions validate the config by ensuring its length to be greater than 0. | +Formally verified via initialize and set. | +
pragma verify = true;
+pragma aborts_if_is_strict;
+invariant [suspendable] chain_status::is_operating() ==> exists<ConsensusConfig>(@aptos_framework);
+
+
+
+
+
+
+### Function `initialize`
+
+
+public(friend) fun initialize(aptos_framework: &signer, config: vector<u8>)
+
+
+
+Ensure caller is admin.
+Aborts if StateStorageUsage already exists.
+
+
+let addr = signer::address_of(aptos_framework);
+// This enforces high-level requirement 1:
+aborts_if !system_addresses::is_aptos_framework_address(addr);
+aborts_if exists<ConsensusConfig>(@aptos_framework);
+// This enforces high-level requirement 3:
+aborts_if !(len(config) > 0);
+ensures global<ConsensusConfig>(addr) == ConsensusConfig { config };
+
+
+
+
+
+
+### Function `set`
+
+
+public fun set(account: &signer, config: vector<u8>)
+
+
+
+Ensure the caller is admin and ConsensusConfig
should be existed.
+When setting now time must be later than last_reconfiguration_time.
+
+
+pragma verify_duration_estimate = 600;
+include transaction_fee::RequiresCollectedFeesPerValueLeqBlockAptosSupply;
+include staking_config::StakingRewardsConfigRequirement;
+let addr = signer::address_of(account);
+// This enforces high-level requirement 2:
+aborts_if !system_addresses::is_aptos_framework_address(addr);
+aborts_if !exists<ConsensusConfig>(@aptos_framework);
+// This enforces high-level requirement 3:
+aborts_if !(len(config) > 0);
+requires chain_status::is_genesis();
+requires timestamp::spec_now_microseconds() >= reconfiguration::last_reconfiguration_time();
+requires exists<stake::ValidatorFees>(@aptos_framework);
+requires exists<CoinInfo<AptosCoin>>(@aptos_framework);
+ensures global<ConsensusConfig>(@aptos_framework).config == config;
+
+
+
+
+
+
+### Function `set_for_next_epoch`
+
+
+public fun set_for_next_epoch(account: &signer, config: vector<u8>)
+
+
+
+
+
+include config_buffer::SetForNextEpochAbortsIf;
+
+
+
+
+
+
+### Function `on_new_epoch`
+
+
+public(friend) fun on_new_epoch(framework: &signer)
+
+
+
+
+
+requires @aptos_framework == std::signer::address_of(framework);
+include config_buffer::OnNewEpochRequirement<ConsensusConfig>;
+aborts_if false;
+
+
+
+
+
+
+### Function `validator_txn_enabled`
+
+
+public fun validator_txn_enabled(): bool
+
+
+
+
+
+pragma opaque;
+aborts_if !exists<ConsensusConfig>(@aptos_framework);
+ensures [abstract] result == spec_validator_txn_enabled_internal(global<ConsensusConfig>(@aptos_framework).config);
+
+
+
+
+
+
+### Function `validator_txn_enabled_internal`
+
+
+fun validator_txn_enabled_internal(config_bytes: vector<u8>): bool
+
+
+
+
+
+pragma opaque;
+ensures [abstract] result == spec_validator_txn_enabled_internal(config_bytes);
+
+
+
+
+
+
+
+
+fun spec_validator_txn_enabled_internal(config_bytes: vector<u8>): bool;
+
+
+
+[move-book]: https://aptos.dev/move/book/SUMMARY
diff --git a/aptos-move/framework/aptos-framework/tests/compiler-v2-doc/create_signer.md b/aptos-move/framework/aptos-framework/tests/compiler-v2-doc/create_signer.md
new file mode 100644
index 0000000000000..4f1bb93d57071
--- /dev/null
+++ b/aptos-move/framework/aptos-framework/tests/compiler-v2-doc/create_signer.md
@@ -0,0 +1,133 @@
+
+
+
+# Module `0x1::create_signer`
+
+Provides a common place for exporting create_signer
across the Aptos Framework.
+
+To use create_signer, add the module below, such that:
+friend aptos_framework::friend_wants_create_signer
+where friend_wants_create_signer
is the module that needs create_signer
.
+
+Note, that this is only available within the Aptos Framework.
+
+This exists to make auditing straight forward and to limit the need to depend
+on account to have access to this.
+
+
+- [Function `create_signer`](#0x1_create_signer_create_signer)
+- [Specification](#@Specification_0)
+ - [High-level Requirements](#high-level-req)
+ - [Module-level Specification](#module-level-spec)
+ - [Function `create_signer`](#@Specification_0_create_signer)
+
+
+
+
+
+
+
+
+## Function `create_signer`
+
+
+
+public(friend) fun create_signer(addr: address): signer
+
+
+
+
+public(friend) native fun create_signer(addr: address): signer;
+
+
+
+
+No. | Requirement | Criticality | Implementation | Enforcement | +
---|---|---|---|---|
1 | +Obtaining a signer for an arbitrary account should only be available within the Aptos Framework. | +Critical | +The create_signer::create_signer function only allows friend modules to retrieve the signer for an arbitrarily address. | +Enforced through function visibility. | +
2 | +The account owner should have the ability to create a signer for their account. | +Medium | +Before an Account resource is created, a signer is created for the specified new_address, and later, the Account resource is assigned to this signer. | +Enforced by the move vm. | +
3 | +An account should only be able to create a signer for another account if that account has granted it signing capabilities. | +Critical | +The Account resource holds a signer_capability_offer field which allows the owner to share the signer capability with other accounts. | +Formally verified via AccountContainsAddr. | +
4 | +A signer should be returned for addresses that are not registered as accounts. | +Low | +The signer is just a struct that wraps an address, allows for non-accounts to have a signer. | +Formally verified via create_signer. | +
pragma verify = true;
+pragma aborts_if_is_strict;
+
+
+
+
+
+
+### Function `create_signer`
+
+
+public(friend) fun create_signer(addr: address): signer
+
+
+
+Convert address to singer and return.
+
+
+pragma opaque;
+aborts_if [abstract] false;
+ensures [abstract] signer::address_of(result) == addr;
+
+
+
+[move-book]: https://aptos.dev/move/book/SUMMARY
diff --git a/aptos-move/framework/aptos-framework/tests/compiler-v2-doc/delegation_pool.md b/aptos-move/framework/aptos-framework/tests/compiler-v2-doc/delegation_pool.md
new file mode 100644
index 0000000000000..01029999242fe
--- /dev/null
+++ b/aptos-move/framework/aptos-framework/tests/compiler-v2-doc/delegation_pool.md
@@ -0,0 +1,5479 @@
+
+
+
+# Module `0x1::delegation_pool`
+
+
+Allow multiple delegators to participate in the same stake pool in order to collect the minimum
+stake required to join the validator set. Delegators are rewarded out of the validator rewards
+proportionally to their stake and provided the same stake-management API as the stake pool owner.
+
+The main accounting logic in the delegation pool contract handles the following:
+1. Tracks how much stake each delegator owns, privately deposited as well as earned.
+Accounting individual delegator stakes is achieved through the shares-based pool defined at
+aptos_std::pool_u64
, hence delegators own shares rather than absolute stakes into the delegation pool.
+2. Tracks rewards earned by the stake pool, implicitly by the delegation one, in the meantime
+and distribute them accordingly.
+3. Tracks lockup cycles on the stake pool in order to separate inactive stake (not earning rewards)
+from pending_inactive stake (earning rewards) and allow its delegators to withdraw the former.
+4. Tracks how much commission fee has to be paid to the operator out of incoming rewards before
+distributing them to the internal pool_u64 pools.
+
+In order to distinguish between stakes in different states and route rewards accordingly,
+separate pool_u64 pools are used for individual stake states:
+1. one of active
+ pending_active
stake
+2. one of inactive
stake FOR each past observed lockup cycle (OLC) on the stake pool
+3. one of pending_inactive
stake scheduled during this ongoing OLC
+
+As stake-state transitions and rewards are computed only at the stake pool level, the delegation pool
+gets outdated. To mitigate this, at any interaction with the delegation pool, a process of synchronization
+to the underlying stake pool is executed before the requested operation itself.
+
+At synchronization:
+- stake deviations between the two pools are actually the rewards produced in the meantime.
+- the commission fee is extracted from the rewards, the remaining stake is distributed to the internal
+pool_u64 pools and then the commission stake used to buy shares for operator.
+- if detecting that the lockup expired on the stake pool, the delegation pool will isolate its
+pending_inactive stake (now inactive) and create a new pool_u64 to host future pending_inactive stake
+scheduled this newly started lockup.
+Detecting a lockup expiration on the stake pool resumes to detecting new inactive stake.
+
+Accounting main invariants:
+- each stake-management operation (add/unlock/reactivate/withdraw) and operator change triggers
+the synchronization process before executing its own function.
+- each OLC maps to one or more real lockups on the stake pool, but not the opposite. Actually, only a real
+lockup with 'activity' (which inactivated some unlocking stake) triggers the creation of a new OLC.
+- unlocking and/or unlocked stake originating from different real lockups are never mixed together into
+the same pool_u64. This invalidates the accounting of which rewards belong to whom.
+- no delegator can have unlocking and/or unlocked stake (pending withdrawals) in different OLCs. This ensures
+delegators do not have to keep track of the OLCs when they unlocked. When creating a new pending withdrawal,
+the existing one is executed (withdrawn) if is already inactive.
+- add_stake
fees are always refunded, but only after the epoch when they have been charged ends.
+- withdrawing pending_inactive stake (when validator had gone inactive before its lockup expired)
+does not inactivate any stake additional to the requested one to ensure OLC would not advance indefinitely.
+- the pending withdrawal exists at an OLC iff delegator owns some shares within the shares pool of that OLC.
+
+Example flow:
+initialize_delegation_pool
and sets
+its commission fee to 0% (for simplicity). A stake pool is created with no initial stake and owned by
+a resource account controlled by the delegation pool.unlock
for 100 stakereactivate_stake
for 100 stakeunlock
for 100 stakewithdraw
for its entire inactive stakeuse 0x1::account;
+use 0x1::aptos_account;
+use 0x1::aptos_coin;
+use 0x1::aptos_governance;
+use 0x1::coin;
+use 0x1::error;
+use 0x1::event;
+use 0x1::features;
+use 0x1::pool_u64_unbound;
+use 0x1::signer;
+use 0x1::smart_table;
+use 0x1::stake;
+use 0x1::staking_config;
+use 0x1::table;
+use 0x1::table_with_length;
+use 0x1::timestamp;
+use 0x1::vector;
+
+
+
+
+
+
+## Resource `DelegationPoolOwnership`
+
+Capability that represents ownership over privileged operations on the underlying stake pool.
+
+
+struct DelegationPoolOwnership has store, key
+
+
+
+
+pool_address: address
+struct ObservedLockupCycle has copy, drop, store
+
+
+
+
+index: u64
+struct DelegationPool has key
+
+
+
+
+active_shares: pool_u64_unbound::Pool
+observed_lockup_cycle: delegation_pool::ObservedLockupCycle
+inactive_shares: table::Table<delegation_pool::ObservedLockupCycle, pool_u64_unbound::Pool>
+pending_withdrawals: table::Table<address, delegation_pool::ObservedLockupCycle>
+stake_pool_signer_cap: account::SignerCapability
+total_coins_inactive: u64
+operator_commission_percentage: u64
+add_stake_events: event::EventHandle<delegation_pool::AddStakeEvent>
+reactivate_stake_events: event::EventHandle<delegation_pool::ReactivateStakeEvent>
+unlock_stake_events: event::EventHandle<delegation_pool::UnlockStakeEvent>
+withdraw_stake_events: event::EventHandle<delegation_pool::WithdrawStakeEvent>
+distribute_commission_events: event::EventHandle<delegation_pool::DistributeCommissionEvent>
+struct VotingRecordKey has copy, drop, store
+
+
+
+
+voter: address
+proposal_id: u64
+struct VoteDelegation has copy, drop, store
+
+
+
+
+voter: address
+pending_voter: address
+last_locked_until_secs: u64
+struct DelegatedVotes has copy, drop, store
+
+
+
+
+active_shares: u128
+pending_inactive_shares: u128
+active_shares_next_lockup: u128
+last_locked_until_secs: u64
+struct GovernanceRecords has key
+
+
+
+
+votes: smart_table::SmartTable<delegation_pool::VotingRecordKey, u64>
+votes_per_proposal: smart_table::SmartTable<u64, u64>
+vote_delegation: smart_table::SmartTable<address, delegation_pool::VoteDelegation>
+delegated_votes: smart_table::SmartTable<address, delegation_pool::DelegatedVotes>
+vote_events: event::EventHandle<delegation_pool::VoteEvent>
+create_proposal_events: event::EventHandle<delegation_pool::CreateProposalEvent>
+delegate_voting_power_events: event::EventHandle<delegation_pool::DelegateVotingPowerEvent>
+struct BeneficiaryForOperator has key
+
+
+
+
+beneficiary_for_operator: address
+struct NextCommissionPercentage has key
+
+
+
+
+commission_percentage_next_lockup_cycle: u64
+effective_after_secs: u64
+struct DelegationPoolAllowlisting has key
+
+
+
+
+allowlist: smart_table::SmartTable<address, bool>
+#[event]
+struct AddStake has drop, store
+
+
+
+
+pool_address: address
+delegator_address: address
+amount_added: u64
+add_stake_fee: u64
+struct AddStakeEvent has drop, store
+
+
+
+
+pool_address: address
+delegator_address: address
+amount_added: u64
+add_stake_fee: u64
+#[event]
+struct ReactivateStake has drop, store
+
+
+
+
+pool_address: address
+delegator_address: address
+amount_reactivated: u64
+struct ReactivateStakeEvent has drop, store
+
+
+
+
+pool_address: address
+delegator_address: address
+amount_reactivated: u64
+#[event]
+struct UnlockStake has drop, store
+
+
+
+
+pool_address: address
+delegator_address: address
+amount_unlocked: u64
+struct UnlockStakeEvent has drop, store
+
+
+
+
+pool_address: address
+delegator_address: address
+amount_unlocked: u64
+#[event]
+struct WithdrawStake has drop, store
+
+
+
+
+pool_address: address
+delegator_address: address
+amount_withdrawn: u64
+struct WithdrawStakeEvent has drop, store
+
+
+
+
+pool_address: address
+delegator_address: address
+amount_withdrawn: u64
+#[event]
+struct DistributeCommissionEvent has drop, store
+
+
+
+
+pool_address: address
+operator: address
+commission_active: u64
+commission_pending_inactive: u64
+#[event]
+struct DistributeCommission has drop, store
+
+
+
+
+pool_address: address
+operator: address
+beneficiary: address
+commission_active: u64
+commission_pending_inactive: u64
+#[event]
+struct Vote has drop, store
+
+
+
+
+voter: address
+proposal_id: u64
+delegation_pool: address
+num_votes: u64
+should_pass: bool
+struct VoteEvent has drop, store
+
+
+
+
+voter: address
+proposal_id: u64
+delegation_pool: address
+num_votes: u64
+should_pass: bool
+#[event]
+struct CreateProposal has drop, store
+
+
+
+
+proposal_id: u64
+voter: address
+delegation_pool: address
+struct CreateProposalEvent has drop, store
+
+
+
+
+proposal_id: u64
+voter: address
+delegation_pool: address
+#[event]
+struct DelegateVotingPower has drop, store
+
+
+
+
+pool_address: address
+delegator: address
+voter: address
+struct DelegateVotingPowerEvent has drop, store
+
+
+
+
+pool_address: address
+delegator: address
+voter: address
+#[event]
+struct SetBeneficiaryForOperator has drop, store
+
+
+
+
+operator: address
+old_beneficiary: address
+new_beneficiary: address
+#[event]
+struct CommissionPercentageChange has drop, store
+
+
+
+
+pool_address: address
+owner: address
+commission_percentage_next_lockup_cycle: u64
+#[event]
+struct EnableDelegatorsAllowlisting has drop, store
+
+
+
+
+pool_address: address
+#[event]
+struct DisableDelegatorsAllowlisting has drop, store
+
+
+
+
+pool_address: address
+#[event]
+struct AllowlistDelegator has drop, store
+
+
+
+
+pool_address: address
+delegator_address: address
+#[event]
+struct RemoveDelegatorFromAllowlist has drop, store
+
+
+
+
+pool_address: address
+delegator_address: address
+#[event]
+struct EvictDelegator has drop, store
+
+
+
+
+pool_address: address
+delegator_address: address
+const MAX_U64: u64 = 18446744073709551615;
+
+
+
+
+
+
+Function is deprecated.
+
+
+const EDEPRECATED_FUNCTION: u64 = 12;
+
+
+
+
+
+
+The function is disabled or hasn't been enabled.
+
+
+const EDISABLED_FUNCTION: u64 = 13;
+
+
+
+
+
+
+The account is not the operator of the stake pool.
+
+
+const ENOT_OPERATOR: u64 = 18;
+
+
+
+
+
+
+Account is already owning a delegation pool.
+
+
+const EOWNER_CAP_ALREADY_EXISTS: u64 = 2;
+
+
+
+
+
+
+Delegation pool owner capability does not exist at the provided account.
+
+
+const EOWNER_CAP_NOT_FOUND: u64 = 1;
+
+
+
+
+
+
+
+
+const VALIDATOR_STATUS_INACTIVE: u64 = 4;
+
+
+
+
+
+
+The voter does not have sufficient stake to create a proposal.
+
+
+const EINSUFFICIENT_PROPOSER_STAKE: u64 = 15;
+
+
+
+
+
+
+The voter does not have any voting power on this proposal.
+
+
+const ENO_VOTING_POWER: u64 = 16;
+
+
+
+
+
+
+The stake pool has already voted on the proposal before enabling partial governance voting on this delegation pool.
+
+
+const EALREADY_VOTED_BEFORE_ENABLE_PARTIAL_VOTING: u64 = 17;
+
+
+
+
+
+
+Cannot evict an allowlisted delegator, should remove them from the allowlist first.
+
+
+const ECANNOT_EVICT_ALLOWLISTED_DELEGATOR: u64 = 26;
+
+
+
+
+
+
+Cannot unlock the accumulated active stake of NULL_SHAREHOLDER(0x0).
+
+
+const ECANNOT_UNLOCK_NULL_SHAREHOLDER: u64 = 27;
+
+
+
+
+
+
+Changing operator commission rate in delegation pool is not supported.
+
+
+const ECOMMISSION_RATE_CHANGE_NOT_SUPPORTED: u64 = 22;
+
+
+
+
+
+
+Creating delegation pools is not enabled yet.
+
+
+const EDELEGATION_POOLS_DISABLED: u64 = 10;
+
+
+
+
+
+
+Delegation pool does not exist at the provided pool address.
+
+
+const EDELEGATION_POOL_DOES_NOT_EXIST: u64 = 3;
+
+
+
+
+
+
+Delegators allowlisting should be enabled to perform this operation.
+
+
+const EDELEGATORS_ALLOWLISTING_NOT_ENABLED: u64 = 24;
+
+
+
+
+
+
+Delegators allowlisting is not supported.
+
+
+const EDELEGATORS_ALLOWLISTING_NOT_SUPPORTED: u64 = 23;
+
+
+
+
+
+
+Delegator's active balance cannot be less than MIN_COINS_ON_SHARES_POOL
.
+
+
+const EDELEGATOR_ACTIVE_BALANCE_TOO_LOW: u64 = 8;
+
+
+
+
+
+
+Cannot add/reactivate stake unless being allowlisted by the pool owner.
+
+
+const EDELEGATOR_NOT_ALLOWLISTED: u64 = 25;
+
+
+
+
+
+
+Delegator's pending_inactive balance cannot be less than MIN_COINS_ON_SHARES_POOL
.
+
+
+const EDELEGATOR_PENDING_INACTIVE_BALANCE_TOO_LOW: u64 = 9;
+
+
+
+
+
+
+Commission percentage has to be between 0 and MAX_FEE
- 100%.
+
+
+const EINVALID_COMMISSION_PERCENTAGE: u64 = 5;
+
+
+
+
+
+
+There is not enough active
stake on the stake pool to unlock
.
+
+
+const ENOT_ENOUGH_ACTIVE_STAKE_TO_UNLOCK: u64 = 6;
+
+
+
+
+
+
+Changing beneficiaries for operators is not supported.
+
+
+const EOPERATOR_BENEFICIARY_CHANGE_NOT_SUPPORTED: u64 = 19;
+
+
+
+
+
+
+Partial governance voting hasn't been enabled on this delegation pool.
+
+
+const EPARTIAL_GOVERNANCE_VOTING_NOT_ENABLED: u64 = 14;
+
+
+
+
+
+
+There is a pending withdrawal to be executed before unlock
ing any new stake.
+
+
+const EPENDING_WITHDRAWAL_EXISTS: u64 = 4;
+
+
+
+
+
+
+Slashing (if implemented) should not be applied to already inactive
stake.
+Not only it invalidates the accounting of past observed lockup cycles (OLC),
+but is also unfair to delegators whose stake has been inactive before validator started misbehaving.
+Additionally, the inactive stake does not count on the voting power of validator.
+
+
+const ESLASHED_INACTIVE_STAKE_ON_PAST_OLC: u64 = 7;
+
+
+
+
+
+
+Commission percentage increase is too large.
+
+
+const ETOO_LARGE_COMMISSION_INCREASE: u64 = 20;
+
+
+
+
+
+
+Commission percentage change is too late in this lockup period, and should be done at least a quarter (1/4) of the lockup duration before the lockup cycle ends.
+
+
+const ETOO_LATE_COMMISSION_CHANGE: u64 = 21;
+
+
+
+
+
+
+Cannot request to withdraw zero stake.
+
+
+const EWITHDRAW_ZERO_STAKE: u64 = 11;
+
+
+
+
+
+
+Maximum commission percentage increase per lockup cycle. 10% is represented as 1000.
+
+
+const MAX_COMMISSION_INCREASE: u64 = 1000;
+
+
+
+
+
+
+Maximum operator percentage fee(of double digit precision): 22.85% is represented as 2285
+
+
+const MAX_FEE: u64 = 10000;
+
+
+
+
+
+
+Minimum coins to exist on a shares pool at all times.
+Enforced per delegator for both active and pending_inactive pools.
+This constraint ensures the share price cannot overly increase and lead to
+substantial losses when buying shares (can lose at most 1 share which may
+be worth a lot if current share price is high).
+This constraint is not enforced on inactive pools as they only allow redeems
+(can lose at most 1 coin regardless of current share price).
+
+
+const MIN_COINS_ON_SHARES_POOL: u64 = 1000000000;
+
+
+
+
+
+
+
+
+const MODULE_SALT: vector<u8> = [97, 112, 116, 111, 115, 95, 102, 114, 97, 109, 101, 119, 111, 114, 107, 58, 58, 100, 101, 108, 101, 103, 97, 116, 105, 111, 110, 95, 112, 111, 111, 108];
+
+
+
+
+
+
+Special shareholder temporarily owning the add_stake
fees charged during this epoch.
+On each add_stake
operation any resulted fee is used to buy active shares for this shareholder.
+First synchronization after this epoch ends will distribute accumulated fees to the rest of the pool as refunds.
+
+
+const NULL_SHAREHOLDER: address = 0x0;
+
+
+
+
+
+
+Scaling factor of shares pools used within the delegation pool
+
+
+const SHARES_SCALING_FACTOR: u64 = 10000000000000000;
+
+
+
+
+
+
+## Function `owner_cap_exists`
+
+Return whether supplied address addr
is owner of a delegation pool.
+
+
+#[view]
+public fun owner_cap_exists(addr: address): bool
+
+
+
+
+public fun owner_cap_exists(addr: address): bool {
+ exists<DelegationPoolOwnership>(addr)
+}
+
+
+
+
+owner
or fail if there is none.
+
+
+#[view]
+public fun get_owned_pool_address(owner: address): address
+
+
+
+
+public fun get_owned_pool_address(owner: address): address acquires DelegationPoolOwnership {
+ assert_owner_cap_exists(owner);
+ borrow_global<DelegationPoolOwnership>(owner).pool_address
+}
+
+
+
+
+addr
.
+
+
+#[view]
+public fun delegation_pool_exists(addr: address): bool
+
+
+
+
+public fun delegation_pool_exists(addr: address): bool {
+ exists<DelegationPool>(addr)
+}
+
+
+
+
+#[view]
+public fun partial_governance_voting_enabled(pool_address: address): bool
+
+
+
+
+public fun partial_governance_voting_enabled(pool_address: address): bool {
+ exists<GovernanceRecords>(pool_address) && stake::get_delegated_voter(pool_address) == pool_address
+}
+
+
+
+
+pool_address
.
+
+
+#[view]
+public fun observed_lockup_cycle(pool_address: address): u64
+
+
+
+
+public fun observed_lockup_cycle(pool_address: address): u64 acquires DelegationPool {
+ assert_delegation_pool_exists(pool_address);
+ borrow_global<DelegationPool>(pool_address).observed_lockup_cycle.index
+}
+
+
+
+
+#[view]
+public fun is_next_commission_percentage_effective(pool_address: address): bool
+
+
+
+
+public fun is_next_commission_percentage_effective(pool_address: address): bool acquires NextCommissionPercentage {
+ exists<NextCommissionPercentage>(pool_address) &&
+ timestamp::now_seconds() >= borrow_global<NextCommissionPercentage>(pool_address).effective_after_secs
+}
+
+
+
+
+pool_address
.
+
+
+#[view]
+public fun operator_commission_percentage(pool_address: address): u64
+
+
+
+
+public fun operator_commission_percentage(
+ pool_address: address
+): u64 acquires DelegationPool, NextCommissionPercentage {
+ assert_delegation_pool_exists(pool_address);
+ if (is_next_commission_percentage_effective(pool_address)) {
+ operator_commission_percentage_next_lockup_cycle(pool_address)
+ } else {
+ borrow_global<DelegationPool>(pool_address).operator_commission_percentage
+ }
+}
+
+
+
+
+#[view]
+public fun operator_commission_percentage_next_lockup_cycle(pool_address: address): u64
+
+
+
+
+public fun operator_commission_percentage_next_lockup_cycle(
+ pool_address: address
+): u64 acquires DelegationPool, NextCommissionPercentage {
+ assert_delegation_pool_exists(pool_address);
+ if (exists<NextCommissionPercentage>(pool_address)) {
+ borrow_global<NextCommissionPercentage>(pool_address).commission_percentage_next_lockup_cycle
+ } else {
+ borrow_global<DelegationPool>(pool_address).operator_commission_percentage
+ }
+}
+
+
+
+
+pool_address
.
+
+
+#[view]
+public fun shareholders_count_active_pool(pool_address: address): u64
+
+
+
+
+public fun shareholders_count_active_pool(pool_address: address): u64 acquires DelegationPool {
+ assert_delegation_pool_exists(pool_address);
+ pool_u64::shareholders_count(&borrow_global<DelegationPool>(pool_address).active_shares)
+}
+
+
+
+
+pool_address
in the different states:
+(active
,inactive
,pending_active
,pending_inactive
)
+
+
+#[view]
+public fun get_delegation_pool_stake(pool_address: address): (u64, u64, u64, u64)
+
+
+
+
+public fun get_delegation_pool_stake(pool_address: address): (u64, u64, u64, u64) {
+ assert_delegation_pool_exists(pool_address);
+ stake::get_stake(pool_address)
+}
+
+
+
+
+#[view]
+public fun get_pending_withdrawal(pool_address: address, delegator_address: address): (bool, u64)
+
+
+
+
+public fun get_pending_withdrawal(
+ pool_address: address,
+ delegator_address: address
+): (bool, u64) acquires DelegationPool {
+ assert_delegation_pool_exists(pool_address);
+ let pool = borrow_global<DelegationPool>(pool_address);
+ let (
+ lockup_cycle_ended,
+ _,
+ pending_inactive,
+ _,
+ commission_pending_inactive
+ ) = calculate_stake_pool_drift(pool);
+
+ let (withdrawal_exists, withdrawal_olc) = pending_withdrawal_exists(pool, delegator_address);
+ if (!withdrawal_exists) {
+ // if no pending withdrawal, there is neither inactive nor pending_inactive stake
+ (false, 0)
+ } else {
+ // delegator has either inactive or pending_inactive stake due to automatic withdrawals
+ let inactive_shares = table::borrow(&pool.inactive_shares, withdrawal_olc);
+ if (withdrawal_olc.index < pool.observed_lockup_cycle.index) {
+ // if withdrawal's lockup cycle ended on delegation pool then it is inactive
+ (true, pool_u64::balance(inactive_shares, delegator_address))
+ } else {
+ pending_inactive = pool_u64::shares_to_amount_with_total_coins(
+ inactive_shares,
+ pool_u64::shares(inactive_shares, delegator_address),
+ // exclude operator pending_inactive rewards not converted to shares yet
+ pending_inactive - commission_pending_inactive
+ );
+ // if withdrawal's lockup cycle ended ONLY on stake pool then it is also inactive
+ (lockup_cycle_ended, pending_inactive)
+ }
+ }
+}
+
+
+
+
+delegator_address
within delegation pool pool_address
+in each of its individual states: (active
,inactive
,pending_inactive
)
+
+
+#[view]
+public fun get_stake(pool_address: address, delegator_address: address): (u64, u64, u64)
+
+
+
+
+public fun get_stake(
+ pool_address: address,
+ delegator_address: address
+): (u64, u64, u64) acquires DelegationPool, BeneficiaryForOperator {
+ assert_delegation_pool_exists(pool_address);
+ let pool = borrow_global<DelegationPool>(pool_address);
+ let (
+ lockup_cycle_ended,
+ active,
+ _,
+ commission_active,
+ commission_pending_inactive
+ ) = calculate_stake_pool_drift(pool);
+
+ let total_active_shares = pool_u64::total_shares(&pool.active_shares);
+ let delegator_active_shares = pool_u64::shares(&pool.active_shares, delegator_address);
+
+ let (_, _, pending_active, _) = stake::get_stake(pool_address);
+ if (pending_active == 0) {
+ // zero `pending_active` stake indicates that either there are no `add_stake` fees or
+ // previous epoch has ended and should identify shares owning these fees as released
+ total_active_shares = total_active_shares - pool_u64::shares(&pool.active_shares, NULL_SHAREHOLDER);
+ if (delegator_address == NULL_SHAREHOLDER) {
+ delegator_active_shares = 0
+ }
+ };
+ active = pool_u64::shares_to_amount_with_total_stats(
+ &pool.active_shares,
+ delegator_active_shares,
+ // exclude operator active rewards not converted to shares yet
+ active - commission_active,
+ total_active_shares
+ );
+
+ // get state and stake (0 if there is none) of the pending withdrawal
+ let (withdrawal_inactive, withdrawal_stake) = get_pending_withdrawal(pool_address, delegator_address);
+ // report non-active stakes accordingly to the state of the pending withdrawal
+ let (inactive, pending_inactive) = if (withdrawal_inactive) (withdrawal_stake, 0) else (0, withdrawal_stake);
+
+ // should also include commission rewards in case of the operator account
+ // operator rewards are actually used to buy shares which is introducing
+ // some imprecision (received stake would be slightly less)
+ // but adding rewards onto the existing stake is still a good approximation
+ if (delegator_address == beneficiary_for_operator(get_operator(pool_address))) {
+ active = active + commission_active;
+ // in-flight pending_inactive commission can coexist with already inactive withdrawal
+ if (lockup_cycle_ended) {
+ inactive = inactive + commission_pending_inactive
+ } else {
+ pending_inactive = pending_inactive + commission_pending_inactive
+ }
+ };
+
+ (active, inactive, pending_inactive)
+}
+
+
+
+
+amount
at add_stake
operation on pool pool_address
.
+If the validator produces rewards this epoch, added stake goes directly to pending_active
and
+does not earn rewards. However, all shares within a pool appreciate uniformly and when this epoch ends:
+- either added shares are still pending_active
and steal from rewards of existing active
stake
+- or have moved to pending_inactive
and get full rewards (they displaced active
stake at unlock
)
+To mitigate this, some of the added stake is extracted and fed back into the pool as placeholder
+for the rewards the remaining stake would have earned if active:
+extracted-fee = (amount - extracted-fee) * reward-rate% * (100% - operator-commission%)
+
+
+#[view]
+public fun get_add_stake_fee(pool_address: address, amount: u64): u64
+
+
+
+
+public fun get_add_stake_fee(
+ pool_address: address,
+ amount: u64
+): u64 acquires DelegationPool, NextCommissionPercentage {
+ if (stake::is_current_epoch_validator(pool_address)) {
+ let (rewards_rate, rewards_rate_denominator) = staking_config::get_reward_rate(&staking_config::get());
+ if (rewards_rate_denominator > 0) {
+ assert_delegation_pool_exists(pool_address);
+
+ rewards_rate = rewards_rate * (MAX_FEE - operator_commission_percentage(pool_address));
+ rewards_rate_denominator = rewards_rate_denominator * MAX_FEE;
+ ((((amount as u128) * (rewards_rate as u128)) / ((rewards_rate as u128) + (rewards_rate_denominator as u128))) as u64)
+ } else { 0 }
+ } else { 0 }
+}
+
+
+
+
+pending_inactive
stake can be directly withdrawn from
+the delegation pool, implicitly its stake pool, in the special case
+the validator had gone inactive before its lockup expired.
+
+
+#[view]
+public fun can_withdraw_pending_inactive(pool_address: address): bool
+
+
+
+
+public fun can_withdraw_pending_inactive(pool_address: address): bool {
+ stake::get_validator_state(pool_address) == VALIDATOR_STATUS_INACTIVE &&
+ timestamp::now_seconds() >= stake::get_lockup_secs(pool_address)
+}
+
+
+
+
+#[view]
+public fun calculate_and_update_voter_total_voting_power(pool_address: address, voter: address): u64
+
+
+
+
+public fun calculate_and_update_voter_total_voting_power(
+ pool_address: address,
+ voter: address
+): u64 acquires DelegationPool, GovernanceRecords, BeneficiaryForOperator, NextCommissionPercentage {
+ assert_partial_governance_voting_enabled(pool_address);
+ // Delegation pool need to be synced to explain rewards(which could change the coin amount) and
+ // commission(which could cause share transfer).
+ synchronize_delegation_pool(pool_address);
+ let pool = borrow_global<DelegationPool>(pool_address);
+ let governance_records = borrow_global_mut<GovernanceRecords>(pool_address);
+ let latest_delegated_votes = update_and_borrow_mut_delegated_votes(pool, governance_records, voter);
+ calculate_total_voting_power(pool, latest_delegated_votes)
+}
+
+
+
+
+#[view]
+public fun calculate_and_update_remaining_voting_power(pool_address: address, voter_address: address, proposal_id: u64): u64
+
+
+
+
+public fun calculate_and_update_remaining_voting_power(
+ pool_address: address,
+ voter_address: address,
+ proposal_id: u64
+): u64 acquires DelegationPool, GovernanceRecords, BeneficiaryForOperator, NextCommissionPercentage {
+ assert_partial_governance_voting_enabled(pool_address);
+ // If the whole stake pool has no voting power(e.g. it has already voted before partial
+ // governance voting flag is enabled), the delegator also has no voting power.
+ if (aptos_governance::get_remaining_voting_power(pool_address, proposal_id) == 0) {
+ return 0
+ };
+
+ let total_voting_power = calculate_and_update_voter_total_voting_power(pool_address, voter_address);
+ let governance_records = borrow_global<GovernanceRecords>(pool_address);
+ total_voting_power - get_used_voting_power(governance_records, voter_address, proposal_id)
+}
+
+
+
+
+#[view]
+public fun calculate_and_update_delegator_voter(pool_address: address, delegator_address: address): address
+
+
+
+
+public fun calculate_and_update_delegator_voter(
+ pool_address: address,
+ delegator_address: address
+): address acquires DelegationPool, GovernanceRecords {
+ assert_partial_governance_voting_enabled(pool_address);
+ calculate_and_update_delegator_voter_internal(
+ borrow_global<DelegationPool>(pool_address),
+ borrow_global_mut<GovernanceRecords>(pool_address),
+ delegator_address
+ )
+}
+
+
+
+
+#[view]
+public fun calculate_and_update_voting_delegation(pool_address: address, delegator_address: address): (address, address, u64)
+
+
+
+
+public fun calculate_and_update_voting_delegation(
+ pool_address: address,
+ delegator_address: address
+): (address, address, u64) acquires DelegationPool, GovernanceRecords {
+ assert_partial_governance_voting_enabled(pool_address);
+ let vote_delegation = update_and_borrow_mut_delegator_vote_delegation(
+ borrow_global<DelegationPool>(pool_address),
+ borrow_global_mut<GovernanceRecords>(pool_address),
+ delegator_address
+ );
+
+ (vote_delegation.voter, vote_delegation.pending_voter, vote_delegation.last_locked_until_secs)
+}
+
+
+
+
+#[view]
+public fun get_expected_stake_pool_address(owner: address, delegation_pool_creation_seed: vector<u8>): address
+
+
+
+
+public fun get_expected_stake_pool_address(owner: address, delegation_pool_creation_seed: vector<u8>
+): address {
+ let seed = create_resource_account_seed(delegation_pool_creation_seed);
+ account::create_resource_address(&owner, seed)
+}
+
+
+
+
+#[view]
+public fun min_remaining_secs_for_commission_change(): u64
+
+
+
+
+public fun min_remaining_secs_for_commission_change(): u64 {
+ let config = staking_config::get();
+ staking_config::get_recurring_lockup_duration(&config) / 4
+}
+
+
+
+
+#[view]
+public fun allowlisting_enabled(pool_address: address): bool
+
+
+
+
+public fun allowlisting_enabled(pool_address: address): bool {
+ assert_delegation_pool_exists(pool_address);
+ exists<DelegationPoolAllowlisting>(pool_address)
+}
+
+
+
+
+#[view]
+public fun delegator_allowlisted(pool_address: address, delegator_address: address): bool
+
+
+
+
+public fun delegator_allowlisted(
+ pool_address: address,
+ delegator_address: address,
+): bool acquires DelegationPoolAllowlisting {
+ if (!allowlisting_enabled(pool_address)) { return true };
+ smart_table::contains(freeze(borrow_mut_delegators_allowlist(pool_address)), delegator_address)
+}
+
+
+
+
+#[view]
+public fun get_delegators_allowlist(pool_address: address): vector<address>
+
+
+
+
+public fun get_delegators_allowlist(
+ pool_address: address,
+): vector<address> acquires DelegationPoolAllowlisting {
+ assert_allowlisting_enabled(pool_address);
+
+ let allowlist = vector[];
+ smart_table::for_each_ref(freeze(borrow_mut_delegators_allowlist(pool_address)), |delegator, _v| {
+ vector::push_back(&mut allowlist, *delegator);
+ });
+ allowlist
+}
+
+
+
+
+operator_commission_percentage
.
+A resource account is created from owner
signer and its supplied delegation_pool_creation_seed
+to host the delegation pool resource and own the underlying stake pool.
+Ownership over setting the operator/voter is granted to owner
who has both roles initially.
+
+
+public entry fun initialize_delegation_pool(owner: &signer, operator_commission_percentage: u64, delegation_pool_creation_seed: vector<u8>)
+
+
+
+
+public entry fun initialize_delegation_pool(
+ owner: &signer,
+ operator_commission_percentage: u64,
+ delegation_pool_creation_seed: vector<u8>,
+) acquires DelegationPool, GovernanceRecords, BeneficiaryForOperator, NextCommissionPercentage {
+ assert!(features::delegation_pools_enabled(), error::invalid_state(EDELEGATION_POOLS_DISABLED));
+ let owner_address = signer::address_of(owner);
+ assert!(!owner_cap_exists(owner_address), error::already_exists(EOWNER_CAP_ALREADY_EXISTS));
+ assert!(operator_commission_percentage <= MAX_FEE, error::invalid_argument(EINVALID_COMMISSION_PERCENTAGE));
+
+ // generate a seed to be used to create the resource account hosting the delegation pool
+ let seed = create_resource_account_seed(delegation_pool_creation_seed);
+
+ let (stake_pool_signer, stake_pool_signer_cap) = account::create_resource_account(owner, seed);
+ coin::register<AptosCoin>(&stake_pool_signer);
+
+ // stake_pool_signer will be owner of the stake pool and have its `stake::OwnerCapability`
+ let pool_address = signer::address_of(&stake_pool_signer);
+ stake::initialize_stake_owner(&stake_pool_signer, 0, owner_address, owner_address);
+
+ let inactive_shares = table::new<ObservedLockupCycle, pool_u64::Pool>();
+ table::add(
+ &mut inactive_shares,
+ olc_with_index(0),
+ pool_u64::create_with_scaling_factor(SHARES_SCALING_FACTOR)
+ );
+
+ move_to(&stake_pool_signer, DelegationPool {
+ active_shares: pool_u64::create_with_scaling_factor(SHARES_SCALING_FACTOR),
+ observed_lockup_cycle: olc_with_index(0),
+ inactive_shares,
+ pending_withdrawals: table::new<address, ObservedLockupCycle>(),
+ stake_pool_signer_cap,
+ total_coins_inactive: 0,
+ operator_commission_percentage,
+ add_stake_events: account::new_event_handle<AddStakeEvent>(&stake_pool_signer),
+ reactivate_stake_events: account::new_event_handle<ReactivateStakeEvent>(&stake_pool_signer),
+ unlock_stake_events: account::new_event_handle<UnlockStakeEvent>(&stake_pool_signer),
+ withdraw_stake_events: account::new_event_handle<WithdrawStakeEvent>(&stake_pool_signer),
+ distribute_commission_events: account::new_event_handle<DistributeCommissionEvent>(&stake_pool_signer),
+ });
+
+ // save delegation pool ownership and resource account address (inner stake pool address) on `owner`
+ move_to(owner, DelegationPoolOwnership { pool_address });
+
+ // All delegation pool enable partial governance voting by default once the feature flag is enabled.
+ if (features::partial_governance_voting_enabled(
+ ) && features::delegation_pool_partial_governance_voting_enabled()) {
+ enable_partial_governance_voting(pool_address);
+ }
+}
+
+
+
+
+#[view]
+public fun beneficiary_for_operator(operator: address): address
+
+
+
+
+public fun beneficiary_for_operator(operator: address): address acquires BeneficiaryForOperator {
+ if (exists<BeneficiaryForOperator>(operator)) {
+ return borrow_global<BeneficiaryForOperator>(operator).beneficiary_for_operator
+ } else {
+ operator
+ }
+}
+
+
+
+
+public entry fun enable_partial_governance_voting(pool_address: address)
+
+
+
+
+public entry fun enable_partial_governance_voting(
+ pool_address: address,
+) acquires DelegationPool, GovernanceRecords, BeneficiaryForOperator, NextCommissionPercentage {
+ assert!(features::partial_governance_voting_enabled(), error::invalid_state(EDISABLED_FUNCTION));
+ assert!(
+ features::delegation_pool_partial_governance_voting_enabled(),
+ error::invalid_state(EDISABLED_FUNCTION)
+ );
+ assert_delegation_pool_exists(pool_address);
+ // synchronize delegation and stake pools before any user operation.
+ synchronize_delegation_pool(pool_address);
+
+ let delegation_pool = borrow_global<DelegationPool>(pool_address);
+ let stake_pool_signer = retrieve_stake_pool_owner(delegation_pool);
+ // delegated_voter is managed by the stake pool itself, which signer capability is managed by DelegationPool.
+ // So voting power of this stake pool can only be used through this module.
+ stake::set_delegated_voter(&stake_pool_signer, signer::address_of(&stake_pool_signer));
+
+ move_to(&stake_pool_signer, GovernanceRecords {
+ votes: smart_table::new(),
+ votes_per_proposal: smart_table::new(),
+ vote_delegation: smart_table::new(),
+ delegated_votes: smart_table::new(),
+ vote_events: account::new_event_handle<VoteEvent>(&stake_pool_signer),
+ create_proposal_events: account::new_event_handle<CreateProposalEvent>(&stake_pool_signer),
+ delegate_voting_power_events: account::new_event_handle<DelegateVotingPowerEvent>(&stake_pool_signer),
+ });
+}
+
+
+
+
+public entry fun vote(voter: &signer, pool_address: address, proposal_id: u64, voting_power: u64, should_pass: bool)
+
+
+
+
+public entry fun vote(
+ voter: &signer,
+ pool_address: address,
+ proposal_id: u64,
+ voting_power: u64,
+ should_pass: bool
+) acquires DelegationPool, GovernanceRecords, BeneficiaryForOperator, NextCommissionPercentage {
+ assert_partial_governance_voting_enabled(pool_address);
+ // synchronize delegation and stake pools before any user operation.
+ synchronize_delegation_pool(pool_address);
+
+ let voter_address = signer::address_of(voter);
+ let remaining_voting_power = calculate_and_update_remaining_voting_power(
+ pool_address,
+ voter_address,
+ proposal_id
+ );
+ if (voting_power > remaining_voting_power) {
+ voting_power = remaining_voting_power;
+ };
+ assert!(voting_power > 0, error::invalid_argument(ENO_VOTING_POWER));
+
+ let governance_records = borrow_global_mut<GovernanceRecords>(pool_address);
+ // Check a edge case during the transient period of enabling partial governance voting.
+ assert_and_update_proposal_used_voting_power(governance_records, pool_address, proposal_id, voting_power);
+ let used_voting_power = borrow_mut_used_voting_power(governance_records, voter_address, proposal_id);
+ *used_voting_power = *used_voting_power + voting_power;
+
+ let pool_signer = retrieve_stake_pool_owner(borrow_global<DelegationPool>(pool_address));
+ aptos_governance::partial_vote(&pool_signer, pool_address, proposal_id, voting_power, should_pass);
+
+ if (features::module_event_migration_enabled()) {
+ event::emit(
+ Vote {
+ voter: voter_address,
+ proposal_id,
+ delegation_pool: pool_address,
+ num_votes: voting_power,
+ should_pass,
+ }
+ );
+ };
+
+ event::emit_event(
+ &mut governance_records.vote_events,
+ VoteEvent {
+ voter: voter_address,
+ proposal_id,
+ delegation_pool: pool_address,
+ num_votes: voting_power,
+ should_pass,
+ }
+ );
+}
+
+
+
+
+aptos_governance.move
.
+
+
+public entry fun create_proposal(voter: &signer, pool_address: address, execution_hash: vector<u8>, metadata_location: vector<u8>, metadata_hash: vector<u8>, is_multi_step_proposal: bool)
+
+
+
+
+public entry fun create_proposal(
+ voter: &signer,
+ pool_address: address,
+ execution_hash: vector<u8>,
+ metadata_location: vector<u8>,
+ metadata_hash: vector<u8>,
+ is_multi_step_proposal: bool,
+) acquires DelegationPool, GovernanceRecords, BeneficiaryForOperator, NextCommissionPercentage {
+ assert_partial_governance_voting_enabled(pool_address);
+
+ // synchronize delegation and stake pools before any user operation
+ synchronize_delegation_pool(pool_address);
+
+ let voter_addr = signer::address_of(voter);
+ let pool = borrow_global<DelegationPool>(pool_address);
+ let governance_records = borrow_global_mut<GovernanceRecords>(pool_address);
+ let total_voting_power = calculate_and_update_delegated_votes(pool, governance_records, voter_addr);
+ assert!(
+ total_voting_power >= aptos_governance::get_required_proposer_stake(),
+ error::invalid_argument(EINSUFFICIENT_PROPOSER_STAKE));
+ let pool_signer = retrieve_stake_pool_owner(borrow_global<DelegationPool>(pool_address));
+ let proposal_id = aptos_governance::create_proposal_v2_impl(
+ &pool_signer,
+ pool_address,
+ execution_hash,
+ metadata_location,
+ metadata_hash,
+ is_multi_step_proposal,
+ );
+
+ let governance_records = borrow_global_mut<GovernanceRecords>(pool_address);
+
+ if (features::module_event_migration_enabled()) {
+ event::emit(
+ CreateProposal {
+ proposal_id,
+ voter: voter_addr,
+ delegation_pool: pool_address,
+ }
+ );
+ };
+
+ event::emit_event(
+ &mut governance_records.create_proposal_events,
+ CreateProposalEvent {
+ proposal_id,
+ voter: voter_addr,
+ delegation_pool: pool_address,
+ }
+ );
+}
+
+
+
+
+fun assert_owner_cap_exists(owner: address)
+
+
+
+
+fun assert_owner_cap_exists(owner: address) {
+ assert!(owner_cap_exists(owner), error::not_found(EOWNER_CAP_NOT_FOUND));
+}
+
+
+
+
+fun assert_delegation_pool_exists(pool_address: address)
+
+
+
+
+fun assert_delegation_pool_exists(pool_address: address) {
+ assert!(delegation_pool_exists(pool_address), error::invalid_argument(EDELEGATION_POOL_DOES_NOT_EXIST));
+}
+
+
+
+
+fun assert_min_active_balance(pool: &delegation_pool::DelegationPool, delegator_address: address)
+
+
+
+
+fun assert_min_active_balance(pool: &DelegationPool, delegator_address: address) {
+ let balance = pool_u64::balance(&pool.active_shares, delegator_address);
+ assert!(balance >= MIN_COINS_ON_SHARES_POOL, error::invalid_argument(EDELEGATOR_ACTIVE_BALANCE_TOO_LOW));
+}
+
+
+
+
+fun assert_min_pending_inactive_balance(pool: &delegation_pool::DelegationPool, delegator_address: address)
+
+
+
+
+fun assert_min_pending_inactive_balance(pool: &DelegationPool, delegator_address: address) {
+ let balance = pool_u64::balance(pending_inactive_shares_pool(pool), delegator_address);
+ assert!(
+ balance >= MIN_COINS_ON_SHARES_POOL,
+ error::invalid_argument(EDELEGATOR_PENDING_INACTIVE_BALANCE_TOO_LOW)
+ );
+}
+
+
+
+
+fun assert_partial_governance_voting_enabled(pool_address: address)
+
+
+
+
+fun assert_partial_governance_voting_enabled(pool_address: address) {
+ assert_delegation_pool_exists(pool_address);
+ assert!(
+ partial_governance_voting_enabled(pool_address),
+ error::invalid_state(EPARTIAL_GOVERNANCE_VOTING_NOT_ENABLED)
+ );
+}
+
+
+
+
+fun assert_allowlisting_enabled(pool_address: address)
+
+
+
+
+fun assert_allowlisting_enabled(pool_address: address) {
+ assert!(allowlisting_enabled(pool_address), error::invalid_state(EDELEGATORS_ALLOWLISTING_NOT_ENABLED));
+}
+
+
+
+
+fun assert_delegator_allowlisted(pool_address: address, delegator_address: address)
+
+
+
+
+fun assert_delegator_allowlisted(
+ pool_address: address,
+ delegator_address: address,
+) acquires DelegationPoolAllowlisting {
+ assert!(
+ delegator_allowlisted(pool_address, delegator_address),
+ error::permission_denied(EDELEGATOR_NOT_ALLOWLISTED)
+ );
+}
+
+
+
+
+fun coins_to_redeem_to_ensure_min_stake(src_shares_pool: &pool_u64_unbound::Pool, shareholder: address, amount: u64): u64
+
+
+
+
+fun coins_to_redeem_to_ensure_min_stake(
+ src_shares_pool: &pool_u64::Pool,
+ shareholder: address,
+ amount: u64,
+): u64 {
+ // find how many coins would be redeemed if supplying `amount`
+ let redeemed_coins = pool_u64::shares_to_amount(
+ src_shares_pool,
+ amount_to_shares_to_redeem(src_shares_pool, shareholder, amount)
+ );
+ // if balance drops under threshold then redeem it entirely
+ let src_balance = pool_u64::balance(src_shares_pool, shareholder);
+ if (src_balance - redeemed_coins < MIN_COINS_ON_SHARES_POOL) {
+ amount = src_balance;
+ };
+ amount
+}
+
+
+
+
+fun coins_to_transfer_to_ensure_min_stake(src_shares_pool: &pool_u64_unbound::Pool, dst_shares_pool: &pool_u64_unbound::Pool, shareholder: address, amount: u64): u64
+
+
+
+
+fun coins_to_transfer_to_ensure_min_stake(
+ src_shares_pool: &pool_u64::Pool,
+ dst_shares_pool: &pool_u64::Pool,
+ shareholder: address,
+ amount: u64,
+): u64 {
+ // find how many coins would be redeemed from source if supplying `amount`
+ let redeemed_coins = pool_u64::shares_to_amount(
+ src_shares_pool,
+ amount_to_shares_to_redeem(src_shares_pool, shareholder, amount)
+ );
+ // if balance on destination would be less than threshold then redeem difference to threshold
+ let dst_balance = pool_u64::balance(dst_shares_pool, shareholder);
+ if (dst_balance + redeemed_coins < MIN_COINS_ON_SHARES_POOL) {
+ // `redeemed_coins` >= `amount` - 1 as redeem can lose at most 1 coin
+ amount = MIN_COINS_ON_SHARES_POOL - dst_balance + 1;
+ };
+ // check if new `amount` drops balance on source under threshold and adjust
+ coins_to_redeem_to_ensure_min_stake(src_shares_pool, shareholder, amount)
+}
+
+
+
+
+fun retrieve_stake_pool_owner(pool: &delegation_pool::DelegationPool): signer
+
+
+
+
+fun retrieve_stake_pool_owner(pool: &DelegationPool): signer {
+ account::create_signer_with_capability(&pool.stake_pool_signer_cap)
+}
+
+
+
+
+pool
.
+
+
+fun get_pool_address(pool: &delegation_pool::DelegationPool): address
+
+
+
+
+fun get_pool_address(pool: &DelegationPool): address {
+ account::get_signer_capability_address(&pool.stake_pool_signer_cap)
+}
+
+
+
+
+fun get_delegator_active_shares(pool: &delegation_pool::DelegationPool, delegator: address): u128
+
+
+
+
+fun get_delegator_active_shares(pool: &DelegationPool, delegator: address): u128 {
+ pool_u64::shares(&pool.active_shares, delegator)
+}
+
+
+
+
+fun get_delegator_pending_inactive_shares(pool: &delegation_pool::DelegationPool, delegator: address): u128
+
+
+
+
+fun get_delegator_pending_inactive_shares(pool: &DelegationPool, delegator: address): u128 {
+ pool_u64::shares(pending_inactive_shares_pool(pool), delegator)
+}
+
+
+
+
+fun get_used_voting_power(governance_records: &delegation_pool::GovernanceRecords, voter: address, proposal_id: u64): u64
+
+
+
+
+fun get_used_voting_power(governance_records: &GovernanceRecords, voter: address, proposal_id: u64): u64 {
+ let votes = &governance_records.votes;
+ let key = VotingRecordKey {
+ voter,
+ proposal_id,
+ };
+ *smart_table::borrow_with_default(votes, key, &0)
+}
+
+
+
+
+fun create_resource_account_seed(delegation_pool_creation_seed: vector<u8>): vector<u8>
+
+
+
+
+fun create_resource_account_seed(
+ delegation_pool_creation_seed: vector<u8>,
+): vector<u8> {
+ let seed = vector::empty<u8>();
+ // include module salt (before any subseeds) to avoid conflicts with other modules creating resource accounts
+ vector::append(&mut seed, MODULE_SALT);
+ // include an additional salt in case the same resource account has already been created
+ vector::append(&mut seed, delegation_pool_creation_seed);
+ seed
+}
+
+
+
+
+fun borrow_mut_used_voting_power(governance_records: &mut delegation_pool::GovernanceRecords, voter: address, proposal_id: u64): &mut u64
+
+
+
+
+inline fun borrow_mut_used_voting_power(
+ governance_records: &mut GovernanceRecords,
+ voter: address,
+ proposal_id: u64
+): &mut u64 {
+ let votes = &mut governance_records.votes;
+ let key = VotingRecordKey {
+ proposal_id,
+ voter,
+ };
+ smart_table::borrow_mut_with_default(votes, key, 0)
+}
+
+
+
+
+fun update_and_borrow_mut_delegator_vote_delegation(pool: &delegation_pool::DelegationPool, governance_records: &mut delegation_pool::GovernanceRecords, delegator: address): &mut delegation_pool::VoteDelegation
+
+
+
+
+fun update_and_borrow_mut_delegator_vote_delegation(
+ pool: &DelegationPool,
+ governance_records: &mut GovernanceRecords,
+ delegator: address
+): &mut VoteDelegation {
+ let pool_address = get_pool_address(pool);
+ let locked_until_secs = stake::get_lockup_secs(pool_address);
+
+ let vote_delegation_table = &mut governance_records.vote_delegation;
+ // By default, a delegator's delegated voter is itself.
+ // TODO: recycle storage when VoteDelegation equals to default value.
+ if (!smart_table::contains(vote_delegation_table, delegator)) {
+ return smart_table::borrow_mut_with_default(vote_delegation_table, delegator, VoteDelegation {
+ voter: delegator,
+ last_locked_until_secs: locked_until_secs,
+ pending_voter: delegator,
+ })
+ };
+
+ let vote_delegation = smart_table::borrow_mut(vote_delegation_table, delegator);
+ // A lockup period has passed since last time `vote_delegation` was updated. Pending voter takes effect.
+ if (vote_delegation.last_locked_until_secs < locked_until_secs) {
+ vote_delegation.voter = vote_delegation.pending_voter;
+ vote_delegation.last_locked_until_secs = locked_until_secs;
+ };
+ vote_delegation
+}
+
+
+
+
+fun update_and_borrow_mut_delegated_votes(pool: &delegation_pool::DelegationPool, governance_records: &mut delegation_pool::GovernanceRecords, voter: address): &mut delegation_pool::DelegatedVotes
+
+
+
+
+fun update_and_borrow_mut_delegated_votes(
+ pool: &DelegationPool,
+ governance_records: &mut GovernanceRecords,
+ voter: address
+): &mut DelegatedVotes {
+ let pool_address = get_pool_address(pool);
+ let locked_until_secs = stake::get_lockup_secs(pool_address);
+
+ let delegated_votes_per_voter = &mut governance_records.delegated_votes;
+ // By default, a delegator's voter is itself.
+ // TODO: recycle storage when DelegatedVotes equals to default value.
+ if (!smart_table::contains(delegated_votes_per_voter, voter)) {
+ let active_shares = get_delegator_active_shares(pool, voter);
+ let inactive_shares = get_delegator_pending_inactive_shares(pool, voter);
+ return smart_table::borrow_mut_with_default(delegated_votes_per_voter, voter, DelegatedVotes {
+ active_shares,
+ pending_inactive_shares: inactive_shares,
+ active_shares_next_lockup: active_shares,
+ last_locked_until_secs: locked_until_secs,
+ })
+ };
+
+ let delegated_votes = smart_table::borrow_mut(delegated_votes_per_voter, voter);
+ // A lockup period has passed since last time `delegated_votes` was updated. Pending voter takes effect.
+ if (delegated_votes.last_locked_until_secs < locked_until_secs) {
+ delegated_votes.active_shares = delegated_votes.active_shares_next_lockup;
+ delegated_votes.pending_inactive_shares = 0;
+ delegated_votes.last_locked_until_secs = locked_until_secs;
+ };
+ delegated_votes
+}
+
+
+
+
+fun olc_with_index(index: u64): delegation_pool::ObservedLockupCycle
+
+
+
+
+fun olc_with_index(index: u64): ObservedLockupCycle {
+ ObservedLockupCycle { index }
+}
+
+
+
+
+active_shares
pool and inactive_shares
pool, calculate the total voting
+power, which equals to the sum of the coin amounts.
+
+
+fun calculate_total_voting_power(delegation_pool: &delegation_pool::DelegationPool, latest_delegated_votes: &delegation_pool::DelegatedVotes): u64
+
+
+
+
+fun calculate_total_voting_power(delegation_pool: &DelegationPool, latest_delegated_votes: &DelegatedVotes): u64 {
+ let active_amount = pool_u64::shares_to_amount(
+ &delegation_pool.active_shares,
+ latest_delegated_votes.active_shares);
+ let pending_inactive_amount = pool_u64::shares_to_amount(
+ pending_inactive_shares_pool(delegation_pool),
+ latest_delegated_votes.pending_inactive_shares);
+ active_amount + pending_inactive_amount
+}
+
+
+
+
+fun calculate_and_update_delegator_voter_internal(pool: &delegation_pool::DelegationPool, governance_records: &mut delegation_pool::GovernanceRecords, delegator: address): address
+
+
+
+
+fun calculate_and_update_delegator_voter_internal(
+ pool: &DelegationPool,
+ governance_records: &mut GovernanceRecords,
+ delegator: address
+): address {
+ let vote_delegation = update_and_borrow_mut_delegator_vote_delegation(pool, governance_records, delegator);
+ vote_delegation.voter
+}
+
+
+
+
+fun calculate_and_update_delegated_votes(pool: &delegation_pool::DelegationPool, governance_records: &mut delegation_pool::GovernanceRecords, voter: address): u64
+
+
+
+
+fun calculate_and_update_delegated_votes(
+ pool: &DelegationPool,
+ governance_records: &mut GovernanceRecords,
+ voter: address
+): u64 {
+ let delegated_votes = update_and_borrow_mut_delegated_votes(pool, governance_records, voter);
+ calculate_total_voting_power(pool, delegated_votes)
+}
+
+
+
+
+fun borrow_mut_delegators_allowlist(pool_address: address): &mut smart_table::SmartTable<address, bool>
+
+
+
+
+inline fun borrow_mut_delegators_allowlist(
+ pool_address: address
+): &mut SmartTable<address, bool> acquires DelegationPoolAllowlisting {
+ &mut borrow_global_mut<DelegationPoolAllowlisting>(pool_address).allowlist
+}
+
+
+
+
+public entry fun set_operator(owner: &signer, new_operator: address)
+
+
+
+
+public entry fun set_operator(
+ owner: &signer,
+ new_operator: address
+) acquires DelegationPoolOwnership, DelegationPool, GovernanceRecords, BeneficiaryForOperator, NextCommissionPercentage {
+ let pool_address = get_owned_pool_address(signer::address_of(owner));
+ // synchronize delegation and stake pools before any user operation
+ // ensure the old operator is paid its uncommitted commission rewards
+ synchronize_delegation_pool(pool_address);
+ stake::set_operator(&retrieve_stake_pool_owner(borrow_global<DelegationPool>(pool_address)), new_operator);
+}
+
+
+
+
+synchronize_delegation_pool
+before switching the beneficiary. An operator can set one beneficiary for delegation pools, not a separate
+one for each pool.
+
+
+public entry fun set_beneficiary_for_operator(operator: &signer, new_beneficiary: address)
+
+
+
+
+public entry fun set_beneficiary_for_operator(
+ operator: &signer,
+ new_beneficiary: address
+) acquires BeneficiaryForOperator {
+ assert!(features::operator_beneficiary_change_enabled(), std::error::invalid_state(
+ EOPERATOR_BENEFICIARY_CHANGE_NOT_SUPPORTED
+ ));
+ // The beneficiay address of an operator is stored under the operator's address.
+ // So, the operator does not need to be validated with respect to a staking pool.
+ let operator_addr = signer::address_of(operator);
+ let old_beneficiary = beneficiary_for_operator(operator_addr);
+ if (exists<BeneficiaryForOperator>(operator_addr)) {
+ borrow_global_mut<BeneficiaryForOperator>(operator_addr).beneficiary_for_operator = new_beneficiary;
+ } else {
+ move_to(operator, BeneficiaryForOperator { beneficiary_for_operator: new_beneficiary });
+ };
+
+ emit(SetBeneficiaryForOperator {
+ operator: operator_addr,
+ old_beneficiary,
+ new_beneficiary,
+ });
+}
+
+
+
+
+public entry fun update_commission_percentage(owner: &signer, new_commission_percentage: u64)
+
+
+
+
+public entry fun update_commission_percentage(
+ owner: &signer,
+ new_commission_percentage: u64
+) acquires DelegationPoolOwnership, DelegationPool, GovernanceRecords, BeneficiaryForOperator, NextCommissionPercentage {
+ assert!(features::commission_change_delegation_pool_enabled(), error::invalid_state(
+ ECOMMISSION_RATE_CHANGE_NOT_SUPPORTED
+ ));
+ assert!(new_commission_percentage <= MAX_FEE, error::invalid_argument(EINVALID_COMMISSION_PERCENTAGE));
+ let owner_address = signer::address_of(owner);
+ let pool_address = get_owned_pool_address(owner_address);
+ assert!(
+ operator_commission_percentage(pool_address) + MAX_COMMISSION_INCREASE >= new_commission_percentage,
+ error::invalid_argument(ETOO_LARGE_COMMISSION_INCREASE)
+ );
+ assert!(
+ stake::get_remaining_lockup_secs(pool_address) >= min_remaining_secs_for_commission_change(),
+ error::invalid_state(ETOO_LATE_COMMISSION_CHANGE)
+ );
+
+ // synchronize delegation and stake pools before any user operation. this ensures:
+ // (1) the operator is paid its uncommitted commission rewards with the old commission percentage, and
+ // (2) any pending commission percentage change is applied before the new commission percentage is set.
+ synchronize_delegation_pool(pool_address);
+
+ if (exists<NextCommissionPercentage>(pool_address)) {
+ let commission_percentage = borrow_global_mut<NextCommissionPercentage>(pool_address);
+ commission_percentage.commission_percentage_next_lockup_cycle = new_commission_percentage;
+ commission_percentage.effective_after_secs = stake::get_lockup_secs(pool_address);
+ } else {
+ let delegation_pool = borrow_global<DelegationPool>(pool_address);
+ let pool_signer = account::create_signer_with_capability(&delegation_pool.stake_pool_signer_cap);
+ move_to(&pool_signer, NextCommissionPercentage {
+ commission_percentage_next_lockup_cycle: new_commission_percentage,
+ effective_after_secs: stake::get_lockup_secs(pool_address),
+ });
+ };
+
+ event::emit(CommissionPercentageChange {
+ pool_address,
+ owner: owner_address,
+ commission_percentage_next_lockup_cycle: new_commission_percentage,
+ });
+}
+
+
+
+
+public entry fun set_delegated_voter(owner: &signer, new_voter: address)
+
+
+
+
+public entry fun set_delegated_voter(
+ owner: &signer,
+ new_voter: address
+) acquires DelegationPoolOwnership, DelegationPool, GovernanceRecords, BeneficiaryForOperator, NextCommissionPercentage {
+ // No one can change delegated_voter once the partial governance voting feature is enabled.
+ assert!(
+ !features::delegation_pool_partial_governance_voting_enabled(),
+ error::invalid_state(EDEPRECATED_FUNCTION)
+ );
+ let pool_address = get_owned_pool_address(signer::address_of(owner));
+ // synchronize delegation and stake pools before any user operation
+ synchronize_delegation_pool(pool_address);
+ stake::set_delegated_voter(&retrieve_stake_pool_owner(borrow_global<DelegationPool>(pool_address)), new_voter);
+}
+
+
+
+
+public entry fun delegate_voting_power(delegator: &signer, pool_address: address, new_voter: address)
+
+
+
+
+public entry fun delegate_voting_power(
+ delegator: &signer,
+ pool_address: address,
+ new_voter: address
+) acquires DelegationPool, GovernanceRecords, BeneficiaryForOperator, NextCommissionPercentage {
+ assert_partial_governance_voting_enabled(pool_address);
+
+ // synchronize delegation and stake pools before any user operation
+ synchronize_delegation_pool(pool_address);
+
+ let delegator_address = signer::address_of(delegator);
+ let delegation_pool = borrow_global<DelegationPool>(pool_address);
+ let governance_records = borrow_global_mut<GovernanceRecords>(pool_address);
+ let delegator_vote_delegation = update_and_borrow_mut_delegator_vote_delegation(
+ delegation_pool,
+ governance_records,
+ delegator_address
+ );
+ let pending_voter: address = delegator_vote_delegation.pending_voter;
+
+ // No need to update if the voter doesn't really change.
+ if (pending_voter != new_voter) {
+ delegator_vote_delegation.pending_voter = new_voter;
+ let active_shares = get_delegator_active_shares(delegation_pool, delegator_address);
+ // <active shares> of <pending voter of shareholder> -= <active_shares>
+ // <active shares> of <new voter of shareholder> += <active_shares>
+ let pending_delegated_votes = update_and_borrow_mut_delegated_votes(
+ delegation_pool,
+ governance_records,
+ pending_voter
+ );
+ pending_delegated_votes.active_shares_next_lockup =
+ pending_delegated_votes.active_shares_next_lockup - active_shares;
+
+ let new_delegated_votes = update_and_borrow_mut_delegated_votes(
+ delegation_pool,
+ governance_records,
+ new_voter
+ );
+ new_delegated_votes.active_shares_next_lockup =
+ new_delegated_votes.active_shares_next_lockup + active_shares;
+ };
+
+ if (features::module_event_migration_enabled()) {
+ event::emit(DelegateVotingPower {
+ pool_address,
+ delegator: delegator_address,
+ voter: new_voter,
+ })
+ };
+
+ event::emit_event(&mut governance_records.delegate_voting_power_events, DelegateVotingPowerEvent {
+ pool_address,
+ delegator: delegator_address,
+ voter: new_voter,
+ });
+}
+
+
+
+
+public entry fun enable_delegators_allowlisting(owner: &signer)
+
+
+
+
+public entry fun enable_delegators_allowlisting(
+ owner: &signer,
+) acquires DelegationPoolOwnership, DelegationPool {
+ assert!(
+ features::delegation_pool_allowlisting_enabled(),
+ error::invalid_state(EDELEGATORS_ALLOWLISTING_NOT_SUPPORTED)
+ );
+
+ let pool_address = get_owned_pool_address(signer::address_of(owner));
+ if (allowlisting_enabled(pool_address)) { return };
+
+ let pool_signer = retrieve_stake_pool_owner(borrow_global<DelegationPool>(pool_address));
+ move_to(&pool_signer, DelegationPoolAllowlisting { allowlist: smart_table::new<address, bool>() });
+
+ event::emit(EnableDelegatorsAllowlisting { pool_address });
+}
+
+
+
+
+public entry fun disable_delegators_allowlisting(owner: &signer)
+
+
+
+
+public entry fun disable_delegators_allowlisting(
+ owner: &signer,
+) acquires DelegationPoolOwnership, DelegationPoolAllowlisting {
+ let pool_address = get_owned_pool_address(signer::address_of(owner));
+ assert_allowlisting_enabled(pool_address);
+
+ let DelegationPoolAllowlisting { allowlist } = move_from<DelegationPoolAllowlisting>(pool_address);
+ // if the allowlist becomes too large, the owner can always remove some delegators
+ smart_table::destroy(allowlist);
+
+ event::emit(DisableDelegatorsAllowlisting { pool_address });
+}
+
+
+
+
+public entry fun allowlist_delegator(owner: &signer, delegator_address: address)
+
+
+
+
+public entry fun allowlist_delegator(
+ owner: &signer,
+ delegator_address: address,
+) acquires DelegationPoolOwnership, DelegationPoolAllowlisting {
+ let pool_address = get_owned_pool_address(signer::address_of(owner));
+ assert_allowlisting_enabled(pool_address);
+
+ if (delegator_allowlisted(pool_address, delegator_address)) { return };
+
+ smart_table::add(borrow_mut_delegators_allowlist(pool_address), delegator_address, true);
+
+ event::emit(AllowlistDelegator { pool_address, delegator_address });
+}
+
+
+
+
+public entry fun remove_delegator_from_allowlist(owner: &signer, delegator_address: address)
+
+
+
+
+public entry fun remove_delegator_from_allowlist(
+ owner: &signer,
+ delegator_address: address,
+) acquires DelegationPoolOwnership, DelegationPoolAllowlisting {
+ let pool_address = get_owned_pool_address(signer::address_of(owner));
+ assert_allowlisting_enabled(pool_address);
+
+ if (!delegator_allowlisted(pool_address, delegator_address)) { return };
+
+ smart_table::remove(borrow_mut_delegators_allowlist(pool_address), delegator_address);
+
+ event::emit(RemoveDelegatorFromAllowlist { pool_address, delegator_address });
+}
+
+
+
+
+public entry fun evict_delegator(owner: &signer, delegator_address: address)
+
+
+
+
+public entry fun evict_delegator(
+ owner: &signer,
+ delegator_address: address,
+) acquires DelegationPoolOwnership, DelegationPool, GovernanceRecords, BeneficiaryForOperator, NextCommissionPercentage, DelegationPoolAllowlisting {
+ let pool_address = get_owned_pool_address(signer::address_of(owner));
+ assert_allowlisting_enabled(pool_address);
+ assert!(
+ !delegator_allowlisted(pool_address, delegator_address),
+ error::invalid_state(ECANNOT_EVICT_ALLOWLISTED_DELEGATOR)
+ );
+
+ // synchronize pool in order to query latest balance of delegator
+ synchronize_delegation_pool(pool_address);
+
+ let pool = borrow_global<DelegationPool>(pool_address);
+ if (get_delegator_active_shares(pool, delegator_address) == 0) { return };
+
+ unlock_internal(delegator_address, pool_address, pool_u64::balance(&pool.active_shares, delegator_address));
+
+ event::emit(EvictDelegator { pool_address, delegator_address });
+}
+
+
+
+
+amount
of coins to the delegation pool pool_address
.
+
+
+public entry fun add_stake(delegator: &signer, pool_address: address, amount: u64)
+
+
+
+
+public entry fun add_stake(
+ delegator: &signer,
+ pool_address: address,
+ amount: u64
+) acquires DelegationPool, GovernanceRecords, BeneficiaryForOperator, NextCommissionPercentage, DelegationPoolAllowlisting {
+ // short-circuit if amount to add is 0 so no event is emitted
+ if (amount == 0) { return };
+
+ let delegator_address = signer::address_of(delegator);
+ assert_delegator_allowlisted(pool_address, delegator_address);
+
+ // synchronize delegation and stake pools before any user operation
+ synchronize_delegation_pool(pool_address);
+
+ // fee to be charged for adding `amount` stake on this delegation pool at this epoch
+ let add_stake_fee = get_add_stake_fee(pool_address, amount);
+
+ let pool = borrow_global_mut<DelegationPool>(pool_address);
+
+ // stake the entire amount to the stake pool
+ aptos_account::transfer(delegator, pool_address, amount);
+ stake::add_stake(&retrieve_stake_pool_owner(pool), amount);
+
+ // but buy shares for delegator just for the remaining amount after fee
+ buy_in_active_shares(pool, delegator_address, amount - add_stake_fee);
+ assert_min_active_balance(pool, delegator_address);
+
+ // grant temporary ownership over `add_stake` fees to a separate shareholder in order to:
+ // - not mistake them for rewards to pay the operator from
+ // - distribute them together with the `active` rewards when this epoch ends
+ // in order to appreciate all shares on the active pool atomically
+ buy_in_active_shares(pool, NULL_SHAREHOLDER, add_stake_fee);
+
+ if (features::module_event_migration_enabled()) {
+ event::emit(
+ AddStake {
+ pool_address,
+ delegator_address,
+ amount_added: amount,
+ add_stake_fee,
+ },
+ );
+ };
+
+ event::emit_event(
+ &mut pool.add_stake_events,
+ AddStakeEvent {
+ pool_address,
+ delegator_address,
+ amount_added: amount,
+ add_stake_fee,
+ },
+ );
+}
+
+
+
+
+amount
from the active + pending_active stake of delegator
or
+at most how much active stake there is on the stake pool.
+
+
+public entry fun unlock(delegator: &signer, pool_address: address, amount: u64)
+
+
+
+
+public entry fun unlock(
+ delegator: &signer,
+ pool_address: address,
+ amount: u64
+) acquires DelegationPool, GovernanceRecords, BeneficiaryForOperator, NextCommissionPercentage {
+ // short-circuit if amount to unlock is 0 so no event is emitted
+ if (amount == 0) { return };
+
+ // synchronize delegation and stake pools before any user operation
+ synchronize_delegation_pool(pool_address);
+
+ let delegator_address = signer::address_of(delegator);
+ unlock_internal(delegator_address, pool_address, amount);
+}
+
+
+
+
+fun unlock_internal(delegator_address: address, pool_address: address, amount: u64)
+
+
+
+
+fun unlock_internal(
+ delegator_address: address,
+ pool_address: address,
+ amount: u64
+) acquires DelegationPool, GovernanceRecords {
+ assert!(delegator_address != NULL_SHAREHOLDER, error::invalid_argument(ECANNOT_UNLOCK_NULL_SHAREHOLDER));
+
+ // fail unlock of more stake than `active` on the stake pool
+ let (active, _, _, _) = stake::get_stake(pool_address);
+ assert!(amount <= active, error::invalid_argument(ENOT_ENOUGH_ACTIVE_STAKE_TO_UNLOCK));
+
+ let pool = borrow_global_mut<DelegationPool>(pool_address);
+ amount = coins_to_transfer_to_ensure_min_stake(
+ &pool.active_shares,
+ pending_inactive_shares_pool(pool),
+ delegator_address,
+ amount,
+ );
+ amount = redeem_active_shares(pool, delegator_address, amount);
+
+ stake::unlock(&retrieve_stake_pool_owner(pool), amount);
+
+ buy_in_pending_inactive_shares(pool, delegator_address, amount);
+ assert_min_pending_inactive_balance(pool, delegator_address);
+
+ if (features::module_event_migration_enabled()) {
+ event::emit(
+ UnlockStake {
+ pool_address,
+ delegator_address,
+ amount_unlocked: amount,
+ },
+ );
+ };
+
+ event::emit_event(
+ &mut pool.unlock_stake_events,
+ UnlockStakeEvent {
+ pool_address,
+ delegator_address,
+ amount_unlocked: amount,
+ },
+ );
+}
+
+
+
+
+amount
of coins from pending_inactive to active.
+
+
+public entry fun reactivate_stake(delegator: &signer, pool_address: address, amount: u64)
+
+
+
+
+public entry fun reactivate_stake(
+ delegator: &signer,
+ pool_address: address,
+ amount: u64
+) acquires DelegationPool, GovernanceRecords, BeneficiaryForOperator, NextCommissionPercentage, DelegationPoolAllowlisting {
+ // short-circuit if amount to reactivate is 0 so no event is emitted
+ if (amount == 0) { return };
+
+ let delegator_address = signer::address_of(delegator);
+ assert_delegator_allowlisted(pool_address, delegator_address);
+
+ // synchronize delegation and stake pools before any user operation
+ synchronize_delegation_pool(pool_address);
+
+ let pool = borrow_global_mut<DelegationPool>(pool_address);
+ amount = coins_to_transfer_to_ensure_min_stake(
+ pending_inactive_shares_pool(pool),
+ &pool.active_shares,
+ delegator_address,
+ amount,
+ );
+ let observed_lockup_cycle = pool.observed_lockup_cycle;
+ amount = redeem_inactive_shares(pool, delegator_address, amount, observed_lockup_cycle);
+
+ stake::reactivate_stake(&retrieve_stake_pool_owner(pool), amount);
+
+ buy_in_active_shares(pool, delegator_address, amount);
+ assert_min_active_balance(pool, delegator_address);
+
+ if (features::module_event_migration_enabled()) {
+ event::emit(
+ ReactivateStake {
+ pool_address,
+ delegator_address,
+ amount_reactivated: amount,
+ },
+ );
+ };
+
+ event::emit_event(
+ &mut pool.reactivate_stake_events,
+ ReactivateStakeEvent {
+ pool_address,
+ delegator_address,
+ amount_reactivated: amount,
+ },
+ );
+}
+
+
+
+
+amount
of owned inactive stake from the delegation pool at pool_address
.
+
+
+public entry fun withdraw(delegator: &signer, pool_address: address, amount: u64)
+
+
+
+
+public entry fun withdraw(
+ delegator: &signer,
+ pool_address: address,
+ amount: u64
+) acquires DelegationPool, GovernanceRecords, BeneficiaryForOperator, NextCommissionPercentage {
+ assert!(amount > 0, error::invalid_argument(EWITHDRAW_ZERO_STAKE));
+ // synchronize delegation and stake pools before any user operation
+ synchronize_delegation_pool(pool_address);
+ withdraw_internal(borrow_global_mut<DelegationPool>(pool_address), signer::address_of(delegator), amount);
+}
+
+
+
+
+fun withdraw_internal(pool: &mut delegation_pool::DelegationPool, delegator_address: address, amount: u64)
+
+
+
+
+fun withdraw_internal(
+ pool: &mut DelegationPool,
+ delegator_address: address,
+ amount: u64
+) acquires GovernanceRecords {
+ // TODO: recycle storage when a delegator fully exits the delegation pool.
+ // short-circuit if amount to withdraw is 0 so no event is emitted
+ if (amount == 0) { return };
+
+ let pool_address = get_pool_address(pool);
+ let (withdrawal_exists, withdrawal_olc) = pending_withdrawal_exists(pool, delegator_address);
+ // exit if no withdrawal or (it is pending and cannot withdraw pending_inactive stake from stake pool)
+ if (!(
+ withdrawal_exists &&
+ (withdrawal_olc.index < pool.observed_lockup_cycle.index || can_withdraw_pending_inactive(pool_address))
+ )) { return };
+
+ if (withdrawal_olc.index == pool.observed_lockup_cycle.index) {
+ amount = coins_to_redeem_to_ensure_min_stake(
+ pending_inactive_shares_pool(pool),
+ delegator_address,
+ amount,
+ )
+ };
+ amount = redeem_inactive_shares(pool, delegator_address, amount, withdrawal_olc);
+
+ let stake_pool_owner = &retrieve_stake_pool_owner(pool);
+ // stake pool will inactivate entire pending_inactive stake at `stake::withdraw` to make it withdrawable
+ // however, bypassing the inactivation of excess stake (inactivated but not withdrawn) ensures
+ // the OLC is not advanced indefinitely on `unlock`-`withdraw` paired calls
+ if (can_withdraw_pending_inactive(pool_address)) {
+ // get excess stake before being entirely inactivated
+ let (_, _, _, pending_inactive) = stake::get_stake(pool_address);
+ if (withdrawal_olc.index == pool.observed_lockup_cycle.index) {
+ // `amount` less excess if withdrawing pending_inactive stake
+ pending_inactive = pending_inactive - amount
+ };
+ // escape excess stake from inactivation
+ stake::reactivate_stake(stake_pool_owner, pending_inactive);
+ stake::withdraw(stake_pool_owner, amount);
+ // restore excess stake to the pending_inactive state
+ stake::unlock(stake_pool_owner, pending_inactive);
+ } else {
+ // no excess stake if `stake::withdraw` does not inactivate at all
+ stake::withdraw(stake_pool_owner, amount);
+ };
+ aptos_account::transfer(stake_pool_owner, delegator_address, amount);
+
+ // commit withdrawal of possibly inactive stake to the `total_coins_inactive`
+ // known by the delegation pool in order to not mistake it for slashing at next synchronization
+ let (_, inactive, _, _) = stake::get_stake(pool_address);
+ pool.total_coins_inactive = inactive;
+
+ if (features::module_event_migration_enabled()) {
+ event::emit(
+ WithdrawStake {
+ pool_address,
+ delegator_address,
+ amount_withdrawn: amount,
+ },
+ );
+ };
+
+ event::emit_event(
+ &mut pool.withdraw_stake_events,
+ WithdrawStakeEvent {
+ pool_address,
+ delegator_address,
+ amount_withdrawn: amount,
+ },
+ );
+}
+
+
+
+
+delegator_address
may have
+unlocking (or already unlocked) stake to be withdrawn from delegation pool pool
.
+A bool is returned to signal if a pending withdrawal exists at all.
+
+
+fun pending_withdrawal_exists(pool: &delegation_pool::DelegationPool, delegator_address: address): (bool, delegation_pool::ObservedLockupCycle)
+
+
+
+
+fun pending_withdrawal_exists(pool: &DelegationPool, delegator_address: address): (bool, ObservedLockupCycle) {
+ if (table::contains(&pool.pending_withdrawals, delegator_address)) {
+ (true, *table::borrow(&pool.pending_withdrawals, delegator_address))
+ } else {
+ (false, olc_with_index(0))
+ }
+}
+
+
+
+
+pending_inactive
stake on the
+delegation pool, always the last item in inactive_shares
.
+
+
+fun pending_inactive_shares_pool_mut(pool: &mut delegation_pool::DelegationPool): &mut pool_u64_unbound::Pool
+
+
+
+
+fun pending_inactive_shares_pool_mut(pool: &mut DelegationPool): &mut pool_u64::Pool {
+ let observed_lockup_cycle = pool.observed_lockup_cycle;
+ table::borrow_mut(&mut pool.inactive_shares, observed_lockup_cycle)
+}
+
+
+
+
+fun pending_inactive_shares_pool(pool: &delegation_pool::DelegationPool): &pool_u64_unbound::Pool
+
+
+
+
+fun pending_inactive_shares_pool(pool: &DelegationPool): &pool_u64::Pool {
+ table::borrow(&pool.inactive_shares, pool.observed_lockup_cycle)
+}
+
+
+
+
+delegator_address
on delegation pool pool
+if existing and already inactive to allow the creation of a new one.
+pending_inactive
stake would be left untouched even if withdrawable and should
+be explicitly withdrawn by delegator
+
+
+fun execute_pending_withdrawal(pool: &mut delegation_pool::DelegationPool, delegator_address: address)
+
+
+
+
+fun execute_pending_withdrawal(pool: &mut DelegationPool, delegator_address: address) acquires GovernanceRecords {
+ let (withdrawal_exists, withdrawal_olc) = pending_withdrawal_exists(pool, delegator_address);
+ if (withdrawal_exists && withdrawal_olc.index < pool.observed_lockup_cycle.index) {
+ withdraw_internal(pool, delegator_address, MAX_U64);
+ }
+}
+
+
+
+
+shareholder
who
+deposited coins_amount
. This function doesn't make any coin transfer.
+
+
+fun buy_in_active_shares(pool: &mut delegation_pool::DelegationPool, shareholder: address, coins_amount: u64): u128
+
+
+
+
+fun buy_in_active_shares(
+ pool: &mut DelegationPool,
+ shareholder: address,
+ coins_amount: u64,
+): u128 acquires GovernanceRecords {
+ let new_shares = pool_u64::amount_to_shares(&pool.active_shares, coins_amount);
+ // No need to buy 0 shares.
+ if (new_shares == 0) { return 0 };
+
+ // Always update governance records before any change to the shares pool.
+ let pool_address = get_pool_address(pool);
+ if (partial_governance_voting_enabled(pool_address)) {
+ update_governance_records_for_buy_in_active_shares(pool, pool_address, new_shares, shareholder);
+ };
+
+ pool_u64::buy_in(&mut pool.active_shares, shareholder, coins_amount);
+ new_shares
+}
+
+
+
+
+shareholder
who
+redeemed coins_amount
from the active pool to schedule it for unlocking.
+If delegator's pending withdrawal exists and has been inactivated, execute it firstly
+to ensure there is always only one withdrawal request.
+
+
+fun buy_in_pending_inactive_shares(pool: &mut delegation_pool::DelegationPool, shareholder: address, coins_amount: u64): u128
+
+
+
+
+fun buy_in_pending_inactive_shares(
+ pool: &mut DelegationPool,
+ shareholder: address,
+ coins_amount: u64,
+): u128 acquires GovernanceRecords {
+ let new_shares = pool_u64::amount_to_shares(pending_inactive_shares_pool(pool), coins_amount);
+ // never create a new pending withdrawal unless delegator owns some pending_inactive shares
+ if (new_shares == 0) { return 0 };
+
+ // Always update governance records before any change to the shares pool.
+ let pool_address = get_pool_address(pool);
+ if (partial_governance_voting_enabled(pool_address)) {
+ update_governance_records_for_buy_in_pending_inactive_shares(pool, pool_address, new_shares, shareholder);
+ };
+
+ // cannot buy inactive shares, only pending_inactive at current lockup cycle
+ pool_u64::buy_in(pending_inactive_shares_pool_mut(pool), shareholder, coins_amount);
+
+ // execute the pending withdrawal if exists and is inactive before creating a new one
+ execute_pending_withdrawal(pool, shareholder);
+
+ // save observed lockup cycle for the new pending withdrawal
+ let observed_lockup_cycle = pool.observed_lockup_cycle;
+ assert!(*table::borrow_mut_with_default(
+ &mut pool.pending_withdrawals,
+ shareholder,
+ observed_lockup_cycle
+ ) == observed_lockup_cycle,
+ error::invalid_state(EPENDING_WITHDRAWAL_EXISTS)
+ );
+
+ new_shares
+}
+
+
+
+
+coins_amount
of coins to be redeemed from shares pool shares_pool
+to the exact number of shares to redeem in order to achieve this.
+
+
+fun amount_to_shares_to_redeem(shares_pool: &pool_u64_unbound::Pool, shareholder: address, coins_amount: u64): u128
+
+
+
+
+fun amount_to_shares_to_redeem(
+ shares_pool: &pool_u64::Pool,
+ shareholder: address,
+ coins_amount: u64,
+): u128 {
+ if (coins_amount >= pool_u64::balance(shares_pool, shareholder)) {
+ // cap result at total shares of shareholder to pass `EINSUFFICIENT_SHARES` on subsequent redeem
+ pool_u64::shares(shares_pool, shareholder)
+ } else {
+ pool_u64::amount_to_shares(shares_pool, coins_amount)
+ }
+}
+
+
+
+
+shareholder
who
+wants to unlock coins_amount
of its active stake.
+Extracted coins will be used to buy shares into the pending_inactive pool and
+be available for withdrawal when current OLC ends.
+
+
+fun redeem_active_shares(pool: &mut delegation_pool::DelegationPool, shareholder: address, coins_amount: u64): u64
+
+
+
+
+fun redeem_active_shares(
+ pool: &mut DelegationPool,
+ shareholder: address,
+ coins_amount: u64,
+): u64 acquires GovernanceRecords {
+ let shares_to_redeem = amount_to_shares_to_redeem(&pool.active_shares, shareholder, coins_amount);
+ // silently exit if not a shareholder otherwise redeem would fail with `ESHAREHOLDER_NOT_FOUND`
+ if (shares_to_redeem == 0) return 0;
+
+ // Always update governance records before any change to the shares pool.
+ let pool_address = get_pool_address(pool);
+ if (partial_governance_voting_enabled(pool_address)) {
+ update_governanace_records_for_redeem_active_shares(pool, pool_address, shares_to_redeem, shareholder);
+ };
+
+ pool_u64::redeem_shares(&mut pool.active_shares, shareholder, shares_to_redeem)
+}
+
+
+
+
+lockup_cycle
< current OLC on behalf of
+delegator shareholder
who wants to withdraw coins_amount
of its unlocked stake.
+Redeem shares from the pending_inactive pool at lockup_cycle
== current OLC on behalf of
+delegator shareholder
who wants to reactivate coins_amount
of its unlocking stake.
+For latter case, extracted coins will be used to buy shares into the active pool and
+escape inactivation when current lockup ends.
+
+
+fun redeem_inactive_shares(pool: &mut delegation_pool::DelegationPool, shareholder: address, coins_amount: u64, lockup_cycle: delegation_pool::ObservedLockupCycle): u64
+
+
+
+
+fun redeem_inactive_shares(
+ pool: &mut DelegationPool,
+ shareholder: address,
+ coins_amount: u64,
+ lockup_cycle: ObservedLockupCycle,
+): u64 acquires GovernanceRecords {
+ let shares_to_redeem = amount_to_shares_to_redeem(
+ table::borrow(&pool.inactive_shares, lockup_cycle),
+ shareholder,
+ coins_amount);
+ // silently exit if not a shareholder otherwise redeem would fail with `ESHAREHOLDER_NOT_FOUND`
+ if (shares_to_redeem == 0) return 0;
+
+ // Always update governance records before any change to the shares pool.
+ let pool_address = get_pool_address(pool);
+ // Only redeem shares from the pending_inactive pool at `lockup_cycle` == current OLC.
+ if (partial_governance_voting_enabled(pool_address) && lockup_cycle.index == pool.observed_lockup_cycle.index) {
+ update_governanace_records_for_redeem_pending_inactive_shares(
+ pool,
+ pool_address,
+ shares_to_redeem,
+ shareholder
+ );
+ };
+
+ let inactive_shares = table::borrow_mut(&mut pool.inactive_shares, lockup_cycle);
+ // 1. reaching here means delegator owns inactive/pending_inactive shares at OLC `lockup_cycle`
+ let redeemed_coins = pool_u64::redeem_shares(inactive_shares, shareholder, shares_to_redeem);
+
+ // if entirely reactivated pending_inactive stake or withdrawn inactive one,
+ // re-enable unlocking for delegator by deleting this pending withdrawal
+ if (pool_u64::shares(inactive_shares, shareholder) == 0) {
+ // 2. a delegator owns inactive/pending_inactive shares only at the OLC of its pending withdrawal
+ // 1 & 2: the pending withdrawal itself has been emptied of shares and can be safely deleted
+ table::remove(&mut pool.pending_withdrawals, shareholder);
+ };
+ // destroy inactive shares pool of past OLC if all its stake has been withdrawn
+ if (lockup_cycle.index < pool.observed_lockup_cycle.index && total_coins(inactive_shares) == 0) {
+ pool_u64::destroy_empty(table::remove(&mut pool.inactive_shares, lockup_cycle));
+ };
+
+ redeemed_coins
+}
+
+
+
+
+fun calculate_stake_pool_drift(pool: &delegation_pool::DelegationPool): (bool, u64, u64, u64, u64)
+
+
+
+
+fun calculate_stake_pool_drift(pool: &DelegationPool): (bool, u64, u64, u64, u64) {
+ let (active, inactive, pending_active, pending_inactive) = stake::get_stake(get_pool_address(pool));
+ assert!(
+ inactive >= pool.total_coins_inactive,
+ error::invalid_state(ESLASHED_INACTIVE_STAKE_ON_PAST_OLC)
+ );
+ // determine whether a new lockup cycle has been ended on the stake pool and
+ // inactivated SOME `pending_inactive` stake which should stop earning rewards now,
+ // thus requiring separation of the `pending_inactive` stake on current observed lockup
+ // and the future one on the newly started lockup
+ let lockup_cycle_ended = inactive > pool.total_coins_inactive;
+
+ // actual coins on stake pool belonging to the active shares pool
+ active = active + pending_active;
+ // actual coins on stake pool belonging to the shares pool hosting `pending_inactive` stake
+ // at current observed lockup cycle, either pending: `pending_inactive` or already inactivated:
+ if (lockup_cycle_ended) {
+ // `inactive` on stake pool = any previous `inactive` stake +
+ // any previous `pending_inactive` stake and its rewards (both inactivated)
+ pending_inactive = inactive - pool.total_coins_inactive
+ };
+
+ // on stake-management operations, total coins on the internal shares pools and individual
+ // stakes on the stake pool are updated simultaneously, thus the only stakes becoming
+ // unsynced are rewards and slashes routed exclusively to/out the stake pool
+
+ // operator `active` rewards not persisted yet to the active shares pool
+ let pool_active = total_coins(&pool.active_shares);
+ let commission_active = if (active > pool_active) {
+ math64::mul_div(active - pool_active, pool.operator_commission_percentage, MAX_FEE)
+ } else {
+ // handle any slashing applied to `active` stake
+ 0
+ };
+ // operator `pending_inactive` rewards not persisted yet to the pending_inactive shares pool
+ let pool_pending_inactive = total_coins(pending_inactive_shares_pool(pool));
+ let commission_pending_inactive = if (pending_inactive > pool_pending_inactive) {
+ math64::mul_div(
+ pending_inactive - pool_pending_inactive,
+ pool.operator_commission_percentage,
+ MAX_FEE
+ )
+ } else {
+ // handle any slashing applied to `pending_inactive` stake
+ 0
+ };
+
+ (lockup_cycle_ended, active, pending_inactive, commission_active, commission_pending_inactive)
+}
+
+
+
+
+public entry fun synchronize_delegation_pool(pool_address: address)
+
+
+
+
+public entry fun synchronize_delegation_pool(
+ pool_address: address
+) acquires DelegationPool, GovernanceRecords, BeneficiaryForOperator, NextCommissionPercentage {
+ assert_delegation_pool_exists(pool_address);
+ let pool = borrow_global_mut<DelegationPool>(pool_address);
+ let (
+ lockup_cycle_ended,
+ active,
+ pending_inactive,
+ commission_active,
+ commission_pending_inactive
+ ) = calculate_stake_pool_drift(pool);
+
+ // zero `pending_active` stake indicates that either there are no `add_stake` fees or
+ // previous epoch has ended and should release the shares owning the existing fees
+ let (_, _, pending_active, _) = stake::get_stake(pool_address);
+ if (pending_active == 0) {
+ // renounce ownership over the `add_stake` fees by redeeming all shares of
+ // the special shareholder, implicitly their equivalent coins, out of the active shares pool
+ redeem_active_shares(pool, NULL_SHAREHOLDER, MAX_U64);
+ };
+
+ // distribute rewards remaining after commission, to delegators (to already existing shares)
+ // before buying shares for the operator for its entire commission fee
+ // otherwise, operator's new shares would additionally appreciate from rewards it does not own
+
+ // update total coins accumulated by `active` + `pending_active` shares
+ // redeemed `add_stake` fees are restored and distributed to the rest of the pool as rewards
+ pool_u64::update_total_coins(&mut pool.active_shares, active - commission_active);
+ // update total coins accumulated by `pending_inactive` shares at current observed lockup cycle
+ pool_u64::update_total_coins(
+ pending_inactive_shares_pool_mut(pool),
+ pending_inactive - commission_pending_inactive
+ );
+
+ // reward operator its commission out of uncommitted active rewards (`add_stake` fees already excluded)
+ buy_in_active_shares(pool, beneficiary_for_operator(stake::get_operator(pool_address)), commission_active);
+ // reward operator its commission out of uncommitted pending_inactive rewards
+ buy_in_pending_inactive_shares(
+ pool,
+ beneficiary_for_operator(stake::get_operator(pool_address)),
+ commission_pending_inactive
+ );
+
+ event::emit_event(
+ &mut pool.distribute_commission_events,
+ DistributeCommissionEvent {
+ pool_address,
+ operator: stake::get_operator(pool_address),
+ commission_active,
+ commission_pending_inactive,
+ },
+ );
+
+ if (features::operator_beneficiary_change_enabled()) {
+ emit(DistributeCommission {
+ pool_address,
+ operator: stake::get_operator(pool_address),
+ beneficiary: beneficiary_for_operator(stake::get_operator(pool_address)),
+ commission_active,
+ commission_pending_inactive,
+ })
+ };
+
+ // advance lockup cycle on delegation pool if already ended on stake pool (AND stake explicitly inactivated)
+ if (lockup_cycle_ended) {
+ // capture inactive coins over all ended lockup cycles (including this ending one)
+ let (_, inactive, _, _) = stake::get_stake(pool_address);
+ pool.total_coins_inactive = inactive;
+
+ // advance lockup cycle on the delegation pool
+ pool.observed_lockup_cycle.index = pool.observed_lockup_cycle.index + 1;
+ // start new lockup cycle with a fresh shares pool for `pending_inactive` stake
+ table::add(
+ &mut pool.inactive_shares,
+ pool.observed_lockup_cycle,
+ pool_u64::create_with_scaling_factor(SHARES_SCALING_FACTOR)
+ );
+ };
+
+ if (is_next_commission_percentage_effective(pool_address)) {
+ pool.operator_commission_percentage = borrow_global<NextCommissionPercentage>(
+ pool_address
+ ).commission_percentage_next_lockup_cycle;
+ }
+}
+
+
+
+
+fun assert_and_update_proposal_used_voting_power(governance_records: &mut delegation_pool::GovernanceRecords, pool_address: address, proposal_id: u64, voting_power: u64)
+
+
+
+
+inline fun assert_and_update_proposal_used_voting_power(
+ governance_records: &mut GovernanceRecords, pool_address: address, proposal_id: u64, voting_power: u64
+) {
+ let stake_pool_remaining_voting_power = aptos_governance::get_remaining_voting_power(pool_address, proposal_id);
+ let stake_pool_used_voting_power = aptos_governance::get_voting_power(
+ pool_address
+ ) - stake_pool_remaining_voting_power;
+ let proposal_used_voting_power = smart_table::borrow_mut_with_default(
+ &mut governance_records.votes_per_proposal,
+ proposal_id,
+ 0
+ );
+ // A edge case: Before enabling partial governance voting on a delegation pool, the delegation pool has
+ // a voter which can vote with all voting power of this delegation pool. If the voter votes on a proposal after
+ // partial governance voting flag is enabled, the delegation pool doesn't have enough voting power on this
+ // proposal for all the delegators. To be fair, no one can vote on this proposal through this delegation pool.
+ // To detect this case, check if the stake pool had used voting power not through delegation_pool module.
+ assert!(
+ stake_pool_used_voting_power == *proposal_used_voting_power,
+ error::invalid_argument(EALREADY_VOTED_BEFORE_ENABLE_PARTIAL_VOTING)
+ );
+ *proposal_used_voting_power = *proposal_used_voting_power + voting_power;
+}
+
+
+
+
+fun update_governance_records_for_buy_in_active_shares(pool: &delegation_pool::DelegationPool, pool_address: address, new_shares: u128, shareholder: address)
+
+
+
+
+fun update_governance_records_for_buy_in_active_shares(
+ pool: &DelegationPool, pool_address: address, new_shares: u128, shareholder: address
+) acquires GovernanceRecords {
+ // <active shares> of <shareholder> += <new_shares> ---->
+ // <active shares> of <current voter of shareholder> += <new_shares>
+ // <active shares> of <next voter of shareholder> += <new_shares>
+ let governance_records = borrow_global_mut<GovernanceRecords>(pool_address);
+ let vote_delegation = update_and_borrow_mut_delegator_vote_delegation(pool, governance_records, shareholder);
+ let current_voter = vote_delegation.voter;
+ let pending_voter = vote_delegation.pending_voter;
+ let current_delegated_votes =
+ update_and_borrow_mut_delegated_votes(pool, governance_records, current_voter);
+ current_delegated_votes.active_shares = current_delegated_votes.active_shares + new_shares;
+ if (pending_voter == current_voter) {
+ current_delegated_votes.active_shares_next_lockup =
+ current_delegated_votes.active_shares_next_lockup + new_shares;
+ } else {
+ let pending_delegated_votes =
+ update_and_borrow_mut_delegated_votes(pool, governance_records, pending_voter);
+ pending_delegated_votes.active_shares_next_lockup =
+ pending_delegated_votes.active_shares_next_lockup + new_shares;
+ };
+}
+
+
+
+
+fun update_governance_records_for_buy_in_pending_inactive_shares(pool: &delegation_pool::DelegationPool, pool_address: address, new_shares: u128, shareholder: address)
+
+
+
+
+fun update_governance_records_for_buy_in_pending_inactive_shares(
+ pool: &DelegationPool, pool_address: address, new_shares: u128, shareholder: address
+) acquires GovernanceRecords {
+ // <pending inactive shares> of <shareholder> += <new_shares> ---->
+ // <pending inactive shares> of <current voter of shareholder> += <new_shares>
+ // no impact on <pending inactive shares> of <next voter of shareholder>
+ let governance_records = borrow_global_mut<GovernanceRecords>(pool_address);
+ let current_voter = calculate_and_update_delegator_voter_internal(pool, governance_records, shareholder);
+ let current_delegated_votes = update_and_borrow_mut_delegated_votes(pool, governance_records, current_voter);
+ current_delegated_votes.pending_inactive_shares = current_delegated_votes.pending_inactive_shares + new_shares;
+}
+
+
+
+
+fun update_governanace_records_for_redeem_active_shares(pool: &delegation_pool::DelegationPool, pool_address: address, shares_to_redeem: u128, shareholder: address)
+
+
+
+
+fun update_governanace_records_for_redeem_active_shares(
+ pool: &DelegationPool, pool_address: address, shares_to_redeem: u128, shareholder: address
+) acquires GovernanceRecords {
+ // <active shares> of <shareholder> -= <shares_to_redeem> ---->
+ // <active shares> of <current voter of shareholder> -= <shares_to_redeem>
+ // <active shares> of <next voter of shareholder> -= <shares_to_redeem>
+ let governance_records = borrow_global_mut<GovernanceRecords>(pool_address);
+ let vote_delegation = update_and_borrow_mut_delegator_vote_delegation(
+ pool,
+ governance_records,
+ shareholder
+ );
+ let current_voter = vote_delegation.voter;
+ let pending_voter = vote_delegation.pending_voter;
+ let current_delegated_votes = update_and_borrow_mut_delegated_votes(pool, governance_records, current_voter);
+ current_delegated_votes.active_shares = current_delegated_votes.active_shares - shares_to_redeem;
+ if (current_voter == pending_voter) {
+ current_delegated_votes.active_shares_next_lockup =
+ current_delegated_votes.active_shares_next_lockup - shares_to_redeem;
+ } else {
+ let pending_delegated_votes =
+ update_and_borrow_mut_delegated_votes(pool, governance_records, pending_voter);
+ pending_delegated_votes.active_shares_next_lockup =
+ pending_delegated_votes.active_shares_next_lockup - shares_to_redeem;
+ };
+}
+
+
+
+
+fun update_governanace_records_for_redeem_pending_inactive_shares(pool: &delegation_pool::DelegationPool, pool_address: address, shares_to_redeem: u128, shareholder: address)
+
+
+
+
+fun update_governanace_records_for_redeem_pending_inactive_shares(
+ pool: &DelegationPool, pool_address: address, shares_to_redeem: u128, shareholder: address
+) acquires GovernanceRecords {
+ // <pending inactive shares> of <shareholder> -= <shares_to_redeem> ---->
+ // <pending inactive shares> of <current voter of shareholder> -= <shares_to_redeem>
+ // no impact on <pending inactive shares> of <next voter of shareholder>
+ let governance_records = borrow_global_mut<GovernanceRecords>(pool_address);
+ let current_voter = calculate_and_update_delegator_voter_internal(pool, governance_records, shareholder);
+ let current_delegated_votes = update_and_borrow_mut_delegated_votes(pool, governance_records, current_voter);
+ current_delegated_votes.pending_inactive_shares = current_delegated_votes.pending_inactive_shares - shares_to_redeem;
+}
+
+
+
+
+#[deprecated]
+public fun multiply_then_divide(x: u64, y: u64, z: u64): u64
+
+
+
+
+public fun multiply_then_divide(x: u64, y: u64, z: u64): u64 {
+ math64::mul_div(x, y, z)
+}
+
+
+
+
+No. | Requirement | Criticality | Implementation | Enforcement | +
---|---|---|---|---|
1 | +Every DelegationPool has only one corresponding StakePool stored at the same address. | +Critical | +Upon calling the initialize_delegation_pool function, a resource account is created from the "owner" signer to host the delegation pool resource and own the underlying stake pool. | +Audited that the address of StakePool equals address of DelegationPool and the data invariant on the DelegationPool. | +
2 | +The signer capability within the delegation pool has an address equal to the address of the delegation pool. | +Critical | +The initialize_delegation_pool function moves the DelegationPool resource to the address associated with stake_pool_signer, which also possesses the signer capability. | +Audited that the address of signer cap equals address of DelegationPool. | +
3 | +A delegator holds shares exclusively in one inactive shares pool, which could either be an already inactive pool or the pending_inactive pool. | +High | +The get_stake function returns the inactive stake owned by a delegator and checks which state the shares are in via the get_pending_withdrawal function. | +Audited that either inactive or pending_inactive stake after invoking the get_stake function is zero and both are never non-zero. | +
4 | +The specific pool in which the delegator possesses inactive shares becomes designated as the pending withdrawal pool for that delegator. | +Medium | +The get_pending_withdrawal function checks if any pending withdrawal exists for a delegate address and if there is neither inactive nor pending_inactive stake, the pending_withdrawal_exists returns false. | +This has been audited. | +
5 | +The existence of a pending withdrawal implies that it is associated with a pool where the delegator possesses inactive shares. | +Medium | +In the get_pending_withdrawal function, if withdrawal_exists is true, the function returns true and a non-zero amount | +get_pending_withdrawal has been audited. | +
6 | +An inactive shares pool should have coins allocated to it; otherwise, it should become deleted. | +Medium | +The redeem_inactive_shares function has a check that destroys the inactive shares pool, given that it is empty. | +shares pools have been audited. | +
7 | +The index of the pending withdrawal will not exceed the current OLC on DelegationPool. | +High | +The get_pending_withdrawal function has a check which ensures that withdrawal_olc.index < pool.observed_lockup_cycle.index. | +This has been audited. | +
8 | +Slashing is not possible for inactive stakes. | +Critical | +The number of inactive staked coins must be greater than or equal to the total_coins_inactive of the pool. | +This has been audited. | +
9 | +The delegator's active or pending inactive stake will always meet or exceed the minimum allowed value. | +Medium | +The add_stake, unlock and reactivate_stake functions ensure the active_shares or pending_inactive_shares balance for the delegator is greater than or equal to the MIN_COINS_ON_SHARES_POOL value. | +Audited the comparison of active_shares or inactive_shares balance for the delegator with the MIN_COINS_ON_SHARES_POOL value. | +
10 | +The delegation pool exists at a given address. | +Low | +Functions that operate on the DelegationPool abort if there is no DelegationPool struct under the given pool_address. | +Audited that there is no DelegationPool structure assigned to the pool_address given as a parameter. | +
11 | +The initialization of the delegation pool is contingent upon enabling the delegation pools feature. | +Critical | +The initialize_delegation_pool function should proceed if the DELEGATION_POOLS feature is enabled. | +This has been audited. | +
pragma verify=false;
+
+
+
+[move-book]: https://aptos.dev/move/book/SUMMARY
diff --git a/aptos-move/framework/aptos-framework/tests/compiler-v2-doc/dispatchable_fungible_asset.md b/aptos-move/framework/aptos-framework/tests/compiler-v2-doc/dispatchable_fungible_asset.md
new file mode 100644
index 0000000000000..c9a4a263f1a2a
--- /dev/null
+++ b/aptos-move/framework/aptos-framework/tests/compiler-v2-doc/dispatchable_fungible_asset.md
@@ -0,0 +1,537 @@
+
+
+
+# Module `0x1::dispatchable_fungible_asset`
+
+This defines the fungible asset module that can issue fungible asset of any Metadata
object. The
+metadata object can be any object that equipped with Metadata
resource.
+
+The dispatchable_fungible_asset wraps the existing fungible_asset module and adds the ability for token issuer
+to customize the logic for withdraw and deposit operations. For example:
+
+- Deflation token: a fixed percentage of token will be destructed upon transfer.
+- Transfer allowlist: token can only be transfered to addresses in the allow list.
+- Predicated transfer: transfer can only happen when some certain predicate has been met.
+- Loyalty token: a fixed loyalty will be paid to a designated address when a fungible asset transfer happens
+
+The api listed here intended to be an in-place replacement for defi applications that uses fungible_asset api directly
+and is safe for non-dispatchable (aka vanilla) fungible assets as well.
+
+See AIP-73 for further discussion
+
+
+- [Resource `TransferRefStore`](#0x1_dispatchable_fungible_asset_TransferRefStore)
+- [Constants](#@Constants_0)
+- [Function `register_dispatch_functions`](#0x1_dispatchable_fungible_asset_register_dispatch_functions)
+- [Function `withdraw`](#0x1_dispatchable_fungible_asset_withdraw)
+- [Function `deposit`](#0x1_dispatchable_fungible_asset_deposit)
+- [Function `transfer`](#0x1_dispatchable_fungible_asset_transfer)
+- [Function `transfer_assert_minimum_deposit`](#0x1_dispatchable_fungible_asset_transfer_assert_minimum_deposit)
+- [Function `derived_balance`](#0x1_dispatchable_fungible_asset_derived_balance)
+- [Function `borrow_transfer_ref`](#0x1_dispatchable_fungible_asset_borrow_transfer_ref)
+- [Function `dispatchable_withdraw`](#0x1_dispatchable_fungible_asset_dispatchable_withdraw)
+- [Function `dispatchable_deposit`](#0x1_dispatchable_fungible_asset_dispatchable_deposit)
+- [Function `dispatchable_derived_balance`](#0x1_dispatchable_fungible_asset_dispatchable_derived_balance)
+- [Specification](#@Specification_1)
+ - [Function `dispatchable_withdraw`](#@Specification_1_dispatchable_withdraw)
+ - [Function `dispatchable_deposit`](#@Specification_1_dispatchable_deposit)
+ - [Function `dispatchable_derived_balance`](#@Specification_1_dispatchable_derived_balance)
+
+
+use 0x1::error;
+use 0x1::features;
+use 0x1::function_info;
+use 0x1::fungible_asset;
+use 0x1::object;
+use 0x1::option;
+
+
+
+
+
+
+## Resource `TransferRefStore`
+
+
+
+#[resource_group_member(#[group = 0x1::object::ObjectGroup])]
+struct TransferRefStore has key
+
+
+
+
+transfer_ref: fungible_asset::TransferRef
+const ENOT_ACTIVATED: u64 = 3;
+
+
+
+
+
+
+Recipient is not getting the guaranteed value;
+
+
+const EAMOUNT_MISMATCH: u64 = 2;
+
+
+
+
+
+
+Dispatch target is not loaded.
+
+
+const ENOT_LOADED: u64 = 4;
+
+
+
+
+
+
+TransferRefStore doesn't exist on the fungible asset type.
+
+
+const ESTORE_NOT_FOUND: u64 = 1;
+
+
+
+
+
+
+## Function `register_dispatch_functions`
+
+
+
+public fun register_dispatch_functions(constructor_ref: &object::ConstructorRef, withdraw_function: option::Option<function_info::FunctionInfo>, deposit_function: option::Option<function_info::FunctionInfo>, derived_balance_function: option::Option<function_info::FunctionInfo>)
+
+
+
+
+public fun register_dispatch_functions(
+ constructor_ref: &ConstructorRef,
+ withdraw_function: Option<FunctionInfo>,
+ deposit_function: Option<FunctionInfo>,
+ derived_balance_function: Option<FunctionInfo>,
+) {
+ fungible_asset::register_dispatch_functions(
+ constructor_ref,
+ withdraw_function,
+ deposit_function,
+ derived_balance_function,
+ );
+ let store_obj = &object::generate_signer(constructor_ref);
+ move_to<TransferRefStore>(
+ store_obj,
+ TransferRefStore {
+ transfer_ref: fungible_asset::generate_transfer_ref(constructor_ref),
+ }
+ );
+}
+
+
+
+
+amount
of the fungible asset from store
by the owner.
+
+The semantics of deposit will be governed by the function specified in DispatchFunctionStore.
+
+
+public fun withdraw<T: key>(owner: &signer, store: object::Object<T>, amount: u64): fungible_asset::FungibleAsset
+
+
+
+
+public fun withdraw<T: key>(
+ owner: &signer,
+ store: Object<T>,
+ amount: u64,
+): FungibleAsset acquires TransferRefStore {
+ fungible_asset::withdraw_sanity_check(owner, store, false);
+ let func_opt = fungible_asset::withdraw_dispatch_function(store);
+ if (option::is_some(&func_opt)) {
+ assert!(
+ features::dispatchable_fungible_asset_enabled(),
+ error::aborted(ENOT_ACTIVATED)
+ );
+ let start_balance = fungible_asset::balance(store);
+ let func = option::borrow(&func_opt);
+ function_info::load_module_from_function(func);
+ let fa = dispatchable_withdraw(
+ store,
+ amount,
+ borrow_transfer_ref(store),
+ func,
+ );
+ let end_balance = fungible_asset::balance(store);
+ assert!(amount <= start_balance - end_balance, error::aborted(EAMOUNT_MISMATCH));
+ fa
+ } else {
+ fungible_asset::withdraw_internal(object::object_address(&store), amount)
+ }
+}
+
+
+
+
+amount
of the fungible asset to store
.
+
+The semantics of deposit will be governed by the function specified in DispatchFunctionStore.
+
+
+public fun deposit<T: key>(store: object::Object<T>, fa: fungible_asset::FungibleAsset)
+
+
+
+
+public fun deposit<T: key>(store: Object<T>, fa: FungibleAsset) acquires TransferRefStore {
+ fungible_asset::deposit_sanity_check(store, false);
+ let func_opt = fungible_asset::deposit_dispatch_function(store);
+ if (option::is_some(&func_opt)) {
+ assert!(
+ features::dispatchable_fungible_asset_enabled(),
+ error::aborted(ENOT_ACTIVATED)
+ );
+ let func = option::borrow(&func_opt);
+ function_info::load_module_from_function(func);
+ dispatchable_deposit(
+ store,
+ fa,
+ borrow_transfer_ref(store),
+ func
+ )
+ } else {
+ fungible_asset::deposit_internal(object::object_address(&store), fa)
+ }
+}
+
+
+
+
+amount
of fungible asset from from_store
, which should be owned by sender
, to receiver
.
+Note: it does not move the underlying object.
+
+
+public entry fun transfer<T: key>(sender: &signer, from: object::Object<T>, to: object::Object<T>, amount: u64)
+
+
+
+
+public entry fun transfer<T: key>(
+ sender: &signer,
+ from: Object<T>,
+ to: Object<T>,
+ amount: u64,
+) acquires TransferRefStore {
+ let fa = withdraw(sender, from, amount);
+ deposit(to, fa);
+}
+
+
+
+
+amount
of fungible asset from from_store
, which should be owned by sender
, to receiver
.
+The recipient is guranteed to receive asset greater than the expected amount.
+Note: it does not move the underlying object.
+
+
+public entry fun transfer_assert_minimum_deposit<T: key>(sender: &signer, from: object::Object<T>, to: object::Object<T>, amount: u64, expected: u64)
+
+
+
+
+public entry fun transfer_assert_minimum_deposit<T: key>(
+ sender: &signer,
+ from: Object<T>,
+ to: Object<T>,
+ amount: u64,
+ expected: u64
+) acquires TransferRefStore {
+ let start = fungible_asset::balance(to);
+ let fa = withdraw(sender, from, amount);
+ deposit(to, fa);
+ let end = fungible_asset::balance(to);
+ assert!(end - start >= expected, error::aborted(EAMOUNT_MISMATCH));
+}
+
+
+
+
+#[view]
+public fun derived_balance<T: key>(store: object::Object<T>): u64
+
+
+
+
+public fun derived_balance<T: key>(store: Object<T>): u64 {
+ let func_opt = fungible_asset::derived_balance_dispatch_function(store);
+ if (option::is_some(&func_opt)) {
+ assert!(
+ features::dispatchable_fungible_asset_enabled(),
+ error::aborted(ENOT_ACTIVATED)
+ );
+ let func = option::borrow(&func_opt);
+ function_info::load_module_from_function(func);
+ dispatchable_derived_balance(store, func)
+ } else {
+ fungible_asset::balance(store)
+ }
+}
+
+
+
+
+fun borrow_transfer_ref<T: key>(metadata: object::Object<T>): &fungible_asset::TransferRef
+
+
+
+
+inline fun borrow_transfer_ref<T: key>(metadata: Object<T>): &TransferRef acquires TransferRefStore {
+ let metadata_addr = object::object_address(
+ &fungible_asset::store_metadata(metadata)
+ );
+ assert!(
+ exists<TransferRefStore>(metadata_addr),
+ error::not_found(ESTORE_NOT_FOUND)
+ );
+ &borrow_global<TransferRefStore>(metadata_addr).transfer_ref
+}
+
+
+
+
+fun dispatchable_withdraw<T: key>(store: object::Object<T>, amount: u64, transfer_ref: &fungible_asset::TransferRef, function: &function_info::FunctionInfo): fungible_asset::FungibleAsset
+
+
+
+
+native fun dispatchable_withdraw<T: key>(
+ store: Object<T>,
+ amount: u64,
+ transfer_ref: &TransferRef,
+ function: &FunctionInfo,
+): FungibleAsset;
+
+
+
+
+fun dispatchable_deposit<T: key>(store: object::Object<T>, fa: fungible_asset::FungibleAsset, transfer_ref: &fungible_asset::TransferRef, function: &function_info::FunctionInfo)
+
+
+
+
+native fun dispatchable_deposit<T: key>(
+ store: Object<T>,
+ fa: FungibleAsset,
+ transfer_ref: &TransferRef,
+ function: &FunctionInfo,
+);
+
+
+
+
+fun dispatchable_derived_balance<T: key>(store: object::Object<T>, function: &function_info::FunctionInfo): u64
+
+
+
+
+native fun dispatchable_derived_balance<T: key>(
+ store: Object<T>,
+ function: &FunctionInfo,
+): u64;
+
+
+
+
+pragma verify = false;
+
+
+
+
+
+
+### Function `dispatchable_withdraw`
+
+
+fun dispatchable_withdraw<T: key>(store: object::Object<T>, amount: u64, transfer_ref: &fungible_asset::TransferRef, function: &function_info::FunctionInfo): fungible_asset::FungibleAsset
+
+
+
+
+
+pragma opaque;
+
+
+
+
+
+
+### Function `dispatchable_deposit`
+
+
+fun dispatchable_deposit<T: key>(store: object::Object<T>, fa: fungible_asset::FungibleAsset, transfer_ref: &fungible_asset::TransferRef, function: &function_info::FunctionInfo)
+
+
+
+
+
+pragma opaque;
+
+
+
+
+
+
+### Function `dispatchable_derived_balance`
+
+
+fun dispatchable_derived_balance<T: key>(store: object::Object<T>, function: &function_info::FunctionInfo): u64
+
+
+
+
+
+pragma opaque;
+
+
+
+[move-book]: https://aptos.dev/move/book/SUMMARY
diff --git a/aptos-move/framework/aptos-framework/tests/compiler-v2-doc/dkg.md b/aptos-move/framework/aptos-framework/tests/compiler-v2-doc/dkg.md
new file mode 100644
index 0000000000000..fde1df92a990e
--- /dev/null
+++ b/aptos-move/framework/aptos-framework/tests/compiler-v2-doc/dkg.md
@@ -0,0 +1,524 @@
+
+
+
+# Module `0x1::dkg`
+
+DKG on-chain states and helper functions.
+
+
+- [Struct `DKGSessionMetadata`](#0x1_dkg_DKGSessionMetadata)
+- [Struct `DKGStartEvent`](#0x1_dkg_DKGStartEvent)
+- [Struct `DKGSessionState`](#0x1_dkg_DKGSessionState)
+- [Resource `DKGState`](#0x1_dkg_DKGState)
+- [Constants](#@Constants_0)
+- [Function `initialize`](#0x1_dkg_initialize)
+- [Function `start`](#0x1_dkg_start)
+- [Function `finish`](#0x1_dkg_finish)
+- [Function `try_clear_incomplete_session`](#0x1_dkg_try_clear_incomplete_session)
+- [Function `incomplete_session`](#0x1_dkg_incomplete_session)
+- [Function `session_dealer_epoch`](#0x1_dkg_session_dealer_epoch)
+- [Specification](#@Specification_1)
+ - [Function `initialize`](#@Specification_1_initialize)
+ - [Function `start`](#@Specification_1_start)
+ - [Function `finish`](#@Specification_1_finish)
+ - [Function `try_clear_incomplete_session`](#@Specification_1_try_clear_incomplete_session)
+ - [Function `incomplete_session`](#@Specification_1_incomplete_session)
+
+
+use 0x1::error;
+use 0x1::event;
+use 0x1::option;
+use 0x1::randomness_config;
+use 0x1::system_addresses;
+use 0x1::timestamp;
+use 0x1::validator_consensus_info;
+
+
+
+
+
+
+## Struct `DKGSessionMetadata`
+
+This can be considered as the public input of DKG.
+
+
+struct DKGSessionMetadata has copy, drop, store
+
+
+
+
+dealer_epoch: u64
+randomness_config: randomness_config::RandomnessConfig
+dealer_validator_set: vector<validator_consensus_info::ValidatorConsensusInfo>
+target_validator_set: vector<validator_consensus_info::ValidatorConsensusInfo>
+#[event]
+struct DKGStartEvent has drop, store
+
+
+
+
+session_metadata: dkg::DKGSessionMetadata
+start_time_us: u64
+x
works together for an DKG output for the target validator set of epoch x+1
.
+
+
+struct DKGSessionState has copy, drop, store
+
+
+
+
+metadata: dkg::DKGSessionMetadata
+start_time_us: u64
+transcript: vector<u8>
+struct DKGState has key
+
+
+
+
+last_completed: option::Option<dkg::DKGSessionState>
+in_progress: option::Option<dkg::DKGSessionState>
+const EDKG_IN_PROGRESS: u64 = 1;
+
+
+
+
+
+
+
+
+const EDKG_NOT_IN_PROGRESS: u64 = 2;
+
+
+
+
+
+
+## Function `initialize`
+
+Called in genesis to initialize on-chain states.
+
+
+public fun initialize(aptos_framework: &signer)
+
+
+
+
+public fun initialize(aptos_framework: &signer) {
+ system_addresses::assert_aptos_framework(aptos_framework);
+ if (!exists<DKGState>(@aptos_framework)) {
+ move_to<DKGState>(
+ aptos_framework,
+ DKGState {
+ last_completed: std::option::none(),
+ in_progress: std::option::none(),
+ }
+ );
+ }
+}
+
+
+
+
+public(friend) fun start(dealer_epoch: u64, randomness_config: randomness_config::RandomnessConfig, dealer_validator_set: vector<validator_consensus_info::ValidatorConsensusInfo>, target_validator_set: vector<validator_consensus_info::ValidatorConsensusInfo>)
+
+
+
+
+public(friend) fun start(
+ dealer_epoch: u64,
+ randomness_config: RandomnessConfig,
+ dealer_validator_set: vector<ValidatorConsensusInfo>,
+ target_validator_set: vector<ValidatorConsensusInfo>,
+) acquires DKGState {
+ let dkg_state = borrow_global_mut<DKGState>(@aptos_framework);
+ let new_session_metadata = DKGSessionMetadata {
+ dealer_epoch,
+ randomness_config,
+ dealer_validator_set,
+ target_validator_set,
+ };
+ let start_time_us = timestamp::now_microseconds();
+ dkg_state.in_progress = std::option::some(DKGSessionState {
+ metadata: new_session_metadata,
+ start_time_us,
+ transcript: vector[],
+ });
+
+ emit(DKGStartEvent {
+ start_time_us,
+ session_metadata: new_session_metadata,
+ });
+}
+
+
+
+
+public(friend) fun finish(transcript: vector<u8>)
+
+
+
+
+public(friend) fun finish(transcript: vector<u8>) acquires DKGState {
+ let dkg_state = borrow_global_mut<DKGState>(@aptos_framework);
+ assert!(option::is_some(&dkg_state.in_progress), error::invalid_state(EDKG_NOT_IN_PROGRESS));
+ let session = option::extract(&mut dkg_state.in_progress);
+ session.transcript = transcript;
+ dkg_state.last_completed = option::some(session);
+ dkg_state.in_progress = option::none();
+}
+
+
+
+
+public fun try_clear_incomplete_session(fx: &signer)
+
+
+
+
+public fun try_clear_incomplete_session(fx: &signer) acquires DKGState {
+ system_addresses::assert_aptos_framework(fx);
+ if (exists<DKGState>(@aptos_framework)) {
+ let dkg_state = borrow_global_mut<DKGState>(@aptos_framework);
+ dkg_state.in_progress = option::none();
+ }
+}
+
+
+
+
+public fun incomplete_session(): option::Option<dkg::DKGSessionState>
+
+
+
+
+public fun incomplete_session(): Option<DKGSessionState> acquires DKGState {
+ if (exists<DKGState>(@aptos_framework)) {
+ borrow_global<DKGState>(@aptos_framework).in_progress
+ } else {
+ option::none()
+ }
+}
+
+
+
+
+DKGSessionState
.
+
+
+public fun session_dealer_epoch(session: &dkg::DKGSessionState): u64
+
+
+
+
+public fun session_dealer_epoch(session: &DKGSessionState): u64 {
+ session.metadata.dealer_epoch
+}
+
+
+
+
+invariant [suspendable] chain_status::is_operating() ==> exists<DKGState>(@aptos_framework);
+
+
+
+
+
+
+### Function `initialize`
+
+
+public fun initialize(aptos_framework: &signer)
+
+
+
+
+
+let aptos_framework_addr = signer::address_of(aptos_framework);
+aborts_if aptos_framework_addr != @aptos_framework;
+
+
+
+
+
+
+### Function `start`
+
+
+public(friend) fun start(dealer_epoch: u64, randomness_config: randomness_config::RandomnessConfig, dealer_validator_set: vector<validator_consensus_info::ValidatorConsensusInfo>, target_validator_set: vector<validator_consensus_info::ValidatorConsensusInfo>)
+
+
+
+
+
+aborts_if !exists<DKGState>(@aptos_framework);
+aborts_if !exists<timestamp::CurrentTimeMicroseconds>(@aptos_framework);
+
+
+
+
+
+
+### Function `finish`
+
+
+public(friend) fun finish(transcript: vector<u8>)
+
+
+
+
+
+requires exists<DKGState>(@aptos_framework);
+requires option::is_some(global<DKGState>(@aptos_framework).in_progress);
+aborts_if false;
+
+
+
+
+
+
+
+
+fun has_incomplete_session(): bool {
+ if (exists<DKGState>(@aptos_framework)) {
+ option::spec_is_some(global<DKGState>(@aptos_framework).in_progress)
+ } else {
+ false
+ }
+}
+
+
+
+
+
+
+### Function `try_clear_incomplete_session`
+
+
+public fun try_clear_incomplete_session(fx: &signer)
+
+
+
+
+
+let addr = signer::address_of(fx);
+aborts_if addr != @aptos_framework;
+
+
+
+
+
+
+### Function `incomplete_session`
+
+
+public fun incomplete_session(): option::Option<dkg::DKGSessionState>
+
+
+
+
+
+aborts_if false;
+
+
+
+[move-book]: https://aptos.dev/move/book/SUMMARY
diff --git a/aptos-move/framework/aptos-framework/tests/compiler-v2-doc/event.md b/aptos-move/framework/aptos-framework/tests/compiler-v2-doc/event.md
new file mode 100644
index 0000000000000..89fcbb69e6279
--- /dev/null
+++ b/aptos-move/framework/aptos-framework/tests/compiler-v2-doc/event.md
@@ -0,0 +1,480 @@
+
+
+
+# Module `0x1::event`
+
+The Event module defines an EventHandleGenerator
that is used to create
+EventHandle
s with unique GUIDs. It contains a counter for the number
+of EventHandle
s it generates. An EventHandle
is used to count the number of
+events emitted to a handle and emit events to the event store.
+
+
+- [Struct `EventHandle`](#0x1_event_EventHandle)
+- [Function `emit`](#0x1_event_emit)
+- [Function `write_module_event_to_store`](#0x1_event_write_module_event_to_store)
+- [Function `new_event_handle`](#0x1_event_new_event_handle)
+- [Function `emit_event`](#0x1_event_emit_event)
+- [Function `guid`](#0x1_event_guid)
+- [Function `counter`](#0x1_event_counter)
+- [Function `write_to_event_store`](#0x1_event_write_to_event_store)
+- [Function `destroy_handle`](#0x1_event_destroy_handle)
+- [Specification](#@Specification_0)
+ - [High-level Requirements](#high-level-req)
+ - [Module-level Specification](#module-level-spec)
+ - [Function `emit`](#@Specification_0_emit)
+ - [Function `write_module_event_to_store`](#@Specification_0_write_module_event_to_store)
+ - [Function `emit_event`](#@Specification_0_emit_event)
+ - [Function `guid`](#@Specification_0_guid)
+ - [Function `counter`](#@Specification_0_counter)
+ - [Function `write_to_event_store`](#@Specification_0_write_to_event_store)
+ - [Function `destroy_handle`](#@Specification_0_destroy_handle)
+
+
+use 0x1::bcs;
+use 0x1::guid;
+
+
+
+
+
+
+## Struct `EventHandle`
+
+A handle for an event such that:
+1. Other modules can emit events to this handle.
+2. Storage can use this handle to prove the total number of events that happened in the past.
+
+
+#[deprecated]
+struct EventHandle<T: drop, store> has store
+
+
+
+
+counter: u64
+guid: guid::GUID
+msg
.
+
+
+public fun emit<T: drop, store>(msg: T)
+
+
+
+
+public fun emit<T: store + drop>(msg: T) {
+ write_module_event_to_store<T>(msg);
+}
+
+
+
+
+msg
with the event stream identified by T
+
+
+fun write_module_event_to_store<T: drop, store>(msg: T)
+
+
+
+
+native fun write_module_event_to_store<T: drop + store>(msg: T);
+
+
+
+
+sig
+
+
+#[deprecated]
+public(friend) fun new_event_handle<T: drop, store>(guid: guid::GUID): event::EventHandle<T>
+
+
+
+
+public(friend) fun new_event_handle<T: drop + store>(guid: GUID): EventHandle<T> {
+ EventHandle<T> {
+ counter: 0,
+ guid,
+ }
+}
+
+
+
+
+msg
by using handle_ref
's key and counter.
+
+
+#[deprecated]
+public fun emit_event<T: drop, store>(handle_ref: &mut event::EventHandle<T>, msg: T)
+
+
+
+
+public fun emit_event<T: drop + store>(handle_ref: &mut EventHandle<T>, msg: T) {
+ write_to_event_store<T>(bcs::to_bytes(&handle_ref.guid), handle_ref.counter, msg);
+ spec {
+ assume handle_ref.counter + 1 <= MAX_U64;
+ };
+ handle_ref.counter = handle_ref.counter + 1;
+}
+
+
+
+
+#[deprecated]
+public fun guid<T: drop, store>(handle_ref: &event::EventHandle<T>): &guid::GUID
+
+
+
+
+public fun guid<T: drop + store>(handle_ref: &EventHandle<T>): &GUID {
+ &handle_ref.guid
+}
+
+
+
+
+#[deprecated]
+public fun counter<T: drop, store>(handle_ref: &event::EventHandle<T>): u64
+
+
+
+
+public fun counter<T: drop + store>(handle_ref: &EventHandle<T>): u64 {
+ handle_ref.counter
+}
+
+
+
+
+msg
as the count
th event associated with the event stream identified by guid
+
+
+#[deprecated]
+fun write_to_event_store<T: drop, store>(guid: vector<u8>, count: u64, msg: T)
+
+
+
+
+native fun write_to_event_store<T: drop + store>(guid: vector<u8>, count: u64, msg: T);
+
+
+
+
+#[deprecated]
+public fun destroy_handle<T: drop, store>(handle: event::EventHandle<T>)
+
+
+
+
+public fun destroy_handle<T: drop + store>(handle: EventHandle<T>) {
+ EventHandle<T> { counter: _, guid: _ } = handle;
+}
+
+
+
+
+No. | Requirement | Criticality | Implementation | Enforcement | +
---|---|---|---|---|
1 | +Each event handle possesses a distinct and unique GUID. | +Critical | +The new_event_handle function creates an EventHandle object with a unique GUID, ensuring distinct identification. | +Audited: GUIDs are created in guid::create. Each time the function is called, it increments creation_num_ref. Multiple calls to the function will result in distinct GUID values. | +
2 | +Unable to publish two events with the same GUID & sequence number. | +Critical | +Two events may either have the same GUID with a different counter or the same counter with a different GUID. | +This is implied by high-level requirement 1. | +
3 | +Event native functions respect normal Move rules around object creation and destruction. | +Critical | +Must follow the same rules and principles that apply to object creation and destruction in Move when using event native functions. | +The native functions of this module have been manually audited. | +
4 | +Counter increases monotonically between event emissions | +Medium | +With each event emission, the emit_event function increments the counter of the EventHandle by one. | +Formally verified in the post condition of emit_event. | +
5 | +For a given EventHandle, it should always be possible to: (1) return the GUID associated with this EventHandle, (2) return the current counter associated with this EventHandle, and (3) destroy the handle. | +Low | +The following functions should not abort if EventHandle exists: guid(), counter(), destroy_handle(). | +Formally verified via guid, counter and destroy_handle. | +
pragma verify = true;
+pragma aborts_if_is_strict;
+
+
+
+
+
+
+### Function `emit`
+
+
+public fun emit<T: drop, store>(msg: T)
+
+
+
+
+
+pragma opaque;
+
+
+
+
+
+
+### Function `write_module_event_to_store`
+
+
+fun write_module_event_to_store<T: drop, store>(msg: T)
+
+
+
+Native function use opaque.
+
+
+pragma opaque;
+
+
+
+
+
+
+### Function `emit_event`
+
+
+#[deprecated]
+public fun emit_event<T: drop, store>(handle_ref: &mut event::EventHandle<T>, msg: T)
+
+
+
+
+
+pragma opaque;
+aborts_if [abstract] false;
+// This enforces high-level requirement 4:
+ensures [concrete] handle_ref.counter == old(handle_ref.counter) + 1;
+
+
+
+
+
+
+### Function `guid`
+
+
+#[deprecated]
+public fun guid<T: drop, store>(handle_ref: &event::EventHandle<T>): &guid::GUID
+
+
+
+
+
+// This enforces high-level requirement 5:
+aborts_if false;
+
+
+
+
+
+
+### Function `counter`
+
+
+#[deprecated]
+public fun counter<T: drop, store>(handle_ref: &event::EventHandle<T>): u64
+
+
+
+
+
+// This enforces high-level requirement 5:
+aborts_if false;
+
+
+
+
+
+
+### Function `write_to_event_store`
+
+
+#[deprecated]
+fun write_to_event_store<T: drop, store>(guid: vector<u8>, count: u64, msg: T)
+
+
+
+Native function use opaque.
+
+
+pragma opaque;
+
+
+
+
+
+
+### Function `destroy_handle`
+
+
+#[deprecated]
+public fun destroy_handle<T: drop, store>(handle: event::EventHandle<T>)
+
+
+
+
+
+// This enforces high-level requirement 5:
+aborts_if false;
+
+
+
+[move-book]: https://aptos.dev/move/book/SUMMARY
diff --git a/aptos-move/framework/aptos-framework/tests/compiler-v2-doc/execution_config.md b/aptos-move/framework/aptos-framework/tests/compiler-v2-doc/execution_config.md
new file mode 100644
index 0000000000000..27d894d51a795
--- /dev/null
+++ b/aptos-move/framework/aptos-framework/tests/compiler-v2-doc/execution_config.md
@@ -0,0 +1,252 @@
+
+
+
+# Module `0x1::execution_config`
+
+Maintains the execution config for the blockchain. The config is stored in a
+Reconfiguration, and may be updated by root.
+
+
+- [Resource `ExecutionConfig`](#0x1_execution_config_ExecutionConfig)
+- [Constants](#@Constants_0)
+- [Function `set`](#0x1_execution_config_set)
+- [Function `set_for_next_epoch`](#0x1_execution_config_set_for_next_epoch)
+- [Function `on_new_epoch`](#0x1_execution_config_on_new_epoch)
+- [Specification](#@Specification_1)
+ - [Function `set`](#@Specification_1_set)
+ - [Function `set_for_next_epoch`](#@Specification_1_set_for_next_epoch)
+ - [Function `on_new_epoch`](#@Specification_1_on_new_epoch)
+
+
+use 0x1::chain_status;
+use 0x1::config_buffer;
+use 0x1::error;
+use 0x1::reconfiguration;
+use 0x1::system_addresses;
+
+
+
+
+
+
+## Resource `ExecutionConfig`
+
+
+
+struct ExecutionConfig has drop, store, key
+
+
+
+
+config: vector<u8>
+const EINVALID_CONFIG: u64 = 1;
+
+
+
+
+
+
+## Function `set`
+
+Deprecated by set_for_next_epoch()
.
+
+WARNING: calling this while randomness is enabled will trigger a new epoch without randomness!
+
+TODO: update all the tests that reference this function, then disable this function.
+
+
+public fun set(account: &signer, config: vector<u8>)
+
+
+
+
+public fun set(account: &signer, config: vector<u8>) acquires ExecutionConfig {
+ system_addresses::assert_aptos_framework(account);
+ chain_status::assert_genesis();
+
+ assert!(vector::length(&config) > 0, error::invalid_argument(EINVALID_CONFIG));
+
+ if (exists<ExecutionConfig>(@aptos_framework)) {
+ let config_ref = &mut borrow_global_mut<ExecutionConfig>(@aptos_framework).config;
+ *config_ref = config;
+ } else {
+ move_to(account, ExecutionConfig { config });
+ };
+ // Need to trigger reconfiguration so validator nodes can sync on the updated configs.
+ reconfiguration::reconfigure();
+}
+
+
+
+
+public fun set_for_next_epoch(account: &signer, config: vector<u8>)
+
+
+
+
+public fun set_for_next_epoch(account: &signer, config: vector<u8>) {
+ system_addresses::assert_aptos_framework(account);
+ assert!(vector::length(&config) > 0, error::invalid_argument(EINVALID_CONFIG));
+ config_buffer::upsert(ExecutionConfig { config });
+}
+
+
+
+
+ExecutionConfig
, if there is any.
+
+
+public(friend) fun on_new_epoch(framework: &signer)
+
+
+
+
+public(friend) fun on_new_epoch(framework: &signer) acquires ExecutionConfig {
+ system_addresses::assert_aptos_framework(framework);
+ if (config_buffer::does_exist<ExecutionConfig>()) {
+ let config = config_buffer::extract<ExecutionConfig>();
+ if (exists<ExecutionConfig>(@aptos_framework)) {
+ *borrow_global_mut<ExecutionConfig>(@aptos_framework) = config;
+ } else {
+ move_to(framework, config);
+ };
+ }
+}
+
+
+
+
+pragma verify = true;
+pragma aborts_if_is_strict;
+
+
+
+
+
+
+### Function `set`
+
+
+public fun set(account: &signer, config: vector<u8>)
+
+
+
+Ensure the caller is admin
+When setting now time must be later than last_reconfiguration_time.
+
+
+pragma verify_duration_estimate = 600;
+let addr = signer::address_of(account);
+include transaction_fee::RequiresCollectedFeesPerValueLeqBlockAptosSupply;
+requires chain_status::is_genesis();
+requires exists<stake::ValidatorFees>(@aptos_framework);
+requires exists<staking_config::StakingRewardsConfig>(@aptos_framework);
+requires len(config) > 0;
+include features::spec_periodical_reward_rate_decrease_enabled() ==> staking_config::StakingRewardsConfigEnabledRequirement;
+include aptos_coin::ExistsAptosCoin;
+requires system_addresses::is_aptos_framework_address(addr);
+requires timestamp::spec_now_microseconds() >= reconfiguration::last_reconfiguration_time();
+ensures exists<ExecutionConfig>(@aptos_framework);
+
+
+
+
+
+
+### Function `set_for_next_epoch`
+
+
+public fun set_for_next_epoch(account: &signer, config: vector<u8>)
+
+
+
+
+
+include config_buffer::SetForNextEpochAbortsIf;
+
+
+
+
+
+
+### Function `on_new_epoch`
+
+
+public(friend) fun on_new_epoch(framework: &signer)
+
+
+
+
+
+requires @aptos_framework == std::signer::address_of(framework);
+include config_buffer::OnNewEpochRequirement<ExecutionConfig>;
+aborts_if false;
+
+
+
+[move-book]: https://aptos.dev/move/book/SUMMARY
diff --git a/aptos-move/framework/aptos-framework/tests/compiler-v2-doc/function_info.md b/aptos-move/framework/aptos-framework/tests/compiler-v2-doc/function_info.md
new file mode 100644
index 0000000000000..95e8a6710a7d6
--- /dev/null
+++ b/aptos-move/framework/aptos-framework/tests/compiler-v2-doc/function_info.md
@@ -0,0 +1,362 @@
+
+
+
+# Module `0x1::function_info`
+
+The function_info
module defines the FunctionInfo
type which simulates a function pointer.
+
+
+- [Struct `FunctionInfo`](#0x1_function_info_FunctionInfo)
+- [Constants](#@Constants_0)
+- [Function `new_function_info`](#0x1_function_info_new_function_info)
+- [Function `new_function_info_from_address`](#0x1_function_info_new_function_info_from_address)
+- [Function `check_dispatch_type_compatibility`](#0x1_function_info_check_dispatch_type_compatibility)
+- [Function `load_module_from_function`](#0x1_function_info_load_module_from_function)
+- [Function `check_dispatch_type_compatibility_impl`](#0x1_function_info_check_dispatch_type_compatibility_impl)
+- [Function `is_identifier`](#0x1_function_info_is_identifier)
+- [Function `load_function_impl`](#0x1_function_info_load_function_impl)
+- [Specification](#@Specification_1)
+ - [Function `check_dispatch_type_compatibility_impl`](#@Specification_1_check_dispatch_type_compatibility_impl)
+ - [Function `load_function_impl`](#@Specification_1_load_function_impl)
+
+
+use 0x1::error;
+use 0x1::features;
+use 0x1::signer;
+use 0x1::string;
+
+
+
+
+
+
+## Struct `FunctionInfo`
+
+A String
holds a sequence of bytes which is guaranteed to be in utf8 format.
+
+
+struct FunctionInfo has copy, drop, store
+
+
+
+
+module_address: address
+module_name: string::String
+function_name: string::String
+const EINVALID_FUNCTION: u64 = 2;
+
+
+
+
+
+
+String is not a valid Move identifier
+
+
+const EINVALID_IDENTIFIER: u64 = 1;
+
+
+
+
+
+
+Feature hasn't been activated yet.
+
+
+const ENOT_ACTIVATED: u64 = 3;
+
+
+
+
+
+
+## Function `new_function_info`
+
+Creates a new function info from names.
+
+
+public fun new_function_info(module_signer: &signer, module_name: string::String, function_name: string::String): function_info::FunctionInfo
+
+
+
+
+public fun new_function_info(
+ module_signer: &signer,
+ module_name: String,
+ function_name: String,
+): FunctionInfo {
+ new_function_info_from_address(
+ signer::address_of(module_signer),
+ module_name,
+ function_name,
+ )
+}
+
+
+
+
+public(friend) fun new_function_info_from_address(module_address: address, module_name: string::String, function_name: string::String): function_info::FunctionInfo
+
+
+
+
+public(friend) fun new_function_info_from_address(
+ module_address: address,
+ module_name: String,
+ function_name: String,
+): FunctionInfo {
+ assert!(
+ is_identifier(string::bytes(&module_name)),
+ EINVALID_IDENTIFIER
+ );
+ assert!(
+ is_identifier(string::bytes(&function_name)),
+ EINVALID_IDENTIFIER
+ );
+ FunctionInfo {
+ module_address,
+ module_name,
+ function_name,
+ }
+}
+
+
+
+
+&FunctionInfo
in the last argument that will instruct the VM which
+function to jump to.
+
+dispatch_target also needs to be public so the type signature will remain unchanged.
+
+
+public(friend) fun check_dispatch_type_compatibility(framework_function: &function_info::FunctionInfo, dispatch_target: &function_info::FunctionInfo): bool
+
+
+
+
+public(friend) fun check_dispatch_type_compatibility(
+ framework_function: &FunctionInfo,
+ dispatch_target: &FunctionInfo,
+): bool {
+ assert!(
+ features::dispatchable_fungible_asset_enabled(),
+ error::aborted(ENOT_ACTIVATED)
+ );
+ load_function_impl(dispatch_target);
+ check_dispatch_type_compatibility_impl(framework_function, dispatch_target)
+}
+
+
+
+
+check_dispatch_type_compatibility
+or performing any other dispatching logic to ensure:
+1. We properly charge gas for the function to dispatch.
+2. The function is loaded in the cache so that we can perform further type checking/dispatching logic.
+
+Calling check_dispatch_type_compatibility_impl
or dispatch without loading up the module would yield an error
+if such module isn't accessed previously in the transaction.
+
+
+public(friend) fun load_module_from_function(f: &function_info::FunctionInfo)
+
+
+
+
+public(friend) fun load_module_from_function(f: &FunctionInfo) {
+ load_function_impl(f)
+}
+
+
+
+
+fun check_dispatch_type_compatibility_impl(lhs: &function_info::FunctionInfo, r: &function_info::FunctionInfo): bool
+
+
+
+
+native fun check_dispatch_type_compatibility_impl(lhs: &FunctionInfo, r: &FunctionInfo): bool;
+
+
+
+
+fun is_identifier(s: &vector<u8>): bool
+
+
+
+
+native fun is_identifier(s: &vector<u8>): bool;
+
+
+
+
+fun load_function_impl(f: &function_info::FunctionInfo)
+
+
+
+
+native fun load_function_impl(f: &FunctionInfo);
+
+
+
+
+pragma verify = false;
+
+
+
+
+
+
+### Function `check_dispatch_type_compatibility_impl`
+
+
+fun check_dispatch_type_compatibility_impl(lhs: &function_info::FunctionInfo, r: &function_info::FunctionInfo): bool
+
+
+
+
+
+pragma opaque;
+
+
+
+
+
+
+### Function `load_function_impl`
+
+
+fun load_function_impl(f: &function_info::FunctionInfo)
+
+
+
+
+
+pragma opaque;
+
+
+
+[move-book]: https://aptos.dev/move/book/SUMMARY
diff --git a/aptos-move/framework/aptos-framework/tests/compiler-v2-doc/fungible_asset.md b/aptos-move/framework/aptos-framework/tests/compiler-v2-doc/fungible_asset.md
new file mode 100644
index 0000000000000..3f215d00bed96
--- /dev/null
+++ b/aptos-move/framework/aptos-framework/tests/compiler-v2-doc/fungible_asset.md
@@ -0,0 +1,3683 @@
+
+
+
+# Module `0x1::fungible_asset`
+
+This defines the fungible asset module that can issue fungible asset of any Metadata
object. The
+metadata object can be any object that equipped with Metadata
resource.
+
+
+- [Resource `Supply`](#0x1_fungible_asset_Supply)
+- [Resource `ConcurrentSupply`](#0x1_fungible_asset_ConcurrentSupply)
+- [Resource `Metadata`](#0x1_fungible_asset_Metadata)
+- [Resource `Untransferable`](#0x1_fungible_asset_Untransferable)
+- [Resource `FungibleStore`](#0x1_fungible_asset_FungibleStore)
+- [Resource `DispatchFunctionStore`](#0x1_fungible_asset_DispatchFunctionStore)
+- [Resource `ConcurrentFungibleBalance`](#0x1_fungible_asset_ConcurrentFungibleBalance)
+- [Struct `FungibleAsset`](#0x1_fungible_asset_FungibleAsset)
+- [Struct `MintRef`](#0x1_fungible_asset_MintRef)
+- [Struct `TransferRef`](#0x1_fungible_asset_TransferRef)
+- [Struct `BurnRef`](#0x1_fungible_asset_BurnRef)
+- [Struct `MutateMetadataRef`](#0x1_fungible_asset_MutateMetadataRef)
+- [Struct `Deposit`](#0x1_fungible_asset_Deposit)
+- [Struct `Withdraw`](#0x1_fungible_asset_Withdraw)
+- [Struct `Frozen`](#0x1_fungible_asset_Frozen)
+- [Resource `FungibleAssetEvents`](#0x1_fungible_asset_FungibleAssetEvents)
+- [Struct `DepositEvent`](#0x1_fungible_asset_DepositEvent)
+- [Struct `WithdrawEvent`](#0x1_fungible_asset_WithdrawEvent)
+- [Struct `FrozenEvent`](#0x1_fungible_asset_FrozenEvent)
+- [Constants](#@Constants_0)
+- [Function `default_to_concurrent_fungible_supply`](#0x1_fungible_asset_default_to_concurrent_fungible_supply)
+- [Function `allow_upgrade_to_concurrent_fungible_balance`](#0x1_fungible_asset_allow_upgrade_to_concurrent_fungible_balance)
+- [Function `default_to_concurrent_fungible_balance`](#0x1_fungible_asset_default_to_concurrent_fungible_balance)
+- [Function `add_fungibility`](#0x1_fungible_asset_add_fungibility)
+- [Function `set_untransferable`](#0x1_fungible_asset_set_untransferable)
+- [Function `is_untransferable`](#0x1_fungible_asset_is_untransferable)
+- [Function `register_dispatch_functions`](#0x1_fungible_asset_register_dispatch_functions)
+- [Function `generate_mint_ref`](#0x1_fungible_asset_generate_mint_ref)
+- [Function `generate_burn_ref`](#0x1_fungible_asset_generate_burn_ref)
+- [Function `generate_transfer_ref`](#0x1_fungible_asset_generate_transfer_ref)
+- [Function `generate_mutate_metadata_ref`](#0x1_fungible_asset_generate_mutate_metadata_ref)
+- [Function `supply`](#0x1_fungible_asset_supply)
+- [Function `maximum`](#0x1_fungible_asset_maximum)
+- [Function `name`](#0x1_fungible_asset_name)
+- [Function `symbol`](#0x1_fungible_asset_symbol)
+- [Function `decimals`](#0x1_fungible_asset_decimals)
+- [Function `icon_uri`](#0x1_fungible_asset_icon_uri)
+- [Function `project_uri`](#0x1_fungible_asset_project_uri)
+- [Function `metadata`](#0x1_fungible_asset_metadata)
+- [Function `store_exists`](#0x1_fungible_asset_store_exists)
+- [Function `store_exists_inline`](#0x1_fungible_asset_store_exists_inline)
+- [Function `concurrent_fungible_balance_exists_inline`](#0x1_fungible_asset_concurrent_fungible_balance_exists_inline)
+- [Function `metadata_from_asset`](#0x1_fungible_asset_metadata_from_asset)
+- [Function `store_metadata`](#0x1_fungible_asset_store_metadata)
+- [Function `amount`](#0x1_fungible_asset_amount)
+- [Function `balance`](#0x1_fungible_asset_balance)
+- [Function `is_balance_at_least`](#0x1_fungible_asset_is_balance_at_least)
+- [Function `is_address_balance_at_least`](#0x1_fungible_asset_is_address_balance_at_least)
+- [Function `is_frozen`](#0x1_fungible_asset_is_frozen)
+- [Function `is_store_dispatchable`](#0x1_fungible_asset_is_store_dispatchable)
+- [Function `deposit_dispatch_function`](#0x1_fungible_asset_deposit_dispatch_function)
+- [Function `has_deposit_dispatch_function`](#0x1_fungible_asset_has_deposit_dispatch_function)
+- [Function `withdraw_dispatch_function`](#0x1_fungible_asset_withdraw_dispatch_function)
+- [Function `has_withdraw_dispatch_function`](#0x1_fungible_asset_has_withdraw_dispatch_function)
+- [Function `derived_balance_dispatch_function`](#0x1_fungible_asset_derived_balance_dispatch_function)
+- [Function `asset_metadata`](#0x1_fungible_asset_asset_metadata)
+- [Function `mint_ref_metadata`](#0x1_fungible_asset_mint_ref_metadata)
+- [Function `transfer_ref_metadata`](#0x1_fungible_asset_transfer_ref_metadata)
+- [Function `burn_ref_metadata`](#0x1_fungible_asset_burn_ref_metadata)
+- [Function `object_from_metadata_ref`](#0x1_fungible_asset_object_from_metadata_ref)
+- [Function `transfer`](#0x1_fungible_asset_transfer)
+- [Function `create_store`](#0x1_fungible_asset_create_store)
+- [Function `remove_store`](#0x1_fungible_asset_remove_store)
+- [Function `withdraw`](#0x1_fungible_asset_withdraw)
+- [Function `withdraw_sanity_check`](#0x1_fungible_asset_withdraw_sanity_check)
+- [Function `deposit_sanity_check`](#0x1_fungible_asset_deposit_sanity_check)
+- [Function `deposit`](#0x1_fungible_asset_deposit)
+- [Function `mint`](#0x1_fungible_asset_mint)
+- [Function `mint_internal`](#0x1_fungible_asset_mint_internal)
+- [Function `mint_to`](#0x1_fungible_asset_mint_to)
+- [Function `set_frozen_flag`](#0x1_fungible_asset_set_frozen_flag)
+- [Function `set_frozen_flag_internal`](#0x1_fungible_asset_set_frozen_flag_internal)
+- [Function `burn`](#0x1_fungible_asset_burn)
+- [Function `burn_internal`](#0x1_fungible_asset_burn_internal)
+- [Function `burn_from`](#0x1_fungible_asset_burn_from)
+- [Function `address_burn_from`](#0x1_fungible_asset_address_burn_from)
+- [Function `withdraw_with_ref`](#0x1_fungible_asset_withdraw_with_ref)
+- [Function `deposit_with_ref`](#0x1_fungible_asset_deposit_with_ref)
+- [Function `transfer_with_ref`](#0x1_fungible_asset_transfer_with_ref)
+- [Function `mutate_metadata`](#0x1_fungible_asset_mutate_metadata)
+- [Function `zero`](#0x1_fungible_asset_zero)
+- [Function `extract`](#0x1_fungible_asset_extract)
+- [Function `merge`](#0x1_fungible_asset_merge)
+- [Function `destroy_zero`](#0x1_fungible_asset_destroy_zero)
+- [Function `deposit_internal`](#0x1_fungible_asset_deposit_internal)
+- [Function `withdraw_internal`](#0x1_fungible_asset_withdraw_internal)
+- [Function `increase_supply`](#0x1_fungible_asset_increase_supply)
+- [Function `decrease_supply`](#0x1_fungible_asset_decrease_supply)
+- [Function `borrow_fungible_metadata`](#0x1_fungible_asset_borrow_fungible_metadata)
+- [Function `borrow_fungible_metadata_mut`](#0x1_fungible_asset_borrow_fungible_metadata_mut)
+- [Function `borrow_store_resource`](#0x1_fungible_asset_borrow_store_resource)
+- [Function `upgrade_to_concurrent`](#0x1_fungible_asset_upgrade_to_concurrent)
+- [Function `upgrade_store_to_concurrent`](#0x1_fungible_asset_upgrade_store_to_concurrent)
+- [Function `ensure_store_upgraded_to_concurrent_internal`](#0x1_fungible_asset_ensure_store_upgraded_to_concurrent_internal)
+- [Specification](#@Specification_1)
+ - [High-level Requirements](#high-level-req)
+ - [Module-level Specification](#module-level-spec)
+
+
+use 0x1::aggregator_v2;
+use 0x1::create_signer;
+use 0x1::error;
+use 0x1::event;
+use 0x1::features;
+use 0x1::function_info;
+use 0x1::object;
+use 0x1::option;
+use 0x1::signer;
+use 0x1::string;
+
+
+
+
+
+
+## Resource `Supply`
+
+
+
+#[resource_group_member(#[group = 0x1::object::ObjectGroup])]
+struct Supply has key
+
+
+
+
+current: u128
+maximum: option::Option<u128>
+#[resource_group_member(#[group = 0x1::object::ObjectGroup])]
+struct ConcurrentSupply has key
+
+
+
+
+current: aggregator_v2::Aggregator<u128>
+#[resource_group_member(#[group = 0x1::object::ObjectGroup])]
+struct Metadata has copy, drop, key
+
+
+
+
+name: string::String
+symbol: string::String
+decimals: u8
+decimals
equals 2
, a balance of 505
coins should
+ be displayed to a user as 5.05
(505 / 10 ** 2
).
+icon_uri: string::String
+project_uri: string::String
+FungibleAsset
, such that all FungibleStore
s stores are untransferable at
+the object layer.
+
+
+#[resource_group_member(#[group = 0x1::object::ObjectGroup])]
+struct Untransferable has key
+
+
+
+
+dummy_field: bool
+#[resource_group_member(#[group = 0x1::object::ObjectGroup])]
+struct FungibleStore has key
+
+
+
+
+metadata: object::Object<fungible_asset::Metadata>
+balance: u64
+frozen: bool
+TransferRef
can move in/out from this store.
+#[resource_group_member(#[group = 0x1::object::ObjectGroup])]
+struct DispatchFunctionStore has key
+
+
+
+
+withdraw_function: option::Option<function_info::FunctionInfo>
+deposit_function: option::Option<function_info::FunctionInfo>
+derived_balance_function: option::Option<function_info::FunctionInfo>
+#[resource_group_member(#[group = 0x1::object::ObjectGroup])]
+struct ConcurrentFungibleBalance has key
+
+
+
+
+balance: aggregator_v2::Aggregator<u64>
+struct FungibleAsset
+
+
+
+
+metadata: object::Object<fungible_asset::Metadata>
+amount: u64
+struct MintRef has drop, store
+
+
+
+
+metadata: object::Object<fungible_asset::Metadata>
+struct TransferRef has drop, store
+
+
+
+
+metadata: object::Object<fungible_asset::Metadata>
+struct BurnRef has drop, store
+
+
+
+
+metadata: object::Object<fungible_asset::Metadata>
+struct MutateMetadataRef has drop, store
+
+
+
+
+metadata: object::Object<fungible_asset::Metadata>
+#[event]
+struct Deposit has drop, store
+
+
+
+
+store: address
+amount: u64
+#[event]
+struct Withdraw has drop, store
+
+
+
+
+store: address
+amount: u64
+#[event]
+struct Frozen has drop, store
+
+
+
+
+store: address
+frozen: bool
+#[resource_group_member(#[group = 0x1::object::ObjectGroup])]
+#[deprecated]
+struct FungibleAssetEvents has key
+
+
+
+
+deposit_events: event::EventHandle<fungible_asset::DepositEvent>
+withdraw_events: event::EventHandle<fungible_asset::WithdrawEvent>
+frozen_events: event::EventHandle<fungible_asset::FrozenEvent>
+#[deprecated]
+struct DepositEvent has drop, store
+
+
+
+
+amount: u64
+#[deprecated]
+struct WithdrawEvent has drop, store
+
+
+
+
+amount: u64
+#[deprecated]
+struct FrozenEvent has drop, store
+
+
+
+
+frozen: bool
+const MAX_U128: u128 = 340282366920938463463374607431768211455;
+
+
+
+
+
+
+Trying to re-register dispatch hook on a fungible asset.
+
+
+const EALREADY_REGISTERED: u64 = 29;
+
+
+
+
+
+
+Amount cannot be zero.
+
+
+const EAMOUNT_CANNOT_BE_ZERO: u64 = 1;
+
+
+
+
+
+
+Cannot destroy non-empty fungible assets.
+
+
+const EAMOUNT_IS_NOT_ZERO: u64 = 12;
+
+
+
+
+
+
+Cannot register dispatch hook for APT.
+
+
+const EAPT_NOT_DISPATCHABLE: u64 = 31;
+
+
+
+
+
+
+Cannot destroy fungible stores with a non-zero balance.
+
+
+const EBALANCE_IS_NOT_ZERO: u64 = 14;
+
+
+
+
+
+
+Burn ref and fungible asset do not match.
+
+
+const EBURN_REF_AND_FUNGIBLE_ASSET_MISMATCH: u64 = 13;
+
+
+
+
+
+
+Burn ref and store do not match.
+
+
+const EBURN_REF_AND_STORE_MISMATCH: u64 = 10;
+
+
+
+
+
+
+Flag for Concurrent Supply not enabled
+
+
+const ECONCURRENT_BALANCE_NOT_ENABLED: u64 = 32;
+
+
+
+
+
+
+Flag for Concurrent Supply not enabled
+
+
+const ECONCURRENT_SUPPLY_NOT_ENABLED: u64 = 22;
+
+
+
+
+
+
+Decimals is over the maximum of 32
+
+
+const EDECIMALS_TOO_LARGE: u64 = 17;
+
+
+
+
+
+
+Provided deposit function type doesn't meet the signature requirement.
+
+
+const EDEPOSIT_FUNCTION_SIGNATURE_MISMATCH: u64 = 26;
+
+
+
+
+
+
+Provided derived_balance function type doesn't meet the signature requirement.
+
+
+const EDERIVED_BALANCE_FUNCTION_SIGNATURE_MISMATCH: u64 = 27;
+
+
+
+
+
+
+Fungible asset and store do not match.
+
+
+const EFUNGIBLE_ASSET_AND_STORE_MISMATCH: u64 = 11;
+
+
+
+
+
+
+Fungible asset do not match when merging.
+
+
+const EFUNGIBLE_ASSET_MISMATCH: u64 = 6;
+
+
+
+
+
+
+Fungible metadata does not exist on this account.
+
+
+const EFUNGIBLE_METADATA_EXISTENCE: u64 = 30;
+
+
+
+
+
+
+Flag for the existence of fungible store.
+
+
+const EFUNGIBLE_STORE_EXISTENCE: u64 = 23;
+
+
+
+
+
+
+Insufficient balance to withdraw or transfer.
+
+
+const EINSUFFICIENT_BALANCE: u64 = 4;
+
+
+
+
+
+
+Invalid withdraw/deposit on dispatchable token. The specified token has a dispatchable function hook.
+Need to invoke dispatchable_fungible_asset::withdraw/deposit to perform transfer.
+
+
+const EINVALID_DISPATCHABLE_OPERATIONS: u64 = 28;
+
+
+
+
+
+
+The fungible asset's supply has exceeded maximum.
+
+
+const EMAX_SUPPLY_EXCEEDED: u64 = 5;
+
+
+
+
+
+
+The mint ref and the store do not match.
+
+
+const EMINT_REF_AND_STORE_MISMATCH: u64 = 7;
+
+
+
+
+
+
+Name of the fungible asset metadata is too long
+
+
+const ENAME_TOO_LONG: u64 = 15;
+
+
+
+
+
+
+Account is not the owner of metadata object.
+
+
+const ENOT_METADATA_OWNER: u64 = 24;
+
+
+
+
+
+
+Account is not the store's owner.
+
+
+const ENOT_STORE_OWNER: u64 = 8;
+
+
+
+
+
+
+Fungibility is only available for non-deletable objects.
+
+
+const EOBJECT_IS_DELETABLE: u64 = 18;
+
+
+
+
+
+
+Store is disabled from sending and receiving this fungible asset.
+
+
+const ESTORE_IS_FROZEN: u64 = 3;
+
+
+
+
+
+
+Supply resource is not found for a metadata object.
+
+
+const ESUPPLY_NOT_FOUND: u64 = 21;
+
+
+
+
+
+
+The fungible asset's supply will be negative which should be impossible.
+
+
+const ESUPPLY_UNDERFLOW: u64 = 20;
+
+
+
+
+
+
+Symbol of the fungible asset metadata is too long
+
+
+const ESYMBOL_TOO_LONG: u64 = 16;
+
+
+
+
+
+
+The transfer ref and the fungible asset do not match.
+
+
+const ETRANSFER_REF_AND_FUNGIBLE_ASSET_MISMATCH: u64 = 2;
+
+
+
+
+
+
+Transfer ref and store do not match.
+
+
+const ETRANSFER_REF_AND_STORE_MISMATCH: u64 = 9;
+
+
+
+
+
+
+URI for the icon of the fungible asset metadata is too long
+
+
+const EURI_TOO_LONG: u64 = 19;
+
+
+
+
+
+
+Provided withdraw function type doesn't meet the signature requirement.
+
+
+const EWITHDRAW_FUNCTION_SIGNATURE_MISMATCH: u64 = 25;
+
+
+
+
+
+
+
+
+const MAX_DECIMALS: u8 = 32;
+
+
+
+
+
+
+
+
+const MAX_NAME_LENGTH: u64 = 32;
+
+
+
+
+
+
+
+
+const MAX_SYMBOL_LENGTH: u64 = 10;
+
+
+
+
+
+
+
+
+const MAX_URI_LENGTH: u64 = 512;
+
+
+
+
+
+
+## Function `default_to_concurrent_fungible_supply`
+
+
+
+fun default_to_concurrent_fungible_supply(): bool
+
+
+
+
+inline fun default_to_concurrent_fungible_supply(): bool {
+ features::concurrent_fungible_assets_enabled()
+}
+
+
+
+
+fun allow_upgrade_to_concurrent_fungible_balance(): bool
+
+
+
+
+inline fun allow_upgrade_to_concurrent_fungible_balance(): bool {
+ features::concurrent_fungible_balance_enabled()
+}
+
+
+
+
+fun default_to_concurrent_fungible_balance(): bool
+
+
+
+
+inline fun default_to_concurrent_fungible_balance(): bool {
+ features::default_to_concurrent_fungible_balance_enabled()
+}
+
+
+
+
+max
as the maximum supply.
+
+
+public fun add_fungibility(constructor_ref: &object::ConstructorRef, maximum_supply: option::Option<u128>, name: string::String, symbol: string::String, decimals: u8, icon_uri: string::String, project_uri: string::String): object::Object<fungible_asset::Metadata>
+
+
+
+
+public fun add_fungibility(
+ constructor_ref: &ConstructorRef,
+ maximum_supply: Option<u128>,
+ name: String,
+ symbol: String,
+ decimals: u8,
+ icon_uri: String,
+ project_uri: String,
+): Object<Metadata> {
+ assert!(!object::can_generate_delete_ref(constructor_ref), error::invalid_argument(EOBJECT_IS_DELETABLE));
+ let metadata_object_signer = &object::generate_signer(constructor_ref);
+ assert!(string::length(&name) <= MAX_NAME_LENGTH, error::out_of_range(ENAME_TOO_LONG));
+ assert!(string::length(&symbol) <= MAX_SYMBOL_LENGTH, error::out_of_range(ESYMBOL_TOO_LONG));
+ assert!(decimals <= MAX_DECIMALS, error::out_of_range(EDECIMALS_TOO_LARGE));
+ assert!(string::length(&icon_uri) <= MAX_URI_LENGTH, error::out_of_range(EURI_TOO_LONG));
+ assert!(string::length(&project_uri) <= MAX_URI_LENGTH, error::out_of_range(EURI_TOO_LONG));
+ move_to(metadata_object_signer,
+ Metadata {
+ name,
+ symbol,
+ decimals,
+ icon_uri,
+ project_uri,
+ }
+ );
+
+ if (default_to_concurrent_fungible_supply()) {
+ let unlimited = option::is_none(&maximum_supply);
+ move_to(metadata_object_signer, ConcurrentSupply {
+ current: if (unlimited) {
+ aggregator_v2::create_unbounded_aggregator()
+ } else {
+ aggregator_v2::create_aggregator(option::extract(&mut maximum_supply))
+ },
+ });
+ } else {
+ move_to(metadata_object_signer, Supply {
+ current: 0,
+ maximum: maximum_supply
+ });
+ };
+
+ object::object_from_constructor_ref<Metadata>(constructor_ref)
+}
+
+
+
+
+public fun set_untransferable(constructor_ref: &object::ConstructorRef)
+
+
+
+
+public fun set_untransferable(constructor_ref: &ConstructorRef) {
+ let metadata_addr = object::address_from_constructor_ref(constructor_ref);
+ assert!(exists<Metadata>(metadata_addr), error::not_found(EFUNGIBLE_METADATA_EXISTENCE));
+ let metadata_signer = &object::generate_signer(constructor_ref);
+ move_to(metadata_signer, Untransferable {});
+}
+
+
+
+
+#[view]
+public fun is_untransferable<T: key>(metadata: object::Object<T>): bool
+
+
+
+
+public fun is_untransferable<T: key>(metadata: Object<T>): bool {
+ exists<Untransferable>(object::object_address(&metadata))
+}
+
+
+
+
+public(friend) fun register_dispatch_functions(constructor_ref: &object::ConstructorRef, withdraw_function: option::Option<function_info::FunctionInfo>, deposit_function: option::Option<function_info::FunctionInfo>, derived_balance_function: option::Option<function_info::FunctionInfo>)
+
+
+
+
+public(friend) fun register_dispatch_functions(
+ constructor_ref: &ConstructorRef,
+ withdraw_function: Option<FunctionInfo>,
+ deposit_function: Option<FunctionInfo>,
+ derived_balance_function: Option<FunctionInfo>,
+) {
+ // Verify that caller type matches callee type so wrongly typed function cannot be registered.
+ option::for_each_ref(&withdraw_function, |withdraw_function| {
+ let dispatcher_withdraw_function_info = function_info::new_function_info_from_address(
+ @aptos_framework,
+ string::utf8(b"dispatchable_fungible_asset"),
+ string::utf8(b"dispatchable_withdraw"),
+ );
+
+ assert!(
+ function_info::check_dispatch_type_compatibility(
+ &dispatcher_withdraw_function_info,
+ withdraw_function
+ ),
+ error::invalid_argument(
+ EWITHDRAW_FUNCTION_SIGNATURE_MISMATCH
+ )
+ );
+ });
+
+ option::for_each_ref(&deposit_function, |deposit_function| {
+ let dispatcher_deposit_function_info = function_info::new_function_info_from_address(
+ @aptos_framework,
+ string::utf8(b"dispatchable_fungible_asset"),
+ string::utf8(b"dispatchable_deposit"),
+ );
+ // Verify that caller type matches callee type so wrongly typed function cannot be registered.
+ assert!(
+ function_info::check_dispatch_type_compatibility(
+ &dispatcher_deposit_function_info,
+ deposit_function
+ ),
+ error::invalid_argument(
+ EDEPOSIT_FUNCTION_SIGNATURE_MISMATCH
+ )
+ );
+ });
+
+ option::for_each_ref(&derived_balance_function, |balance_function| {
+ let dispatcher_derived_balance_function_info = function_info::new_function_info_from_address(
+ @aptos_framework,
+ string::utf8(b"dispatchable_fungible_asset"),
+ string::utf8(b"dispatchable_derived_balance"),
+ );
+ // Verify that caller type matches callee type so wrongly typed function cannot be registered.
+ assert!(
+ function_info::check_dispatch_type_compatibility(
+ &dispatcher_derived_balance_function_info,
+ balance_function
+ ),
+ error::invalid_argument(
+ EDERIVED_BALANCE_FUNCTION_SIGNATURE_MISMATCH
+ )
+ );
+ });
+
+ // Cannot register hook for APT.
+ assert!(
+ object::address_from_constructor_ref(constructor_ref) != @aptos_fungible_asset,
+ error::permission_denied(EAPT_NOT_DISPATCHABLE)
+ );
+ assert!(
+ !object::can_generate_delete_ref(constructor_ref),
+ error::invalid_argument(EOBJECT_IS_DELETABLE)
+ );
+ assert!(
+ !exists<DispatchFunctionStore>(
+ object::address_from_constructor_ref(constructor_ref)
+ ),
+ error::already_exists(EALREADY_REGISTERED)
+ );
+ assert!(
+ exists<Metadata>(
+ object::address_from_constructor_ref(constructor_ref)
+ ),
+ error::not_found(EFUNGIBLE_METADATA_EXISTENCE),
+ );
+
+ let store_obj = &object::generate_signer(constructor_ref);
+
+ // Store the overload function hook.
+ move_to<DispatchFunctionStore>(
+ store_obj,
+ DispatchFunctionStore {
+ withdraw_function,
+ deposit_function,
+ derived_balance_function,
+ }
+ );
+}
+
+
+
+
+public fun generate_mint_ref(constructor_ref: &object::ConstructorRef): fungible_asset::MintRef
+
+
+
+
+public fun generate_mint_ref(constructor_ref: &ConstructorRef): MintRef {
+ let metadata = object::object_from_constructor_ref<Metadata>(constructor_ref);
+ MintRef { metadata }
+}
+
+
+
+
+public fun generate_burn_ref(constructor_ref: &object::ConstructorRef): fungible_asset::BurnRef
+
+
+
+
+public fun generate_burn_ref(constructor_ref: &ConstructorRef): BurnRef {
+ let metadata = object::object_from_constructor_ref<Metadata>(constructor_ref);
+ BurnRef { metadata }
+}
+
+
+
+
+public fun generate_transfer_ref(constructor_ref: &object::ConstructorRef): fungible_asset::TransferRef
+
+
+
+
+public fun generate_transfer_ref(constructor_ref: &ConstructorRef): TransferRef {
+ let metadata = object::object_from_constructor_ref<Metadata>(constructor_ref);
+ TransferRef { metadata }
+}
+
+
+
+
+public fun generate_mutate_metadata_ref(constructor_ref: &object::ConstructorRef): fungible_asset::MutateMetadataRef
+
+
+
+
+public fun generate_mutate_metadata_ref(constructor_ref: &ConstructorRef): MutateMetadataRef {
+ let metadata = object::object_from_constructor_ref<Metadata>(constructor_ref);
+ MutateMetadataRef { metadata }
+}
+
+
+
+
+metadata
object.
+
+
+#[view]
+public fun supply<T: key>(metadata: object::Object<T>): option::Option<u128>
+
+
+
+
+public fun supply<T: key>(metadata: Object<T>): Option<u128> acquires Supply, ConcurrentSupply {
+ let metadata_address = object::object_address(&metadata);
+ if (exists<ConcurrentSupply>(metadata_address)) {
+ let supply = borrow_global<ConcurrentSupply>(metadata_address);
+ option::some(aggregator_v2::read(&supply.current))
+ } else if (exists<Supply>(metadata_address)) {
+ let supply = borrow_global<Supply>(metadata_address);
+ option::some(supply.current)
+ } else {
+ option::none()
+ }
+}
+
+
+
+
+metadata
object.
+If supply is unlimited (or set explicitly to MAX_U128), none is returned
+
+
+#[view]
+public fun maximum<T: key>(metadata: object::Object<T>): option::Option<u128>
+
+
+
+
+public fun maximum<T: key>(metadata: Object<T>): Option<u128> acquires Supply, ConcurrentSupply {
+ let metadata_address = object::object_address(&metadata);
+ if (exists<ConcurrentSupply>(metadata_address)) {
+ let supply = borrow_global<ConcurrentSupply>(metadata_address);
+ let max_value = aggregator_v2::max_value(&supply.current);
+ if (max_value == MAX_U128) {
+ option::none()
+ } else {
+ option::some(max_value)
+ }
+ } else if (exists<Supply>(metadata_address)) {
+ let supply = borrow_global<Supply>(metadata_address);
+ supply.maximum
+ } else {
+ option::none()
+ }
+}
+
+
+
+
+metadata
object.
+
+
+#[view]
+public fun name<T: key>(metadata: object::Object<T>): string::String
+
+
+
+
+public fun name<T: key>(metadata: Object<T>): String acquires Metadata {
+ borrow_fungible_metadata(&metadata).name
+}
+
+
+
+
+metadata
object.
+
+
+#[view]
+public fun symbol<T: key>(metadata: object::Object<T>): string::String
+
+
+
+
+public fun symbol<T: key>(metadata: Object<T>): String acquires Metadata {
+ borrow_fungible_metadata(&metadata).symbol
+}
+
+
+
+
+metadata
object.
+
+
+#[view]
+public fun decimals<T: key>(metadata: object::Object<T>): u8
+
+
+
+
+public fun decimals<T: key>(metadata: Object<T>): u8 acquires Metadata {
+ borrow_fungible_metadata(&metadata).decimals
+}
+
+
+
+
+metadata
object.
+
+
+#[view]
+public fun icon_uri<T: key>(metadata: object::Object<T>): string::String
+
+
+
+
+public fun icon_uri<T: key>(metadata: Object<T>): String acquires Metadata {
+ borrow_fungible_metadata(&metadata).icon_uri
+}
+
+
+
+
+metadata
object.
+
+
+#[view]
+public fun project_uri<T: key>(metadata: object::Object<T>): string::String
+
+
+
+
+public fun project_uri<T: key>(metadata: Object<T>): String acquires Metadata {
+ borrow_fungible_metadata(&metadata).project_uri
+}
+
+
+
+
+metadata
object.
+
+
+#[view]
+public fun metadata<T: key>(metadata: object::Object<T>): fungible_asset::Metadata
+
+
+
+
+public fun metadata<T: key>(metadata: Object<T>): Metadata acquires Metadata {
+ *borrow_fungible_metadata(&metadata)
+}
+
+
+
+
+#[view]
+public fun store_exists(store: address): bool
+
+
+
+
+public fun store_exists(store: address): bool {
+ store_exists_inline(store)
+}
+
+
+
+
+fun store_exists_inline(store: address): bool
+
+
+
+
+inline fun store_exists_inline(store: address): bool {
+ exists<FungibleStore>(store)
+}
+
+
+
+
+fun concurrent_fungible_balance_exists_inline(store: address): bool
+
+
+
+
+inline fun concurrent_fungible_balance_exists_inline(store: address): bool {
+ exists<ConcurrentFungibleBalance>(store)
+}
+
+
+
+
+public fun metadata_from_asset(fa: &fungible_asset::FungibleAsset): object::Object<fungible_asset::Metadata>
+
+
+
+
+public fun metadata_from_asset(fa: &FungibleAsset): Object<Metadata> {
+ fa.metadata
+}
+
+
+
+
+#[view]
+public fun store_metadata<T: key>(store: object::Object<T>): object::Object<fungible_asset::Metadata>
+
+
+
+
+public fun store_metadata<T: key>(store: Object<T>): Object<Metadata> acquires FungibleStore {
+ borrow_store_resource(&store).metadata
+}
+
+
+
+
+amount
of a given fungible asset.
+
+
+public fun amount(fa: &fungible_asset::FungibleAsset): u64
+
+
+
+
+public fun amount(fa: &FungibleAsset): u64 {
+ fa.amount
+}
+
+
+
+
+#[view]
+public fun balance<T: key>(store: object::Object<T>): u64
+
+
+
+
+public fun balance<T: key>(store: Object<T>): u64 acquires FungibleStore, ConcurrentFungibleBalance {
+ let store_addr = object::object_address(&store);
+ if (store_exists_inline(store_addr)) {
+ let store_balance = borrow_store_resource(&store).balance;
+ if (store_balance == 0 && concurrent_fungible_balance_exists_inline(store_addr)) {
+ let balance_resource = borrow_global<ConcurrentFungibleBalance>(store_addr);
+ aggregator_v2::read(&balance_resource.balance)
+ } else {
+ store_balance
+ }
+ } else {
+ 0
+ }
+}
+
+
+
+
+amount
.
+
+
+#[view]
+public fun is_balance_at_least<T: key>(store: object::Object<T>, amount: u64): bool
+
+
+
+
+public fun is_balance_at_least<T: key>(store: Object<T>, amount: u64): bool acquires FungibleStore, ConcurrentFungibleBalance {
+ let store_addr = object::object_address(&store);
+ is_address_balance_at_least(store_addr, amount)
+}
+
+
+
+
+amount
.
+
+
+public(friend) fun is_address_balance_at_least(store_addr: address, amount: u64): bool
+
+
+
+
+public(friend) fun is_address_balance_at_least(store_addr: address, amount: u64): bool acquires FungibleStore, ConcurrentFungibleBalance {
+ if (store_exists_inline(store_addr)) {
+ let store_balance = borrow_global<FungibleStore>(store_addr).balance;
+ if (store_balance == 0 && concurrent_fungible_balance_exists_inline(store_addr)) {
+ let balance_resource = borrow_global<ConcurrentFungibleBalance>(store_addr);
+ aggregator_v2::is_at_least(&balance_resource.balance, amount)
+ } else {
+ store_balance >= amount
+ }
+ } else {
+ amount == 0
+ }
+}
+
+
+
+
+#[view]
+public fun is_frozen<T: key>(store: object::Object<T>): bool
+
+
+
+
+public fun is_frozen<T: key>(store: Object<T>): bool acquires FungibleStore {
+ let store_addr = object::object_address(&store);
+ store_exists_inline(store_addr) && borrow_global<FungibleStore>(store_addr).frozen
+}
+
+
+
+
+#[view]
+public fun is_store_dispatchable<T: key>(store: object::Object<T>): bool
+
+
+
+
+public fun is_store_dispatchable<T: key>(store: Object<T>): bool acquires FungibleStore {
+ let fa_store = borrow_store_resource(&store);
+ let metadata_addr = object::object_address(&fa_store.metadata);
+ exists<DispatchFunctionStore>(metadata_addr)
+}
+
+
+
+
+public fun deposit_dispatch_function<T: key>(store: object::Object<T>): option::Option<function_info::FunctionInfo>
+
+
+
+
+public fun deposit_dispatch_function<T: key>(store: Object<T>): Option<FunctionInfo> acquires FungibleStore, DispatchFunctionStore {
+ let fa_store = borrow_store_resource(&store);
+ let metadata_addr = object::object_address(&fa_store.metadata);
+ if(exists<DispatchFunctionStore>(metadata_addr)) {
+ borrow_global<DispatchFunctionStore>(metadata_addr).deposit_function
+ } else {
+ option::none()
+ }
+}
+
+
+
+
+fun has_deposit_dispatch_function(metadata: object::Object<fungible_asset::Metadata>): bool
+
+
+
+
+fun has_deposit_dispatch_function(metadata: Object<Metadata>): bool acquires DispatchFunctionStore {
+ let metadata_addr = object::object_address(&metadata);
+ // Short circuit on APT for better perf
+ if(metadata_addr != @aptos_fungible_asset && exists<DispatchFunctionStore>(metadata_addr)) {
+ option::is_some(&borrow_global<DispatchFunctionStore>(metadata_addr).deposit_function)
+ } else {
+ false
+ }
+}
+
+
+
+
+public fun withdraw_dispatch_function<T: key>(store: object::Object<T>): option::Option<function_info::FunctionInfo>
+
+
+
+
+public fun withdraw_dispatch_function<T: key>(store: Object<T>): Option<FunctionInfo> acquires FungibleStore, DispatchFunctionStore {
+ let fa_store = borrow_store_resource(&store);
+ let metadata_addr = object::object_address(&fa_store.metadata);
+ if(exists<DispatchFunctionStore>(metadata_addr)) {
+ borrow_global<DispatchFunctionStore>(metadata_addr).withdraw_function
+ } else {
+ option::none()
+ }
+}
+
+
+
+
+fun has_withdraw_dispatch_function(metadata: object::Object<fungible_asset::Metadata>): bool
+
+
+
+
+fun has_withdraw_dispatch_function(metadata: Object<Metadata>): bool acquires DispatchFunctionStore {
+ let metadata_addr = object::object_address(&metadata);
+ // Short circuit on APT for better perf
+ if (metadata_addr != @aptos_fungible_asset && exists<DispatchFunctionStore>(metadata_addr)) {
+ option::is_some(&borrow_global<DispatchFunctionStore>(metadata_addr).withdraw_function)
+ } else {
+ false
+ }
+}
+
+
+
+
+public(friend) fun derived_balance_dispatch_function<T: key>(store: object::Object<T>): option::Option<function_info::FunctionInfo>
+
+
+
+
+public(friend) fun derived_balance_dispatch_function<T: key>(store: Object<T>): Option<FunctionInfo> acquires FungibleStore, DispatchFunctionStore {
+ let fa_store = borrow_store_resource(&store);
+ let metadata_addr = object::object_address(&fa_store.metadata);
+ if (exists<DispatchFunctionStore>(metadata_addr)) {
+ borrow_global<DispatchFunctionStore>(metadata_addr).derived_balance_function
+ } else {
+ option::none()
+ }
+}
+
+
+
+
+public fun asset_metadata(fa: &fungible_asset::FungibleAsset): object::Object<fungible_asset::Metadata>
+
+
+
+
+public fun asset_metadata(fa: &FungibleAsset): Object<Metadata> {
+ fa.metadata
+}
+
+
+
+
+MintRef
.
+
+
+public fun mint_ref_metadata(ref: &fungible_asset::MintRef): object::Object<fungible_asset::Metadata>
+
+
+
+
+public fun mint_ref_metadata(ref: &MintRef): Object<Metadata> {
+ ref.metadata
+}
+
+
+
+
+TransferRef
.
+
+
+public fun transfer_ref_metadata(ref: &fungible_asset::TransferRef): object::Object<fungible_asset::Metadata>
+
+
+
+
+public fun transfer_ref_metadata(ref: &TransferRef): Object<Metadata> {
+ ref.metadata
+}
+
+
+
+
+BurnRef
.
+
+
+public fun burn_ref_metadata(ref: &fungible_asset::BurnRef): object::Object<fungible_asset::Metadata>
+
+
+
+
+public fun burn_ref_metadata(ref: &BurnRef): Object<Metadata> {
+ ref.metadata
+}
+
+
+
+
+MutateMetadataRef
.
+
+
+public fun object_from_metadata_ref(ref: &fungible_asset::MutateMetadataRef): object::Object<fungible_asset::Metadata>
+
+
+
+
+public fun object_from_metadata_ref(ref: &MutateMetadataRef): Object<Metadata> {
+ ref.metadata
+}
+
+
+
+
+amount
of fungible asset from from_store
, which should be owned by sender
, to receiver
.
+Note: it does not move the underlying object.
+
+
+public entry fun transfer<T: key>(sender: &signer, from: object::Object<T>, to: object::Object<T>, amount: u64)
+
+
+
+
+public entry fun transfer<T: key>(
+ sender: &signer,
+ from: Object<T>,
+ to: Object<T>,
+ amount: u64,
+) acquires FungibleStore, DispatchFunctionStore, ConcurrentFungibleBalance {
+ let fa = withdraw(sender, from, amount);
+ deposit(to, fa);
+}
+
+
+
+
+public fun create_store<T: key>(constructor_ref: &object::ConstructorRef, metadata: object::Object<T>): object::Object<fungible_asset::FungibleStore>
+
+
+
+
+public fun create_store<T: key>(
+ constructor_ref: &ConstructorRef,
+ metadata: Object<T>,
+): Object<FungibleStore> {
+ let store_obj = &object::generate_signer(constructor_ref);
+ move_to(store_obj, FungibleStore {
+ metadata: object::convert(metadata),
+ balance: 0,
+ frozen: false,
+ });
+
+ if (is_untransferable(metadata)) {
+ object::set_untransferable(constructor_ref);
+ };
+
+ if (default_to_concurrent_fungible_balance()) {
+ move_to(store_obj, ConcurrentFungibleBalance {
+ balance: aggregator_v2::create_unbounded_aggregator(),
+ });
+ };
+
+ object::object_from_constructor_ref<FungibleStore>(constructor_ref)
+}
+
+
+
+
+public fun remove_store(delete_ref: &object::DeleteRef)
+
+
+
+
+public fun remove_store(delete_ref: &DeleteRef) acquires FungibleStore, FungibleAssetEvents, ConcurrentFungibleBalance {
+ let store = &object::object_from_delete_ref<FungibleStore>(delete_ref);
+ let addr = object::object_address(store);
+ let FungibleStore { metadata: _, balance, frozen: _ }
+ = move_from<FungibleStore>(addr);
+ assert!(balance == 0, error::permission_denied(EBALANCE_IS_NOT_ZERO));
+
+ if (concurrent_fungible_balance_exists_inline(addr)) {
+ let ConcurrentFungibleBalance { balance } = move_from<ConcurrentFungibleBalance>(addr);
+ assert!(aggregator_v2::read(&balance) == 0, error::permission_denied(EBALANCE_IS_NOT_ZERO));
+ };
+
+ // Cleanup deprecated event handles if exist.
+ if (exists<FungibleAssetEvents>(addr)) {
+ let FungibleAssetEvents {
+ deposit_events,
+ withdraw_events,
+ frozen_events,
+ } = move_from<FungibleAssetEvents>(addr);
+ event::destroy_handle(deposit_events);
+ event::destroy_handle(withdraw_events);
+ event::destroy_handle(frozen_events);
+ };
+}
+
+
+
+
+amount
of the fungible asset from store
by the owner.
+
+
+public fun withdraw<T: key>(owner: &signer, store: object::Object<T>, amount: u64): fungible_asset::FungibleAsset
+
+
+
+
+public fun withdraw<T: key>(
+ owner: &signer,
+ store: Object<T>,
+ amount: u64,
+): FungibleAsset acquires FungibleStore, DispatchFunctionStore, ConcurrentFungibleBalance {
+ withdraw_sanity_check(owner, store, true);
+ withdraw_internal(object::object_address(&store), amount)
+}
+
+
+
+
+public(friend) fun withdraw_sanity_check<T: key>(owner: &signer, store: object::Object<T>, abort_on_dispatch: bool)
+
+
+
+
+public(friend) fun withdraw_sanity_check<T: key>(
+ owner: &signer,
+ store: Object<T>,
+ abort_on_dispatch: bool,
+) acquires FungibleStore, DispatchFunctionStore {
+ assert!(object::owns(store, signer::address_of(owner)), error::permission_denied(ENOT_STORE_OWNER));
+ let fa_store = borrow_store_resource(&store);
+ assert!(
+ !abort_on_dispatch || !has_withdraw_dispatch_function(fa_store.metadata),
+ error::invalid_argument(EINVALID_DISPATCHABLE_OPERATIONS)
+ );
+ assert!(!fa_store.frozen, error::permission_denied(ESTORE_IS_FROZEN));
+}
+
+
+
+
+amount
of the fungible asset to store
.
+
+
+public fun deposit_sanity_check<T: key>(store: object::Object<T>, abort_on_dispatch: bool)
+
+
+
+
+public fun deposit_sanity_check<T: key>(
+ store: Object<T>,
+ abort_on_dispatch: bool
+) acquires FungibleStore, DispatchFunctionStore {
+ let fa_store = borrow_store_resource(&store);
+ assert!(
+ !abort_on_dispatch || !has_deposit_dispatch_function(fa_store.metadata),
+ error::invalid_argument(EINVALID_DISPATCHABLE_OPERATIONS)
+ );
+ assert!(!fa_store.frozen, error::permission_denied(ESTORE_IS_FROZEN));
+}
+
+
+
+
+amount
of the fungible asset to store
.
+
+
+public fun deposit<T: key>(store: object::Object<T>, fa: fungible_asset::FungibleAsset)
+
+
+
+
+public fun deposit<T: key>(store: Object<T>, fa: FungibleAsset) acquires FungibleStore, DispatchFunctionStore, ConcurrentFungibleBalance {
+ deposit_sanity_check(store, true);
+ deposit_internal(object::object_address(&store), fa);
+}
+
+
+
+
+amount
of the fungible asset.
+
+
+public fun mint(ref: &fungible_asset::MintRef, amount: u64): fungible_asset::FungibleAsset
+
+
+
+
+public fun mint(ref: &MintRef, amount: u64): FungibleAsset acquires Supply, ConcurrentSupply {
+ let metadata = ref.metadata;
+ mint_internal(metadata, amount)
+}
+
+
+
+
+public(friend) fun mint_internal(metadata: object::Object<fungible_asset::Metadata>, amount: u64): fungible_asset::FungibleAsset
+
+
+
+
+public(friend) fun mint_internal(
+ metadata: Object<Metadata>,
+ amount: u64
+): FungibleAsset acquires Supply, ConcurrentSupply {
+ increase_supply(&metadata, amount);
+ FungibleAsset {
+ metadata,
+ amount
+ }
+}
+
+
+
+
+amount
of the fungible asset to a destination store.
+
+
+public fun mint_to<T: key>(ref: &fungible_asset::MintRef, store: object::Object<T>, amount: u64)
+
+
+
+
+public fun mint_to<T: key>(ref: &MintRef, store: Object<T>, amount: u64)
+acquires FungibleStore, Supply, ConcurrentSupply, DispatchFunctionStore, ConcurrentFungibleBalance {
+ deposit_sanity_check(store, false);
+ deposit_internal(object::object_address(&store), mint(ref, amount));
+}
+
+
+
+
+public fun set_frozen_flag<T: key>(ref: &fungible_asset::TransferRef, store: object::Object<T>, frozen: bool)
+
+
+
+
+public fun set_frozen_flag<T: key>(
+ ref: &TransferRef,
+ store: Object<T>,
+ frozen: bool,
+) acquires FungibleStore {
+ assert!(
+ ref.metadata == store_metadata(store),
+ error::invalid_argument(ETRANSFER_REF_AND_STORE_MISMATCH),
+ );
+ set_frozen_flag_internal(store, frozen)
+}
+
+
+
+
+public(friend) fun set_frozen_flag_internal<T: key>(store: object::Object<T>, frozen: bool)
+
+
+
+
+public(friend) fun set_frozen_flag_internal<T: key>(
+ store: Object<T>,
+ frozen: bool
+) acquires FungibleStore {
+ let store_addr = object::object_address(&store);
+ borrow_global_mut<FungibleStore>(store_addr).frozen = frozen;
+
+ event::emit(Frozen { store: store_addr, frozen });
+}
+
+
+
+
+public fun burn(ref: &fungible_asset::BurnRef, fa: fungible_asset::FungibleAsset)
+
+
+
+
+public fun burn(ref: &BurnRef, fa: FungibleAsset) acquires Supply, ConcurrentSupply {
+ assert!(
+ ref.metadata == metadata_from_asset(&fa),
+ error::invalid_argument(EBURN_REF_AND_FUNGIBLE_ASSET_MISMATCH)
+ );
+ burn_internal(fa);
+}
+
+
+
+
+public(friend) fun burn_internal(fa: fungible_asset::FungibleAsset): u64
+
+
+
+
+public(friend) fun burn_internal(
+ fa: FungibleAsset
+): u64 acquires Supply, ConcurrentSupply {
+ let FungibleAsset {
+ metadata,
+ amount
+ } = fa;
+ decrease_supply(&metadata, amount);
+ amount
+}
+
+
+
+
+amount
of the fungible asset from the given store.
+
+
+public fun burn_from<T: key>(ref: &fungible_asset::BurnRef, store: object::Object<T>, amount: u64)
+
+
+
+
+public fun burn_from<T: key>(
+ ref: &BurnRef,
+ store: Object<T>,
+ amount: u64
+) acquires FungibleStore, Supply, ConcurrentSupply, ConcurrentFungibleBalance {
+ // ref metadata match is checked in burn() call
+ burn(ref, withdraw_internal(object::object_address(&store), amount));
+}
+
+
+
+
+public(friend) fun address_burn_from(ref: &fungible_asset::BurnRef, store_addr: address, amount: u64)
+
+
+
+
+public(friend) fun address_burn_from(
+ ref: &BurnRef,
+ store_addr: address,
+ amount: u64
+) acquires FungibleStore, Supply, ConcurrentSupply, ConcurrentFungibleBalance {
+ // ref metadata match is checked in burn() call
+ burn(ref, withdraw_internal(store_addr, amount));
+}
+
+
+
+
+amount
of the fungible asset from the store
ignoring frozen
.
+
+
+public fun withdraw_with_ref<T: key>(ref: &fungible_asset::TransferRef, store: object::Object<T>, amount: u64): fungible_asset::FungibleAsset
+
+
+
+
+public fun withdraw_with_ref<T: key>(
+ ref: &TransferRef,
+ store: Object<T>,
+ amount: u64
+): FungibleAsset acquires FungibleStore, ConcurrentFungibleBalance {
+ assert!(
+ ref.metadata == store_metadata(store),
+ error::invalid_argument(ETRANSFER_REF_AND_STORE_MISMATCH),
+ );
+ withdraw_internal(object::object_address(&store), amount)
+}
+
+
+
+
+store
ignoring frozen
.
+
+
+public fun deposit_with_ref<T: key>(ref: &fungible_asset::TransferRef, store: object::Object<T>, fa: fungible_asset::FungibleAsset)
+
+
+
+
+public fun deposit_with_ref<T: key>(
+ ref: &TransferRef,
+ store: Object<T>,
+ fa: FungibleAsset
+) acquires FungibleStore, ConcurrentFungibleBalance {
+ assert!(
+ ref.metadata == fa.metadata,
+ error::invalid_argument(ETRANSFER_REF_AND_FUNGIBLE_ASSET_MISMATCH)
+ );
+ deposit_internal(object::object_address(&store), fa);
+}
+
+
+
+
+amount
of the fungible asset with TransferRef
even it is frozen.
+
+
+public fun transfer_with_ref<T: key>(transfer_ref: &fungible_asset::TransferRef, from: object::Object<T>, to: object::Object<T>, amount: u64)
+
+
+
+
+public fun transfer_with_ref<T: key>(
+ transfer_ref: &TransferRef,
+ from: Object<T>,
+ to: Object<T>,
+ amount: u64,
+) acquires FungibleStore, ConcurrentFungibleBalance {
+ let fa = withdraw_with_ref(transfer_ref, from, amount);
+ deposit_with_ref(transfer_ref, to, fa);
+}
+
+
+
+
+Metadata
.
+
+
+public fun mutate_metadata(metadata_ref: &fungible_asset::MutateMetadataRef, name: option::Option<string::String>, symbol: option::Option<string::String>, decimals: option::Option<u8>, icon_uri: option::Option<string::String>, project_uri: option::Option<string::String>)
+
+
+
+
+public fun mutate_metadata(
+ metadata_ref: &MutateMetadataRef,
+ name: Option<String>,
+ symbol: Option<String>,
+ decimals: Option<u8>,
+ icon_uri: Option<String>,
+ project_uri: Option<String>,
+) acquires Metadata {
+ let metadata_address = object::object_address(&metadata_ref.metadata);
+ let mutable_metadata = borrow_global_mut<Metadata>(metadata_address);
+
+ if (option::is_some(&name)){
+ mutable_metadata.name = option::extract(&mut name);
+ };
+ if (option::is_some(&symbol)){
+ mutable_metadata.symbol = option::extract(&mut symbol);
+ };
+ if (option::is_some(&decimals)){
+ mutable_metadata.decimals = option::extract(&mut decimals);
+ };
+ if (option::is_some(&icon_uri)){
+ mutable_metadata.icon_uri = option::extract(&mut icon_uri);
+ };
+ if (option::is_some(&project_uri)){
+ mutable_metadata.project_uri = option::extract(&mut project_uri);
+ };
+}
+
+
+
+
+public fun zero<T: key>(metadata: object::Object<T>): fungible_asset::FungibleAsset
+
+
+
+
+public fun zero<T: key>(metadata: Object<T>): FungibleAsset {
+ FungibleAsset {
+ metadata: object::convert(metadata),
+ amount: 0,
+ }
+}
+
+
+
+
+public fun extract(fungible_asset: &mut fungible_asset::FungibleAsset, amount: u64): fungible_asset::FungibleAsset
+
+
+
+
+public fun extract(fungible_asset: &mut FungibleAsset, amount: u64): FungibleAsset {
+ assert!(fungible_asset.amount >= amount, error::invalid_argument(EINSUFFICIENT_BALANCE));
+ fungible_asset.amount = fungible_asset.amount - amount;
+ FungibleAsset {
+ metadata: fungible_asset.metadata,
+ amount,
+ }
+}
+
+
+
+
+dst_fungible_asset
will have a value
+equal to the sum of the two (dst_fungible_asset
and src_fungible_asset
).
+
+
+public fun merge(dst_fungible_asset: &mut fungible_asset::FungibleAsset, src_fungible_asset: fungible_asset::FungibleAsset)
+
+
+
+
+public fun merge(dst_fungible_asset: &mut FungibleAsset, src_fungible_asset: FungibleAsset) {
+ let FungibleAsset { metadata, amount } = src_fungible_asset;
+ assert!(metadata == dst_fungible_asset.metadata, error::invalid_argument(EFUNGIBLE_ASSET_MISMATCH));
+ dst_fungible_asset.amount = dst_fungible_asset.amount + amount;
+}
+
+
+
+
+public fun destroy_zero(fungible_asset: fungible_asset::FungibleAsset)
+
+
+
+
+public fun destroy_zero(fungible_asset: FungibleAsset) {
+ let FungibleAsset { amount, metadata: _ } = fungible_asset;
+ assert!(amount == 0, error::invalid_argument(EAMOUNT_IS_NOT_ZERO));
+}
+
+
+
+
+public(friend) fun deposit_internal(store_addr: address, fa: fungible_asset::FungibleAsset)
+
+
+
+
+public(friend) fun deposit_internal(store_addr: address, fa: FungibleAsset) acquires FungibleStore, ConcurrentFungibleBalance {
+ let FungibleAsset { metadata, amount } = fa;
+ if (amount == 0) return;
+
+ assert!(exists<FungibleStore>(store_addr), error::not_found(EFUNGIBLE_STORE_EXISTENCE));
+ let store = borrow_global_mut<FungibleStore>(store_addr);
+ assert!(metadata == store.metadata, error::invalid_argument(EFUNGIBLE_ASSET_AND_STORE_MISMATCH));
+
+ if (store.balance == 0 && concurrent_fungible_balance_exists_inline(store_addr)) {
+ let balance_resource = borrow_global_mut<ConcurrentFungibleBalance>(store_addr);
+ aggregator_v2::add(&mut balance_resource.balance, amount);
+ } else {
+ store.balance = store.balance + amount;
+ };
+
+ event::emit(Deposit { store: store_addr, amount });
+}
+
+
+
+
+amount
of the fungible asset from store
.
+
+
+public(friend) fun withdraw_internal(store_addr: address, amount: u64): fungible_asset::FungibleAsset
+
+
+
+
+public(friend) fun withdraw_internal(
+ store_addr: address,
+ amount: u64,
+): FungibleAsset acquires FungibleStore, ConcurrentFungibleBalance {
+ assert!(exists<FungibleStore>(store_addr), error::not_found(EFUNGIBLE_STORE_EXISTENCE));
+
+ let store = borrow_global_mut<FungibleStore>(store_addr);
+ let metadata = store.metadata;
+ if (amount != 0) {
+ if (store.balance == 0 && concurrent_fungible_balance_exists_inline(store_addr)) {
+ let balance_resource = borrow_global_mut<ConcurrentFungibleBalance>(store_addr);
+ assert!(
+ aggregator_v2::try_sub(&mut balance_resource.balance, amount),
+ error::invalid_argument(EINSUFFICIENT_BALANCE)
+ );
+ } else {
+ assert!(store.balance >= amount, error::invalid_argument(EINSUFFICIENT_BALANCE));
+ store.balance = store.balance - amount;
+ };
+
+ event::emit<Withdraw>(Withdraw { store: store_addr, amount });
+ };
+ FungibleAsset { metadata, amount }
+}
+
+
+
+
+fun increase_supply<T: key>(metadata: &object::Object<T>, amount: u64)
+
+
+
+
+fun increase_supply<T: key>(metadata: &Object<T>, amount: u64) acquires Supply, ConcurrentSupply {
+ if (amount == 0) {
+ return
+ };
+ let metadata_address = object::object_address(metadata);
+
+ if (exists<ConcurrentSupply>(metadata_address)) {
+ let supply = borrow_global_mut<ConcurrentSupply>(metadata_address);
+ assert!(
+ aggregator_v2::try_add(&mut supply.current, (amount as u128)),
+ error::out_of_range(EMAX_SUPPLY_EXCEEDED)
+ );
+ } else if (exists<Supply>(metadata_address)) {
+ let supply = borrow_global_mut<Supply>(metadata_address);
+ if (option::is_some(&supply.maximum)) {
+ let max = *option::borrow_mut(&mut supply.maximum);
+ assert!(
+ max - supply.current >= (amount as u128),
+ error::out_of_range(EMAX_SUPPLY_EXCEEDED)
+ )
+ };
+ supply.current = supply.current + (amount as u128);
+ } else {
+ abort error::not_found(ESUPPLY_NOT_FOUND)
+ }
+}
+
+
+
+
+fun decrease_supply<T: key>(metadata: &object::Object<T>, amount: u64)
+
+
+
+
+fun decrease_supply<T: key>(metadata: &Object<T>, amount: u64) acquires Supply, ConcurrentSupply {
+ if (amount == 0) {
+ return
+ };
+ let metadata_address = object::object_address(metadata);
+
+ if (exists<ConcurrentSupply>(metadata_address)) {
+ let supply = borrow_global_mut<ConcurrentSupply>(metadata_address);
+
+ assert!(
+ aggregator_v2::try_sub(&mut supply.current, (amount as u128)),
+ error::out_of_range(ESUPPLY_UNDERFLOW)
+ );
+ } else if (exists<Supply>(metadata_address)) {
+ assert!(exists<Supply>(metadata_address), error::not_found(ESUPPLY_NOT_FOUND));
+ let supply = borrow_global_mut<Supply>(metadata_address);
+ assert!(
+ supply.current >= (amount as u128),
+ error::invalid_state(ESUPPLY_UNDERFLOW)
+ );
+ supply.current = supply.current - (amount as u128);
+ } else {
+ assert!(false, error::not_found(ESUPPLY_NOT_FOUND));
+ }
+}
+
+
+
+
+fun borrow_fungible_metadata<T: key>(metadata: &object::Object<T>): &fungible_asset::Metadata
+
+
+
+
+inline fun borrow_fungible_metadata<T: key>(
+ metadata: &Object<T>
+): &Metadata acquires Metadata {
+ let addr = object::object_address(metadata);
+ borrow_global<Metadata>(addr)
+}
+
+
+
+
+fun borrow_fungible_metadata_mut<T: key>(metadata: &object::Object<T>): &mut fungible_asset::Metadata
+
+
+
+
+inline fun borrow_fungible_metadata_mut<T: key>(
+ metadata: &Object<T>
+): &mut Metadata acquires Metadata {
+ let addr = object::object_address(metadata);
+ borrow_global_mut<Metadata>(addr)
+}
+
+
+
+
+fun borrow_store_resource<T: key>(store: &object::Object<T>): &fungible_asset::FungibleStore
+
+
+
+
+inline fun borrow_store_resource<T: key>(store: &Object<T>): &FungibleStore acquires FungibleStore {
+ let store_addr = object::object_address(store);
+ assert!(exists<FungibleStore>(store_addr), error::not_found(EFUNGIBLE_STORE_EXISTENCE));
+ borrow_global<FungibleStore>(store_addr)
+}
+
+
+
+
+public fun upgrade_to_concurrent(ref: &object::ExtendRef)
+
+
+
+
+public fun upgrade_to_concurrent(
+ ref: &ExtendRef,
+) acquires Supply {
+ let metadata_object_address = object::address_from_extend_ref(ref);
+ let metadata_object_signer = object::generate_signer_for_extending(ref);
+ assert!(
+ features::concurrent_fungible_assets_enabled(),
+ error::invalid_argument(ECONCURRENT_SUPPLY_NOT_ENABLED)
+ );
+ assert!(exists<Supply>(metadata_object_address), error::not_found(ESUPPLY_NOT_FOUND));
+ let Supply {
+ current,
+ maximum,
+ } = move_from<Supply>(metadata_object_address);
+
+ let unlimited = option::is_none(&maximum);
+ let supply = ConcurrentSupply {
+ current: if (unlimited) {
+ aggregator_v2::create_unbounded_aggregator_with_value(current)
+ }
+ else {
+ aggregator_v2::create_aggregator_with_value(current, option::extract(&mut maximum))
+ },
+ };
+ move_to(&metadata_object_signer, supply);
+}
+
+
+
+
+public entry fun upgrade_store_to_concurrent<T: key>(owner: &signer, store: object::Object<T>)
+
+
+
+
+public entry fun upgrade_store_to_concurrent<T: key>(
+ owner: &signer,
+ store: Object<T>,
+) acquires FungibleStore {
+ assert!(object::owns(store, signer::address_of(owner)), error::permission_denied(ENOT_STORE_OWNER));
+ assert!(!is_frozen(store), error::invalid_argument(ESTORE_IS_FROZEN));
+ assert!(allow_upgrade_to_concurrent_fungible_balance(), error::invalid_argument(ECONCURRENT_BALANCE_NOT_ENABLED));
+ ensure_store_upgraded_to_concurrent_internal(object::object_address(&store));
+}
+
+
+
+
+FungibleStore
has ConcurrentFungibleBalance
.
+
+
+fun ensure_store_upgraded_to_concurrent_internal(fungible_store_address: address)
+
+
+
+
+fun ensure_store_upgraded_to_concurrent_internal(
+ fungible_store_address: address,
+) acquires FungibleStore {
+ if (exists<ConcurrentFungibleBalance>(fungible_store_address)) {
+ return
+ };
+ let store = borrow_global_mut<FungibleStore>(fungible_store_address);
+ let balance = aggregator_v2::create_unbounded_aggregator_with_value(store.balance);
+ store.balance = 0;
+ let object_signer = create_signer::create_signer(fungible_store_address);
+ move_to(&object_signer, ConcurrentFungibleBalance { balance });
+}
+
+
+
+
+No. | Requirement | Criticality | Implementation | Enforcement | +
---|---|---|---|---|
1 | +The metadata associated with the fungible asset is subject to precise size constraints. | +Medium | +The add_fungibility function has size limitations for the name, symbol, number of decimals, icon_uri, and project_uri field of the Metadata resource. | +This has been audited. | +
2 | +Adding fungibility to an existing object should initialize the metadata and supply resources and store them under the metadata object address. | +Low | +The add_fungibility function initializes the Metadata and Supply resources and moves them under the metadata object. | +Audited that the Metadata and Supply resources are initialized properly. | +
3 | +Generating mint, burn and transfer references can only be done at object creation time and if the object was added fungibility. | +Low | +The following functions generate the related references of the Metadata object: 1. generate_mint_ref 2. generate_burn_ref 3. generate_transfer_ref | +Audited that the Metadata object exists within the constructor ref. | +
4 | +Only the owner of a store should be allowed to withdraw fungible assets from it. | +High | +The fungible_asset::withdraw function ensures that the signer owns the store by asserting that the object address matches the address of the signer. | +Audited that the address of the signer owns the object. | +
5 | +The transfer, withdrawal and deposit operation should never change the current supply of the fungible asset. | +High | +The transfer function withdraws the fungible assets from the store and deposits them to the receiver. The withdraw function extracts the fungible asset from the fungible asset store. The deposit function adds the balance to the fungible asset store. | +Audited that the supply before and after the operation remains constant. | +
6 | +The owner of the store should only be able to withdraw a certain amount if its store has sufficient balance and is not frozen, unless the withdrawal is performed with a reference, and afterwards the store balance should be decreased. | +High | +The withdraw function ensures that the store is not frozen before calling withdraw_internal which ensures that the withdrawing amount is greater than 0 and less than the total balance from the store. The withdraw_with_ref ensures that the reference's metadata matches the store metadata. | +Audited that it aborts if the withdrawing store is frozen. Audited that it aborts if the store doesn't have sufficient balance. Audited that the balance of the withdrawing store is reduced by amount. | +
7 | +Only the same type of fungible assets should be deposited in a fungible asset store, if the store is not frozen, unless the deposit is performed with a reference, and afterwards the store balance should be increased. | +High | +The deposit function ensures that store is not frozen and proceeds to call the deposit_internal function which validates the store's metadata and the depositing asset's metadata followed by increasing the store balance by the given amount. The deposit_with_ref ensures that the reference's metadata matches the depositing asset's metadata. | +Audited that it aborts if the store is frozen. Audited that it aborts if the asset and asset store are different. Audited that the store's balance is increased by the deposited amount. | +
8 | +An object should only be allowed to hold one store for fungible assets. | +Medium | +The create_store function initializes a new FungibleStore resource and moves it under the object address. | +Audited that the resource was moved under the object. | +
9 | +When a new store is created, the balance should be set by default to the value zero. | +High | +The create_store function initializes a new fungible asset store with zero balance and stores it under the given construtorRef object. | +Audited that the store is properly initialized with zero balance. | +
10 | +A store should only be deleted if its balance is zero. | +Medium | +The remove_store function validates the store's balance and removes the store under the object address. | +Audited that aborts if the balance of the store is not zero. Audited that store is removed from the object address. | +
11 | +Minting and burning should alter the total supply value, and the store balances. | +High | +The mint process increases the total supply by the amount minted using the increase_supply function. The burn process withdraws the burn amount from the given store and decreases the total supply by the amount burned using the decrease_supply function. | +Audited the mint and burn functions that the supply was adjusted accordingly. | +
12 | +It must not be possible to burn an amount of fungible assets larger than their current supply. | +High | +The burn process ensures that the store has enough balance to burn, by asserting that the supply.current >= amount inside the decrease_supply function. | +Audited that it aborts if the provided store doesn't have sufficient balance. | +
13 | +Enabling or disabling store's frozen status should only be done with a valid transfer reference. | +High | +The set_frozen_flag function ensures that the TransferRef is provided via function argument and that the store's metadata matches the metadata from the reference. It then proceeds to update the frozen flag of the store. | +Audited that it aborts if the metadata doesn't match. Audited that the frozen flag is updated properly. | +
14 | +Extracting a specific amount from the fungible asset should be possible only if the total amount that it holds is greater or equal to the provided amount. | +High | +The extract function validates that the fungible asset has enough balance to extract and then updates it by subtracting the extracted amount. | +Audited that it aborts if the asset didn't have sufficient balance. Audited that the balance of the asset is updated. Audited that the extract function returns the extracted asset. | +
15 | +Merging two fungible assets should only be possible if both share the same metadata. | +Medium | +The merge function validates the metadata of the src and dst asset. | +Audited that it aborts if the metadata of the src and dst are not the same. | +
16 | +Post merging two fungible assets, the source asset should have the amount value equal to the sum of the two. | +High | +The merge function increases dst_fungible_asset.amount by src_fungible_asset.amount. | +Audited that the dst_fungible_asset balance is increased by amount. | +
17 | +Fungible assets with zero balance should be destroyed when the amount reaches value 0. | +Medium | +The destroy_zero ensures that the balance of the asset has the value 0 and destroy the asset. | +Audited that it aborts if the balance of the asset is non zero. | +
pragma verify=false;
+
+
+
+[move-book]: https://aptos.dev/move/book/SUMMARY
diff --git a/aptos-move/framework/aptos-framework/tests/compiler-v2-doc/gas_schedule.md b/aptos-move/framework/aptos-framework/tests/compiler-v2-doc/gas_schedule.md
new file mode 100644
index 0000000000000..5ff6876819e9e
--- /dev/null
+++ b/aptos-move/framework/aptos-framework/tests/compiler-v2-doc/gas_schedule.md
@@ -0,0 +1,658 @@
+
+
+
+# Module `0x1::gas_schedule`
+
+This module defines structs and methods to initialize the gas schedule, which dictates how much
+it costs to execute Move on the network.
+
+
+- [Struct `GasEntry`](#0x1_gas_schedule_GasEntry)
+- [Resource `GasSchedule`](#0x1_gas_schedule_GasSchedule)
+- [Resource `GasScheduleV2`](#0x1_gas_schedule_GasScheduleV2)
+- [Constants](#@Constants_0)
+- [Function `initialize`](#0x1_gas_schedule_initialize)
+- [Function `set_gas_schedule`](#0x1_gas_schedule_set_gas_schedule)
+- [Function `set_for_next_epoch`](#0x1_gas_schedule_set_for_next_epoch)
+- [Function `set_for_next_epoch_check_hash`](#0x1_gas_schedule_set_for_next_epoch_check_hash)
+- [Function `on_new_epoch`](#0x1_gas_schedule_on_new_epoch)
+- [Function `set_storage_gas_config`](#0x1_gas_schedule_set_storage_gas_config)
+- [Function `set_storage_gas_config_for_next_epoch`](#0x1_gas_schedule_set_storage_gas_config_for_next_epoch)
+- [Specification](#@Specification_1)
+ - [High-level Requirements](#high-level-req)
+ - [Module-level Specification](#module-level-spec)
+ - [Function `initialize`](#@Specification_1_initialize)
+ - [Function `set_gas_schedule`](#@Specification_1_set_gas_schedule)
+ - [Function `set_for_next_epoch`](#@Specification_1_set_for_next_epoch)
+ - [Function `set_for_next_epoch_check_hash`](#@Specification_1_set_for_next_epoch_check_hash)
+ - [Function `on_new_epoch`](#@Specification_1_on_new_epoch)
+ - [Function `set_storage_gas_config`](#@Specification_1_set_storage_gas_config)
+ - [Function `set_storage_gas_config_for_next_epoch`](#@Specification_1_set_storage_gas_config_for_next_epoch)
+
+
+use 0x1::aptos_hash;
+use 0x1::bcs;
+use 0x1::chain_status;
+use 0x1::config_buffer;
+use 0x1::error;
+use 0x1::reconfiguration;
+use 0x1::storage_gas;
+use 0x1::string;
+use 0x1::system_addresses;
+use 0x1::util;
+use 0x1::vector;
+
+
+
+
+
+
+## Struct `GasEntry`
+
+
+
+struct GasEntry has copy, drop, store
+
+
+
+
+key: string::String
+val: u64
+struct GasSchedule has copy, drop, key
+
+
+
+
+entries: vector<gas_schedule::GasEntry>
+struct GasScheduleV2 has copy, drop, store, key
+
+
+
+
+feature_version: u64
+entries: vector<gas_schedule::GasEntry>
+const EINVALID_GAS_FEATURE_VERSION: u64 = 2;
+
+
+
+
+
+
+The provided gas schedule bytes are empty or invalid
+
+
+const EINVALID_GAS_SCHEDULE: u64 = 1;
+
+
+
+
+
+
+
+
+const EINVALID_GAS_SCHEDULE_HASH: u64 = 3;
+
+
+
+
+
+
+## Function `initialize`
+
+Only called during genesis.
+
+
+public(friend) fun initialize(aptos_framework: &signer, gas_schedule_blob: vector<u8>)
+
+
+
+
+public(friend) fun initialize(aptos_framework: &signer, gas_schedule_blob: vector<u8>) {
+ system_addresses::assert_aptos_framework(aptos_framework);
+ assert!(!vector::is_empty(&gas_schedule_blob), error::invalid_argument(EINVALID_GAS_SCHEDULE));
+
+ // TODO(Gas): check if gas schedule is consistent
+ let gas_schedule: GasScheduleV2 = from_bytes(gas_schedule_blob);
+ move_to<GasScheduleV2>(aptos_framework, gas_schedule);
+}
+
+
+
+
+set_for_next_epoch()
.
+
+WARNING: calling this while randomness is enabled will trigger a new epoch without randomness!
+
+TODO: update all the tests that reference this function, then disable this function.
+
+
+public fun set_gas_schedule(aptos_framework: &signer, gas_schedule_blob: vector<u8>)
+
+
+
+
+public fun set_gas_schedule(aptos_framework: &signer, gas_schedule_blob: vector<u8>) acquires GasSchedule, GasScheduleV2 {
+ system_addresses::assert_aptos_framework(aptos_framework);
+ assert!(!vector::is_empty(&gas_schedule_blob), error::invalid_argument(EINVALID_GAS_SCHEDULE));
+ chain_status::assert_genesis();
+
+ if (exists<GasScheduleV2>(@aptos_framework)) {
+ let gas_schedule = borrow_global_mut<GasScheduleV2>(@aptos_framework);
+ let new_gas_schedule: GasScheduleV2 = from_bytes(gas_schedule_blob);
+ assert!(new_gas_schedule.feature_version >= gas_schedule.feature_version,
+ error::invalid_argument(EINVALID_GAS_FEATURE_VERSION));
+ // TODO(Gas): check if gas schedule is consistent
+ *gas_schedule = new_gas_schedule;
+ }
+ else {
+ if (exists<GasSchedule>(@aptos_framework)) {
+ _ = move_from<GasSchedule>(@aptos_framework);
+ };
+ let new_gas_schedule: GasScheduleV2 = from_bytes(gas_schedule_blob);
+ // TODO(Gas): check if gas schedule is consistent
+ move_to<GasScheduleV2>(aptos_framework, new_gas_schedule);
+ };
+
+ // Need to trigger reconfiguration so validator nodes can sync on the updated gas schedule.
+ reconfiguration::reconfigure();
+}
+
+
+
+
+public fun set_for_next_epoch(aptos_framework: &signer, gas_schedule_blob: vector<u8>)
+
+
+
+
+public fun set_for_next_epoch(aptos_framework: &signer, gas_schedule_blob: vector<u8>) acquires GasScheduleV2 {
+ system_addresses::assert_aptos_framework(aptos_framework);
+ assert!(!vector::is_empty(&gas_schedule_blob), error::invalid_argument(EINVALID_GAS_SCHEDULE));
+ let new_gas_schedule: GasScheduleV2 = from_bytes(gas_schedule_blob);
+ if (exists<GasScheduleV2>(@aptos_framework)) {
+ let cur_gas_schedule = borrow_global<GasScheduleV2>(@aptos_framework);
+ assert!(
+ new_gas_schedule.feature_version >= cur_gas_schedule.feature_version,
+ error::invalid_argument(EINVALID_GAS_FEATURE_VERSION)
+ );
+ };
+ config_buffer::upsert(new_gas_schedule);
+}
+
+
+
+
+public fun set_for_next_epoch_check_hash(aptos_framework: &signer, old_gas_schedule_hash: vector<u8>, new_gas_schedule_blob: vector<u8>)
+
+
+
+
+public fun set_for_next_epoch_check_hash(
+ aptos_framework: &signer,
+ old_gas_schedule_hash: vector<u8>,
+ new_gas_schedule_blob: vector<u8>
+) acquires GasScheduleV2 {
+ system_addresses::assert_aptos_framework(aptos_framework);
+ assert!(!vector::is_empty(&new_gas_schedule_blob), error::invalid_argument(EINVALID_GAS_SCHEDULE));
+
+ let new_gas_schedule: GasScheduleV2 = from_bytes(new_gas_schedule_blob);
+ if (exists<GasScheduleV2>(@aptos_framework)) {
+ let cur_gas_schedule = borrow_global<GasScheduleV2>(@aptos_framework);
+ assert!(
+ new_gas_schedule.feature_version >= cur_gas_schedule.feature_version,
+ error::invalid_argument(EINVALID_GAS_FEATURE_VERSION)
+ );
+ let cur_gas_schedule_bytes = bcs::to_bytes(cur_gas_schedule);
+ let cur_gas_schedule_hash = aptos_hash::sha3_512(cur_gas_schedule_bytes);
+ assert!(
+ cur_gas_schedule_hash == old_gas_schedule_hash,
+ error::invalid_argument(EINVALID_GAS_SCHEDULE_HASH)
+ );
+ };
+
+ config_buffer::upsert(new_gas_schedule);
+}
+
+
+
+
+GasScheduleV2
, if there is any.
+
+
+public(friend) fun on_new_epoch(framework: &signer)
+
+
+
+
+public(friend) fun on_new_epoch(framework: &signer) acquires GasScheduleV2 {
+ system_addresses::assert_aptos_framework(framework);
+ if (config_buffer::does_exist<GasScheduleV2>()) {
+ let new_gas_schedule = config_buffer::extract<GasScheduleV2>();
+ if (exists<GasScheduleV2>(@aptos_framework)) {
+ *borrow_global_mut<GasScheduleV2>(@aptos_framework) = new_gas_schedule;
+ } else {
+ move_to(framework, new_gas_schedule);
+ }
+ }
+}
+
+
+
+
+public fun set_storage_gas_config(aptos_framework: &signer, config: storage_gas::StorageGasConfig)
+
+
+
+
+public fun set_storage_gas_config(aptos_framework: &signer, config: StorageGasConfig) {
+ storage_gas::set_config(aptos_framework, config);
+ // Need to trigger reconfiguration so the VM is guaranteed to load the new gas fee starting from the next
+ // transaction.
+ reconfiguration::reconfigure();
+}
+
+
+
+
+public fun set_storage_gas_config_for_next_epoch(aptos_framework: &signer, config: storage_gas::StorageGasConfig)
+
+
+
+
+public fun set_storage_gas_config_for_next_epoch(aptos_framework: &signer, config: StorageGasConfig) {
+ storage_gas::set_config(aptos_framework, config);
+}
+
+
+
+
+No. | Requirement | Criticality | Implementation | Enforcement | +
---|---|---|---|---|
1 | +During genesis, the Aptos framework account should be assigned the gas schedule resource. | +Medium | +The gas_schedule::initialize function calls the assert_aptos_framework function to ensure that the signer is the aptos_framework and then assigns the GasScheduleV2 resource to it. | +Formally verified via initialize. | +
2 | +Only the Aptos framework account should be allowed to update the gas schedule resource. | +Critical | +The gas_schedule::set_gas_schedule function calls the assert_aptos_framework function to ensure that the signer is the aptos framework account. | +Formally verified via set_gas_schedule. | +
3 | +Only valid gas schedule should be allowed for initialization and update. | +Medium | +The initialize and set_gas_schedule functions ensures that the gas_schedule_blob is not empty. | +Formally verified via initialize and set_gas_schedule. | +
4 | +Only a gas schedule with the feature version greater or equal than the current feature version is allowed to be provided when performing an update operation. | +Medium | +The set_gas_schedule function validates the feature_version of the new_gas_schedule by ensuring that it is greater or equal than the current gas_schedule.feature_version. | +Formally verified via set_gas_schedule. | +
pragma verify = true;
+pragma aborts_if_is_strict;
+
+
+
+
+
+
+### Function `initialize`
+
+
+public(friend) fun initialize(aptos_framework: &signer, gas_schedule_blob: vector<u8>)
+
+
+
+
+
+let addr = signer::address_of(aptos_framework);
+// This enforces high-level requirement 1:
+include system_addresses::AbortsIfNotAptosFramework{ account: aptos_framework };
+// This enforces high-level requirement 3:
+aborts_if len(gas_schedule_blob) == 0;
+aborts_if exists<GasScheduleV2>(addr);
+ensures exists<GasScheduleV2>(addr);
+
+
+
+
+
+
+### Function `set_gas_schedule`
+
+
+public fun set_gas_schedule(aptos_framework: &signer, gas_schedule_blob: vector<u8>)
+
+
+
+
+
+pragma verify_duration_estimate = 600;
+requires exists<stake::ValidatorFees>(@aptos_framework);
+requires exists<CoinInfo<AptosCoin>>(@aptos_framework);
+requires chain_status::is_genesis();
+include transaction_fee::RequiresCollectedFeesPerValueLeqBlockAptosSupply;
+include staking_config::StakingRewardsConfigRequirement;
+// This enforces high-level requirement 2:
+include system_addresses::AbortsIfNotAptosFramework{ account: aptos_framework };
+// This enforces high-level requirement 3:
+aborts_if len(gas_schedule_blob) == 0;
+let new_gas_schedule = util::spec_from_bytes<GasScheduleV2>(gas_schedule_blob);
+let gas_schedule = global<GasScheduleV2>(@aptos_framework);
+// This enforces high-level requirement 4:
+aborts_if exists<GasScheduleV2>(@aptos_framework) && new_gas_schedule.feature_version < gas_schedule.feature_version;
+ensures exists<GasScheduleV2>(signer::address_of(aptos_framework));
+ensures global<GasScheduleV2>(@aptos_framework) == new_gas_schedule;
+
+
+
+
+
+
+### Function `set_for_next_epoch`
+
+
+public fun set_for_next_epoch(aptos_framework: &signer, gas_schedule_blob: vector<u8>)
+
+
+
+
+
+include system_addresses::AbortsIfNotAptosFramework{ account: aptos_framework };
+include config_buffer::SetForNextEpochAbortsIf {
+ account: aptos_framework,
+ config: gas_schedule_blob
+};
+let new_gas_schedule = util::spec_from_bytes<GasScheduleV2>(gas_schedule_blob);
+let cur_gas_schedule = global<GasScheduleV2>(@aptos_framework);
+aborts_if exists<GasScheduleV2>(@aptos_framework) && new_gas_schedule.feature_version < cur_gas_schedule.feature_version;
+
+
+
+
+
+
+### Function `set_for_next_epoch_check_hash`
+
+
+public fun set_for_next_epoch_check_hash(aptos_framework: &signer, old_gas_schedule_hash: vector<u8>, new_gas_schedule_blob: vector<u8>)
+
+
+
+
+
+include system_addresses::AbortsIfNotAptosFramework{ account: aptos_framework };
+include config_buffer::SetForNextEpochAbortsIf {
+ account: aptos_framework,
+ config: new_gas_schedule_blob
+};
+let new_gas_schedule = util::spec_from_bytes<GasScheduleV2>(new_gas_schedule_blob);
+let cur_gas_schedule = global<GasScheduleV2>(@aptos_framework);
+aborts_if exists<GasScheduleV2>(@aptos_framework) && new_gas_schedule.feature_version < cur_gas_schedule.feature_version;
+aborts_if exists<GasScheduleV2>(@aptos_framework) && (!features::spec_sha_512_and_ripemd_160_enabled() || aptos_hash::spec_sha3_512_internal(bcs::serialize(cur_gas_schedule)) != old_gas_schedule_hash);
+
+
+
+
+
+
+### Function `on_new_epoch`
+
+
+public(friend) fun on_new_epoch(framework: &signer)
+
+
+
+
+
+requires @aptos_framework == std::signer::address_of(framework);
+include config_buffer::OnNewEpochRequirement<GasScheduleV2>;
+aborts_if false;
+
+
+
+
+
+
+### Function `set_storage_gas_config`
+
+
+public fun set_storage_gas_config(aptos_framework: &signer, config: storage_gas::StorageGasConfig)
+
+
+
+
+
+pragma verify_duration_estimate = 600;
+requires exists<stake::ValidatorFees>(@aptos_framework);
+requires exists<CoinInfo<AptosCoin>>(@aptos_framework);
+include system_addresses::AbortsIfNotAptosFramework{ account: aptos_framework };
+include transaction_fee::RequiresCollectedFeesPerValueLeqBlockAptosSupply;
+include staking_config::StakingRewardsConfigRequirement;
+aborts_if !exists<StorageGasConfig>(@aptos_framework);
+ensures global<StorageGasConfig>(@aptos_framework) == config;
+
+
+
+
+
+include system_addresses::AbortsIfNotAptosFramework{ account: aptos_framework };
+aborts_if !exists<storage_gas::StorageGasConfig>(@aptos_framework);
+
+
+
+
+
+
+### Function `set_storage_gas_config_for_next_epoch`
+
+
+public fun set_storage_gas_config_for_next_epoch(aptos_framework: &signer, config: storage_gas::StorageGasConfig)
+
+
+
+
+
+include system_addresses::AbortsIfNotAptosFramework{ account: aptos_framework };
+aborts_if !exists<storage_gas::StorageGasConfig>(@aptos_framework);
+
+
+
+[move-book]: https://aptos.dev/move/book/SUMMARY
diff --git a/aptos-move/framework/aptos-framework/tests/compiler-v2-doc/genesis.md b/aptos-move/framework/aptos-framework/tests/compiler-v2-doc/genesis.md
new file mode 100644
index 0000000000000..3136d00fa3994
--- /dev/null
+++ b/aptos-move/framework/aptos-framework/tests/compiler-v2-doc/genesis.md
@@ -0,0 +1,1085 @@
+
+
+
+# Module `0x1::genesis`
+
+
+
+- [Struct `AccountMap`](#0x1_genesis_AccountMap)
+- [Struct `EmployeeAccountMap`](#0x1_genesis_EmployeeAccountMap)
+- [Struct `ValidatorConfiguration`](#0x1_genesis_ValidatorConfiguration)
+- [Struct `ValidatorConfigurationWithCommission`](#0x1_genesis_ValidatorConfigurationWithCommission)
+- [Constants](#@Constants_0)
+- [Function `initialize`](#0x1_genesis_initialize)
+- [Function `initialize_aptos_coin`](#0x1_genesis_initialize_aptos_coin)
+- [Function `initialize_core_resources_and_aptos_coin`](#0x1_genesis_initialize_core_resources_and_aptos_coin)
+- [Function `create_accounts`](#0x1_genesis_create_accounts)
+- [Function `create_account`](#0x1_genesis_create_account)
+- [Function `create_employee_validators`](#0x1_genesis_create_employee_validators)
+- [Function `create_initialize_validators_with_commission`](#0x1_genesis_create_initialize_validators_with_commission)
+- [Function `create_initialize_validators`](#0x1_genesis_create_initialize_validators)
+- [Function `create_initialize_validator`](#0x1_genesis_create_initialize_validator)
+- [Function `initialize_validator`](#0x1_genesis_initialize_validator)
+- [Function `set_genesis_end`](#0x1_genesis_set_genesis_end)
+- [Specification](#@Specification_1)
+ - [High-level Requirements](#high-level-req)
+ - [Module-level Specification](#module-level-spec)
+ - [Function `initialize`](#@Specification_1_initialize)
+ - [Function `initialize_aptos_coin`](#@Specification_1_initialize_aptos_coin)
+ - [Function `create_initialize_validators_with_commission`](#@Specification_1_create_initialize_validators_with_commission)
+ - [Function `create_initialize_validators`](#@Specification_1_create_initialize_validators)
+ - [Function `create_initialize_validator`](#@Specification_1_create_initialize_validator)
+ - [Function `set_genesis_end`](#@Specification_1_set_genesis_end)
+
+
+use 0x1::account;
+use 0x1::aggregator_factory;
+use 0x1::aptos_account;
+use 0x1::aptos_coin;
+use 0x1::aptos_governance;
+use 0x1::block;
+use 0x1::chain_id;
+use 0x1::chain_status;
+use 0x1::coin;
+use 0x1::consensus_config;
+use 0x1::create_signer;
+use 0x1::error;
+use 0x1::execution_config;
+use 0x1::fixed_point32;
+use 0x1::gas_schedule;
+use 0x1::reconfiguration;
+use 0x1::simple_map;
+use 0x1::stake;
+use 0x1::staking_config;
+use 0x1::staking_contract;
+use 0x1::state_storage;
+use 0x1::storage_gas;
+use 0x1::timestamp;
+use 0x1::transaction_fee;
+use 0x1::transaction_validation;
+use 0x1::vector;
+use 0x1::version;
+use 0x1::vesting;
+
+
+
+
+
+
+## Struct `AccountMap`
+
+
+
+struct AccountMap has drop
+
+
+
+
+account_address: address
+balance: u64
+struct EmployeeAccountMap has copy, drop
+
+
+
+
+accounts: vector<address>
+validator: genesis::ValidatorConfigurationWithCommission
+vesting_schedule_numerator: vector<u64>
+vesting_schedule_denominator: u64
+beneficiary_resetter: address
+struct ValidatorConfiguration has copy, drop
+
+
+
+
+owner_address: address
+operator_address: address
+voter_address: address
+stake_amount: u64
+consensus_pubkey: vector<u8>
+proof_of_possession: vector<u8>
+network_addresses: vector<u8>
+full_node_network_addresses: vector<u8>
+struct ValidatorConfigurationWithCommission has copy, drop
+
+
+
+
+validator_config: genesis::ValidatorConfiguration
+commission_percentage: u64
+join_during_genesis: bool
+const EACCOUNT_DOES_NOT_EXIST: u64 = 2;
+
+
+
+
+
+
+
+
+const EDUPLICATE_ACCOUNT: u64 = 1;
+
+
+
+
+
+
+## Function `initialize`
+
+Genesis step 1: Initialize aptos framework account and core modules on chain.
+
+
+fun initialize(gas_schedule: vector<u8>, chain_id: u8, initial_version: u64, consensus_config: vector<u8>, execution_config: vector<u8>, epoch_interval_microsecs: u64, minimum_stake: u64, maximum_stake: u64, recurring_lockup_duration_secs: u64, allow_validator_set_change: bool, rewards_rate: u64, rewards_rate_denominator: u64, voting_power_increase_limit: u64)
+
+
+
+
+fun initialize(
+ gas_schedule: vector<u8>,
+ chain_id: u8,
+ initial_version: u64,
+ consensus_config: vector<u8>,
+ execution_config: vector<u8>,
+ epoch_interval_microsecs: u64,
+ minimum_stake: u64,
+ maximum_stake: u64,
+ recurring_lockup_duration_secs: u64,
+ allow_validator_set_change: bool,
+ rewards_rate: u64,
+ rewards_rate_denominator: u64,
+ voting_power_increase_limit: u64,
+) {
+ // Initialize the aptos framework account. This is the account where system resources and modules will be
+ // deployed to. This will be entirely managed by on-chain governance and no entities have the key or privileges
+ // to use this account.
+ let (aptos_framework_account, aptos_framework_signer_cap) = account::create_framework_reserved_account(@aptos_framework);
+ // Initialize account configs on aptos framework account.
+ account::initialize(&aptos_framework_account);
+
+ transaction_validation::initialize(
+ &aptos_framework_account,
+ b"script_prologue",
+ b"module_prologue",
+ b"multi_agent_script_prologue",
+ b"epilogue",
+ );
+
+ // Give the decentralized on-chain governance control over the core framework account.
+ aptos_governance::store_signer_cap(&aptos_framework_account, @aptos_framework, aptos_framework_signer_cap);
+
+ // put reserved framework reserved accounts under aptos governance
+ let framework_reserved_addresses = vector<address>[@0x2, @0x3, @0x4, @0x5, @0x6, @0x7, @0x8, @0x9, @0xa];
+ while (!vector::is_empty(&framework_reserved_addresses)) {
+ let address = vector::pop_back<address>(&mut framework_reserved_addresses);
+ let (_, framework_signer_cap) = account::create_framework_reserved_account(address);
+ aptos_governance::store_signer_cap(&aptos_framework_account, address, framework_signer_cap);
+ };
+
+ consensus_config::initialize(&aptos_framework_account, consensus_config);
+ execution_config::set(&aptos_framework_account, execution_config);
+ version::initialize(&aptos_framework_account, initial_version);
+ stake::initialize(&aptos_framework_account);
+ staking_config::initialize(
+ &aptos_framework_account,
+ minimum_stake,
+ maximum_stake,
+ recurring_lockup_duration_secs,
+ allow_validator_set_change,
+ rewards_rate,
+ rewards_rate_denominator,
+ voting_power_increase_limit,
+ );
+ storage_gas::initialize(&aptos_framework_account);
+ gas_schedule::initialize(&aptos_framework_account, gas_schedule);
+
+ // Ensure we can create aggregators for supply, but not enable it for common use just yet.
+ aggregator_factory::initialize_aggregator_factory(&aptos_framework_account);
+ coin::initialize_supply_config(&aptos_framework_account);
+
+ chain_id::initialize(&aptos_framework_account, chain_id);
+ reconfiguration::initialize(&aptos_framework_account);
+ block::initialize(&aptos_framework_account, epoch_interval_microsecs);
+ state_storage::initialize(&aptos_framework_account);
+ timestamp::set_time_has_started(&aptos_framework_account);
+}
+
+
+
+
+fun initialize_aptos_coin(aptos_framework: &signer)
+
+
+
+
+fun initialize_aptos_coin(aptos_framework: &signer) {
+ let (burn_cap, mint_cap) = aptos_coin::initialize(aptos_framework);
+
+ coin::create_coin_conversion_map(aptos_framework);
+ coin::create_pairing<AptosCoin>(aptos_framework);
+
+ // Give stake module MintCapability<AptosCoin> so it can mint rewards.
+ stake::store_aptos_coin_mint_cap(aptos_framework, mint_cap);
+ // Give transaction_fee module BurnCapability<AptosCoin> so it can burn gas.
+ transaction_fee::store_aptos_coin_burn_cap(aptos_framework, burn_cap);
+ // Give transaction_fee module MintCapability<AptosCoin> so it can mint refunds.
+ transaction_fee::store_aptos_coin_mint_cap(aptos_framework, mint_cap);
+}
+
+
+
+
+fun initialize_core_resources_and_aptos_coin(aptos_framework: &signer, core_resources_auth_key: vector<u8>)
+
+
+
+
+fun initialize_core_resources_and_aptos_coin(
+ aptos_framework: &signer,
+ core_resources_auth_key: vector<u8>,
+) {
+ let (burn_cap, mint_cap) = aptos_coin::initialize(aptos_framework);
+
+ coin::create_coin_conversion_map(aptos_framework);
+ coin::create_pairing<AptosCoin>(aptos_framework);
+
+ // Give stake module MintCapability<AptosCoin> so it can mint rewards.
+ stake::store_aptos_coin_mint_cap(aptos_framework, mint_cap);
+ // Give transaction_fee module BurnCapability<AptosCoin> so it can burn gas.
+ transaction_fee::store_aptos_coin_burn_cap(aptos_framework, burn_cap);
+ // Give transaction_fee module MintCapability<AptosCoin> so it can mint refunds.
+ transaction_fee::store_aptos_coin_mint_cap(aptos_framework, mint_cap);
+
+ let core_resources = account::create_account(@core_resources);
+ account::rotate_authentication_key_internal(&core_resources, core_resources_auth_key);
+ aptos_account::register_apt(&core_resources); // registers APT store
+ aptos_coin::configure_accounts_for_test(aptos_framework, &core_resources, mint_cap);
+}
+
+
+
+
+fun create_accounts(aptos_framework: &signer, accounts: vector<genesis::AccountMap>)
+
+
+
+
+fun create_accounts(aptos_framework: &signer, accounts: vector<AccountMap>) {
+ let unique_accounts = vector::empty();
+ vector::for_each_ref(&accounts, |account_map| {
+ let account_map: &AccountMap = account_map;
+ assert!(
+ !vector::contains(&unique_accounts, &account_map.account_address),
+ error::already_exists(EDUPLICATE_ACCOUNT),
+ );
+ vector::push_back(&mut unique_accounts, account_map.account_address);
+
+ create_account(
+ aptos_framework,
+ account_map.account_address,
+ account_map.balance,
+ );
+ });
+}
+
+
+
+
+fun create_account(aptos_framework: &signer, account_address: address, balance: u64): signer
+
+
+
+
+fun create_account(aptos_framework: &signer, account_address: address, balance: u64): signer {
+ if (account::exists_at(account_address)) {
+ create_signer(account_address)
+ } else {
+ let account = account::create_account(account_address);
+ coin::register<AptosCoin>(&account);
+ aptos_coin::mint(aptos_framework, account_address, balance);
+ account
+ }
+}
+
+
+
+
+fun create_employee_validators(employee_vesting_start: u64, employee_vesting_period_duration: u64, employees: vector<genesis::EmployeeAccountMap>)
+
+
+
+
+fun create_employee_validators(
+ employee_vesting_start: u64,
+ employee_vesting_period_duration: u64,
+ employees: vector<EmployeeAccountMap>,
+) {
+ let unique_accounts = vector::empty();
+
+ vector::for_each_ref(&employees, |employee_group| {
+ let j = 0;
+ let employee_group: &EmployeeAccountMap = employee_group;
+ let num_employees_in_group = vector::length(&employee_group.accounts);
+
+ let buy_ins = simple_map::create();
+
+ while (j < num_employees_in_group) {
+ let account = vector::borrow(&employee_group.accounts, j);
+ assert!(
+ !vector::contains(&unique_accounts, account),
+ error::already_exists(EDUPLICATE_ACCOUNT),
+ );
+ vector::push_back(&mut unique_accounts, *account);
+
+ let employee = create_signer(*account);
+ let total = coin::balance<AptosCoin>(*account);
+ let coins = coin::withdraw<AptosCoin>(&employee, total);
+ simple_map::add(&mut buy_ins, *account, coins);
+
+ j = j + 1;
+ };
+
+ let j = 0;
+ let num_vesting_events = vector::length(&employee_group.vesting_schedule_numerator);
+ let schedule = vector::empty();
+
+ while (j < num_vesting_events) {
+ let numerator = vector::borrow(&employee_group.vesting_schedule_numerator, j);
+ let event = fixed_point32::create_from_rational(*numerator, employee_group.vesting_schedule_denominator);
+ vector::push_back(&mut schedule, event);
+
+ j = j + 1;
+ };
+
+ let vesting_schedule = vesting::create_vesting_schedule(
+ schedule,
+ employee_vesting_start,
+ employee_vesting_period_duration,
+ );
+
+ let admin = employee_group.validator.validator_config.owner_address;
+ let admin_signer = &create_signer(admin);
+ let contract_address = vesting::create_vesting_contract(
+ admin_signer,
+ &employee_group.accounts,
+ buy_ins,
+ vesting_schedule,
+ admin,
+ employee_group.validator.validator_config.operator_address,
+ employee_group.validator.validator_config.voter_address,
+ employee_group.validator.commission_percentage,
+ x"",
+ );
+ let pool_address = vesting::stake_pool_address(contract_address);
+
+ if (employee_group.beneficiary_resetter != @0x0) {
+ vesting::set_beneficiary_resetter(admin_signer, contract_address, employee_group.beneficiary_resetter);
+ };
+
+ let validator = &employee_group.validator.validator_config;
+ assert!(
+ account::exists_at(validator.owner_address),
+ error::not_found(EACCOUNT_DOES_NOT_EXIST),
+ );
+ assert!(
+ account::exists_at(validator.operator_address),
+ error::not_found(EACCOUNT_DOES_NOT_EXIST),
+ );
+ assert!(
+ account::exists_at(validator.voter_address),
+ error::not_found(EACCOUNT_DOES_NOT_EXIST),
+ );
+ if (employee_group.validator.join_during_genesis) {
+ initialize_validator(pool_address, validator);
+ };
+ });
+}
+
+
+
+
+fun create_initialize_validators_with_commission(aptos_framework: &signer, use_staking_contract: bool, validators: vector<genesis::ValidatorConfigurationWithCommission>)
+
+
+
+
+fun create_initialize_validators_with_commission(
+ aptos_framework: &signer,
+ use_staking_contract: bool,
+ validators: vector<ValidatorConfigurationWithCommission>,
+) {
+ vector::for_each_ref(&validators, |validator| {
+ let validator: &ValidatorConfigurationWithCommission = validator;
+ create_initialize_validator(aptos_framework, validator, use_staking_contract);
+ });
+
+ // Destroy the aptos framework account's ability to mint coins now that we're done with setting up the initial
+ // validators.
+ aptos_coin::destroy_mint_cap(aptos_framework);
+
+ stake::on_new_epoch();
+}
+
+
+
+
+owners
+Each validator signs consensus messages with the private key corresponding to the Ed25519
+public key in consensus_pubkeys
.
+Finally, each validator must specify the network address
+(see types/src/network_address/mod.rs) for itself and its full nodes.
+
+Network address fields are a vector per account, where each entry is a vector of addresses
+encoded in a single BCS byte array.
+
+
+fun create_initialize_validators(aptos_framework: &signer, validators: vector<genesis::ValidatorConfiguration>)
+
+
+
+
+fun create_initialize_validators(aptos_framework: &signer, validators: vector<ValidatorConfiguration>) {
+ let validators_with_commission = vector::empty();
+ vector::for_each_reverse(validators, |validator| {
+ let validator_with_commission = ValidatorConfigurationWithCommission {
+ validator_config: validator,
+ commission_percentage: 0,
+ join_during_genesis: true,
+ };
+ vector::push_back(&mut validators_with_commission, validator_with_commission);
+ });
+
+ create_initialize_validators_with_commission(aptos_framework, false, validators_with_commission);
+}
+
+
+
+
+fun create_initialize_validator(aptos_framework: &signer, commission_config: &genesis::ValidatorConfigurationWithCommission, use_staking_contract: bool)
+
+
+
+
+fun create_initialize_validator(
+ aptos_framework: &signer,
+ commission_config: &ValidatorConfigurationWithCommission,
+ use_staking_contract: bool,
+) {
+ let validator = &commission_config.validator_config;
+
+ let owner = &create_account(aptos_framework, validator.owner_address, validator.stake_amount);
+ create_account(aptos_framework, validator.operator_address, 0);
+ create_account(aptos_framework, validator.voter_address, 0);
+
+ // Initialize the stake pool and join the validator set.
+ let pool_address = if (use_staking_contract) {
+ staking_contract::create_staking_contract(
+ owner,
+ validator.operator_address,
+ validator.voter_address,
+ validator.stake_amount,
+ commission_config.commission_percentage,
+ x"",
+ );
+ staking_contract::stake_pool_address(validator.owner_address, validator.operator_address)
+ } else {
+ stake::initialize_stake_owner(
+ owner,
+ validator.stake_amount,
+ validator.operator_address,
+ validator.voter_address,
+ );
+ validator.owner_address
+ };
+
+ if (commission_config.join_during_genesis) {
+ initialize_validator(pool_address, validator);
+ };
+}
+
+
+
+
+fun initialize_validator(pool_address: address, validator: &genesis::ValidatorConfiguration)
+
+
+
+
+fun initialize_validator(pool_address: address, validator: &ValidatorConfiguration) {
+ let operator = &create_signer(validator.operator_address);
+
+ stake::rotate_consensus_key(
+ operator,
+ pool_address,
+ validator.consensus_pubkey,
+ validator.proof_of_possession,
+ );
+ stake::update_network_and_fullnode_addresses(
+ operator,
+ pool_address,
+ validator.network_addresses,
+ validator.full_node_network_addresses,
+ );
+ stake::join_validator_set_internal(operator, pool_address);
+}
+
+
+
+
+fun set_genesis_end(aptos_framework: &signer)
+
+
+
+
+fun set_genesis_end(aptos_framework: &signer) {
+ chain_status::set_genesis_end(aptos_framework);
+}
+
+
+
+
+No. | Requirement | Criticality | Implementation | Enforcement | +
---|---|---|---|---|
1 | +All the core resources and modules should be created during genesis and owned by the Aptos framework account. | +Critical | +Resources created during genesis initialization: GovernanceResponsbility, ConsensusConfig, ExecutionConfig, Version, SetVersionCapability, ValidatorSet, ValidatorPerformance, StakingConfig, StorageGasConfig, StorageGas, GasScheduleV2, AggregatorFactory, SupplyConfig, ChainId, Configuration, BlockResource, StateStorageUsage, CurrentTimeMicroseconds. If some of the resources were to be owned by a malicious account, it could lead to the compromise of the chain, as these are core resources. It should be formally verified by a post condition to ensure that all the critical resources are owned by the Aptos framework. | +Formally verified via initialize. | +
2 | +Addresses ranging from 0x0 - 0xa should be reserved for the framework and part of aptos governance. | +Critical | +The function genesis::initialize calls account::create_framework_reserved_account for addresses 0x0, 0x2, 0x3, 0x4, ..., 0xa which creates an account and authentication_key for them. This should be formally verified by ensuring that at the beginning of the genesis::initialize function no Account resource exists for the reserved addresses, and at the end of the function, an Account resource exists. | +Formally verified via initialize. | +
3 | +The Aptos coin should be initialized during genesis and only the Aptos framework account should own the mint and burn capabilities for the APT token. | +Critical | +Both mint and burn capabilities are wrapped inside the stake::AptosCoinCapabilities and transaction_fee::AptosCoinCapabilities resources which are stored under the aptos framework account. | +Formally verified via initialize_aptos_coin. | +
4 | +An initial set of validators should exist before the end of genesis. | +Low | +To ensure that there will be a set of validators available to validate the genesis block, the length of the ValidatorSet.active_validators vector should be > 0. | +Formally verified via set_genesis_end. | +
5 | +The end of genesis should be marked on chain. | +Low | +The end of genesis is marked, on chain, via the chain_status::GenesisEndMarker resource. The ownership of this resource marks the operating state of the chain. | +Formally verified via set_genesis_end. | +
pragma verify = true;
+
+
+
+
+
+
+### Function `initialize`
+
+
+fun initialize(gas_schedule: vector<u8>, chain_id: u8, initial_version: u64, consensus_config: vector<u8>, execution_config: vector<u8>, epoch_interval_microsecs: u64, minimum_stake: u64, maximum_stake: u64, recurring_lockup_duration_secs: u64, allow_validator_set_change: bool, rewards_rate: u64, rewards_rate_denominator: u64, voting_power_increase_limit: u64)
+
+
+
+
+
+pragma aborts_if_is_partial;
+include InitalizeRequires;
+// This enforces high-level requirement 2:
+aborts_if exists<account::Account>(@0x0);
+aborts_if exists<account::Account>(@0x2);
+aborts_if exists<account::Account>(@0x3);
+aborts_if exists<account::Account>(@0x4);
+aborts_if exists<account::Account>(@0x5);
+aborts_if exists<account::Account>(@0x6);
+aborts_if exists<account::Account>(@0x7);
+aborts_if exists<account::Account>(@0x8);
+aborts_if exists<account::Account>(@0x9);
+aborts_if exists<account::Account>(@0xa);
+ensures exists<account::Account>(@0x0);
+ensures exists<account::Account>(@0x2);
+ensures exists<account::Account>(@0x3);
+ensures exists<account::Account>(@0x4);
+ensures exists<account::Account>(@0x5);
+ensures exists<account::Account>(@0x6);
+ensures exists<account::Account>(@0x7);
+ensures exists<account::Account>(@0x8);
+ensures exists<account::Account>(@0x9);
+ensures exists<account::Account>(@0xa);
+// This enforces high-level requirement 1:
+ensures exists<aptos_governance::GovernanceResponsbility>(@aptos_framework);
+ensures exists<consensus_config::ConsensusConfig>(@aptos_framework);
+ensures exists<execution_config::ExecutionConfig>(@aptos_framework);
+ensures exists<version::Version>(@aptos_framework);
+ensures exists<stake::ValidatorSet>(@aptos_framework);
+ensures exists<stake::ValidatorPerformance>(@aptos_framework);
+ensures exists<storage_gas::StorageGasConfig>(@aptos_framework);
+ensures exists<storage_gas::StorageGas>(@aptos_framework);
+ensures exists<gas_schedule::GasScheduleV2>(@aptos_framework);
+ensures exists<aggregator_factory::AggregatorFactory>(@aptos_framework);
+ensures exists<coin::SupplyConfig>(@aptos_framework);
+ensures exists<chain_id::ChainId>(@aptos_framework);
+ensures exists<reconfiguration::Configuration>(@aptos_framework);
+ensures exists<block::BlockResource>(@aptos_framework);
+ensures exists<state_storage::StateStorageUsage>(@aptos_framework);
+ensures exists<timestamp::CurrentTimeMicroseconds>(@aptos_framework);
+ensures exists<account::Account>(@aptos_framework);
+ensures exists<version::SetVersionCapability>(@aptos_framework);
+ensures exists<staking_config::StakingConfig>(@aptos_framework);
+
+
+
+
+
+
+### Function `initialize_aptos_coin`
+
+
+fun initialize_aptos_coin(aptos_framework: &signer)
+
+
+
+
+
+// This enforces high-level requirement 3:
+requires !exists<stake::AptosCoinCapabilities>(@aptos_framework);
+ensures exists<stake::AptosCoinCapabilities>(@aptos_framework);
+requires exists<transaction_fee::AptosCoinCapabilities>(@aptos_framework);
+ensures exists<transaction_fee::AptosCoinCapabilities>(@aptos_framework);
+
+
+
+
+
+
+### Function `create_initialize_validators_with_commission`
+
+
+fun create_initialize_validators_with_commission(aptos_framework: &signer, use_staking_contract: bool, validators: vector<genesis::ValidatorConfigurationWithCommission>)
+
+
+
+
+
+pragma verify_duration_estimate = 120;
+include stake::ResourceRequirement;
+include stake::GetReconfigStartTimeRequirement;
+include CompareTimeRequires;
+include aptos_coin::ExistsAptosCoin;
+
+
+
+
+
+
+### Function `create_initialize_validators`
+
+
+fun create_initialize_validators(aptos_framework: &signer, validators: vector<genesis::ValidatorConfiguration>)
+
+
+
+
+
+pragma verify_duration_estimate = 120;
+include stake::ResourceRequirement;
+include stake::GetReconfigStartTimeRequirement;
+include CompareTimeRequires;
+include aptos_coin::ExistsAptosCoin;
+
+
+
+
+
+
+### Function `create_initialize_validator`
+
+
+fun create_initialize_validator(aptos_framework: &signer, commission_config: &genesis::ValidatorConfigurationWithCommission, use_staking_contract: bool)
+
+
+
+
+
+include stake::ResourceRequirement;
+
+
+
+
+
+
+### Function `set_genesis_end`
+
+
+fun set_genesis_end(aptos_framework: &signer)
+
+
+
+
+
+pragma delegate_invariants_to_caller;
+// This enforces high-level requirement 4:
+requires len(global<stake::ValidatorSet>(@aptos_framework).active_validators) >= 1;
+// This enforces high-level requirement 5:
+let addr = std::signer::address_of(aptos_framework);
+aborts_if addr != @aptos_framework;
+aborts_if exists<chain_status::GenesisEndMarker>(@aptos_framework);
+ensures global<chain_status::GenesisEndMarker>(@aptos_framework) == chain_status::GenesisEndMarker {};
+
+
+
+
+
+
+
+
+schema InitalizeRequires {
+ execution_config: vector<u8>;
+ requires !exists<account::Account>(@aptos_framework);
+ requires chain_status::is_operating();
+ requires len(execution_config) > 0;
+ requires exists<staking_config::StakingRewardsConfig>(@aptos_framework);
+ requires exists<stake::ValidatorFees>(@aptos_framework);
+ requires exists<coin::CoinInfo<AptosCoin>>(@aptos_framework);
+ include CompareTimeRequires;
+ include transaction_fee::RequiresCollectedFeesPerValueLeqBlockAptosSupply;
+}
+
+
+
+
+
+
+
+
+schema CompareTimeRequires {
+ let staking_rewards_config = global<staking_config::StakingRewardsConfig>(@aptos_framework);
+ requires staking_rewards_config.last_rewards_rate_period_start_in_secs <= timestamp::spec_now_seconds();
+}
+
+
+
+[move-book]: https://aptos.dev/move/book/SUMMARY
diff --git a/aptos-move/framework/aptos-framework/tests/compiler-v2-doc/governance_proposal.md b/aptos-move/framework/aptos-framework/tests/compiler-v2-doc/governance_proposal.md
new file mode 100644
index 0000000000000..e67b67aad759d
--- /dev/null
+++ b/aptos-move/framework/aptos-framework/tests/compiler-v2-doc/governance_proposal.md
@@ -0,0 +1,178 @@
+
+
+
+# Module `0x1::governance_proposal`
+
+Define the GovernanceProposal that will be used as part of on-chain governance by AptosGovernance.
+
+This is separate from the AptosGovernance module to avoid circular dependency between AptosGovernance and Stake.
+
+
+- [Struct `GovernanceProposal`](#0x1_governance_proposal_GovernanceProposal)
+- [Function `create_proposal`](#0x1_governance_proposal_create_proposal)
+- [Function `create_empty_proposal`](#0x1_governance_proposal_create_empty_proposal)
+- [Specification](#@Specification_0)
+ - [Function `create_proposal`](#@Specification_0_create_proposal)
+ - [High-level Requirements](#high-level-req)
+ - [Module-level Specification](#module-level-spec)
+ - [Function `create_empty_proposal`](#@Specification_0_create_empty_proposal)
+
+
+
+
+
+
+
+
+## Struct `GovernanceProposal`
+
+
+
+struct GovernanceProposal has drop, store
+
+
+
+
+dummy_field: bool
+public(friend) fun create_proposal(): governance_proposal::GovernanceProposal
+
+
+
+
+public(friend) fun create_proposal(): GovernanceProposal {
+ GovernanceProposal {}
+}
+
+
+
+
+public(friend) fun create_empty_proposal(): governance_proposal::GovernanceProposal
+
+
+
+
+public(friend) fun create_empty_proposal(): GovernanceProposal {
+ create_proposal()
+}
+
+
+
+
+public(friend) fun create_proposal(): governance_proposal::GovernanceProposal
+
+
+
+
+
+
+
+
+### High-level Requirements
+
+No. | Requirement | Criticality | Implementation | Enforcement | +
---|---|---|---|---|
1 | +Creating a proposal should never abort but should always return a governance proposal resource. | +Medium | +Both create_proposal and create_empty_proposal functions return a GovernanceProposal resource. | +Enforced via create_proposal and create_empty_proposal. | +
2 | +The governance proposal module should only be accessible to the aptos governance. | +Medium | +Both create_proposal and create_empty_proposal functions are only available to the friend module aptos_framework::aptos_governance. | +Enforced via friend module relationship. | +
aborts_if false;
+// This enforces high-level requirement 1:
+ensures result == GovernanceProposal {};
+
+
+
+
+
+
+### Function `create_empty_proposal`
+
+
+public(friend) fun create_empty_proposal(): governance_proposal::GovernanceProposal
+
+
+
+
+
+aborts_if false;
+// This enforces high-level requirement 1:
+ensures result == GovernanceProposal {};
+
+
+
+[move-book]: https://aptos.dev/move/book/SUMMARY
diff --git a/aptos-move/framework/aptos-framework/tests/compiler-v2-doc/guid.md b/aptos-move/framework/aptos-framework/tests/compiler-v2-doc/guid.md
new file mode 100644
index 0000000000000..de8f0986edfd6
--- /dev/null
+++ b/aptos-move/framework/aptos-framework/tests/compiler-v2-doc/guid.md
@@ -0,0 +1,522 @@
+
+
+
+# Module `0x1::guid`
+
+A module for generating globally unique identifiers
+
+
+- [Struct `GUID`](#0x1_guid_GUID)
+- [Struct `ID`](#0x1_guid_ID)
+- [Constants](#@Constants_0)
+- [Function `create`](#0x1_guid_create)
+- [Function `create_id`](#0x1_guid_create_id)
+- [Function `id`](#0x1_guid_id)
+- [Function `creator_address`](#0x1_guid_creator_address)
+- [Function `id_creator_address`](#0x1_guid_id_creator_address)
+- [Function `creation_num`](#0x1_guid_creation_num)
+- [Function `id_creation_num`](#0x1_guid_id_creation_num)
+- [Function `eq_id`](#0x1_guid_eq_id)
+- [Specification](#@Specification_1)
+ - [High-level Requirements](#high-level-req)
+ - [Module-level Specification](#module-level-spec)
+ - [Function `create`](#@Specification_1_create)
+ - [Function `create_id`](#@Specification_1_create_id)
+ - [Function `id`](#@Specification_1_id)
+ - [Function `creator_address`](#@Specification_1_creator_address)
+ - [Function `id_creator_address`](#@Specification_1_id_creator_address)
+ - [Function `creation_num`](#@Specification_1_creation_num)
+ - [Function `id_creation_num`](#@Specification_1_id_creation_num)
+ - [Function `eq_id`](#@Specification_1_eq_id)
+
+
+
+
+
+
+
+
+## Struct `GUID`
+
+A globally unique identifier derived from the sender's address and a counter
+
+
+struct GUID has drop, store
+
+
+
+
+id: guid::ID
+struct ID has copy, drop, store
+
+
+
+
+creation_num: u64
+i
, this is the i+1
th GUID created by addr
+addr: address
+create_with_capability
function.
+
+
+const EGUID_GENERATOR_NOT_PUBLISHED: u64 = 0;
+
+
+
+
+
+
+## Function `create`
+
+Create and return a new GUID from a trusted module.
+
+
+public(friend) fun create(addr: address, creation_num_ref: &mut u64): guid::GUID
+
+
+
+
+public(friend) fun create(addr: address, creation_num_ref: &mut u64): GUID {
+ let creation_num = *creation_num_ref;
+ *creation_num_ref = creation_num + 1;
+ GUID {
+ id: ID {
+ creation_num,
+ addr,
+ }
+ }
+}
+
+
+
+
+addr
and creation_num
+
+
+public fun create_id(addr: address, creation_num: u64): guid::ID
+
+
+
+
+public fun create_id(addr: address, creation_num: u64): ID {
+ ID { creation_num, addr }
+}
+
+
+
+
+public fun id(guid: &guid::GUID): guid::ID
+
+
+
+
+public fun id(guid: &GUID): ID {
+ guid.id
+}
+
+
+
+
+public fun creator_address(guid: &guid::GUID): address
+
+
+
+
+public fun creator_address(guid: &GUID): address {
+ guid.id.addr
+}
+
+
+
+
+public fun id_creator_address(id: &guid::ID): address
+
+
+
+
+public fun id_creator_address(id: &ID): address {
+ id.addr
+}
+
+
+
+
+public fun creation_num(guid: &guid::GUID): u64
+
+
+
+
+public fun creation_num(guid: &GUID): u64 {
+ guid.id.creation_num
+}
+
+
+
+
+public fun id_creation_num(id: &guid::ID): u64
+
+
+
+
+public fun id_creation_num(id: &ID): u64 {
+ id.creation_num
+}
+
+
+
+
+id
+
+
+public fun eq_id(guid: &guid::GUID, id: &guid::ID): bool
+
+
+
+
+public fun eq_id(guid: &GUID, id: &ID): bool {
+ &guid.id == id
+}
+
+
+
+
+No. | Requirement | Criticality | Implementation | Enforcement | +
---|---|---|---|---|
1 | +The creation of GUID constructs a unique GUID by combining an address with an incremented creation number. | +Low | +The create function generates a new GUID by combining an address with an incremented creation number, effectively creating a unique identifier. | +Enforced via create. | +
2 | +The operations on GUID and ID, such as construction, field access, and equality comparison, should not abort. | +Low | +The following functions will never abort: (1) create_id, (2) id, (3) creator_address, (4) id_creator_address, (5) creation_num, (6) id_creation_num, and (7) eq_id. | +Verified via create_id, id, creator_address, id_creator_address, creation_num, id_creation_num, and eq_id. | +
3 | +The creation number should increment by 1 with each new creation. | +Low | +An account can only own up to MAX_U64 resources. Not incrementing the guid_creation_num constantly could lead to shrinking the space for new resources. | +Enforced via create. | +
4 | +The creation number and address of an ID / GUID must be immutable. | +Medium | +The addr and creation_num values are meant to be constant and never updated as they are unique and used for identification. | +Audited: This is enforced through missing functionality to update the creation_num or addr. | +
pragma verify = true;
+pragma aborts_if_is_strict;
+
+
+
+
+
+
+### Function `create`
+
+
+public(friend) fun create(addr: address, creation_num_ref: &mut u64): guid::GUID
+
+
+
+
+
+aborts_if creation_num_ref + 1 > MAX_U64;
+// This enforces high-level requirement 1:
+ensures result.id.creation_num == old(creation_num_ref);
+// This enforces high-level requirement 3:
+ensures creation_num_ref == old(creation_num_ref) + 1;
+
+
+
+
+
+
+### Function `create_id`
+
+
+public fun create_id(addr: address, creation_num: u64): guid::ID
+
+
+
+
+
+// This enforces high-level requirement 2:
+aborts_if false;
+
+
+
+
+
+
+### Function `id`
+
+
+public fun id(guid: &guid::GUID): guid::ID
+
+
+
+
+
+// This enforces high-level requirement 2:
+aborts_if false;
+
+
+
+
+
+
+### Function `creator_address`
+
+
+public fun creator_address(guid: &guid::GUID): address
+
+
+
+
+
+// This enforces high-level requirement 2:
+aborts_if false;
+
+
+
+
+
+
+### Function `id_creator_address`
+
+
+public fun id_creator_address(id: &guid::ID): address
+
+
+
+
+
+// This enforces high-level requirement 2:
+aborts_if false;
+
+
+
+
+
+
+### Function `creation_num`
+
+
+public fun creation_num(guid: &guid::GUID): u64
+
+
+
+
+
+// This enforces high-level requirement 2:
+aborts_if false;
+
+
+
+
+
+
+### Function `id_creation_num`
+
+
+public fun id_creation_num(id: &guid::ID): u64
+
+
+
+
+
+// This enforces high-level requirement 2:
+aborts_if false;
+
+
+
+
+
+
+### Function `eq_id`
+
+
+public fun eq_id(guid: &guid::GUID, id: &guid::ID): bool
+
+
+
+
+
+// This enforces high-level requirement 2:
+aborts_if false;
+
+
+
+[move-book]: https://aptos.dev/move/book/SUMMARY
diff --git a/aptos-move/framework/aptos-framework/tests/compiler-v2-doc/jwk_consensus_config.md b/aptos-move/framework/aptos-framework/tests/compiler-v2-doc/jwk_consensus_config.md
new file mode 100644
index 0000000000000..69e93270bcdf3
--- /dev/null
+++ b/aptos-move/framework/aptos-framework/tests/compiler-v2-doc/jwk_consensus_config.md
@@ -0,0 +1,377 @@
+
+
+
+# Module `0x1::jwk_consensus_config`
+
+Structs and functions related to JWK consensus configurations.
+
+
+- [Resource `JWKConsensusConfig`](#0x1_jwk_consensus_config_JWKConsensusConfig)
+- [Struct `ConfigOff`](#0x1_jwk_consensus_config_ConfigOff)
+- [Struct `OIDCProvider`](#0x1_jwk_consensus_config_OIDCProvider)
+- [Struct `ConfigV1`](#0x1_jwk_consensus_config_ConfigV1)
+- [Constants](#@Constants_0)
+- [Function `initialize`](#0x1_jwk_consensus_config_initialize)
+- [Function `set_for_next_epoch`](#0x1_jwk_consensus_config_set_for_next_epoch)
+- [Function `on_new_epoch`](#0x1_jwk_consensus_config_on_new_epoch)
+- [Function `new_off`](#0x1_jwk_consensus_config_new_off)
+- [Function `new_v1`](#0x1_jwk_consensus_config_new_v1)
+- [Function `new_oidc_provider`](#0x1_jwk_consensus_config_new_oidc_provider)
+- [Specification](#@Specification_1)
+ - [Function `on_new_epoch`](#@Specification_1_on_new_epoch)
+
+
+use 0x1::config_buffer;
+use 0x1::copyable_any;
+use 0x1::error;
+use 0x1::option;
+use 0x1::simple_map;
+use 0x1::string;
+use 0x1::system_addresses;
+
+
+
+
+
+
+## Resource `JWKConsensusConfig`
+
+The configuration of the JWK consensus feature.
+
+
+struct JWKConsensusConfig has drop, store, key
+
+
+
+
+variant: copyable_any::Any
+Any
.
+ Currently the variant type is one of the following.
+ - ConfigOff
+ - ConfigV1
+struct ConfigOff has copy, drop, store
+
+
+
+
+dummy_field: bool
+struct OIDCProvider has copy, drop, store
+
+
+
+
+name: string::String
+config_url: string::String
+struct ConfigV1 has copy, drop, store
+
+
+
+
+oidc_providers: vector<jwk_consensus_config::OIDCProvider>
+ConfigV1
creation failed with duplicated providers given.
+
+
+const EDUPLICATE_PROVIDERS: u64 = 1;
+
+
+
+
+
+
+## Function `initialize`
+
+Initialize the configuration. Used in genesis or governance.
+
+
+public fun initialize(framework: &signer, config: jwk_consensus_config::JWKConsensusConfig)
+
+
+
+
+public fun initialize(framework: &signer, config: JWKConsensusConfig) {
+ system_addresses::assert_aptos_framework(framework);
+ if (!exists<JWKConsensusConfig>(@aptos_framework)) {
+ move_to(framework, config);
+ }
+}
+
+
+
+
+public fun set_for_next_epoch(framework: &signer, config: jwk_consensus_config::JWKConsensusConfig)
+
+
+
+
+public fun set_for_next_epoch(framework: &signer, config: JWKConsensusConfig) {
+ system_addresses::assert_aptos_framework(framework);
+ config_buffer::upsert(config);
+}
+
+
+
+
+JWKConsensusConfig
, if there is any.
+
+
+public(friend) fun on_new_epoch(framework: &signer)
+
+
+
+
+public(friend) fun on_new_epoch(framework: &signer) acquires JWKConsensusConfig {
+ system_addresses::assert_aptos_framework(framework);
+ if (config_buffer::does_exist<JWKConsensusConfig>()) {
+ let new_config = config_buffer::extract<JWKConsensusConfig>();
+ if (exists<JWKConsensusConfig>(@aptos_framework)) {
+ *borrow_global_mut<JWKConsensusConfig>(@aptos_framework) = new_config;
+ } else {
+ move_to(framework, new_config);
+ };
+ }
+}
+
+
+
+
+JWKConsensusConfig
of variant ConfigOff
.
+
+
+public fun new_off(): jwk_consensus_config::JWKConsensusConfig
+
+
+
+
+public fun new_off(): JWKConsensusConfig {
+ JWKConsensusConfig {
+ variant: copyable_any::pack( ConfigOff {} )
+ }
+}
+
+
+
+
+JWKConsensusConfig
of variant ConfigV1
.
+
+Abort if the given provider list contains duplicated provider names.
+
+
+public fun new_v1(oidc_providers: vector<jwk_consensus_config::OIDCProvider>): jwk_consensus_config::JWKConsensusConfig
+
+
+
+
+public fun new_v1(oidc_providers: vector<OIDCProvider>): JWKConsensusConfig {
+ let name_set = simple_map::new<String, u64>();
+ vector::for_each_ref(&oidc_providers, |provider| {
+ let provider: &OIDCProvider = provider;
+ let (_, old_value) = simple_map::upsert(&mut name_set, provider.name, 0);
+ if (option::is_some(&old_value)) {
+ abort(error::invalid_argument(EDUPLICATE_PROVIDERS))
+ }
+ });
+ JWKConsensusConfig {
+ variant: copyable_any::pack( ConfigV1 { oidc_providers } )
+ }
+}
+
+
+
+
+OIDCProvider
object.
+
+
+public fun new_oidc_provider(name: string::String, config_url: string::String): jwk_consensus_config::OIDCProvider
+
+
+
+
+public fun new_oidc_provider(name: String, config_url: String): OIDCProvider {
+ OIDCProvider { name, config_url }
+}
+
+
+
+
+public(friend) fun on_new_epoch(framework: &signer)
+
+
+
+
+
+requires @aptos_framework == std::signer::address_of(framework);
+include config_buffer::OnNewEpochRequirement<JWKConsensusConfig>;
+aborts_if false;
+
+
+
+[move-book]: https://aptos.dev/move/book/SUMMARY
diff --git a/aptos-move/framework/aptos-framework/tests/compiler-v2-doc/jwks.md b/aptos-move/framework/aptos-framework/tests/compiler-v2-doc/jwks.md
new file mode 100644
index 0000000000000..7554e55e99f41
--- /dev/null
+++ b/aptos-move/framework/aptos-framework/tests/compiler-v2-doc/jwks.md
@@ -0,0 +1,1675 @@
+
+
+
+# Module `0x1::jwks`
+
+JWK functions and structs.
+
+Note: An important design constraint for this module is that the JWK consensus Rust code is unable to
+spawn a VM and make a Move function call. Instead, the JWK consensus Rust code will have to directly
+write some of the resources in this file. As a result, the structs in this file are declared so as to
+have a simple layout which is easily accessible in Rust.
+
+
+- [Struct `OIDCProvider`](#0x1_jwks_OIDCProvider)
+- [Resource `SupportedOIDCProviders`](#0x1_jwks_SupportedOIDCProviders)
+- [Struct `UnsupportedJWK`](#0x1_jwks_UnsupportedJWK)
+- [Struct `RSA_JWK`](#0x1_jwks_RSA_JWK)
+- [Struct `JWK`](#0x1_jwks_JWK)
+- [Struct `ProviderJWKs`](#0x1_jwks_ProviderJWKs)
+- [Struct `AllProvidersJWKs`](#0x1_jwks_AllProvidersJWKs)
+- [Resource `ObservedJWKs`](#0x1_jwks_ObservedJWKs)
+- [Struct `ObservedJWKsUpdated`](#0x1_jwks_ObservedJWKsUpdated)
+- [Struct `Patch`](#0x1_jwks_Patch)
+- [Struct `PatchRemoveAll`](#0x1_jwks_PatchRemoveAll)
+- [Struct `PatchRemoveIssuer`](#0x1_jwks_PatchRemoveIssuer)
+- [Struct `PatchRemoveJWK`](#0x1_jwks_PatchRemoveJWK)
+- [Struct `PatchUpsertJWK`](#0x1_jwks_PatchUpsertJWK)
+- [Resource `Patches`](#0x1_jwks_Patches)
+- [Resource `PatchedJWKs`](#0x1_jwks_PatchedJWKs)
+- [Constants](#@Constants_0)
+- [Function `get_patched_jwk`](#0x1_jwks_get_patched_jwk)
+- [Function `try_get_patched_jwk`](#0x1_jwks_try_get_patched_jwk)
+- [Function `upsert_oidc_provider`](#0x1_jwks_upsert_oidc_provider)
+- [Function `upsert_oidc_provider_for_next_epoch`](#0x1_jwks_upsert_oidc_provider_for_next_epoch)
+- [Function `remove_oidc_provider`](#0x1_jwks_remove_oidc_provider)
+- [Function `remove_oidc_provider_for_next_epoch`](#0x1_jwks_remove_oidc_provider_for_next_epoch)
+- [Function `on_new_epoch`](#0x1_jwks_on_new_epoch)
+- [Function `set_patches`](#0x1_jwks_set_patches)
+- [Function `new_patch_remove_all`](#0x1_jwks_new_patch_remove_all)
+- [Function `new_patch_remove_issuer`](#0x1_jwks_new_patch_remove_issuer)
+- [Function `new_patch_remove_jwk`](#0x1_jwks_new_patch_remove_jwk)
+- [Function `new_patch_upsert_jwk`](#0x1_jwks_new_patch_upsert_jwk)
+- [Function `new_rsa_jwk`](#0x1_jwks_new_rsa_jwk)
+- [Function `new_unsupported_jwk`](#0x1_jwks_new_unsupported_jwk)
+- [Function `initialize`](#0x1_jwks_initialize)
+- [Function `remove_oidc_provider_internal`](#0x1_jwks_remove_oidc_provider_internal)
+- [Function `upsert_into_observed_jwks`](#0x1_jwks_upsert_into_observed_jwks)
+- [Function `remove_issuer_from_observed_jwks`](#0x1_jwks_remove_issuer_from_observed_jwks)
+- [Function `regenerate_patched_jwks`](#0x1_jwks_regenerate_patched_jwks)
+- [Function `try_get_jwk_by_issuer`](#0x1_jwks_try_get_jwk_by_issuer)
+- [Function `try_get_jwk_by_id`](#0x1_jwks_try_get_jwk_by_id)
+- [Function `get_jwk_id`](#0x1_jwks_get_jwk_id)
+- [Function `upsert_provider_jwks`](#0x1_jwks_upsert_provider_jwks)
+- [Function `remove_issuer`](#0x1_jwks_remove_issuer)
+- [Function `upsert_jwk`](#0x1_jwks_upsert_jwk)
+- [Function `remove_jwk`](#0x1_jwks_remove_jwk)
+- [Function `apply_patch`](#0x1_jwks_apply_patch)
+- [Specification](#@Specification_1)
+ - [Function `on_new_epoch`](#@Specification_1_on_new_epoch)
+
+
+use 0x1::chain_status;
+use 0x1::comparator;
+use 0x1::config_buffer;
+use 0x1::copyable_any;
+use 0x1::error;
+use 0x1::event;
+use 0x1::option;
+use 0x1::reconfiguration;
+use 0x1::string;
+use 0x1::system_addresses;
+use 0x1::vector;
+
+
+
+
+
+
+## Struct `OIDCProvider`
+
+An OIDC provider.
+
+
+struct OIDCProvider has copy, drop, store
+
+
+
+
+name: vector<u8>
+config_url: vector<u8>
+struct SupportedOIDCProviders has copy, drop, store, key
+
+
+
+
+providers: vector<jwks::OIDCProvider>
+UnsupportedJWK
s means the providers adopted a new key type/format, and the system should be updated.
+
+
+struct UnsupportedJWK has copy, drop, store
+
+
+
+
+id: vector<u8>
+payload: vector<u8>
+kty
is RSA
.
+
+
+struct RSA_JWK has copy, drop, store
+
+
+
+
+kid: string::String
+kty: string::String
+alg: string::String
+e: string::String
+n: string::String
+struct JWK has copy, drop, store
+
+
+
+
+variant: copyable_any::Any
+JWK
variant packed as an Any
.
+ Currently the variant type is one of the following.
+ - RSA_JWK
+ - UnsupportedJWK
+JWK
s.
+
+
+struct ProviderJWKs has copy, drop, store
+
+
+
+
+issuer: vector<u8>
+version: u64
+jwks: vector<jwks::JWK>
+JWK
's sorted by their unique ID (from get_jwk_id
) in dictionary order.
+ProviderJWKs
objects, indexed by issuer and key ID.
+
+
+struct AllProvidersJWKs has copy, drop, store
+
+
+
+
+entries: vector<jwks::ProviderJWKs>
+ProviderJWKs
sorted by ProviderJWKs::issuer
in dictionary order.
+AllProvidersJWKs
that validators observed and agreed on.
+
+
+struct ObservedJWKs has copy, drop, store, key
+
+
+
+
+jwks: jwks::AllProvidersJWKs
+ObservedJWKs
is updated, this event is sent to resync the JWK consensus state in all validators.
+
+
+#[event]
+struct ObservedJWKsUpdated has drop, store
+
+
+
+
+epoch: u64
+jwks: jwks::AllProvidersJWKs
+AllProvidersJWKs
to obtain PatchedJWKs
.
+
+
+struct Patch has copy, drop, store
+
+
+
+
+variant: copyable_any::Any
+Patch
variant packed as an Any
.
+ Currently the variant type is one of the following.
+ - PatchRemoveAll
+ - PatchRemoveIssuer
+ - PatchRemoveJWK
+ - PatchUpsertJWK
+Patch
variant to remove all JWKs.
+
+
+struct PatchRemoveAll has copy, drop, store
+
+
+
+
+dummy_field: bool
+Patch
variant to remove an issuer and all its JWKs.
+
+
+struct PatchRemoveIssuer has copy, drop, store
+
+
+
+
+issuer: vector<u8>
+Patch
variant to remove a specific JWK of an issuer.
+
+
+struct PatchRemoveJWK has copy, drop, store
+
+
+
+
+issuer: vector<u8>
+jwk_id: vector<u8>
+Patch
variant to upsert a JWK for an issuer.
+
+
+struct PatchUpsertJWK has copy, drop, store
+
+
+
+
+issuer: vector<u8>
+jwk: jwks::JWK
+Patch
objects that are applied *one by one* to the ObservedJWKs
.
+
+Maintained by governance proposals.
+
+
+struct Patches has key
+
+
+
+
+patches: vector<jwks::Patch>
+Patches
to the ObservedJWKs
.
+This is what applications should consume.
+
+
+struct PatchedJWKs has drop, key
+
+
+
+
+jwks: jwks::AllProvidersJWKs
+const EISSUER_NOT_FOUND: u64 = 5;
+
+
+
+
+
+
+
+
+const EJWK_ID_NOT_FOUND: u64 = 6;
+
+
+
+
+
+
+
+
+const ENATIVE_INCORRECT_VERSION: u64 = 259;
+
+
+
+
+
+
+
+
+const ENATIVE_MISSING_RESOURCE_OBSERVED_JWKS: u64 = 258;
+
+
+
+
+
+
+
+
+const ENATIVE_MISSING_RESOURCE_VALIDATOR_SET: u64 = 257;
+
+
+
+
+
+
+
+
+const ENATIVE_MULTISIG_VERIFICATION_FAILED: u64 = 260;
+
+
+
+
+
+
+
+
+const ENATIVE_NOT_ENOUGH_VOTING_POWER: u64 = 261;
+
+
+
+
+
+
+
+
+const EUNEXPECTED_EPOCH: u64 = 1;
+
+
+
+
+
+
+
+
+const EUNEXPECTED_VERSION: u64 = 2;
+
+
+
+
+
+
+
+
+const EUNKNOWN_JWK_VARIANT: u64 = 4;
+
+
+
+
+
+
+
+
+const EUNKNOWN_PATCH_VARIANT: u64 = 3;
+
+
+
+
+
+
+## Function `get_patched_jwk`
+
+Get a JWK by issuer and key ID from the PatchedJWKs
.
+Abort if such a JWK does not exist.
+More convenient to call from Rust, since it does not wrap the JWK in an Option
.
+
+
+public fun get_patched_jwk(issuer: vector<u8>, jwk_id: vector<u8>): jwks::JWK
+
+
+
+
+public fun get_patched_jwk(issuer: vector<u8>, jwk_id: vector<u8>): JWK acquires PatchedJWKs {
+ option::extract(&mut try_get_patched_jwk(issuer, jwk_id))
+}
+
+
+
+
+PatchedJWKs
, if it exists.
+More convenient to call from Move, since it does not abort.
+
+
+public fun try_get_patched_jwk(issuer: vector<u8>, jwk_id: vector<u8>): option::Option<jwks::JWK>
+
+
+
+
+public fun try_get_patched_jwk(issuer: vector<u8>, jwk_id: vector<u8>): Option<JWK> acquires PatchedJWKs {
+ let jwks = &borrow_global<PatchedJWKs>(@aptos_framework).jwks;
+ try_get_jwk_by_issuer(jwks, issuer, jwk_id)
+}
+
+
+
+
+upsert_oidc_provider_for_next_epoch()
.
+
+TODO: update all the tests that reference this function, then disable this function.
+
+
+public fun upsert_oidc_provider(fx: &signer, name: vector<u8>, config_url: vector<u8>): option::Option<vector<u8>>
+
+
+
+
+public fun upsert_oidc_provider(fx: &signer, name: vector<u8>, config_url: vector<u8>): Option<vector<u8>> acquires SupportedOIDCProviders {
+ system_addresses::assert_aptos_framework(fx);
+ chain_status::assert_genesis();
+
+ let provider_set = borrow_global_mut<SupportedOIDCProviders>(@aptos_framework);
+
+ let old_config_url= remove_oidc_provider_internal(provider_set, name);
+ vector::push_back(&mut provider_set.providers, OIDCProvider { name, config_url });
+ old_config_url
+}
+
+
+
+
+public fun upsert_oidc_provider_for_next_epoch(fx: &signer, name: vector<u8>, config_url: vector<u8>): option::Option<vector<u8>>
+
+
+
+
+public fun upsert_oidc_provider_for_next_epoch(fx: &signer, name: vector<u8>, config_url: vector<u8>): Option<vector<u8>> acquires SupportedOIDCProviders {
+ system_addresses::assert_aptos_framework(fx);
+
+ let provider_set = if (config_buffer::does_exist<SupportedOIDCProviders>()) {
+ config_buffer::extract<SupportedOIDCProviders>()
+ } else {
+ *borrow_global_mut<SupportedOIDCProviders>(@aptos_framework)
+ };
+
+ let old_config_url = remove_oidc_provider_internal(&mut provider_set, name);
+ vector::push_back(&mut provider_set.providers, OIDCProvider { name, config_url });
+ config_buffer::upsert(provider_set);
+ old_config_url
+}
+
+
+
+
+remove_oidc_provider_for_next_epoch()
.
+
+TODO: update all the tests that reference this function, then disable this function.
+
+
+public fun remove_oidc_provider(fx: &signer, name: vector<u8>): option::Option<vector<u8>>
+
+
+
+
+public fun remove_oidc_provider(fx: &signer, name: vector<u8>): Option<vector<u8>> acquires SupportedOIDCProviders {
+ system_addresses::assert_aptos_framework(fx);
+ chain_status::assert_genesis();
+
+ let provider_set = borrow_global_mut<SupportedOIDCProviders>(@aptos_framework);
+ remove_oidc_provider_internal(provider_set, name)
+}
+
+
+
+
+public fun remove_oidc_provider_for_next_epoch(fx: &signer, name: vector<u8>): option::Option<vector<u8>>
+
+
+
+
+public fun remove_oidc_provider_for_next_epoch(fx: &signer, name: vector<u8>): Option<vector<u8>> acquires SupportedOIDCProviders {
+ system_addresses::assert_aptos_framework(fx);
+
+ let provider_set = if (config_buffer::does_exist<SupportedOIDCProviders>()) {
+ config_buffer::extract<SupportedOIDCProviders>()
+ } else {
+ *borrow_global_mut<SupportedOIDCProviders>(@aptos_framework)
+ };
+ let ret = remove_oidc_provider_internal(&mut provider_set, name);
+ config_buffer::upsert(provider_set);
+ ret
+}
+
+
+
+
+SupportedOIDCProviders
, if there is any.
+
+
+public(friend) fun on_new_epoch(framework: &signer)
+
+
+
+
+public(friend) fun on_new_epoch(framework: &signer) acquires SupportedOIDCProviders {
+ system_addresses::assert_aptos_framework(framework);
+ if (config_buffer::does_exist<SupportedOIDCProviders>()) {
+ let new_config = config_buffer::extract<SupportedOIDCProviders>();
+ if (exists<SupportedOIDCProviders>(@aptos_framework)) {
+ *borrow_global_mut<SupportedOIDCProviders>(@aptos_framework) = new_config;
+ } else {
+ move_to(framework, new_config);
+ }
+ }
+}
+
+
+
+
+Patches
. Only called in governance proposals.
+
+
+public fun set_patches(fx: &signer, patches: vector<jwks::Patch>)
+
+
+
+
+public fun set_patches(fx: &signer, patches: vector<Patch>) acquires Patches, PatchedJWKs, ObservedJWKs {
+ system_addresses::assert_aptos_framework(fx);
+ borrow_global_mut<Patches>(@aptos_framework).patches = patches;
+ regenerate_patched_jwks();
+}
+
+
+
+
+Patch
that removes all entries.
+
+
+public fun new_patch_remove_all(): jwks::Patch
+
+
+
+
+public fun new_patch_remove_all(): Patch {
+ Patch {
+ variant: copyable_any::pack(PatchRemoveAll {}),
+ }
+}
+
+
+
+
+Patch
that removes the entry of a given issuer, if exists.
+
+
+public fun new_patch_remove_issuer(issuer: vector<u8>): jwks::Patch
+
+
+
+
+public fun new_patch_remove_issuer(issuer: vector<u8>): Patch {
+ Patch {
+ variant: copyable_any::pack(PatchRemoveIssuer { issuer }),
+ }
+}
+
+
+
+
+Patch
that removes the entry of a given issuer, if exists.
+
+
+public fun new_patch_remove_jwk(issuer: vector<u8>, jwk_id: vector<u8>): jwks::Patch
+
+
+
+
+public fun new_patch_remove_jwk(issuer: vector<u8>, jwk_id: vector<u8>): Patch {
+ Patch {
+ variant: copyable_any::pack(PatchRemoveJWK { issuer, jwk_id })
+ }
+}
+
+
+
+
+Patch
that upserts a JWK into an issuer's JWK set.
+
+
+public fun new_patch_upsert_jwk(issuer: vector<u8>, jwk: jwks::JWK): jwks::Patch
+
+
+
+
+public fun new_patch_upsert_jwk(issuer: vector<u8>, jwk: JWK): Patch {
+ Patch {
+ variant: copyable_any::pack(PatchUpsertJWK { issuer, jwk })
+ }
+}
+
+
+
+
+JWK
of variant RSA_JWK
.
+
+
+public fun new_rsa_jwk(kid: string::String, alg: string::String, e: string::String, n: string::String): jwks::JWK
+
+
+
+
+public fun new_rsa_jwk(kid: String, alg: String, e: String, n: String): JWK {
+ JWK {
+ variant: copyable_any::pack(RSA_JWK {
+ kid,
+ kty: utf8(b"RSA"),
+ e,
+ n,
+ alg,
+ }),
+ }
+}
+
+
+
+
+JWK
of variant UnsupportedJWK
.
+
+
+public fun new_unsupported_jwk(id: vector<u8>, payload: vector<u8>): jwks::JWK
+
+
+
+
+public fun new_unsupported_jwk(id: vector<u8>, payload: vector<u8>): JWK {
+ JWK {
+ variant: copyable_any::pack(UnsupportedJWK { id, payload })
+ }
+}
+
+
+
+
+public fun initialize(fx: &signer)
+
+
+
+
+public fun initialize(fx: &signer) {
+ system_addresses::assert_aptos_framework(fx);
+ move_to(fx, SupportedOIDCProviders { providers: vector[] });
+ move_to(fx, ObservedJWKs { jwks: AllProvidersJWKs { entries: vector[] } });
+ move_to(fx, Patches { patches: vector[] });
+ move_to(fx, PatchedJWKs { jwks: AllProvidersJWKs { entries: vector[] } });
+}
+
+
+
+
+SupportedOIDCProviders
.
+Returns the old config URL of the provider, if any, as an Option
.
+
+
+fun remove_oidc_provider_internal(provider_set: &mut jwks::SupportedOIDCProviders, name: vector<u8>): option::Option<vector<u8>>
+
+
+
+
+fun remove_oidc_provider_internal(provider_set: &mut SupportedOIDCProviders, name: vector<u8>): Option<vector<u8>> {
+ let (name_exists, idx) = vector::find(&provider_set.providers, |obj| {
+ let provider: &OIDCProvider = obj;
+ provider.name == name
+ });
+
+ if (name_exists) {
+ let old_provider = vector::swap_remove(&mut provider_set.providers, idx);
+ option::some(old_provider.config_url)
+ } else {
+ option::none()
+ }
+}
+
+
+
+
+version
equals to the on-chain version + 1.
+
+
+public fun upsert_into_observed_jwks(fx: &signer, provider_jwks_vec: vector<jwks::ProviderJWKs>)
+
+
+
+
+public fun upsert_into_observed_jwks(fx: &signer, provider_jwks_vec: vector<ProviderJWKs>) acquires ObservedJWKs, PatchedJWKs, Patches {
+ system_addresses::assert_aptos_framework(fx);
+ let observed_jwks = borrow_global_mut<ObservedJWKs>(@aptos_framework);
+ vector::for_each(provider_jwks_vec, |obj| {
+ let provider_jwks: ProviderJWKs = obj;
+ upsert_provider_jwks(&mut observed_jwks.jwks, provider_jwks);
+ });
+
+ let epoch = reconfiguration::current_epoch();
+ emit(ObservedJWKsUpdated { epoch, jwks: observed_jwks.jwks });
+ regenerate_patched_jwks();
+}
+
+
+
+
+ObservedJWKs
, if it exists.
+
+Return the potentially existing ProviderJWKs
of the given issuer.
+
+
+public fun remove_issuer_from_observed_jwks(fx: &signer, issuer: vector<u8>): option::Option<jwks::ProviderJWKs>
+
+
+
+
+public fun remove_issuer_from_observed_jwks(fx: &signer, issuer: vector<u8>): Option<ProviderJWKs> acquires ObservedJWKs, PatchedJWKs, Patches {
+ system_addresses::assert_aptos_framework(fx);
+ let observed_jwks = borrow_global_mut<ObservedJWKs>(@aptos_framework);
+ let old_value = remove_issuer(&mut observed_jwks.jwks, issuer);
+
+ let epoch = reconfiguration::current_epoch();
+ emit(ObservedJWKsUpdated { epoch, jwks: observed_jwks.jwks });
+ regenerate_patched_jwks();
+
+ old_value
+}
+
+
+
+
+PatchedJWKs
from ObservedJWKs
and Patches
and save the result.
+
+
+fun regenerate_patched_jwks()
+
+
+
+
+fun regenerate_patched_jwks() acquires PatchedJWKs, Patches, ObservedJWKs {
+ let jwks = borrow_global<ObservedJWKs>(@aptos_framework).jwks;
+ let patches = borrow_global<Patches>(@aptos_framework);
+ vector::for_each_ref(&patches.patches, |obj|{
+ let patch: &Patch = obj;
+ apply_patch(&mut jwks, *patch);
+ });
+ *borrow_global_mut<PatchedJWKs>(@aptos_framework) = PatchedJWKs { jwks };
+}
+
+
+
+
+AllProvidersJWKs
, if it exists.
+
+
+fun try_get_jwk_by_issuer(jwks: &jwks::AllProvidersJWKs, issuer: vector<u8>, jwk_id: vector<u8>): option::Option<jwks::JWK>
+
+
+
+
+fun try_get_jwk_by_issuer(jwks: &AllProvidersJWKs, issuer: vector<u8>, jwk_id: vector<u8>): Option<JWK> {
+ let (issuer_found, index) = vector::find(&jwks.entries, |obj| {
+ let provider_jwks: &ProviderJWKs = obj;
+ issuer == provider_jwks.issuer
+ });
+
+ if (issuer_found) {
+ try_get_jwk_by_id(vector::borrow(&jwks.entries, index), jwk_id)
+ } else {
+ option::none()
+ }
+}
+
+
+
+
+ProviderJWKs
, if it exists.
+
+
+fun try_get_jwk_by_id(provider_jwks: &jwks::ProviderJWKs, jwk_id: vector<u8>): option::Option<jwks::JWK>
+
+
+
+
+fun try_get_jwk_by_id(provider_jwks: &ProviderJWKs, jwk_id: vector<u8>): Option<JWK> {
+ let (jwk_id_found, index) = vector::find(&provider_jwks.jwks, |obj|{
+ let jwk: &JWK = obj;
+ jwk_id == get_jwk_id(jwk)
+ });
+
+ if (jwk_id_found) {
+ option::some(*vector::borrow(&provider_jwks.jwks, index))
+ } else {
+ option::none()
+ }
+}
+
+
+
+
+fun get_jwk_id(jwk: &jwks::JWK): vector<u8>
+
+
+
+
+fun get_jwk_id(jwk: &JWK): vector<u8> {
+ let variant_type_name = *string::bytes(copyable_any::type_name(&jwk.variant));
+ if (variant_type_name == b"0x1::jwks::RSA_JWK") {
+ let rsa = copyable_any::unpack<RSA_JWK>(jwk.variant);
+ *string::bytes(&rsa.kid)
+ } else if (variant_type_name == b"0x1::jwks::UnsupportedJWK") {
+ let unsupported = copyable_any::unpack<UnsupportedJWK>(jwk.variant);
+ unsupported.id
+ } else {
+ abort(error::invalid_argument(EUNKNOWN_JWK_VARIANT))
+ }
+}
+
+
+
+
+ProviderJWKs
into an AllProvidersJWKs
. If this upsert replaced an existing entry, return it.
+Maintains the sorted-by-issuer invariant in AllProvidersJWKs
.
+
+
+fun upsert_provider_jwks(jwks: &mut jwks::AllProvidersJWKs, provider_jwks: jwks::ProviderJWKs): option::Option<jwks::ProviderJWKs>
+
+
+
+
+fun upsert_provider_jwks(jwks: &mut AllProvidersJWKs, provider_jwks: ProviderJWKs): Option<ProviderJWKs> {
+ // NOTE: Using a linear-time search here because we do not expect too many providers.
+ let found = false;
+ let index = 0;
+ let num_entries = vector::length(&jwks.entries);
+ while (index < num_entries) {
+ let cur_entry = vector::borrow(&jwks.entries, index);
+ let comparison = compare_u8_vector(provider_jwks.issuer, cur_entry.issuer);
+ if (is_greater_than(&comparison)) {
+ index = index + 1;
+ } else {
+ found = is_equal(&comparison);
+ break
+ }
+ };
+
+ // Now if `found == true`, `index` points to the JWK we want to update/remove; otherwise, `index` points to
+ // where we want to insert.
+ let ret = if (found) {
+ let entry = vector::borrow_mut(&mut jwks.entries, index);
+ let old_entry = option::some(*entry);
+ *entry = provider_jwks;
+ old_entry
+ } else {
+ vector::insert(&mut jwks.entries, index, provider_jwks);
+ option::none()
+ };
+
+ ret
+}
+
+
+
+
+AllProvidersJWKs
and return the entry, if exists.
+Maintains the sorted-by-issuer invariant in AllProvidersJWKs
.
+
+
+fun remove_issuer(jwks: &mut jwks::AllProvidersJWKs, issuer: vector<u8>): option::Option<jwks::ProviderJWKs>
+
+
+
+
+fun remove_issuer(jwks: &mut AllProvidersJWKs, issuer: vector<u8>): Option<ProviderJWKs> {
+ let (found, index) = vector::find(&jwks.entries, |obj| {
+ let provider_jwk_set: &ProviderJWKs = obj;
+ provider_jwk_set.issuer == issuer
+ });
+
+ let ret = if (found) {
+ option::some(vector::remove(&mut jwks.entries, index))
+ } else {
+ option::none()
+ };
+
+ ret
+}
+
+
+
+
+JWK
into a ProviderJWKs
. If this upsert replaced an existing entry, return it.
+
+
+fun upsert_jwk(set: &mut jwks::ProviderJWKs, jwk: jwks::JWK): option::Option<jwks::JWK>
+
+
+
+
+fun upsert_jwk(set: &mut ProviderJWKs, jwk: JWK): Option<JWK> {
+ let found = false;
+ let index = 0;
+ let num_entries = vector::length(&set.jwks);
+ while (index < num_entries) {
+ let cur_entry = vector::borrow(&set.jwks, index);
+ let comparison = compare_u8_vector(get_jwk_id(&jwk), get_jwk_id(cur_entry));
+ if (is_greater_than(&comparison)) {
+ index = index + 1;
+ } else {
+ found = is_equal(&comparison);
+ break
+ }
+ };
+
+ // Now if `found == true`, `index` points to the JWK we want to update/remove; otherwise, `index` points to
+ // where we want to insert.
+ let ret = if (found) {
+ let entry = vector::borrow_mut(&mut set.jwks, index);
+ let old_entry = option::some(*entry);
+ *entry = jwk;
+ old_entry
+ } else {
+ vector::insert(&mut set.jwks, index, jwk);
+ option::none()
+ };
+
+ ret
+}
+
+
+
+
+ProviderJWKs
and return the entry, if exists.
+
+
+fun remove_jwk(jwks: &mut jwks::ProviderJWKs, jwk_id: vector<u8>): option::Option<jwks::JWK>
+
+
+
+
+fun remove_jwk(jwks: &mut ProviderJWKs, jwk_id: vector<u8>): Option<JWK> {
+ let (found, index) = vector::find(&jwks.jwks, |obj| {
+ let jwk: &JWK = obj;
+ jwk_id == get_jwk_id(jwk)
+ });
+
+ let ret = if (found) {
+ option::some(vector::remove(&mut jwks.jwks, index))
+ } else {
+ option::none()
+ };
+
+ ret
+}
+
+
+
+
+AllProvidersJWKs
object with a Patch
.
+Maintains the sorted-by-issuer invariant in AllProvidersJWKs
.
+
+
+fun apply_patch(jwks: &mut jwks::AllProvidersJWKs, patch: jwks::Patch)
+
+
+
+
+fun apply_patch(jwks: &mut AllProvidersJWKs, patch: Patch) {
+ let variant_type_name = *string::bytes(copyable_any::type_name(&patch.variant));
+ if (variant_type_name == b"0x1::jwks::PatchRemoveAll") {
+ jwks.entries = vector[];
+ } else if (variant_type_name == b"0x1::jwks::PatchRemoveIssuer") {
+ let cmd = copyable_any::unpack<PatchRemoveIssuer>(patch.variant);
+ remove_issuer(jwks, cmd.issuer);
+ } else if (variant_type_name == b"0x1::jwks::PatchRemoveJWK") {
+ let cmd = copyable_any::unpack<PatchRemoveJWK>(patch.variant);
+ // TODO: This is inefficient: we remove the issuer, modify its JWKs & and reinsert the updated issuer. Why
+ // not just update it in place?
+ let existing_jwk_set = remove_issuer(jwks, cmd.issuer);
+ if (option::is_some(&existing_jwk_set)) {
+ let jwk_set = option::extract(&mut existing_jwk_set);
+ remove_jwk(&mut jwk_set, cmd.jwk_id);
+ upsert_provider_jwks(jwks, jwk_set);
+ };
+ } else if (variant_type_name == b"0x1::jwks::PatchUpsertJWK") {
+ let cmd = copyable_any::unpack<PatchUpsertJWK>(patch.variant);
+ // TODO: This is inefficient: we remove the issuer, modify its JWKs & and reinsert the updated issuer. Why
+ // not just update it in place?
+ let existing_jwk_set = remove_issuer(jwks, cmd.issuer);
+ let jwk_set = if (option::is_some(&existing_jwk_set)) {
+ option::extract(&mut existing_jwk_set)
+ } else {
+ ProviderJWKs {
+ version: 0,
+ issuer: cmd.issuer,
+ jwks: vector[],
+ }
+ };
+ upsert_jwk(&mut jwk_set, cmd.jwk);
+ upsert_provider_jwks(jwks, jwk_set);
+ } else {
+ abort(std::error::invalid_argument(EUNKNOWN_PATCH_VARIANT))
+ }
+}
+
+
+
+
+public(friend) fun on_new_epoch(framework: &signer)
+
+
+
+
+
+requires @aptos_framework == std::signer::address_of(framework);
+include config_buffer::OnNewEpochRequirement<SupportedOIDCProviders>;
+aborts_if false;
+
+
+
+[move-book]: https://aptos.dev/move/book/SUMMARY
diff --git a/aptos-move/framework/aptos-framework/tests/compiler-v2-doc/keyless_account.md b/aptos-move/framework/aptos-framework/tests/compiler-v2-doc/keyless_account.md
new file mode 100644
index 0000000000000..d3d37f1abce46
--- /dev/null
+++ b/aptos-move/framework/aptos-framework/tests/compiler-v2-doc/keyless_account.md
@@ -0,0 +1,800 @@
+
+
+
+# Module `0x1::keyless_account`
+
+This module is responsible for configuring keyless blockchain accounts which were introduced in
+[AIP-61](https://github.com/aptos-foundation/AIPs/blob/main/aips/aip-61.md).
+
+
+- [Struct `Group`](#0x1_keyless_account_Group)
+- [Resource `Groth16VerificationKey`](#0x1_keyless_account_Groth16VerificationKey)
+- [Resource `Configuration`](#0x1_keyless_account_Configuration)
+- [Constants](#@Constants_0)
+- [Function `new_groth16_verification_key`](#0x1_keyless_account_new_groth16_verification_key)
+- [Function `new_configuration`](#0x1_keyless_account_new_configuration)
+- [Function `validate_groth16_vk`](#0x1_keyless_account_validate_groth16_vk)
+- [Function `update_groth16_verification_key`](#0x1_keyless_account_update_groth16_verification_key)
+- [Function `update_configuration`](#0x1_keyless_account_update_configuration)
+- [Function `update_training_wheels`](#0x1_keyless_account_update_training_wheels)
+- [Function `update_max_exp_horizon`](#0x1_keyless_account_update_max_exp_horizon)
+- [Function `remove_all_override_auds`](#0x1_keyless_account_remove_all_override_auds)
+- [Function `add_override_aud`](#0x1_keyless_account_add_override_aud)
+- [Function `set_groth16_verification_key_for_next_epoch`](#0x1_keyless_account_set_groth16_verification_key_for_next_epoch)
+- [Function `set_configuration_for_next_epoch`](#0x1_keyless_account_set_configuration_for_next_epoch)
+- [Function `update_training_wheels_for_next_epoch`](#0x1_keyless_account_update_training_wheels_for_next_epoch)
+- [Function `update_max_exp_horizon_for_next_epoch`](#0x1_keyless_account_update_max_exp_horizon_for_next_epoch)
+- [Function `remove_all_override_auds_for_next_epoch`](#0x1_keyless_account_remove_all_override_auds_for_next_epoch)
+- [Function `add_override_aud_for_next_epoch`](#0x1_keyless_account_add_override_aud_for_next_epoch)
+- [Function `on_new_epoch`](#0x1_keyless_account_on_new_epoch)
+- [Specification](#@Specification_1)
+
+
+use 0x1::bn254_algebra;
+use 0x1::chain_status;
+use 0x1::config_buffer;
+use 0x1::crypto_algebra;
+use 0x1::ed25519;
+use 0x1::option;
+use 0x1::signer;
+use 0x1::string;
+use 0x1::system_addresses;
+
+
+
+
+
+
+## Struct `Group`
+
+
+
+#[resource_group(#[scope = global])]
+struct Group
+
+
+
+
+dummy_field: bool
+#[resource_group_member(#[group = 0x1::keyless_account::Group])]
+struct Groth16VerificationKey has drop, store, key
+
+
+
+
+alpha_g1: vector<u8>
+alpha * G
, where G
is the generator of G1
.
+beta_g2: vector<u8>
+alpha * H
, where H
is the generator of G2
.
+gamma_g2: vector<u8>
+gamma * H
, where H
is the generator of G2
.
+delta_g2: vector<u8>
+delta * H
, where H
is the generator of G2
.
+gamma_abc_g1: vector<vector<u8>>
+\forall i \in {0, ..., \ell}, 64-byte serialization of gamma^{-1} * (beta * a_i + alpha * b_i + c_i) * H
, where
+ H
is the generator of G1
and \ell
is 1 for the ZK relation.
+#[resource_group_member(#[group = 0x1::keyless_account::Group])]
+struct Configuration has copy, drop, store, key
+
+
+
+
+override_aud_vals: vector<string::String>
+aud
for the identity of a recovery service, which will help users recover their keyless accounts
+ associated with dapps or wallets that have disappeared.
+ IMPORTANT: This recovery service **cannot** on its own take over user accounts; a user must first sign in
+ via OAuth in the recovery service in order to allow it to rotate any of that user's keyless accounts.
+max_signatures_per_txn: u16
+max_exp_horizon_secs: u64
+training_wheels_pubkey: option::Option<vector<u8>>
+max_commited_epk_bytes: u16
+max_iss_val_bytes: u16
+iss
field supported in our circuit (e.g., "https://accounts.google.com"
)
+max_extra_field_bytes: u16
+"max_age":"18"
) supported in our circuit
+max_jwt_header_b64_bytes: u32
+const E_INVALID_BN254_G1_SERIALIZATION: u64 = 2;
+
+
+
+
+
+
+A serialized BN254 G2 point is invalid.
+
+
+const E_INVALID_BN254_G2_SERIALIZATION: u64 = 3;
+
+
+
+
+
+
+The training wheels PK needs to be 32 bytes long.
+
+
+const E_TRAINING_WHEELS_PK_WRONG_SIZE: u64 = 1;
+
+
+
+
+
+
+## Function `new_groth16_verification_key`
+
+
+
+public fun new_groth16_verification_key(alpha_g1: vector<u8>, beta_g2: vector<u8>, gamma_g2: vector<u8>, delta_g2: vector<u8>, gamma_abc_g1: vector<vector<u8>>): keyless_account::Groth16VerificationKey
+
+
+
+
+public fun new_groth16_verification_key(alpha_g1: vector<u8>,
+ beta_g2: vector<u8>,
+ gamma_g2: vector<u8>,
+ delta_g2: vector<u8>,
+ gamma_abc_g1: vector<vector<u8>>
+): Groth16VerificationKey {
+ Groth16VerificationKey {
+ alpha_g1,
+ beta_g2,
+ gamma_g2,
+ delta_g2,
+ gamma_abc_g1,
+ }
+}
+
+
+
+
+public fun new_configuration(override_aud_val: vector<string::String>, max_signatures_per_txn: u16, max_exp_horizon_secs: u64, training_wheels_pubkey: option::Option<vector<u8>>, max_commited_epk_bytes: u16, max_iss_val_bytes: u16, max_extra_field_bytes: u16, max_jwt_header_b64_bytes: u32): keyless_account::Configuration
+
+
+
+
+public fun new_configuration(
+ override_aud_val: vector<String>,
+ max_signatures_per_txn: u16,
+ max_exp_horizon_secs: u64,
+ training_wheels_pubkey: Option<vector<u8>>,
+ max_commited_epk_bytes: u16,
+ max_iss_val_bytes: u16,
+ max_extra_field_bytes: u16,
+ max_jwt_header_b64_bytes: u32
+): Configuration {
+ Configuration {
+ override_aud_vals: override_aud_val,
+ max_signatures_per_txn,
+ max_exp_horizon_secs,
+ training_wheels_pubkey,
+ max_commited_epk_bytes,
+ max_iss_val_bytes,
+ max_extra_field_bytes,
+ max_jwt_header_b64_bytes,
+ }
+}
+
+
+
+
+fun validate_groth16_vk(vk: &keyless_account::Groth16VerificationKey)
+
+
+
+
+fun validate_groth16_vk(vk: &Groth16VerificationKey) {
+ // Could be leveraged to speed up the VM deserialization of the VK by 2x, since it can assume the points are valid.
+ assert!(option::is_some(&crypto_algebra::deserialize<bn254_algebra::G1, bn254_algebra::FormatG1Compr>(&vk.alpha_g1)), E_INVALID_BN254_G1_SERIALIZATION);
+ assert!(option::is_some(&crypto_algebra::deserialize<bn254_algebra::G2, bn254_algebra::FormatG2Compr>(&vk.beta_g2)), E_INVALID_BN254_G2_SERIALIZATION);
+ assert!(option::is_some(&crypto_algebra::deserialize<bn254_algebra::G2, bn254_algebra::FormatG2Compr>(&vk.gamma_g2)), E_INVALID_BN254_G2_SERIALIZATION);
+ assert!(option::is_some(&crypto_algebra::deserialize<bn254_algebra::G2, bn254_algebra::FormatG2Compr>(&vk.delta_g2)), E_INVALID_BN254_G2_SERIALIZATION);
+ for (i in 0..vector::length(&vk.gamma_abc_g1)) {
+ assert!(option::is_some(&crypto_algebra::deserialize<bn254_algebra::G1, bn254_algebra::FormatG1Compr>(vector::borrow(&vk.gamma_abc_g1, i))), E_INVALID_BN254_G1_SERIALIZATION);
+ };
+}
+
+
+
+
+set_groth16_verification_key_for_next_epoch
.
+
+WARNING: See set_groth16_verification_key_for_next_epoch
for caveats.
+
+
+public fun update_groth16_verification_key(fx: &signer, vk: keyless_account::Groth16VerificationKey)
+
+
+
+
+public fun update_groth16_verification_key(fx: &signer, vk: Groth16VerificationKey) {
+ system_addresses::assert_aptos_framework(fx);
+ chain_status::assert_genesis();
+ // There should not be a previous resource set here.
+ move_to(fx, vk);
+}
+
+
+
+
+set_configuration_for_next_epoch
.
+
+WARNING: See set_configuration_for_next_epoch
for caveats.
+
+
+public fun update_configuration(fx: &signer, config: keyless_account::Configuration)
+
+
+
+
+public fun update_configuration(fx: &signer, config: Configuration) {
+ system_addresses::assert_aptos_framework(fx);
+ chain_status::assert_genesis();
+ // There should not be a previous resource set here.
+ move_to(fx, config);
+}
+
+
+
+
+#[deprecated]
+public fun update_training_wheels(fx: &signer, pk: option::Option<vector<u8>>)
+
+
+
+
+public fun update_training_wheels(fx: &signer, pk: Option<vector<u8>>) acquires Configuration {
+ system_addresses::assert_aptos_framework(fx);
+ chain_status::assert_genesis();
+
+ if (option::is_some(&pk)) {
+ assert!(vector::length(option::borrow(&pk)) == 32, E_TRAINING_WHEELS_PK_WRONG_SIZE)
+ };
+
+ let config = borrow_global_mut<Configuration>(signer::address_of(fx));
+ config.training_wheels_pubkey = pk;
+}
+
+
+
+
+#[deprecated]
+public fun update_max_exp_horizon(fx: &signer, max_exp_horizon_secs: u64)
+
+
+
+
+public fun update_max_exp_horizon(fx: &signer, max_exp_horizon_secs: u64) acquires Configuration {
+ system_addresses::assert_aptos_framework(fx);
+ chain_status::assert_genesis();
+
+ let config = borrow_global_mut<Configuration>(signer::address_of(fx));
+ config.max_exp_horizon_secs = max_exp_horizon_secs;
+}
+
+
+
+
+#[deprecated]
+public fun remove_all_override_auds(fx: &signer)
+
+
+
+
+public fun remove_all_override_auds(fx: &signer) acquires Configuration {
+ system_addresses::assert_aptos_framework(fx);
+ chain_status::assert_genesis();
+
+ let config = borrow_global_mut<Configuration>(signer::address_of(fx));
+ config.override_aud_vals = vector[];
+}
+
+
+
+
+#[deprecated]
+public fun add_override_aud(fx: &signer, aud: string::String)
+
+
+
+
+public fun add_override_aud(fx: &signer, aud: String) acquires Configuration {
+ system_addresses::assert_aptos_framework(fx);
+ chain_status::assert_genesis();
+
+ let config = borrow_global_mut<Configuration>(signer::address_of(fx));
+ vector::push_back(&mut config.override_aud_vals, aud);
+}
+
+
+
+
+public fun set_groth16_verification_key_for_next_epoch(fx: &signer, vk: keyless_account::Groth16VerificationKey)
+
+
+
+
+public fun set_groth16_verification_key_for_next_epoch(fx: &signer, vk: Groth16VerificationKey) {
+ system_addresses::assert_aptos_framework(fx);
+ config_buffer::upsert<Groth16VerificationKey>(vk);
+}
+
+
+
+
+Configuration
could lead to DoS attacks, create liveness issues, or enable a malicious
+recovery service provider to phish users' accounts.
+
+
+public fun set_configuration_for_next_epoch(fx: &signer, config: keyless_account::Configuration)
+
+
+
+
+public fun set_configuration_for_next_epoch(fx: &signer, config: Configuration) {
+ system_addresses::assert_aptos_framework(fx);
+ config_buffer::upsert<Configuration>(config);
+}
+
+
+
+
+public fun update_training_wheels_for_next_epoch(fx: &signer, pk: option::Option<vector<u8>>)
+
+
+
+
+public fun update_training_wheels_for_next_epoch(fx: &signer, pk: Option<vector<u8>>) acquires Configuration {
+ system_addresses::assert_aptos_framework(fx);
+
+ // If a PK is being set, validate it first.
+ if (option::is_some(&pk)) {
+ let bytes = *option::borrow(&pk);
+ let vpk = ed25519::new_validated_public_key_from_bytes(bytes);
+ assert!(option::is_some(&vpk), E_TRAINING_WHEELS_PK_WRONG_SIZE)
+ };
+
+ let config = if (config_buffer::does_exist<Configuration>()) {
+ config_buffer::extract<Configuration>()
+ } else {
+ *borrow_global<Configuration>(signer::address_of(fx))
+ };
+
+ config.training_wheels_pubkey = pk;
+
+ set_configuration_for_next_epoch(fx, config);
+}
+
+
+
+
+public fun update_max_exp_horizon_for_next_epoch(fx: &signer, max_exp_horizon_secs: u64)
+
+
+
+
+public fun update_max_exp_horizon_for_next_epoch(fx: &signer, max_exp_horizon_secs: u64) acquires Configuration {
+ system_addresses::assert_aptos_framework(fx);
+
+ let config = if (config_buffer::does_exist<Configuration>()) {
+ config_buffer::extract<Configuration>()
+ } else {
+ *borrow_global<Configuration>(signer::address_of(fx))
+ };
+
+ config.max_exp_horizon_secs = max_exp_horizon_secs;
+
+ set_configuration_for_next_epoch(fx, config);
+}
+
+
+
+
+aud
's. The change will only be effective after
+reconfiguration. Only callable via governance proposal.
+
+WARNING: When no override aud
is set, recovery of keyless accounts associated with applications that disappeared
+is no longer possible.
+
+
+public fun remove_all_override_auds_for_next_epoch(fx: &signer)
+
+
+
+
+public fun remove_all_override_auds_for_next_epoch(fx: &signer) acquires Configuration {
+ system_addresses::assert_aptos_framework(fx);
+
+ let config = if (config_buffer::does_exist<Configuration>()) {
+ config_buffer::extract<Configuration>()
+ } else {
+ *borrow_global<Configuration>(signer::address_of(fx))
+ };
+
+ config.override_aud_vals = vector[];
+
+ set_configuration_for_next_epoch(fx, config);
+}
+
+
+
+
+aud
's. The change will only be effective
+after reconfiguration. Only callable via governance proposal.
+
+WARNING: If a malicious override aud
is set, this *could* lead to stolen funds.
+
+
+public fun add_override_aud_for_next_epoch(fx: &signer, aud: string::String)
+
+
+
+
+public fun add_override_aud_for_next_epoch(fx: &signer, aud: String) acquires Configuration {
+ system_addresses::assert_aptos_framework(fx);
+
+ let config = if (config_buffer::does_exist<Configuration>()) {
+ config_buffer::extract<Configuration>()
+ } else {
+ *borrow_global<Configuration>(signer::address_of(fx))
+ };
+
+ vector::push_back(&mut config.override_aud_vals, aud);
+
+ set_configuration_for_next_epoch(fx, config);
+}
+
+
+
+
+public(friend) fun on_new_epoch(fx: &signer)
+
+
+
+
+public(friend) fun on_new_epoch(fx: &signer) acquires Groth16VerificationKey, Configuration {
+ system_addresses::assert_aptos_framework(fx);
+
+ if (config_buffer::does_exist<Groth16VerificationKey>()) {
+ let vk = config_buffer::extract();
+ if (exists<Groth16VerificationKey>(@aptos_framework)) {
+ *borrow_global_mut<Groth16VerificationKey>(@aptos_framework) = vk;
+ } else {
+ move_to(fx, vk);
+ }
+ };
+
+ if (config_buffer::does_exist<Configuration>()) {
+ let config = config_buffer::extract();
+ if (exists<Configuration>(@aptos_framework)) {
+ *borrow_global_mut<Configuration>(@aptos_framework) = config;
+ } else {
+ move_to(fx, config);
+ }
+ };
+}
+
+
+
+
+pragma verify=false;
+
+
+
+[move-book]: https://aptos.dev/move/book/SUMMARY
diff --git a/aptos-move/framework/aptos-framework/tests/compiler-v2-doc/managed_coin.md b/aptos-move/framework/aptos-framework/tests/compiler-v2-doc/managed_coin.md
new file mode 100644
index 0000000000000..b6d1c90017f0f
--- /dev/null
+++ b/aptos-move/framework/aptos-framework/tests/compiler-v2-doc/managed_coin.md
@@ -0,0 +1,426 @@
+
+
+
+# Module `0x1::managed_coin`
+
+ManagedCoin is built to make a simple walkthrough of the Coins module.
+It contains scripts you will need to initialize, mint, burn, transfer coins.
+By utilizing this current module, a developer can create his own coin and care less about mint and burn capabilities,
+
+
+- [Resource `Capabilities`](#0x1_managed_coin_Capabilities)
+- [Constants](#@Constants_0)
+- [Function `burn`](#0x1_managed_coin_burn)
+- [Function `initialize`](#0x1_managed_coin_initialize)
+- [Function `mint`](#0x1_managed_coin_mint)
+- [Function `register`](#0x1_managed_coin_register)
+- [Specification](#@Specification_1)
+ - [High-level Requirements](#high-level-req)
+ - [Module-level Specification](#module-level-spec)
+ - [Function `burn`](#@Specification_1_burn)
+ - [Function `initialize`](#@Specification_1_initialize)
+ - [Function `mint`](#@Specification_1_mint)
+ - [Function `register`](#@Specification_1_register)
+
+
+use 0x1::coin;
+use 0x1::error;
+use 0x1::signer;
+use 0x1::string;
+
+
+
+
+
+
+## Resource `Capabilities`
+
+Capabilities resource storing mint and burn capabilities.
+The resource is stored on the account that initialized coin CoinType
.
+
+
+struct Capabilities<CoinType> has key
+
+
+
+
+burn_cap: coin::BurnCapability<CoinType>
+freeze_cap: coin::FreezeCapability<CoinType>
+mint_cap: coin::MintCapability<CoinType>
+const ENO_CAPABILITIES: u64 = 1;
+
+
+
+
+
+
+## Function `burn`
+
+Withdraw an amount
of coin CoinType
from account
and burn it.
+
+
+public entry fun burn<CoinType>(account: &signer, amount: u64)
+
+
+
+
+public entry fun burn<CoinType>(
+ account: &signer,
+ amount: u64,
+) acquires Capabilities {
+ let account_addr = signer::address_of(account);
+
+ assert!(
+ exists<Capabilities<CoinType>>(account_addr),
+ error::not_found(ENO_CAPABILITIES),
+ );
+
+ let capabilities = borrow_global<Capabilities<CoinType>>(account_addr);
+
+ let to_burn = coin::withdraw<CoinType>(account, amount);
+ coin::burn(to_burn, &capabilities.burn_cap);
+}
+
+
+
+
+CoinType
in Aptos Blockchain.
+Mint and Burn Capabilities will be stored under account
in Capabilities
resource.
+
+
+public entry fun initialize<CoinType>(account: &signer, name: vector<u8>, symbol: vector<u8>, decimals: u8, monitor_supply: bool)
+
+
+
+
+public entry fun initialize<CoinType>(
+ account: &signer,
+ name: vector<u8>,
+ symbol: vector<u8>,
+ decimals: u8,
+ monitor_supply: bool,
+) {
+ let (burn_cap, freeze_cap, mint_cap) = coin::initialize<CoinType>(
+ account,
+ string::utf8(name),
+ string::utf8(symbol),
+ decimals,
+ monitor_supply,
+ );
+
+ move_to(account, Capabilities<CoinType> {
+ burn_cap,
+ freeze_cap,
+ mint_cap,
+ });
+}
+
+
+
+
+CoinType
and deposit them into dst_addr's account.
+
+
+public entry fun mint<CoinType>(account: &signer, dst_addr: address, amount: u64)
+
+
+
+
+public entry fun mint<CoinType>(
+ account: &signer,
+ dst_addr: address,
+ amount: u64,
+) acquires Capabilities {
+ let account_addr = signer::address_of(account);
+
+ assert!(
+ exists<Capabilities<CoinType>>(account_addr),
+ error::not_found(ENO_CAPABILITIES),
+ );
+
+ let capabilities = borrow_global<Capabilities<CoinType>>(account_addr);
+ let coins_minted = coin::mint(amount, &capabilities.mint_cap);
+ coin::deposit(dst_addr, coins_minted);
+}
+
+
+
+
+CoinType
on user's account, withdraw and deposit event handlers.
+Required if user wants to start accepting deposits of CoinType
in his account.
+
+
+public entry fun register<CoinType>(account: &signer)
+
+
+
+
+public entry fun register<CoinType>(account: &signer) {
+ coin::register<CoinType>(account);
+}
+
+
+
+
+No. | Requirement | Criticality | Implementation | Enforcement | +
---|---|---|---|---|
1 | +The initializing account should hold the capabilities to operate the coin. | +Critical | +The capabilities are stored under the initializing account under the Capabilities resource, which is distinct for a distinct type of coin. | +Enforced via initialize. | +
2 | +A new coin should be properly initialized. | +High | +In the initialize function, a new coin is initialized via the coin module with the specified properties. | +Enforced via initialize_internal. | +
3 | +Minting/Burning should only be done by the account who hold the valid capabilities. | +High | +The mint and burn capabilities are moved under the initializing account and retrieved, while minting/burning | +Enforced via: initialize, burn, mint. | +
4 | +If the total supply of coins is being monitored, burn and mint operations will appropriately adjust the total supply. | +High | +The coin::burn and coin::mint functions, when tracking the supply, adjusts the total coin supply accordingly. | +Enforced via TotalSupplyNoChange. | +
5 | +Before burning coins, exact amount of coins are withdrawn. | +High | +After utilizing the coin::withdraw function to withdraw coins, they are then burned, and the function ensures the precise return of the initially specified coin amount. | +Enforced via burn_from. | +
6 | +Minted coins are deposited to the provided destination address. | +High | +After the coins are minted via coin::mint they are deposited into the coinstore of the destination address. | +Enforced via mint. | +
pragma verify = true;
+pragma aborts_if_is_strict;
+
+
+
+
+
+
+### Function `burn`
+
+
+public entry fun burn<CoinType>(account: &signer, amount: u64)
+
+
+
+
+
+pragma verify = false;
+let account_addr = signer::address_of(account);
+aborts_if !exists<Capabilities<CoinType>>(account_addr);
+let coin_store = global<coin::CoinStore<CoinType>>(account_addr);
+let balance = coin_store.coin.value;
+// This enforces high-level requirement 3 and high-level requirement 4:
+aborts_if !exists<coin::CoinStore<CoinType>>(account_addr);
+aborts_if coin_store.frozen;
+aborts_if balance < amount;
+let addr = type_info::type_of<CoinType>().account_address;
+let maybe_supply = global<coin::CoinInfo<CoinType>>(addr).supply;
+aborts_if amount == 0;
+aborts_if !exists<coin::CoinInfo<CoinType>>(addr);
+include coin::CoinSubAbortsIf<CoinType> { amount:amount };
+ensures coin::supply<CoinType> == old(coin::supply<CoinType>) - amount;
+
+
+
+
+
+
+### Function `initialize`
+
+
+public entry fun initialize<CoinType>(account: &signer, name: vector<u8>, symbol: vector<u8>, decimals: u8, monitor_supply: bool)
+
+
+
+Make sure name
and symbol
are legal length.
+Only the creator of CoinType
can initialize.
+The 'name' and 'symbol' should be valid utf8 bytes
+The Capabilitiesinclude coin::InitializeInternalSchema<CoinType>;
+aborts_if !string::spec_internal_check_utf8(name);
+aborts_if !string::spec_internal_check_utf8(symbol);
+aborts_if exists<Capabilities<CoinType>>(signer::address_of(account));
+// This enforces high-level requirement 1 and high-level requirement 3:
+ensures exists<Capabilities<CoinType>>(signer::address_of(account));
+
+
+
+
+
+
+### Function `mint`
+
+
+public entry fun mint<CoinType>(account: &signer, dst_addr: address, amount: u64)
+
+
+
+The Capabilitiesdst_addr
should not be frozen.
+
+
+pragma verify = false;
+let account_addr = signer::address_of(account);
+// This enforces high-level requirement 3:
+aborts_if !exists<Capabilities<CoinType>>(account_addr);
+let addr = type_info::type_of<CoinType>().account_address;
+aborts_if (amount != 0) && !exists<coin::CoinInfo<CoinType>>(addr);
+let coin_store = global<coin::CoinStore<CoinType>>(dst_addr);
+aborts_if !exists<coin::CoinStore<CoinType>>(dst_addr);
+aborts_if coin_store.frozen;
+include coin::CoinAddAbortsIf<CoinType>;
+ensures coin::supply<CoinType> == old(coin::supply<CoinType>) + amount;
+// This enforces high-level requirement 6:
+ensures global<coin::CoinStore<CoinType>>(dst_addr).coin.value == old(global<coin::CoinStore<CoinType>>(dst_addr)).coin.value + amount;
+
+
+
+
+
+
+### Function `register`
+
+
+public entry fun register<CoinType>(account: &signer)
+
+
+
+An account can only be registered once.
+Updating Account.guid_creation_num
will not overflow.
+
+
+pragma verify = false;
+let account_addr = signer::address_of(account);
+let acc = global<account::Account>(account_addr);
+aborts_if !exists<coin::CoinStore<CoinType>>(account_addr) && acc.guid_creation_num + 2 >= account::MAX_GUID_CREATION_NUM;
+aborts_if !exists<coin::CoinStore<CoinType>>(account_addr) && acc.guid_creation_num + 2 > MAX_U64;
+aborts_if !exists<coin::CoinStore<CoinType>>(account_addr) && !exists<account::Account>(account_addr);
+aborts_if !exists<coin::CoinStore<CoinType>>(account_addr) && !type_info::spec_is_struct<CoinType>();
+ensures exists<coin::CoinStore<CoinType>>(account_addr);
+
+
+
+[move-book]: https://aptos.dev/move/book/SUMMARY
diff --git a/aptos-move/framework/aptos-framework/tests/compiler-v2-doc/multisig_account.md b/aptos-move/framework/aptos-framework/tests/compiler-v2-doc/multisig_account.md
new file mode 100644
index 0000000000000..601d661ed1af5
--- /dev/null
+++ b/aptos-move/framework/aptos-framework/tests/compiler-v2-doc/multisig_account.md
@@ -0,0 +1,4127 @@
+
+
+
+# Module `0x1::multisig_account`
+
+Enhanced multisig account standard on Aptos. This is different from the native multisig scheme support enforced via
+the account's auth key.
+
+This module allows creating a flexible and powerful multisig account with seamless support for updating owners
+without changing the auth key. Users can choose to store transaction payloads waiting for owner signatures on chain
+or off chain (primary consideration is decentralization/transparency vs gas cost).
+
+The multisig account is a resource account underneath. By default, it has no auth key and can only be controlled via
+the special multisig transaction flow. However, owners can create a transaction to change the auth key to match a
+private key off chain if so desired.
+
+Transactions need to be executed in order of creation, similar to transactions for a normal Aptos account (enforced
+with account nonce).
+
+The flow is like below:
+1. Owners can create a new multisig account by calling create (signer is default single owner) or with
+create_with_owners where multiple initial owner addresses can be specified. This is different (and easier) from
+the native multisig scheme where the owners' public keys have to be specified. Here, only addresses are needed.
+2. Owners can be added/removed any time by calling add_owners or remove_owners. The transactions to do still need
+to follow the k-of-n scheme specified for the multisig account.
+3. To create a new transaction, an owner can call create_transaction with the transaction payload. This will store
+the full transaction payload on chain, which adds decentralization (censorship is not possible as the data is
+available on chain) and makes it easier to fetch all transactions waiting for execution. If saving gas is desired,
+an owner can alternatively call create_transaction_with_hash where only the payload hash is stored. Later execution
+will be verified using the hash. Only owners can create transactions and a transaction id (incremeting id) will be
+assigned.
+4. To approve or reject a transaction, other owners can call approve() or reject() with the transaction id.
+5. If there are enough approvals, any owner can execute the transaction using the special MultisigTransaction type
+with the transaction id if the full payload is already stored on chain or with the transaction payload if only a
+hash is stored. Transaction execution will first check with this module that the transaction payload has gotten
+enough signatures. If so, it will be executed as the multisig account. The owner who executes will pay for gas.
+6. If there are enough rejections, any owner can finalize the rejection by calling execute_rejected_transaction().
+
+Note that this multisig account model is not designed to use with a large number of owners. The more owners there
+are, the more expensive voting on transactions will become. If a large number of owners is designed, such as in a
+flat governance structure, clients are encouraged to write their own modules on top of this multisig account module
+and implement the governance voting logic on top.
+
+
+- [Resource `MultisigAccount`](#0x1_multisig_account_MultisigAccount)
+- [Struct `MultisigTransaction`](#0x1_multisig_account_MultisigTransaction)
+- [Struct `ExecutionError`](#0x1_multisig_account_ExecutionError)
+- [Struct `MultisigAccountCreationMessage`](#0x1_multisig_account_MultisigAccountCreationMessage)
+- [Struct `MultisigAccountCreationWithAuthKeyRevocationMessage`](#0x1_multisig_account_MultisigAccountCreationWithAuthKeyRevocationMessage)
+- [Struct `AddOwnersEvent`](#0x1_multisig_account_AddOwnersEvent)
+- [Struct `AddOwners`](#0x1_multisig_account_AddOwners)
+- [Struct `RemoveOwnersEvent`](#0x1_multisig_account_RemoveOwnersEvent)
+- [Struct `RemoveOwners`](#0x1_multisig_account_RemoveOwners)
+- [Struct `UpdateSignaturesRequiredEvent`](#0x1_multisig_account_UpdateSignaturesRequiredEvent)
+- [Struct `UpdateSignaturesRequired`](#0x1_multisig_account_UpdateSignaturesRequired)
+- [Struct `CreateTransactionEvent`](#0x1_multisig_account_CreateTransactionEvent)
+- [Struct `CreateTransaction`](#0x1_multisig_account_CreateTransaction)
+- [Struct `VoteEvent`](#0x1_multisig_account_VoteEvent)
+- [Struct `Vote`](#0x1_multisig_account_Vote)
+- [Struct `ExecuteRejectedTransactionEvent`](#0x1_multisig_account_ExecuteRejectedTransactionEvent)
+- [Struct `ExecuteRejectedTransaction`](#0x1_multisig_account_ExecuteRejectedTransaction)
+- [Struct `TransactionExecutionSucceededEvent`](#0x1_multisig_account_TransactionExecutionSucceededEvent)
+- [Struct `TransactionExecutionSucceeded`](#0x1_multisig_account_TransactionExecutionSucceeded)
+- [Struct `TransactionExecutionFailedEvent`](#0x1_multisig_account_TransactionExecutionFailedEvent)
+- [Struct `TransactionExecutionFailed`](#0x1_multisig_account_TransactionExecutionFailed)
+- [Struct `MetadataUpdatedEvent`](#0x1_multisig_account_MetadataUpdatedEvent)
+- [Struct `MetadataUpdated`](#0x1_multisig_account_MetadataUpdated)
+- [Constants](#@Constants_0)
+- [Function `metadata`](#0x1_multisig_account_metadata)
+- [Function `num_signatures_required`](#0x1_multisig_account_num_signatures_required)
+- [Function `owners`](#0x1_multisig_account_owners)
+- [Function `is_owner`](#0x1_multisig_account_is_owner)
+- [Function `get_transaction`](#0x1_multisig_account_get_transaction)
+- [Function `get_pending_transactions`](#0x1_multisig_account_get_pending_transactions)
+- [Function `get_next_transaction_payload`](#0x1_multisig_account_get_next_transaction_payload)
+- [Function `can_be_executed`](#0x1_multisig_account_can_be_executed)
+- [Function `can_execute`](#0x1_multisig_account_can_execute)
+- [Function `can_be_rejected`](#0x1_multisig_account_can_be_rejected)
+- [Function `can_reject`](#0x1_multisig_account_can_reject)
+- [Function `get_next_multisig_account_address`](#0x1_multisig_account_get_next_multisig_account_address)
+- [Function `last_resolved_sequence_number`](#0x1_multisig_account_last_resolved_sequence_number)
+- [Function `next_sequence_number`](#0x1_multisig_account_next_sequence_number)
+- [Function `vote`](#0x1_multisig_account_vote)
+- [Function `available_transaction_queue_capacity`](#0x1_multisig_account_available_transaction_queue_capacity)
+- [Function `create_with_existing_account`](#0x1_multisig_account_create_with_existing_account)
+- [Function `create_with_existing_account_and_revoke_auth_key`](#0x1_multisig_account_create_with_existing_account_and_revoke_auth_key)
+- [Function `create`](#0x1_multisig_account_create)
+- [Function `create_with_owners`](#0x1_multisig_account_create_with_owners)
+- [Function `create_with_owners_then_remove_bootstrapper`](#0x1_multisig_account_create_with_owners_then_remove_bootstrapper)
+- [Function `create_with_owners_internal`](#0x1_multisig_account_create_with_owners_internal)
+- [Function `add_owner`](#0x1_multisig_account_add_owner)
+- [Function `add_owners`](#0x1_multisig_account_add_owners)
+- [Function `add_owners_and_update_signatures_required`](#0x1_multisig_account_add_owners_and_update_signatures_required)
+- [Function `remove_owner`](#0x1_multisig_account_remove_owner)
+- [Function `remove_owners`](#0x1_multisig_account_remove_owners)
+- [Function `swap_owner`](#0x1_multisig_account_swap_owner)
+- [Function `swap_owners`](#0x1_multisig_account_swap_owners)
+- [Function `swap_owners_and_update_signatures_required`](#0x1_multisig_account_swap_owners_and_update_signatures_required)
+- [Function `update_signatures_required`](#0x1_multisig_account_update_signatures_required)
+- [Function `update_metadata`](#0x1_multisig_account_update_metadata)
+- [Function `update_metadata_internal`](#0x1_multisig_account_update_metadata_internal)
+- [Function `create_transaction`](#0x1_multisig_account_create_transaction)
+- [Function `create_transaction_with_hash`](#0x1_multisig_account_create_transaction_with_hash)
+- [Function `approve_transaction`](#0x1_multisig_account_approve_transaction)
+- [Function `reject_transaction`](#0x1_multisig_account_reject_transaction)
+- [Function `vote_transanction`](#0x1_multisig_account_vote_transanction)
+- [Function `vote_transaction`](#0x1_multisig_account_vote_transaction)
+- [Function `vote_transactions`](#0x1_multisig_account_vote_transactions)
+- [Function `execute_rejected_transaction`](#0x1_multisig_account_execute_rejected_transaction)
+- [Function `execute_rejected_transactions`](#0x1_multisig_account_execute_rejected_transactions)
+- [Function `validate_multisig_transaction`](#0x1_multisig_account_validate_multisig_transaction)
+- [Function `successful_transaction_execution_cleanup`](#0x1_multisig_account_successful_transaction_execution_cleanup)
+- [Function `failed_transaction_execution_cleanup`](#0x1_multisig_account_failed_transaction_execution_cleanup)
+- [Function `transaction_execution_cleanup_common`](#0x1_multisig_account_transaction_execution_cleanup_common)
+- [Function `remove_executed_transaction`](#0x1_multisig_account_remove_executed_transaction)
+- [Function `add_transaction`](#0x1_multisig_account_add_transaction)
+- [Function `create_multisig_account`](#0x1_multisig_account_create_multisig_account)
+- [Function `create_multisig_account_seed`](#0x1_multisig_account_create_multisig_account_seed)
+- [Function `validate_owners`](#0x1_multisig_account_validate_owners)
+- [Function `assert_is_owner_internal`](#0x1_multisig_account_assert_is_owner_internal)
+- [Function `assert_is_owner`](#0x1_multisig_account_assert_is_owner)
+- [Function `num_approvals_and_rejections_internal`](#0x1_multisig_account_num_approvals_and_rejections_internal)
+- [Function `num_approvals_and_rejections`](#0x1_multisig_account_num_approvals_and_rejections)
+- [Function `has_voted_for_approval`](#0x1_multisig_account_has_voted_for_approval)
+- [Function `has_voted_for_rejection`](#0x1_multisig_account_has_voted_for_rejection)
+- [Function `assert_multisig_account_exists`](#0x1_multisig_account_assert_multisig_account_exists)
+- [Function `assert_valid_sequence_number`](#0x1_multisig_account_assert_valid_sequence_number)
+- [Function `assert_transaction_exists`](#0x1_multisig_account_assert_transaction_exists)
+- [Function `update_owner_schema`](#0x1_multisig_account_update_owner_schema)
+- [Specification](#@Specification_1)
+ - [High-level Requirements](#high-level-req)
+ - [Module-level Specification](#module-level-spec)
+ - [Function `metadata`](#@Specification_1_metadata)
+ - [Function `num_signatures_required`](#@Specification_1_num_signatures_required)
+ - [Function `owners`](#@Specification_1_owners)
+ - [Function `get_transaction`](#@Specification_1_get_transaction)
+ - [Function `get_next_transaction_payload`](#@Specification_1_get_next_transaction_payload)
+ - [Function `get_next_multisig_account_address`](#@Specification_1_get_next_multisig_account_address)
+ - [Function `last_resolved_sequence_number`](#@Specification_1_last_resolved_sequence_number)
+ - [Function `next_sequence_number`](#@Specification_1_next_sequence_number)
+ - [Function `vote`](#@Specification_1_vote)
+
+
+use 0x1::account;
+use 0x1::aptos_coin;
+use 0x1::bcs;
+use 0x1::chain_id;
+use 0x1::coin;
+use 0x1::create_signer;
+use 0x1::error;
+use 0x1::event;
+use 0x1::features;
+use 0x1::hash;
+use 0x1::option;
+use 0x1::signer;
+use 0x1::simple_map;
+use 0x1::string;
+use 0x1::table;
+use 0x1::timestamp;
+use 0x1::vector;
+
+
+
+
+
+
+## Resource `MultisigAccount`
+
+Represents a multisig account's configurations and transactions.
+This will be stored in the multisig account (created as a resource account separate from any owner accounts).
+
+
+struct MultisigAccount has key
+
+
+
+
+owners: vector<address>
+num_signatures_required: u64
+transactions: table::Table<u64, multisig_account::MultisigTransaction>
+last_executed_sequence_number: u64
+next_sequence_number: u64
+signer_cap: option::Option<account::SignerCapability>
+metadata: simple_map::SimpleMap<string::String, vector<u8>>
+add_owners_events: event::EventHandle<multisig_account::AddOwnersEvent>
+remove_owners_events: event::EventHandle<multisig_account::RemoveOwnersEvent>
+update_signature_required_events: event::EventHandle<multisig_account::UpdateSignaturesRequiredEvent>
+create_transaction_events: event::EventHandle<multisig_account::CreateTransactionEvent>
+vote_events: event::EventHandle<multisig_account::VoteEvent>
+execute_rejected_transaction_events: event::EventHandle<multisig_account::ExecuteRejectedTransactionEvent>
+execute_transaction_events: event::EventHandle<multisig_account::TransactionExecutionSucceededEvent>
+transaction_execution_failed_events: event::EventHandle<multisig_account::TransactionExecutionFailedEvent>
+metadata_updated_events: event::EventHandle<multisig_account::MetadataUpdatedEvent>
+struct MultisigTransaction has copy, drop, store
+
+
+
+
+payload: option::Option<vector<u8>>
+payload_hash: option::Option<vector<u8>>
+votes: simple_map::SimpleMap<address, bool>
+creator: address
+creation_time_secs: u64
+struct ExecutionError has copy, drop, store
+
+
+
+
+abort_location: string::String
+error_type: string::String
+error_code: u64
+struct MultisigAccountCreationMessage has copy, drop
+
+
+
+
+chain_id: u8
+account_address: address
+sequence_number: u64
+owners: vector<address>
+num_signatures_required: u64
+struct MultisigAccountCreationWithAuthKeyRevocationMessage has copy, drop
+
+
+
+
+chain_id: u8
+account_address: address
+sequence_number: u64
+owners: vector<address>
+num_signatures_required: u64
+struct AddOwnersEvent has drop, store
+
+
+
+
+owners_added: vector<address>
+#[event]
+struct AddOwners has drop, store
+
+
+
+
+multisig_account: address
+owners_added: vector<address>
+struct RemoveOwnersEvent has drop, store
+
+
+
+
+owners_removed: vector<address>
+#[event]
+struct RemoveOwners has drop, store
+
+
+
+
+multisig_account: address
+owners_removed: vector<address>
+struct UpdateSignaturesRequiredEvent has drop, store
+
+
+
+
+old_num_signatures_required: u64
+new_num_signatures_required: u64
+#[event]
+struct UpdateSignaturesRequired has drop, store
+
+
+
+
+multisig_account: address
+old_num_signatures_required: u64
+new_num_signatures_required: u64
+struct CreateTransactionEvent has drop, store
+
+
+
+
+creator: address
+sequence_number: u64
+transaction: multisig_account::MultisigTransaction
+#[event]
+struct CreateTransaction has drop, store
+
+
+
+
+multisig_account: address
+creator: address
+sequence_number: u64
+transaction: multisig_account::MultisigTransaction
+struct VoteEvent has drop, store
+
+
+
+
+owner: address
+sequence_number: u64
+approved: bool
+#[event]
+struct Vote has drop, store
+
+
+
+
+multisig_account: address
+owner: address
+sequence_number: u64
+approved: bool
+struct ExecuteRejectedTransactionEvent has drop, store
+
+
+
+
+sequence_number: u64
+num_rejections: u64
+executor: address
+#[event]
+struct ExecuteRejectedTransaction has drop, store
+
+
+
+
+multisig_account: address
+sequence_number: u64
+num_rejections: u64
+executor: address
+struct TransactionExecutionSucceededEvent has drop, store
+
+
+
+
+executor: address
+sequence_number: u64
+transaction_payload: vector<u8>
+num_approvals: u64
+#[event]
+struct TransactionExecutionSucceeded has drop, store
+
+
+
+
+multisig_account: address
+executor: address
+sequence_number: u64
+transaction_payload: vector<u8>
+num_approvals: u64
+struct TransactionExecutionFailedEvent has drop, store
+
+
+
+
+executor: address
+sequence_number: u64
+transaction_payload: vector<u8>
+num_approvals: u64
+execution_error: multisig_account::ExecutionError
+#[event]
+struct TransactionExecutionFailed has drop, store
+
+
+
+
+multisig_account: address
+executor: address
+sequence_number: u64
+transaction_payload: vector<u8>
+num_approvals: u64
+execution_error: multisig_account::ExecutionError
+struct MetadataUpdatedEvent has drop, store
+
+
+
+
+old_metadata: simple_map::SimpleMap<string::String, vector<u8>>
+new_metadata: simple_map::SimpleMap<string::String, vector<u8>>
+#[event]
+struct MetadataUpdated has drop, store
+
+
+
+
+multisig_account: address
+old_metadata: simple_map::SimpleMap<string::String, vector<u8>>
+new_metadata: simple_map::SimpleMap<string::String, vector<u8>>
+const ZERO_AUTH_KEY: vector<u8> = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0];
+
+
+
+
+
+
+The salt used to create a resource account during multisig account creation.
+This is used to avoid conflicts with other modules that also create resource accounts with the same owner
+account.
+
+
+const DOMAIN_SEPARATOR: vector<u8> = [97, 112, 116, 111, 115, 95, 102, 114, 97, 109, 101, 119, 111, 114, 107, 58, 58, 109, 117, 108, 116, 105, 115, 105, 103, 95, 97, 99, 99, 111, 117, 110, 116];
+
+
+
+
+
+
+Specified account is not a multisig account.
+
+
+const EACCOUNT_NOT_MULTISIG: u64 = 2002;
+
+
+
+
+
+
+The specified metadata contains duplicate attributes (keys).
+
+
+const EDUPLICATE_METADATA_KEY: u64 = 16;
+
+
+
+
+
+
+Owner list cannot contain the same address more than once.
+
+
+const EDUPLICATE_OWNER: u64 = 1;
+
+
+
+
+
+
+Payload hash must be exactly 32 bytes (sha3-256).
+
+
+const EINVALID_PAYLOAD_HASH: u64 = 12;
+
+
+
+
+
+
+The sequence number provided is invalid. It must be between [1, next pending transaction - 1].
+
+
+const EINVALID_SEQUENCE_NUMBER: u64 = 17;
+
+
+
+
+
+
+Number of signatures required must be more than zero and at most the total number of owners.
+
+
+const EINVALID_SIGNATURES_REQUIRED: u64 = 11;
+
+
+
+
+
+
+The number of pending transactions has exceeded the maximum allowed.
+
+
+const EMAX_PENDING_TRANSACTIONS_EXCEEDED: u64 = 19;
+
+
+
+
+
+
+Multisig accounts has not been enabled on this current network yet.
+
+
+const EMULTISIG_ACCOUNTS_NOT_ENABLED_YET: u64 = 14;
+
+
+
+
+
+
+The multisig v2 enhancement feature is not enabled.
+
+
+const EMULTISIG_V2_ENHANCEMENT_NOT_ENABLED: u64 = 20;
+
+
+
+
+
+
+Transaction has not received enough approvals to be executed.
+
+
+const ENOT_ENOUGH_APPROVALS: u64 = 2009;
+
+
+
+
+
+
+Multisig account must have at least one owner.
+
+
+const ENOT_ENOUGH_OWNERS: u64 = 5;
+
+
+
+
+
+
+Transaction has not received enough rejections to be officially rejected.
+
+
+const ENOT_ENOUGH_REJECTIONS: u64 = 10;
+
+
+
+
+
+
+Account executing this operation is not an owner of the multisig account.
+
+
+const ENOT_OWNER: u64 = 2003;
+
+
+
+
+
+
+The number of metadata keys and values don't match.
+
+
+const ENUMBER_OF_METADATA_KEYS_AND_VALUES_DONT_MATCH: u64 = 15;
+
+
+
+
+
+
+Provided owners to remove and new owners overlap.
+
+
+const EOWNERS_TO_REMOVE_NEW_OWNERS_OVERLAP: u64 = 18;
+
+
+
+
+
+
+The multisig account itself cannot be an owner.
+
+
+const EOWNER_CANNOT_BE_MULTISIG_ACCOUNT_ITSELF: u64 = 13;
+
+
+
+
+
+
+Transaction payload cannot be empty.
+
+
+const EPAYLOAD_CANNOT_BE_EMPTY: u64 = 4;
+
+
+
+
+
+
+Provided target function does not match the payload stored in the on-chain transaction.
+
+
+const EPAYLOAD_DOES_NOT_MATCH: u64 = 2010;
+
+
+
+
+
+
+Provided target function does not match the hash stored in the on-chain transaction.
+
+
+const EPAYLOAD_DOES_NOT_MATCH_HASH: u64 = 2008;
+
+
+
+
+
+
+Transaction with specified id cannot be found.
+
+
+const ETRANSACTION_NOT_FOUND: u64 = 2006;
+
+
+
+
+
+
+
+
+const MAX_PENDING_TRANSACTIONS: u64 = 20;
+
+
+
+
+
+
+## Function `metadata`
+
+Return the multisig account's metadata.
+
+
+#[view]
+public fun metadata(multisig_account: address): simple_map::SimpleMap<string::String, vector<u8>>
+
+
+
+
+public fun metadata(multisig_account: address): SimpleMap<String, vector<u8>> acquires MultisigAccount {
+ borrow_global<MultisigAccount>(multisig_account).metadata
+}
+
+
+
+
+#[view]
+public fun num_signatures_required(multisig_account: address): u64
+
+
+
+
+public fun num_signatures_required(multisig_account: address): u64 acquires MultisigAccount {
+ borrow_global<MultisigAccount>(multisig_account).num_signatures_required
+}
+
+
+
+
+#[view]
+public fun owners(multisig_account: address): vector<address>
+
+
+
+
+public fun owners(multisig_account: address): vector<address> acquires MultisigAccount {
+ borrow_global<MultisigAccount>(multisig_account).owners
+}
+
+
+
+
+#[view]
+public fun is_owner(owner: address, multisig_account: address): bool
+
+
+
+
+public fun is_owner(owner: address, multisig_account: address): bool acquires MultisigAccount {
+ vector::contains(&borrow_global<MultisigAccount>(multisig_account).owners, &owner)
+}
+
+
+
+
+#[view]
+public fun get_transaction(multisig_account: address, sequence_number: u64): multisig_account::MultisigTransaction
+
+
+
+
+public fun get_transaction(
+ multisig_account: address,
+ sequence_number: u64,
+): MultisigTransaction acquires MultisigAccount {
+ let multisig_account_resource = borrow_global<MultisigAccount>(multisig_account);
+ assert!(
+ sequence_number > 0 && sequence_number < multisig_account_resource.next_sequence_number,
+ error::invalid_argument(EINVALID_SEQUENCE_NUMBER),
+ );
+ *table::borrow(&multisig_account_resource.transactions, sequence_number)
+}
+
+
+
+
+#[view]
+public fun get_pending_transactions(multisig_account: address): vector<multisig_account::MultisigTransaction>
+
+
+
+
+public fun get_pending_transactions(
+ multisig_account: address
+): vector<MultisigTransaction> acquires MultisigAccount {
+ let pending_transactions: vector<MultisigTransaction> = vector[];
+ let multisig_account = borrow_global<MultisigAccount>(multisig_account);
+ let i = multisig_account.last_executed_sequence_number + 1;
+ let next_sequence_number = multisig_account.next_sequence_number;
+ while (i < next_sequence_number) {
+ vector::push_back(&mut pending_transactions, *table::borrow(&multisig_account.transactions, i));
+ i = i + 1;
+ };
+ pending_transactions
+}
+
+
+
+
+#[view]
+public fun get_next_transaction_payload(multisig_account: address, provided_payload: vector<u8>): vector<u8>
+
+
+
+
+public fun get_next_transaction_payload(
+ multisig_account: address, provided_payload: vector<u8>): vector<u8> acquires MultisigAccount {
+ let multisig_account_resource = borrow_global<MultisigAccount>(multisig_account);
+ let sequence_number = multisig_account_resource.last_executed_sequence_number + 1;
+ let transaction = table::borrow(&multisig_account_resource.transactions, sequence_number);
+
+ if (option::is_some(&transaction.payload)) {
+ *option::borrow(&transaction.payload)
+ } else {
+ provided_payload
+ }
+}
+
+
+
+
+#[view]
+public fun can_be_executed(multisig_account: address, sequence_number: u64): bool
+
+
+
+
+public fun can_be_executed(multisig_account: address, sequence_number: u64): bool acquires MultisigAccount {
+ assert_valid_sequence_number(multisig_account, sequence_number);
+ let (num_approvals, _) = num_approvals_and_rejections(multisig_account, sequence_number);
+ sequence_number == last_resolved_sequence_number(multisig_account) + 1 &&
+ num_approvals >= num_signatures_required(multisig_account)
+}
+
+
+
+
+#[view]
+public fun can_execute(owner: address, multisig_account: address, sequence_number: u64): bool
+
+
+
+
+public fun can_execute(owner: address, multisig_account: address, sequence_number: u64): bool acquires MultisigAccount {
+ assert_valid_sequence_number(multisig_account, sequence_number);
+ let (num_approvals, _) = num_approvals_and_rejections(multisig_account, sequence_number);
+ if (!has_voted_for_approval(multisig_account, sequence_number, owner)) {
+ num_approvals = num_approvals + 1;
+ };
+ is_owner(owner, multisig_account) &&
+ sequence_number == last_resolved_sequence_number(multisig_account) + 1 &&
+ num_approvals >= num_signatures_required(multisig_account)
+}
+
+
+
+
+#[view]
+public fun can_be_rejected(multisig_account: address, sequence_number: u64): bool
+
+
+
+
+public fun can_be_rejected(multisig_account: address, sequence_number: u64): bool acquires MultisigAccount {
+ assert_valid_sequence_number(multisig_account, sequence_number);
+ let (_, num_rejections) = num_approvals_and_rejections(multisig_account, sequence_number);
+ sequence_number == last_resolved_sequence_number(multisig_account) + 1 &&
+ num_rejections >= num_signatures_required(multisig_account)
+}
+
+
+
+
+#[view]
+public fun can_reject(owner: address, multisig_account: address, sequence_number: u64): bool
+
+
+
+
+public fun can_reject(owner: address, multisig_account: address, sequence_number: u64): bool acquires MultisigAccount {
+ assert_valid_sequence_number(multisig_account, sequence_number);
+ let (_, num_rejections) = num_approvals_and_rejections(multisig_account, sequence_number);
+ if (!has_voted_for_rejection(multisig_account, sequence_number, owner)) {
+ num_rejections = num_rejections + 1;
+ };
+ is_owner(owner, multisig_account) &&
+ sequence_number == last_resolved_sequence_number(multisig_account) + 1 &&
+ num_rejections >= num_signatures_required(multisig_account)
+}
+
+
+
+
+#[view]
+public fun get_next_multisig_account_address(creator: address): address
+
+
+
+
+public fun get_next_multisig_account_address(creator: address): address {
+ let owner_nonce = account::get_sequence_number(creator);
+ create_resource_address(&creator, create_multisig_account_seed(to_bytes(&owner_nonce)))
+}
+
+
+
+
+#[view]
+public fun last_resolved_sequence_number(multisig_account: address): u64
+
+
+
+
+public fun last_resolved_sequence_number(multisig_account: address): u64 acquires MultisigAccount {
+ let multisig_account_resource = borrow_global_mut<MultisigAccount>(multisig_account);
+ multisig_account_resource.last_executed_sequence_number
+}
+
+
+
+
+#[view]
+public fun next_sequence_number(multisig_account: address): u64
+
+
+
+
+public fun next_sequence_number(multisig_account: address): u64 acquires MultisigAccount {
+ let multisig_account_resource = borrow_global_mut<MultisigAccount>(multisig_account);
+ multisig_account_resource.next_sequence_number
+}
+
+
+
+
+#[view]
+public fun vote(multisig_account: address, sequence_number: u64, owner: address): (bool, bool)
+
+
+
+
+public fun vote(
+ multisig_account: address, sequence_number: u64, owner: address): (bool, bool) acquires MultisigAccount {
+ let multisig_account_resource = borrow_global_mut<MultisigAccount>(multisig_account);
+ assert!(
+ sequence_number > 0 && sequence_number < multisig_account_resource.next_sequence_number,
+ error::invalid_argument(EINVALID_SEQUENCE_NUMBER),
+ );
+ let transaction = table::borrow(&multisig_account_resource.transactions, sequence_number);
+ let votes = &transaction.votes;
+ let voted = simple_map::contains_key(votes, &owner);
+ let vote = voted && *simple_map::borrow(votes, &owner);
+ (voted, vote)
+}
+
+
+
+
+#[view]
+public fun available_transaction_queue_capacity(multisig_account: address): u64
+
+
+
+
+public fun available_transaction_queue_capacity(multisig_account: address): u64 acquires MultisigAccount {
+ let multisig_account_resource = borrow_global_mut<MultisigAccount>(multisig_account);
+ let num_pending_transactions = multisig_account_resource.next_sequence_number - multisig_account_resource.last_executed_sequence_number - 1;
+ if (num_pending_transactions > MAX_PENDING_TRANSACTIONS) {
+ 0
+ } else {
+ MAX_PENDING_TRANSACTIONS - num_pending_transactions
+ }
+}
+
+
+
+
+public entry fun create_with_existing_account(multisig_address: address, owners: vector<address>, num_signatures_required: u64, account_scheme: u8, account_public_key: vector<u8>, create_multisig_account_signed_message: vector<u8>, metadata_keys: vector<string::String>, metadata_values: vector<vector<u8>>)
+
+
+
+
+public entry fun create_with_existing_account(
+ multisig_address: address,
+ owners: vector<address>,
+ num_signatures_required: u64,
+ account_scheme: u8,
+ account_public_key: vector<u8>,
+ create_multisig_account_signed_message: vector<u8>,
+ metadata_keys: vector<String>,
+ metadata_values: vector<vector<u8>>,
+) acquires MultisigAccount {
+ // Verify that the `MultisigAccountCreationMessage` has the right information and is signed by the account
+ // owner's key.
+ let proof_challenge = MultisigAccountCreationMessage {
+ chain_id: chain_id::get(),
+ account_address: multisig_address,
+ sequence_number: account::get_sequence_number(multisig_address),
+ owners,
+ num_signatures_required,
+ };
+ account::verify_signed_message(
+ multisig_address,
+ account_scheme,
+ account_public_key,
+ create_multisig_account_signed_message,
+ proof_challenge,
+ );
+
+ // We create the signer for the multisig account here since this is required to add the MultisigAccount resource
+ // This should be safe and authorized because we have verified the signed message from the existing account
+ // that authorizes creating a multisig account with the specified owners and signature threshold.
+ let multisig_account = &create_signer(multisig_address);
+ create_with_owners_internal(
+ multisig_account,
+ owners,
+ num_signatures_required,
+ option::none<SignerCapability>(),
+ metadata_keys,
+ metadata_values,
+ );
+}
+
+
+
+
+public entry fun create_with_existing_account_and_revoke_auth_key(multisig_address: address, owners: vector<address>, num_signatures_required: u64, account_scheme: u8, account_public_key: vector<u8>, create_multisig_account_signed_message: vector<u8>, metadata_keys: vector<string::String>, metadata_values: vector<vector<u8>>)
+
+
+
+
+public entry fun create_with_existing_account_and_revoke_auth_key(
+ multisig_address: address,
+ owners: vector<address>,
+ num_signatures_required: u64,
+ account_scheme: u8,
+ account_public_key: vector<u8>,
+ create_multisig_account_signed_message: vector<u8>,
+ metadata_keys: vector<String>,
+ metadata_values: vector<vector<u8>>,
+) acquires MultisigAccount {
+ // Verify that the `MultisigAccountCreationMessage` has the right information and is signed by the account
+ // owner's key.
+ let proof_challenge = MultisigAccountCreationWithAuthKeyRevocationMessage {
+ chain_id: chain_id::get(),
+ account_address: multisig_address,
+ sequence_number: account::get_sequence_number(multisig_address),
+ owners,
+ num_signatures_required,
+ };
+ account::verify_signed_message(
+ multisig_address,
+ account_scheme,
+ account_public_key,
+ create_multisig_account_signed_message,
+ proof_challenge,
+ );
+
+ // We create the signer for the multisig account here since this is required to add the MultisigAccount resource
+ // This should be safe and authorized because we have verified the signed message from the existing account
+ // that authorizes creating a multisig account with the specified owners and signature threshold.
+ let multisig_account = &create_signer(multisig_address);
+ create_with_owners_internal(
+ multisig_account,
+ owners,
+ num_signatures_required,
+ option::none<SignerCapability>(),
+ metadata_keys,
+ metadata_values,
+ );
+
+ // Rotate the account's auth key to 0x0, which effectively revokes control via auth key.
+ let multisig_address = address_of(multisig_account);
+ account::rotate_authentication_key_internal(multisig_account, ZERO_AUTH_KEY);
+ // This also needs to revoke any signer capability or rotation capability that exists for the account to
+ // completely remove all access to the account.
+ if (account::is_signer_capability_offered(multisig_address)) {
+ account::revoke_any_signer_capability(multisig_account);
+ };
+ if (account::is_rotation_capability_offered(multisig_address)) {
+ account::revoke_any_rotation_capability(multisig_account);
+ };
+}
+
+
+
+
+public entry fun create(owner: &signer, num_signatures_required: u64, metadata_keys: vector<string::String>, metadata_values: vector<vector<u8>>)
+
+
+
+
+public entry fun create(
+ owner: &signer,
+ num_signatures_required: u64,
+ metadata_keys: vector<String>,
+ metadata_values: vector<vector<u8>>,
+) acquires MultisigAccount {
+ create_with_owners(owner, vector[], num_signatures_required, metadata_keys, metadata_values);
+}
+
+
+
+
+public entry fun create_with_owners(owner: &signer, additional_owners: vector<address>, num_signatures_required: u64, metadata_keys: vector<string::String>, metadata_values: vector<vector<u8>>)
+
+
+
+
+public entry fun create_with_owners(
+ owner: &signer,
+ additional_owners: vector<address>,
+ num_signatures_required: u64,
+ metadata_keys: vector<String>,
+ metadata_values: vector<vector<u8>>,
+) acquires MultisigAccount {
+ let (multisig_account, multisig_signer_cap) = create_multisig_account(owner);
+ vector::push_back(&mut additional_owners, address_of(owner));
+ create_with_owners_internal(
+ &multisig_account,
+ additional_owners,
+ num_signatures_required,
+ option::some(multisig_signer_cap),
+ metadata_keys,
+ metadata_values,
+ );
+}
+
+
+
+
+create_with_owners
, but removes the calling account after creation.
+
+This is for creating a vanity multisig account from a bootstrapping account that should not
+be an owner after the vanity multisig address has been secured.
+
+
+public entry fun create_with_owners_then_remove_bootstrapper(bootstrapper: &signer, owners: vector<address>, num_signatures_required: u64, metadata_keys: vector<string::String>, metadata_values: vector<vector<u8>>)
+
+
+
+
+public entry fun create_with_owners_then_remove_bootstrapper(
+ bootstrapper: &signer,
+ owners: vector<address>,
+ num_signatures_required: u64,
+ metadata_keys: vector<String>,
+ metadata_values: vector<vector<u8>>,
+) acquires MultisigAccount {
+ let bootstrapper_address = address_of(bootstrapper);
+ create_with_owners(
+ bootstrapper,
+ owners,
+ num_signatures_required,
+ metadata_keys,
+ metadata_values
+ );
+ update_owner_schema(
+ get_next_multisig_account_address(bootstrapper_address),
+ vector[],
+ vector[bootstrapper_address],
+ option::none()
+ );
+}
+
+
+
+
+fun create_with_owners_internal(multisig_account: &signer, owners: vector<address>, num_signatures_required: u64, multisig_account_signer_cap: option::Option<account::SignerCapability>, metadata_keys: vector<string::String>, metadata_values: vector<vector<u8>>)
+
+
+
+
+fun create_with_owners_internal(
+ multisig_account: &signer,
+ owners: vector<address>,
+ num_signatures_required: u64,
+ multisig_account_signer_cap: Option<SignerCapability>,
+ metadata_keys: vector<String>,
+ metadata_values: vector<vector<u8>>,
+) acquires MultisigAccount {
+ assert!(features::multisig_accounts_enabled(), error::unavailable(EMULTISIG_ACCOUNTS_NOT_ENABLED_YET));
+ assert!(
+ num_signatures_required > 0 && num_signatures_required <= vector::length(&owners),
+ error::invalid_argument(EINVALID_SIGNATURES_REQUIRED),
+ );
+
+ let multisig_address = address_of(multisig_account);
+ validate_owners(&owners, multisig_address);
+ move_to(multisig_account, MultisigAccount {
+ owners,
+ num_signatures_required,
+ transactions: table::new<u64, MultisigTransaction>(),
+ metadata: simple_map::create<String, vector<u8>>(),
+ // First transaction will start at id 1 instead of 0.
+ last_executed_sequence_number: 0,
+ next_sequence_number: 1,
+ signer_cap: multisig_account_signer_cap,
+ add_owners_events: new_event_handle<AddOwnersEvent>(multisig_account),
+ remove_owners_events: new_event_handle<RemoveOwnersEvent>(multisig_account),
+ update_signature_required_events: new_event_handle<UpdateSignaturesRequiredEvent>(multisig_account),
+ create_transaction_events: new_event_handle<CreateTransactionEvent>(multisig_account),
+ vote_events: new_event_handle<VoteEvent>(multisig_account),
+ execute_rejected_transaction_events: new_event_handle<ExecuteRejectedTransactionEvent>(multisig_account),
+ execute_transaction_events: new_event_handle<TransactionExecutionSucceededEvent>(multisig_account),
+ transaction_execution_failed_events: new_event_handle<TransactionExecutionFailedEvent>(multisig_account),
+ metadata_updated_events: new_event_handle<MetadataUpdatedEvent>(multisig_account),
+ });
+
+ update_metadata_internal(multisig_account, metadata_keys, metadata_values, false);
+}
+
+
+
+
+entry fun add_owner(multisig_account: &signer, new_owner: address)
+
+
+
+
+entry fun add_owner(multisig_account: &signer, new_owner: address) acquires MultisigAccount {
+ add_owners(multisig_account, vector[new_owner]);
+}
+
+
+
+
+entry fun add_owners(multisig_account: &signer, new_owners: vector<address>)
+
+
+
+
+entry fun add_owners(
+ multisig_account: &signer, new_owners: vector<address>) acquires MultisigAccount {
+ update_owner_schema(
+ address_of(multisig_account),
+ new_owners,
+ vector[],
+ option::none()
+ );
+}
+
+
+
+
+entry fun add_owners_and_update_signatures_required(multisig_account: &signer, new_owners: vector<address>, new_num_signatures_required: u64)
+
+
+
+
+entry fun add_owners_and_update_signatures_required(
+ multisig_account: &signer,
+ new_owners: vector<address>,
+ new_num_signatures_required: u64
+) acquires MultisigAccount {
+ update_owner_schema(
+ address_of(multisig_account),
+ new_owners,
+ vector[],
+ option::some(new_num_signatures_required)
+ );
+}
+
+
+
+
+entry fun remove_owner(multisig_account: &signer, owner_to_remove: address)
+
+
+
+
+entry fun remove_owner(
+ multisig_account: &signer, owner_to_remove: address) acquires MultisigAccount {
+ remove_owners(multisig_account, vector[owner_to_remove]);
+}
+
+
+
+
+entry fun remove_owners(multisig_account: &signer, owners_to_remove: vector<address>)
+
+
+
+
+entry fun remove_owners(
+ multisig_account: &signer, owners_to_remove: vector<address>) acquires MultisigAccount {
+ update_owner_schema(
+ address_of(multisig_account),
+ vector[],
+ owners_to_remove,
+ option::none()
+ );
+}
+
+
+
+
+entry fun swap_owner(multisig_account: &signer, to_swap_in: address, to_swap_out: address)
+
+
+
+
+entry fun swap_owner(
+ multisig_account: &signer,
+ to_swap_in: address,
+ to_swap_out: address
+) acquires MultisigAccount {
+ update_owner_schema(
+ address_of(multisig_account),
+ vector[to_swap_in],
+ vector[to_swap_out],
+ option::none()
+ );
+}
+
+
+
+
+entry fun swap_owners(multisig_account: &signer, to_swap_in: vector<address>, to_swap_out: vector<address>)
+
+
+
+
+entry fun swap_owners(
+ multisig_account: &signer,
+ to_swap_in: vector<address>,
+ to_swap_out: vector<address>
+) acquires MultisigAccount {
+ update_owner_schema(
+ address_of(multisig_account),
+ to_swap_in,
+ to_swap_out,
+ option::none()
+ );
+}
+
+
+
+
+entry fun swap_owners_and_update_signatures_required(multisig_account: &signer, new_owners: vector<address>, owners_to_remove: vector<address>, new_num_signatures_required: u64)
+
+
+
+
+entry fun swap_owners_and_update_signatures_required(
+ multisig_account: &signer,
+ new_owners: vector<address>,
+ owners_to_remove: vector<address>,
+ new_num_signatures_required: u64
+) acquires MultisigAccount {
+ update_owner_schema(
+ address_of(multisig_account),
+ new_owners,
+ owners_to_remove,
+ option::some(new_num_signatures_required)
+ );
+}
+
+
+
+
+entry fun update_signatures_required(multisig_account: &signer, new_num_signatures_required: u64)
+
+
+
+
+entry fun update_signatures_required(
+ multisig_account: &signer, new_num_signatures_required: u64) acquires MultisigAccount {
+ update_owner_schema(
+ address_of(multisig_account),
+ vector[],
+ vector[],
+ option::some(new_num_signatures_required)
+ );
+}
+
+
+
+
+entry fun update_metadata(multisig_account: &signer, keys: vector<string::String>, values: vector<vector<u8>>)
+
+
+
+
+entry fun update_metadata(
+ multisig_account: &signer, keys: vector<String>, values: vector<vector<u8>>) acquires MultisigAccount {
+ update_metadata_internal(multisig_account, keys, values, true);
+}
+
+
+
+
+fun update_metadata_internal(multisig_account: &signer, keys: vector<string::String>, values: vector<vector<u8>>, emit_event: bool)
+
+
+
+
+fun update_metadata_internal(
+ multisig_account: &signer,
+ keys: vector<String>,
+ values: vector<vector<u8>>,
+ emit_event: bool,
+) acquires MultisigAccount {
+ let num_attributes = vector::length(&keys);
+ assert!(
+ num_attributes == vector::length(&values),
+ error::invalid_argument(ENUMBER_OF_METADATA_KEYS_AND_VALUES_DONT_MATCH),
+ );
+
+ let multisig_address = address_of(multisig_account);
+ assert_multisig_account_exists(multisig_address);
+ let multisig_account_resource = borrow_global_mut<MultisigAccount>(multisig_address);
+ let old_metadata = multisig_account_resource.metadata;
+ multisig_account_resource.metadata = simple_map::create<String, vector<u8>>();
+ let metadata = &mut multisig_account_resource.metadata;
+ let i = 0;
+ while (i < num_attributes) {
+ let key = *vector::borrow(&keys, i);
+ let value = *vector::borrow(&values, i);
+ assert!(
+ !simple_map::contains_key(metadata, &key),
+ error::invalid_argument(EDUPLICATE_METADATA_KEY),
+ );
+
+ simple_map::add(metadata, key, value);
+ i = i + 1;
+ };
+
+ if (emit_event) {
+ if (std::features::module_event_migration_enabled()) {
+ emit(
+ MetadataUpdated {
+ multisig_account: multisig_address,
+ old_metadata,
+ new_metadata: multisig_account_resource.metadata,
+ }
+ )
+ };
+ emit_event(
+ &mut multisig_account_resource.metadata_updated_events,
+ MetadataUpdatedEvent {
+ old_metadata,
+ new_metadata: multisig_account_resource.metadata,
+ }
+ );
+ };
+}
+
+
+
+
+public entry fun create_transaction(owner: &signer, multisig_account: address, payload: vector<u8>)
+
+
+
+
+public entry fun create_transaction(
+ owner: &signer,
+ multisig_account: address,
+ payload: vector<u8>,
+) acquires MultisigAccount {
+ assert!(vector::length(&payload) > 0, error::invalid_argument(EPAYLOAD_CANNOT_BE_EMPTY));
+
+ assert_multisig_account_exists(multisig_account);
+ assert_is_owner(owner, multisig_account);
+
+ let creator = address_of(owner);
+ let transaction = MultisigTransaction {
+ payload: option::some(payload),
+ payload_hash: option::none<vector<u8>>(),
+ votes: simple_map::create<address, bool>(),
+ creator,
+ creation_time_secs: now_seconds(),
+ };
+ add_transaction(creator, multisig_account, transaction);
+}
+
+
+
+
+public entry fun create_transaction_with_hash(owner: &signer, multisig_account: address, payload_hash: vector<u8>)
+
+
+
+
+public entry fun create_transaction_with_hash(
+ owner: &signer,
+ multisig_account: address,
+ payload_hash: vector<u8>,
+) acquires MultisigAccount {
+ // Payload hash is a sha3-256 hash, so it must be exactly 32 bytes.
+ assert!(vector::length(&payload_hash) == 32, error::invalid_argument(EINVALID_PAYLOAD_HASH));
+
+ assert_multisig_account_exists(multisig_account);
+ assert_is_owner(owner, multisig_account);
+
+ let creator = address_of(owner);
+ let transaction = MultisigTransaction {
+ payload: option::none<vector<u8>>(),
+ payload_hash: option::some(payload_hash),
+ votes: simple_map::create<address, bool>(),
+ creator,
+ creation_time_secs: now_seconds(),
+ };
+ add_transaction(creator, multisig_account, transaction);
+}
+
+
+
+
+public entry fun approve_transaction(owner: &signer, multisig_account: address, sequence_number: u64)
+
+
+
+
+public entry fun approve_transaction(
+ owner: &signer, multisig_account: address, sequence_number: u64) acquires MultisigAccount {
+ vote_transanction(owner, multisig_account, sequence_number, true);
+}
+
+
+
+
+public entry fun reject_transaction(owner: &signer, multisig_account: address, sequence_number: u64)
+
+
+
+
+public entry fun reject_transaction(
+ owner: &signer, multisig_account: address, sequence_number: u64) acquires MultisigAccount {
+ vote_transanction(owner, multisig_account, sequence_number, false);
+}
+
+
+
+
+public entry fun vote_transanction(owner: &signer, multisig_account: address, sequence_number: u64, approved: bool)
+
+
+
+
+public entry fun vote_transanction(
+ owner: &signer, multisig_account: address, sequence_number: u64, approved: bool) acquires MultisigAccount {
+ assert_multisig_account_exists(multisig_account);
+ let multisig_account_resource = borrow_global_mut<MultisigAccount>(multisig_account);
+ assert_is_owner_internal(owner, multisig_account_resource);
+
+ assert!(
+ table::contains(&multisig_account_resource.transactions, sequence_number),
+ error::not_found(ETRANSACTION_NOT_FOUND),
+ );
+ let transaction = table::borrow_mut(&mut multisig_account_resource.transactions, sequence_number);
+ let votes = &mut transaction.votes;
+ let owner_addr = address_of(owner);
+
+ if (simple_map::contains_key(votes, &owner_addr)) {
+ *simple_map::borrow_mut(votes, &owner_addr) = approved;
+ } else {
+ simple_map::add(votes, owner_addr, approved);
+ };
+
+ if (std::features::module_event_migration_enabled()) {
+ emit(
+ Vote {
+ multisig_account,
+ owner: owner_addr,
+ sequence_number,
+ approved,
+ }
+ );
+ };
+ emit_event(
+ &mut multisig_account_resource.vote_events,
+ VoteEvent {
+ owner: owner_addr,
+ sequence_number,
+ approved,
+ }
+ );
+}
+
+
+
+
+public entry fun vote_transaction(owner: &signer, multisig_account: address, sequence_number: u64, approved: bool)
+
+
+
+
+public entry fun vote_transaction(
+ owner: &signer, multisig_account: address, sequence_number: u64, approved: bool) acquires MultisigAccount {
+ assert!(features::multisig_v2_enhancement_feature_enabled(), error::invalid_state(EMULTISIG_V2_ENHANCEMENT_NOT_ENABLED));
+ vote_transanction(owner, multisig_account, sequence_number, approved);
+}
+
+
+
+
+public entry fun vote_transactions(owner: &signer, multisig_account: address, starting_sequence_number: u64, final_sequence_number: u64, approved: bool)
+
+
+
+
+public entry fun vote_transactions(
+ owner: &signer, multisig_account: address, starting_sequence_number: u64, final_sequence_number: u64, approved: bool) acquires MultisigAccount {
+ assert!(features::multisig_v2_enhancement_feature_enabled(), error::invalid_state(EMULTISIG_V2_ENHANCEMENT_NOT_ENABLED));
+ let sequence_number = starting_sequence_number;
+ while(sequence_number <= final_sequence_number) {
+ vote_transanction(owner, multisig_account, sequence_number, approved);
+ sequence_number = sequence_number + 1;
+ }
+}
+
+
+
+
+public entry fun execute_rejected_transaction(owner: &signer, multisig_account: address)
+
+
+
+
+public entry fun execute_rejected_transaction(
+ owner: &signer,
+ multisig_account: address,
+) acquires MultisigAccount {
+ assert_multisig_account_exists(multisig_account);
+ assert_is_owner(owner, multisig_account);
+
+ let sequence_number = last_resolved_sequence_number(multisig_account) + 1;
+ let owner_addr = address_of(owner);
+ if (features::multisig_v2_enhancement_feature_enabled()) {
+ // Implicitly vote for rejection if the owner has not voted for rejection yet.
+ if (!has_voted_for_rejection(multisig_account, sequence_number, owner_addr)) {
+ reject_transaction(owner, multisig_account, sequence_number);
+ }
+ };
+
+ let multisig_account_resource = borrow_global_mut<MultisigAccount>(multisig_account);
+ let (_, num_rejections) = remove_executed_transaction(multisig_account_resource);
+ assert!(
+ num_rejections >= multisig_account_resource.num_signatures_required,
+ error::invalid_state(ENOT_ENOUGH_REJECTIONS),
+ );
+
+ if (std::features::module_event_migration_enabled()) {
+ emit(
+ ExecuteRejectedTransaction {
+ multisig_account,
+ sequence_number,
+ num_rejections,
+ executor: address_of(owner),
+ }
+ );
+ };
+ emit_event(
+ &mut multisig_account_resource.execute_rejected_transaction_events,
+ ExecuteRejectedTransactionEvent {
+ sequence_number,
+ num_rejections,
+ executor: owner_addr,
+ }
+ );
+}
+
+
+
+
+public entry fun execute_rejected_transactions(owner: &signer, multisig_account: address, final_sequence_number: u64)
+
+
+
+
+public entry fun execute_rejected_transactions(
+ owner: &signer,
+ multisig_account: address,
+ final_sequence_number: u64,
+) acquires MultisigAccount {
+ assert!(features::multisig_v2_enhancement_feature_enabled(), error::invalid_state(EMULTISIG_V2_ENHANCEMENT_NOT_ENABLED));
+ assert!(last_resolved_sequence_number(multisig_account) < final_sequence_number, error::invalid_argument(EINVALID_SEQUENCE_NUMBER));
+ assert!(final_sequence_number < next_sequence_number(multisig_account), error::invalid_argument(EINVALID_SEQUENCE_NUMBER));
+ while(last_resolved_sequence_number(multisig_account) < final_sequence_number) {
+ execute_rejected_transaction(owner, multisig_account);
+ }
+}
+
+
+
+
+fun validate_multisig_transaction(owner: &signer, multisig_account: address, payload: vector<u8>)
+
+
+
+
+fun validate_multisig_transaction(
+ owner: &signer, multisig_account: address, payload: vector<u8>) acquires MultisigAccount {
+ assert_multisig_account_exists(multisig_account);
+ assert_is_owner(owner, multisig_account);
+ let sequence_number = last_resolved_sequence_number(multisig_account) + 1;
+ assert_transaction_exists(multisig_account, sequence_number);
+
+ if (features::multisig_v2_enhancement_feature_enabled()) {
+ assert!(
+ can_execute(address_of(owner), multisig_account, sequence_number),
+ error::invalid_argument(ENOT_ENOUGH_APPROVALS),
+ );
+ }
+ else {
+ assert!(
+ can_be_executed(multisig_account, sequence_number),
+ error::invalid_argument(ENOT_ENOUGH_APPROVALS),
+ );
+ };
+
+ // If the transaction payload is not stored on chain, verify that the provided payload matches the hashes stored
+ // on chain.
+ let multisig_account_resource = borrow_global<MultisigAccount>(multisig_account);
+ let transaction = table::borrow(&multisig_account_resource.transactions, sequence_number);
+ if (option::is_some(&transaction.payload_hash)) {
+ let payload_hash = option::borrow(&transaction.payload_hash);
+ assert!(
+ sha3_256(payload) == *payload_hash,
+ error::invalid_argument(EPAYLOAD_DOES_NOT_MATCH_HASH),
+ );
+ };
+
+ // If the transaction payload is stored on chain and there is a provided payload,
+ // verify that the provided payload matches the stored payload.
+ if (features::abort_if_multisig_payload_mismatch_enabled()
+ && option::is_some(&transaction.payload)
+ && !vector::is_empty(&payload)
+ ) {
+ let stored_payload = option::borrow(&transaction.payload);
+ assert!(
+ payload == *stored_payload,
+ error::invalid_argument(EPAYLOAD_DOES_NOT_MATCH),
+ );
+ }
+}
+
+
+
+
+fun successful_transaction_execution_cleanup(executor: address, multisig_account: address, transaction_payload: vector<u8>)
+
+
+
+
+fun successful_transaction_execution_cleanup(
+ executor: address,
+ multisig_account: address,
+ transaction_payload: vector<u8>,
+) acquires MultisigAccount {
+ let num_approvals = transaction_execution_cleanup_common(executor, multisig_account);
+ let multisig_account_resource = borrow_global_mut<MultisigAccount>(multisig_account);
+ if (std::features::module_event_migration_enabled()) {
+ emit(
+ TransactionExecutionSucceeded {
+ multisig_account,
+ sequence_number: multisig_account_resource.last_executed_sequence_number,
+ transaction_payload,
+ num_approvals,
+ executor,
+ }
+ );
+ };
+ emit_event(
+ &mut multisig_account_resource.execute_transaction_events,
+ TransactionExecutionSucceededEvent {
+ sequence_number: multisig_account_resource.last_executed_sequence_number,
+ transaction_payload,
+ num_approvals,
+ executor,
+ }
+ );
+}
+
+
+
+
+fun failed_transaction_execution_cleanup(executor: address, multisig_account: address, transaction_payload: vector<u8>, execution_error: multisig_account::ExecutionError)
+
+
+
+
+fun failed_transaction_execution_cleanup(
+ executor: address,
+ multisig_account: address,
+ transaction_payload: vector<u8>,
+ execution_error: ExecutionError,
+) acquires MultisigAccount {
+ let num_approvals = transaction_execution_cleanup_common(executor, multisig_account);
+ let multisig_account_resource = borrow_global_mut<MultisigAccount>(multisig_account);
+ if (std::features::module_event_migration_enabled()) {
+ emit(
+ TransactionExecutionFailed {
+ multisig_account,
+ executor,
+ sequence_number: multisig_account_resource.last_executed_sequence_number,
+ transaction_payload,
+ num_approvals,
+ execution_error,
+ }
+ );
+ };
+ emit_event(
+ &mut multisig_account_resource.transaction_execution_failed_events,
+ TransactionExecutionFailedEvent {
+ executor,
+ sequence_number: multisig_account_resource.last_executed_sequence_number,
+ transaction_payload,
+ num_approvals,
+ execution_error,
+ }
+ );
+}
+
+
+
+
+fun transaction_execution_cleanup_common(executor: address, multisig_account: address): u64
+
+
+
+
+inline fun transaction_execution_cleanup_common(executor: address, multisig_account: address): u64 acquires MultisigAccount {
+ let sequence_number = last_resolved_sequence_number(multisig_account) + 1;
+ let implicit_approval = !has_voted_for_approval(multisig_account, sequence_number, executor);
+
+ let multisig_account_resource = borrow_global_mut<MultisigAccount>(multisig_account);
+ let (num_approvals, _) = remove_executed_transaction(multisig_account_resource);
+
+ if (features::multisig_v2_enhancement_feature_enabled() && implicit_approval) {
+ if (std::features::module_event_migration_enabled()) {
+ emit(
+ Vote {
+ multisig_account,
+ owner: executor,
+ sequence_number,
+ approved: true,
+ }
+ );
+ };
+ num_approvals = num_approvals + 1;
+ emit_event(
+ &mut multisig_account_resource.vote_events,
+ VoteEvent {
+ owner: executor,
+ sequence_number,
+ approved: true,
+ }
+ );
+ };
+
+ num_approvals
+}
+
+
+
+
+fun remove_executed_transaction(multisig_account_resource: &mut multisig_account::MultisigAccount): (u64, u64)
+
+
+
+
+fun remove_executed_transaction(multisig_account_resource: &mut MultisigAccount): (u64, u64) {
+ let sequence_number = multisig_account_resource.last_executed_sequence_number + 1;
+ let transaction = table::remove(&mut multisig_account_resource.transactions, sequence_number);
+ multisig_account_resource.last_executed_sequence_number = sequence_number;
+ num_approvals_and_rejections_internal(&multisig_account_resource.owners, &transaction)
+}
+
+
+
+
+fun add_transaction(creator: address, multisig_account: address, transaction: multisig_account::MultisigTransaction)
+
+
+
+
+inline fun add_transaction(
+ creator: address,
+ multisig_account: address,
+ transaction: MultisigTransaction
+) {
+ if (features::multisig_v2_enhancement_feature_enabled()) {
+ assert!(
+ available_transaction_queue_capacity(multisig_account) > 0,
+ error::invalid_state(EMAX_PENDING_TRANSACTIONS_EXCEEDED)
+ );
+ };
+
+ let multisig_account_resource = borrow_global_mut<MultisigAccount>(multisig_account);
+
+ // The transaction creator also automatically votes for the transaction.
+ simple_map::add(&mut transaction.votes, creator, true);
+
+ let sequence_number = multisig_account_resource.next_sequence_number;
+ multisig_account_resource.next_sequence_number = sequence_number + 1;
+ table::add(&mut multisig_account_resource.transactions, sequence_number, transaction);
+ if (std::features::module_event_migration_enabled()) {
+ emit(
+ CreateTransaction { multisig_account: multisig_account, creator, sequence_number, transaction }
+ );
+ };
+ emit_event(
+ &mut multisig_account_resource.create_transaction_events,
+ CreateTransactionEvent { creator, sequence_number, transaction },
+ );
+}
+
+
+
+
+fun create_multisig_account(owner: &signer): (signer, account::SignerCapability)
+
+
+
+
+fun create_multisig_account(owner: &signer): (signer, SignerCapability) {
+ let owner_nonce = account::get_sequence_number(address_of(owner));
+ let (multisig_signer, multisig_signer_cap) =
+ account::create_resource_account(owner, create_multisig_account_seed(to_bytes(&owner_nonce)));
+ // Register the account to receive APT as this is not done by default as part of the resource account creation
+ // flow.
+ if (!coin::is_account_registered<AptosCoin>(address_of(&multisig_signer))) {
+ coin::register<AptosCoin>(&multisig_signer);
+ };
+
+ (multisig_signer, multisig_signer_cap)
+}
+
+
+
+
+fun create_multisig_account_seed(seed: vector<u8>): vector<u8>
+
+
+
+
+fun create_multisig_account_seed(seed: vector<u8>): vector<u8> {
+ // Generate a seed that will be used to create the resource account that hosts the multisig account.
+ let multisig_account_seed = vector::empty<u8>();
+ vector::append(&mut multisig_account_seed, DOMAIN_SEPARATOR);
+ vector::append(&mut multisig_account_seed, seed);
+
+ multisig_account_seed
+}
+
+
+
+
+fun validate_owners(owners: &vector<address>, multisig_account: address)
+
+
+
+
+fun validate_owners(owners: &vector<address>, multisig_account: address) {
+ let distinct_owners: vector<address> = vector[];
+ vector::for_each_ref(owners, |owner| {
+ let owner = *owner;
+ assert!(owner != multisig_account, error::invalid_argument(EOWNER_CANNOT_BE_MULTISIG_ACCOUNT_ITSELF));
+ let (found, _) = vector::index_of(&distinct_owners, &owner);
+ assert!(!found, error::invalid_argument(EDUPLICATE_OWNER));
+ vector::push_back(&mut distinct_owners, owner);
+ });
+}
+
+
+
+
+fun assert_is_owner_internal(owner: &signer, multisig_account: &multisig_account::MultisigAccount)
+
+
+
+
+inline fun assert_is_owner_internal(owner: &signer, multisig_account: &MultisigAccount) {
+ assert!(
+ vector::contains(&multisig_account.owners, &address_of(owner)),
+ error::permission_denied(ENOT_OWNER),
+ );
+}
+
+
+
+
+fun assert_is_owner(owner: &signer, multisig_account: address)
+
+
+
+
+inline fun assert_is_owner(owner: &signer, multisig_account: address) acquires MultisigAccount {
+ let multisig_account_resource = borrow_global<MultisigAccount>(multisig_account);
+ assert_is_owner_internal(owner, multisig_account_resource);
+}
+
+
+
+
+fun num_approvals_and_rejections_internal(owners: &vector<address>, transaction: &multisig_account::MultisigTransaction): (u64, u64)
+
+
+
+
+inline fun num_approvals_and_rejections_internal(owners: &vector<address>, transaction: &MultisigTransaction): (u64, u64) {
+ let num_approvals = 0;
+ let num_rejections = 0;
+
+ let votes = &transaction.votes;
+ vector::for_each_ref(owners, |owner| {
+ if (simple_map::contains_key(votes, owner)) {
+ if (*simple_map::borrow(votes, owner)) {
+ num_approvals = num_approvals + 1;
+ } else {
+ num_rejections = num_rejections + 1;
+ };
+ }
+ });
+
+ (num_approvals, num_rejections)
+}
+
+
+
+
+fun num_approvals_and_rejections(multisig_account: address, sequence_number: u64): (u64, u64)
+
+
+
+
+inline fun num_approvals_and_rejections(multisig_account: address, sequence_number: u64): (u64, u64) acquires MultisigAccount {
+ let multisig_account_resource = borrow_global<MultisigAccount>(multisig_account);
+ let transaction = table::borrow(&multisig_account_resource.transactions, sequence_number);
+ num_approvals_and_rejections_internal(&multisig_account_resource.owners, transaction)
+}
+
+
+
+
+fun has_voted_for_approval(multisig_account: address, sequence_number: u64, owner: address): bool
+
+
+
+
+inline fun has_voted_for_approval(multisig_account: address, sequence_number: u64, owner: address): bool acquires MultisigAccount {
+ let (voted, vote) = vote(multisig_account, sequence_number, owner);
+ voted && vote
+}
+
+
+
+
+fun has_voted_for_rejection(multisig_account: address, sequence_number: u64, owner: address): bool
+
+
+
+
+inline fun has_voted_for_rejection(multisig_account: address, sequence_number: u64, owner: address): bool acquires MultisigAccount {
+ let (voted, vote) = vote(multisig_account, sequence_number, owner);
+ voted && !vote
+}
+
+
+
+
+fun assert_multisig_account_exists(multisig_account: address)
+
+
+
+
+inline fun assert_multisig_account_exists(multisig_account: address) {
+ assert!(exists<MultisigAccount>(multisig_account), error::invalid_state(EACCOUNT_NOT_MULTISIG));
+}
+
+
+
+
+fun assert_valid_sequence_number(multisig_account: address, sequence_number: u64)
+
+
+
+
+inline fun assert_valid_sequence_number(multisig_account: address, sequence_number: u64) acquires MultisigAccount {
+ let multisig_account_resource = borrow_global<MultisigAccount>(multisig_account);
+ assert!(
+ sequence_number > 0 && sequence_number < multisig_account_resource.next_sequence_number,
+ error::invalid_argument(EINVALID_SEQUENCE_NUMBER),
+ );
+}
+
+
+
+
+fun assert_transaction_exists(multisig_account: address, sequence_number: u64)
+
+
+
+
+inline fun assert_transaction_exists(multisig_account: address, sequence_number: u64) acquires MultisigAccount {
+ let multisig_account_resource = borrow_global<MultisigAccount>(multisig_account);
+ assert!(
+ table::contains(&multisig_account_resource.transactions, sequence_number),
+ error::not_found(ETRANSACTION_NOT_FOUND),
+ );
+}
+
+
+
+
+fun update_owner_schema(multisig_address: address, new_owners: vector<address>, owners_to_remove: vector<address>, optional_new_num_signatures_required: option::Option<u64>)
+
+
+
+
+fun update_owner_schema(
+ multisig_address: address,
+ new_owners: vector<address>,
+ owners_to_remove: vector<address>,
+ optional_new_num_signatures_required: Option<u64>,
+) acquires MultisigAccount {
+ assert_multisig_account_exists(multisig_address);
+ let multisig_account_ref_mut =
+ borrow_global_mut<MultisigAccount>(multisig_address);
+ // Verify no overlap between new owners and owners to remove.
+ vector::for_each_ref(&new_owners, |new_owner_ref| {
+ assert!(
+ !vector::contains(&owners_to_remove, new_owner_ref),
+ error::invalid_argument(EOWNERS_TO_REMOVE_NEW_OWNERS_OVERLAP)
+ )
+ });
+ // If new owners provided, try to add them and emit an event.
+ if (vector::length(&new_owners) > 0) {
+ vector::append(&mut multisig_account_ref_mut.owners, new_owners);
+ validate_owners(
+ &multisig_account_ref_mut.owners,
+ multisig_address
+ );
+ if (std::features::module_event_migration_enabled()) {
+ emit(AddOwners { multisig_account: multisig_address, owners_added: new_owners });
+ };
+ emit_event(
+ &mut multisig_account_ref_mut.add_owners_events,
+ AddOwnersEvent { owners_added: new_owners }
+ );
+ };
+ // If owners to remove provided, try to remove them.
+ if (vector::length(&owners_to_remove) > 0) {
+ let owners_ref_mut = &mut multisig_account_ref_mut.owners;
+ let owners_removed = vector[];
+ vector::for_each_ref(&owners_to_remove, |owner_to_remove_ref| {
+ let (found, index) =
+ vector::index_of(owners_ref_mut, owner_to_remove_ref);
+ if (found) {
+ vector::push_back(
+ &mut owners_removed,
+ vector::swap_remove(owners_ref_mut, index)
+ );
+ }
+ });
+ // Only emit event if owner(s) actually removed.
+ if (vector::length(&owners_removed) > 0) {
+ if (std::features::module_event_migration_enabled()) {
+ emit(
+ RemoveOwners { multisig_account: multisig_address, owners_removed }
+ );
+ };
+ emit_event(
+ &mut multisig_account_ref_mut.remove_owners_events,
+ RemoveOwnersEvent { owners_removed }
+ );
+ }
+ };
+ // If new signature count provided, try to update count.
+ if (option::is_some(&optional_new_num_signatures_required)) {
+ let new_num_signatures_required =
+ option::extract(&mut optional_new_num_signatures_required);
+ assert!(
+ new_num_signatures_required > 0,
+ error::invalid_argument(EINVALID_SIGNATURES_REQUIRED)
+ );
+ let old_num_signatures_required =
+ multisig_account_ref_mut.num_signatures_required;
+ // Only apply update and emit event if a change indicated.
+ if (new_num_signatures_required != old_num_signatures_required) {
+ multisig_account_ref_mut.num_signatures_required =
+ new_num_signatures_required;
+ if (std::features::module_event_migration_enabled()) {
+ emit(
+ UpdateSignaturesRequired {
+ multisig_account: multisig_address,
+ old_num_signatures_required,
+ new_num_signatures_required,
+ }
+ );
+ };
+ emit_event(
+ &mut multisig_account_ref_mut.update_signature_required_events,
+ UpdateSignaturesRequiredEvent {
+ old_num_signatures_required,
+ new_num_signatures_required,
+ }
+ );
+ }
+ };
+ // Verify number of owners.
+ let num_owners = vector::length(&multisig_account_ref_mut.owners);
+ assert!(
+ num_owners >= multisig_account_ref_mut.num_signatures_required,
+ error::invalid_state(ENOT_ENOUGH_OWNERS)
+ );
+}
+
+
+
+
+No. | Requirement | Criticality | Implementation | Enforcement | +
---|---|---|---|---|
1 | +For every multi-signature account, the range of required signatures should always be in the range of one to the total number of owners. | +Critical | +While creating a MultisigAccount, the function create_with_owners_internal checks that num_signatures_required is in the span from 1 to total count of owners. | +This has been audited. | +
2 | +The list of owners for a multi-signature account should not contain any duplicate owners, and the multi-signature account itself cannot be listed as one of its owners. | +Critical | +The function validate_owners validates the owner vector that no duplicate entries exists. | +This has been audited. | +
3 | +The current value of the next sequence number should not be present in the transaction table, until the next sequence number gets increased. | +Medium | +The add_transaction function increases the next sequence number and only then adds the transaction with the old next sequence number to the transaction table. | +This has been audited. | +
4 | +When the last executed sequence number is smaller than the next sequence number by only one unit, no transactions should exist in the multi-signature account's transactions list. | +High | +The get_pending_transactions function retrieves pending transactions by iterating through the transactions table, starting from the last_executed_sequence_number + 1 to the next_sequence_number. | +Audited that MultisigAccount.transactions is empty when last_executed_sequence_number == next_sequence_number -1 | +
5 | +The last executed sequence number is always smaller than the next sequence number. | +Medium | +When creating a new MultisigAccount, the last_executed_sequence_number and next_sequence_number are assigned with 0 and 1 respectively, and from there both these values increase monotonically when a transaction is executed and removed from the table and when new transaction are added respectively. | +This has been audited. | +
6 | +The number of pending transactions should be equal to the difference between the next sequence number and the last executed sequence number. | +High | +When a transaction is added, next_sequence_number is incremented. And when a transaction is removed after execution, last_executed_sequence_number is incremented. | +This has been audited. | +
7 | +Only transactions with valid sequence number should be fetched. | +Medium | +Functions such as: 1. get_transaction 2. can_be_executed 3. can_be_rejected 4. vote always validate the given sequence number and only then fetch the associated transaction. | +Audited that it aborts if the sequence number is not valid. | +
8 | +The execution or rejection of a transaction should enforce that the minimum number of required signatures is less or equal to the total number of approvals. | +Critical | +The functions can_be_executed and can_be_rejected perform validation on the number of votes required for execution or rejection. | +Audited that these functions return the correct value. | +
9 | +The creation of a multi-signature account properly initializes the resources and then it gets published under the corresponding account. | +Medium | +When creating a MultisigAccount via one of the functions: create_with_existing_account, create_with_existing_account_and_revoke_auth_key, create_with_owners, create, the MultisigAccount data is initialized properly and published to the multisig_account (new or existing). | +Audited that the MultisigAccount is initialized properly. | +
10 | +Creation of a multi-signature account on top of an existing account should revoke auth key and any previous offered capabilities or control. | +Critical | +The function create_with_existing_account_and_revoke_auth_key, after successfully creating the MultisigAccount, rotates the account to ZeroAuthKey and revokes any offered capabilities of that account. | +Audited that the account's auth key and the offered capabilities are revoked. | +
11 | +Upon the creation of a multi-signature account from a bootstrapping account, the ownership of the resultant account should not pertain to the bootstrapping account. | +High | +In create_with_owners_then_remove_bootstrapper function after successful creation of the account the bootstrapping account is removed from the owner vector of the account. | +Audited that the bootstrapping account is not in the owners list. | +
12 | +Performing any changes on the list of owners such as adding new owners, removing owners, swapping owners should ensure that the number of required signature, for the multi-signature account remains valid. | +Critical | +The following function as used to modify the owners list and the required signature of the account: add_owner, add_owners, add_owners_and_update_signatures_required, remove_owner, remove_owners, swap_owner, swap_owners, swap_owners_and_update_signatures_required, update_signatures_required. All of these functions use update_owner_schema function to process these changes, the function validates the owner list while adding and verifies that the account has enough required signatures and updates the owner's schema. | +Audited that the owners are added successfully. (add_owner, add_owners, add_owners_and_update_signatures_required, swap_owner, swap_owners, swap_owners_and_update_signatures_required, update_owner_schema) Audited that the owners are removed successfully. (remove_owner, remove_owners, swap_owner, swap_owners, swap_owners_and_update_signatures_required, update_owner_schema) Audited that the num_signatures_required is updated successfully. (add_owners_and_update_signatures_required, swap_owners_and_update_signatures_required, update_signatures_required, update_owner_schema) | +
13 | +The creation of a transaction should be limited to an account owner, which should be automatically considered a voter; additionally, the account's sequence should increase monotonically. | +Critical | +The following functions can only be called by the owners of the account and create a transaction and uses add_transaction function to gives approval on behalf of the creator and increments the next_sequence_number and finally adds the transaction to the MultsigAccount: create_transaction_with_hash, create_transaction. | +Audited it aborts if the caller is not in the owner's list of the account. (create_transaction_with_hash, create_transaction) Audited that the transaction is successfully stored in the MultisigAccount.(create_transaction_with_hash, create_transaction, add_transaction) Audited that the creators voted to approve the transaction. (create_transaction_with_hash, create_transaction, add_transaction) Audited that the next_sequence_number increases monotonically. (create_transaction_with_hash, create_transaction, add_transaction) | +
14 | +Only owners are allowed to vote for a valid transaction. | +Critical | +Any owner of the MultisigAccount can either approve (approve_transaction) or reject (reject_transaction) a transaction. Both these functions use a generic function to vote for the transaction which validates the caller and the transaction id and adds/updates the vote. | +Audited that it aborts if the caller is not in the owner's list (approve_transaction, reject_transaction, vote_transaction, assert_is_owner). Audited that it aborts if the transaction with the given sequence number doesn't exist in the account (approve_transaction, reject_transaction, vote_transaction). Audited that the vote is recorded as intended. | +
15 | +Only owners are allowed to execute a valid transaction, if the number of approvals meets the k-of-n criteria, finally the executed transaction should be removed. | +Critical | +Functions execute_rejected_transaction and validate_multisig_transaction can only be called by the owner which validates the transaction and based on the number of approvals and rejections it proceeds to execute the transactions. For rejected transaction, the transactions are immediately removed from the MultisigAccount via remove_executed_transaction. VM validates the transaction via validate_multisig_transaction and cleans up the transaction via successful_transaction_execution_cleanup and failed_transaction_execution_cleanup. | +Audited that it aborts if the caller is not in the owner's list (execute_rejected_transaction, validate_multisig_transaction). Audited that it aborts if the transaction with the given sequence number doesn't exist in the account (execute_rejected_transaction, validate_multisig_transaction). Audited that it aborts if the votes (approvals or rejections) are less than num_signatures_required (execute_rejected_transaction, validate_multisig_transaction). Audited that the transaction is removed from the MultisigAccount (execute_rejected_transaction, remove_executed_transaction, successful_transaction_execution_cleanup, failed_transaction_execution_cleanup). | +
16 | +Removing an executed transaction from the transactions list should increase the last sequence number monotonically. | +High | +When transactions are removed via remove_executed_transaction (maybe called by VM cleanup or execute_rejected_transaction), the last_executed_sequence_number increases by 1. | +Audited that last_executed_sequence_number is incremented. | +
17 | +The voting and transaction creation operations should only be available if a multi-signature account exists. | +Low | +The function assert_multisig_account_exists validates the existence of MultisigAccount under the account. | +Audited that it aborts if the MultisigAccount doesn't exist on the account. | +
#[view]
+public fun metadata(multisig_account: address): simple_map::SimpleMap<string::String, vector<u8>>
+
+
+
+
+
+aborts_if !exists<MultisigAccount>(multisig_account);
+ensures result == global<MultisigAccount>(multisig_account).metadata;
+
+
+
+
+
+
+### Function `num_signatures_required`
+
+
+#[view]
+public fun num_signatures_required(multisig_account: address): u64
+
+
+
+
+
+aborts_if !exists<MultisigAccount>(multisig_account);
+ensures result == global<MultisigAccount>(multisig_account).num_signatures_required;
+
+
+
+
+
+
+### Function `owners`
+
+
+#[view]
+public fun owners(multisig_account: address): vector<address>
+
+
+
+
+
+aborts_if !exists<MultisigAccount>(multisig_account);
+ensures result == global<MultisigAccount>(multisig_account).owners;
+
+
+
+
+
+
+### Function `get_transaction`
+
+
+#[view]
+public fun get_transaction(multisig_account: address, sequence_number: u64): multisig_account::MultisigTransaction
+
+
+
+
+
+let multisig_account_resource = global<MultisigAccount>(multisig_account);
+aborts_if !exists<MultisigAccount>(multisig_account);
+aborts_if sequence_number == 0 || sequence_number >= multisig_account_resource.next_sequence_number;
+aborts_if !table::spec_contains(multisig_account_resource.transactions, sequence_number);
+ensures result == table::spec_get(multisig_account_resource.transactions, sequence_number);
+
+
+
+
+
+
+### Function `get_next_transaction_payload`
+
+
+#[view]
+public fun get_next_transaction_payload(multisig_account: address, provided_payload: vector<u8>): vector<u8>
+
+
+
+
+
+let multisig_account_resource = global<MultisigAccount>(multisig_account);
+let sequence_number = multisig_account_resource.last_executed_sequence_number + 1;
+let transaction = table::spec_get(multisig_account_resource.transactions, sequence_number);
+aborts_if !exists<MultisigAccount>(multisig_account);
+aborts_if multisig_account_resource.last_executed_sequence_number + 1 > MAX_U64;
+aborts_if !table::spec_contains(multisig_account_resource.transactions, sequence_number);
+ensures option::spec_is_none(transaction.payload) ==> result == provided_payload;
+
+
+
+
+
+
+### Function `get_next_multisig_account_address`
+
+
+#[view]
+public fun get_next_multisig_account_address(creator: address): address
+
+
+
+
+
+aborts_if !exists<account::Account>(creator);
+let owner_nonce = global<account::Account>(creator).sequence_number;
+
+
+
+
+
+
+### Function `last_resolved_sequence_number`
+
+
+#[view]
+public fun last_resolved_sequence_number(multisig_account: address): u64
+
+
+
+
+
+let multisig_account_resource = global<MultisigAccount>(multisig_account);
+aborts_if !exists<MultisigAccount>(multisig_account);
+ensures result == multisig_account_resource.last_executed_sequence_number;
+
+
+
+
+
+
+### Function `next_sequence_number`
+
+
+#[view]
+public fun next_sequence_number(multisig_account: address): u64
+
+
+
+
+
+let multisig_account_resource = global<MultisigAccount>(multisig_account);
+aborts_if !exists<MultisigAccount>(multisig_account);
+ensures result == multisig_account_resource.next_sequence_number;
+
+
+
+
+
+
+### Function `vote`
+
+
+#[view]
+public fun vote(multisig_account: address, sequence_number: u64, owner: address): (bool, bool)
+
+
+
+
+
+let multisig_account_resource = global<MultisigAccount>(multisig_account);
+aborts_if !exists<MultisigAccount>(multisig_account);
+aborts_if sequence_number == 0 || sequence_number >= multisig_account_resource.next_sequence_number;
+aborts_if !table::spec_contains(multisig_account_resource.transactions, sequence_number);
+let transaction = table::spec_get(multisig_account_resource.transactions, sequence_number);
+let votes = transaction.votes;
+let voted = simple_map::spec_contains_key(votes, owner);
+let vote = voted && simple_map::spec_get(votes, owner);
+ensures result_1 == voted;
+ensures result_2 == vote;
+
+
+
+[move-book]: https://aptos.dev/move/book/SUMMARY
diff --git a/aptos-move/framework/aptos-framework/tests/compiler-v2-doc/object.md b/aptos-move/framework/aptos-framework/tests/compiler-v2-doc/object.md
new file mode 100644
index 0000000000000..7a5f1c49ba86e
--- /dev/null
+++ b/aptos-move/framework/aptos-framework/tests/compiler-v2-doc/object.md
@@ -0,0 +1,3398 @@
+
+
+
+# Module `0x1::object`
+
+This defines the Move object model with the following properties:
+- Simplified storage interface that supports a heterogeneous collection of resources to be
+stored together. This enables data types to share a common core data layer (e.g., tokens),
+while having richer extensions (e.g., concert ticket, sword).
+- Globally accessible data and ownership model that enables creators and developers to dictate
+the application and lifetime of data.
+- Extensible programming model that supports individualization of user applications that
+leverage the core framework including tokens.
+- Support emitting events directly, thus improving discoverability of events associated with
+objects.
+- Considerate of the underlying system by leveraging resource groups for gas efficiency,
+avoiding costly deserialization and serialization costs, and supporting deletability.
+
+TODO:
+* There is no means to borrow an object or a reference to an object. We are exploring how to
+make it so that a reference to a global object can be returned from a function.
+
+
+- [Resource `ObjectCore`](#0x1_object_ObjectCore)
+- [Resource `TombStone`](#0x1_object_TombStone)
+- [Resource `Untransferable`](#0x1_object_Untransferable)
+- [Struct `ObjectGroup`](#0x1_object_ObjectGroup)
+- [Struct `Object`](#0x1_object_Object)
+- [Struct `ConstructorRef`](#0x1_object_ConstructorRef)
+- [Struct `DeleteRef`](#0x1_object_DeleteRef)
+- [Struct `ExtendRef`](#0x1_object_ExtendRef)
+- [Struct `TransferRef`](#0x1_object_TransferRef)
+- [Struct `LinearTransferRef`](#0x1_object_LinearTransferRef)
+- [Struct `DeriveRef`](#0x1_object_DeriveRef)
+- [Struct `TransferEvent`](#0x1_object_TransferEvent)
+- [Struct `Transfer`](#0x1_object_Transfer)
+- [Constants](#@Constants_0)
+- [Function `is_untransferable`](#0x1_object_is_untransferable)
+- [Function `is_burnt`](#0x1_object_is_burnt)
+- [Function `address_to_object`](#0x1_object_address_to_object)
+- [Function `is_object`](#0x1_object_is_object)
+- [Function `object_exists`](#0x1_object_object_exists)
+- [Function `create_object_address`](#0x1_object_create_object_address)
+- [Function `create_user_derived_object_address_impl`](#0x1_object_create_user_derived_object_address_impl)
+- [Function `create_user_derived_object_address`](#0x1_object_create_user_derived_object_address)
+- [Function `create_guid_object_address`](#0x1_object_create_guid_object_address)
+- [Function `exists_at`](#0x1_object_exists_at)
+- [Function `object_address`](#0x1_object_object_address)
+- [Function `convert`](#0x1_object_convert)
+- [Function `create_named_object`](#0x1_object_create_named_object)
+- [Function `create_user_derived_object`](#0x1_object_create_user_derived_object)
+- [Function `create_object`](#0x1_object_create_object)
+- [Function `create_sticky_object`](#0x1_object_create_sticky_object)
+- [Function `create_sticky_object_at_address`](#0x1_object_create_sticky_object_at_address)
+- [Function `create_object_from_account`](#0x1_object_create_object_from_account)
+- [Function `create_object_from_object`](#0x1_object_create_object_from_object)
+- [Function `create_object_from_guid`](#0x1_object_create_object_from_guid)
+- [Function `create_object_internal`](#0x1_object_create_object_internal)
+- [Function `generate_delete_ref`](#0x1_object_generate_delete_ref)
+- [Function `generate_extend_ref`](#0x1_object_generate_extend_ref)
+- [Function `generate_transfer_ref`](#0x1_object_generate_transfer_ref)
+- [Function `generate_derive_ref`](#0x1_object_generate_derive_ref)
+- [Function `generate_signer`](#0x1_object_generate_signer)
+- [Function `address_from_constructor_ref`](#0x1_object_address_from_constructor_ref)
+- [Function `object_from_constructor_ref`](#0x1_object_object_from_constructor_ref)
+- [Function `can_generate_delete_ref`](#0x1_object_can_generate_delete_ref)
+- [Function `create_guid`](#0x1_object_create_guid)
+- [Function `new_event_handle`](#0x1_object_new_event_handle)
+- [Function `address_from_delete_ref`](#0x1_object_address_from_delete_ref)
+- [Function `object_from_delete_ref`](#0x1_object_object_from_delete_ref)
+- [Function `delete`](#0x1_object_delete)
+- [Function `generate_signer_for_extending`](#0x1_object_generate_signer_for_extending)
+- [Function `address_from_extend_ref`](#0x1_object_address_from_extend_ref)
+- [Function `disable_ungated_transfer`](#0x1_object_disable_ungated_transfer)
+- [Function `set_untransferable`](#0x1_object_set_untransferable)
+- [Function `enable_ungated_transfer`](#0x1_object_enable_ungated_transfer)
+- [Function `generate_linear_transfer_ref`](#0x1_object_generate_linear_transfer_ref)
+- [Function `transfer_with_ref`](#0x1_object_transfer_with_ref)
+- [Function `transfer_call`](#0x1_object_transfer_call)
+- [Function `transfer`](#0x1_object_transfer)
+- [Function `transfer_raw`](#0x1_object_transfer_raw)
+- [Function `transfer_raw_inner`](#0x1_object_transfer_raw_inner)
+- [Function `transfer_to_object`](#0x1_object_transfer_to_object)
+- [Function `verify_ungated_and_descendant`](#0x1_object_verify_ungated_and_descendant)
+- [Function `burn`](#0x1_object_burn)
+- [Function `unburn`](#0x1_object_unburn)
+- [Function `ungated_transfer_allowed`](#0x1_object_ungated_transfer_allowed)
+- [Function `owner`](#0x1_object_owner)
+- [Function `is_owner`](#0x1_object_is_owner)
+- [Function `owns`](#0x1_object_owns)
+- [Function `root_owner`](#0x1_object_root_owner)
+- [Specification](#@Specification_1)
+ - [High-level Requirements](#high-level-req)
+ - [Module-level Specification](#module-level-spec)
+ - [Function `address_to_object`](#@Specification_1_address_to_object)
+ - [Function `create_object_address`](#@Specification_1_create_object_address)
+ - [Function `create_user_derived_object_address_impl`](#@Specification_1_create_user_derived_object_address_impl)
+ - [Function `create_user_derived_object_address`](#@Specification_1_create_user_derived_object_address)
+ - [Function `create_guid_object_address`](#@Specification_1_create_guid_object_address)
+ - [Function `exists_at`](#@Specification_1_exists_at)
+ - [Function `object_address`](#@Specification_1_object_address)
+ - [Function `convert`](#@Specification_1_convert)
+ - [Function `create_named_object`](#@Specification_1_create_named_object)
+ - [Function `create_user_derived_object`](#@Specification_1_create_user_derived_object)
+ - [Function `create_object`](#@Specification_1_create_object)
+ - [Function `create_sticky_object`](#@Specification_1_create_sticky_object)
+ - [Function `create_sticky_object_at_address`](#@Specification_1_create_sticky_object_at_address)
+ - [Function `create_object_from_account`](#@Specification_1_create_object_from_account)
+ - [Function `create_object_from_object`](#@Specification_1_create_object_from_object)
+ - [Function `create_object_from_guid`](#@Specification_1_create_object_from_guid)
+ - [Function `create_object_internal`](#@Specification_1_create_object_internal)
+ - [Function `generate_delete_ref`](#@Specification_1_generate_delete_ref)
+ - [Function `generate_transfer_ref`](#@Specification_1_generate_transfer_ref)
+ - [Function `object_from_constructor_ref`](#@Specification_1_object_from_constructor_ref)
+ - [Function `create_guid`](#@Specification_1_create_guid)
+ - [Function `new_event_handle`](#@Specification_1_new_event_handle)
+ - [Function `object_from_delete_ref`](#@Specification_1_object_from_delete_ref)
+ - [Function `delete`](#@Specification_1_delete)
+ - [Function `disable_ungated_transfer`](#@Specification_1_disable_ungated_transfer)
+ - [Function `set_untransferable`](#@Specification_1_set_untransferable)
+ - [Function `enable_ungated_transfer`](#@Specification_1_enable_ungated_transfer)
+ - [Function `generate_linear_transfer_ref`](#@Specification_1_generate_linear_transfer_ref)
+ - [Function `transfer_with_ref`](#@Specification_1_transfer_with_ref)
+ - [Function `transfer_call`](#@Specification_1_transfer_call)
+ - [Function `transfer`](#@Specification_1_transfer)
+ - [Function `transfer_raw`](#@Specification_1_transfer_raw)
+ - [Function `transfer_to_object`](#@Specification_1_transfer_to_object)
+ - [Function `verify_ungated_and_descendant`](#@Specification_1_verify_ungated_and_descendant)
+ - [Function `burn`](#@Specification_1_burn)
+ - [Function `unburn`](#@Specification_1_unburn)
+ - [Function `ungated_transfer_allowed`](#@Specification_1_ungated_transfer_allowed)
+ - [Function `owner`](#@Specification_1_owner)
+ - [Function `is_owner`](#@Specification_1_is_owner)
+ - [Function `owns`](#@Specification_1_owns)
+ - [Function `root_owner`](#@Specification_1_root_owner)
+
+
+use 0x1::account;
+use 0x1::bcs;
+use 0x1::create_signer;
+use 0x1::error;
+use 0x1::event;
+use 0x1::features;
+use 0x1::from_bcs;
+use 0x1::guid;
+use 0x1::hash;
+use 0x1::signer;
+use 0x1::transaction_context;
+use 0x1::vector;
+
+
+
+
+
+
+## Resource `ObjectCore`
+
+The core of the object model that defines ownership, transferability, and events.
+
+
+#[resource_group_member(#[group = 0x1::object::ObjectGroup])]
+struct ObjectCore has key
+
+
+
+
+guid_creation_num: u64
+owner: address
+allow_ungated_transfer: bool
+transfer_events: event::EventHandle<object::TransferEvent>
+#[resource_group_member(#[group = 0x1::object::ObjectGroup])]
+struct TombStone has key
+
+
+
+
+original_owner: address
+TransferRef
s irrelevant. The object cannot be moved.
+
+
+#[resource_group_member(#[group = 0x1::object::ObjectGroup])]
+struct Untransferable has key
+
+
+
+
+dummy_field: bool
+#[resource_group(#[scope = global])]
+struct ObjectGroup
+
+
+
+
+dummy_field: bool
+struct Object<T> has copy, drop, store
+
+
+
+
+inner: address
+struct ConstructorRef has drop
+
+
+
+
+self: address
+can_delete: bool
+struct DeleteRef has drop, store
+
+
+
+
+self: address
+struct ExtendRef has drop, store
+
+
+
+
+self: address
+struct TransferRef has drop, store
+
+
+
+
+self: address
+struct LinearTransferRef has drop
+
+
+
+
+self: address
+owner: address
+struct DeriveRef has drop, store
+
+
+
+
+self: address
+struct TransferEvent has drop, store
+
+
+
+
+object: address
+from: address
+to: address
+#[event]
+struct Transfer has drop, store
+
+
+
+
+object: address
+from: address
+to: address
+const BURN_ADDRESS: address = 0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff;
+
+
+
+
+
+
+generate_unique_address uses this for domain separation within its native implementation
+
+
+const DERIVE_AUID_ADDRESS_SCHEME: u8 = 251;
+
+
+
+
+
+
+The object does not allow for deletion
+
+
+const ECANNOT_DELETE: u64 = 5;
+
+
+
+
+
+
+Exceeds maximum nesting for an object transfer.
+
+
+const EMAXIMUM_NESTING: u64 = 6;
+
+
+
+
+
+
+The caller does not have ownership permissions
+
+
+const ENOT_OBJECT_OWNER: u64 = 4;
+
+
+
+
+
+
+The object does not have ungated transfers enabled
+
+
+const ENO_UNGATED_TRANSFERS: u64 = 3;
+
+
+
+
+
+
+An object does not exist at this address
+
+
+const EOBJECT_DOES_NOT_EXIST: u64 = 2;
+
+
+
+
+
+
+An object already exists at this address
+
+
+const EOBJECT_EXISTS: u64 = 1;
+
+
+
+
+
+
+Cannot reclaim objects that weren't burnt.
+
+
+const EOBJECT_NOT_BURNT: u64 = 8;
+
+
+
+
+
+
+Object is untransferable any operations that might result in a transfer are disallowed.
+
+
+const EOBJECT_NOT_TRANSFERRABLE: u64 = 9;
+
+
+
+
+
+
+The resource is not stored at the specified address.
+
+
+const ERESOURCE_DOES_NOT_EXIST: u64 = 7;
+
+
+
+
+
+
+Explicitly separate the GUID space between Object and Account to prevent accidental overlap.
+
+
+const INIT_GUID_CREATION_NUM: u64 = 1125899906842624;
+
+
+
+
+
+
+Maximum nesting from one object to another. That is objects can technically have infinte
+nesting, but any checks such as transfer will only be evaluated this deep.
+
+
+const MAXIMUM_OBJECT_NESTING: u8 = 8;
+
+
+
+
+
+
+Scheme identifier used to generate an object's address obj_addr
as derived from another object.
+The object's address is generated as:
+```
+obj_addr = sha3_256(account addr | derived from object's address | 0xFC)
+```
+
+This 0xFC constant serves as a domain separation tag to prevent existing authentication key and resource account
+derivation to produce an object address.
+
+
+const OBJECT_DERIVED_SCHEME: u8 = 252;
+
+
+
+
+
+
+Scheme identifier used to generate an object's address obj_addr
via a fresh GUID generated by the creator at
+source_addr
. The object's address is generated as:
+```
+obj_addr = sha3_256(guid | 0xFD)
+```
+where guid = account::create_guid(create_signer(source_addr))
+
+This 0xFD constant serves as a domain separation tag to prevent existing authentication key and resource account
+derivation to produce an object address.
+
+
+const OBJECT_FROM_GUID_ADDRESS_SCHEME: u8 = 253;
+
+
+
+
+
+
+Scheme identifier used to generate an object's address obj_addr
from the creator's source_addr
and a seed
as:
+obj_addr = sha3_256(source_addr | seed | 0xFE).
+
+This 0xFE constant serves as a domain separation tag to prevent existing authentication key and resource account
+derivation to produce an object address.
+
+
+const OBJECT_FROM_SEED_ADDRESS_SCHEME: u8 = 254;
+
+
+
+
+
+
+## Function `is_untransferable`
+
+
+
+#[view]
+public fun is_untransferable<T: key>(object: object::Object<T>): bool
+
+
+
+
+public fun is_untransferable<T: key>(object: Object<T>): bool {
+ exists<Untransferable>(object.inner)
+}
+
+
+
+
+#[view]
+public fun is_burnt<T: key>(object: object::Object<T>): bool
+
+
+
+
+public fun is_burnt<T: key>(object: Object<T>): bool {
+ exists<TombStone>(object.inner)
+}
+
+
+
+
+public fun address_to_object<T: key>(object: address): object::Object<T>
+
+
+
+
+public fun address_to_object<T: key>(object: address): Object<T> {
+ assert!(exists<ObjectCore>(object), error::not_found(EOBJECT_DOES_NOT_EXIST));
+ assert!(exists_at<T>(object), error::not_found(ERESOURCE_DOES_NOT_EXIST));
+ Object<T> { inner: object }
+}
+
+
+
+
+public fun is_object(object: address): bool
+
+
+
+
+public fun is_object(object: address): bool {
+ exists<ObjectCore>(object)
+}
+
+
+
+
+public fun object_exists<T: key>(object: address): bool
+
+
+
+
+public fun object_exists<T: key>(object: address): bool {
+ exists<ObjectCore>(object) && exists_at<T>(object)
+}
+
+
+
+
+public fun create_object_address(source: &address, seed: vector<u8>): address
+
+
+
+
+public fun create_object_address(source: &address, seed: vector<u8>): address {
+ let bytes = bcs::to_bytes(source);
+ vector::append(&mut bytes, seed);
+ vector::push_back(&mut bytes, OBJECT_FROM_SEED_ADDRESS_SCHEME);
+ from_bcs::to_address(hash::sha3_256(bytes))
+}
+
+
+
+
+fun create_user_derived_object_address_impl(source: address, derive_from: address): address
+
+
+
+
+native fun create_user_derived_object_address_impl(source: address, derive_from: address): address;
+
+
+
+
+public fun create_user_derived_object_address(source: address, derive_from: address): address
+
+
+
+
+public fun create_user_derived_object_address(source: address, derive_from: address): address {
+ if (std::features::object_native_derived_address_enabled()) {
+ create_user_derived_object_address_impl(source, derive_from)
+ } else {
+ let bytes = bcs::to_bytes(&source);
+ vector::append(&mut bytes, bcs::to_bytes(&derive_from));
+ vector::push_back(&mut bytes, OBJECT_DERIVED_SCHEME);
+ from_bcs::to_address(hash::sha3_256(bytes))
+ }
+}
+
+
+
+
+public fun create_guid_object_address(source: address, creation_num: u64): address
+
+
+
+
+public fun create_guid_object_address(source: address, creation_num: u64): address {
+ let id = guid::create_id(source, creation_num);
+ let bytes = bcs::to_bytes(&id);
+ vector::push_back(&mut bytes, OBJECT_FROM_GUID_ADDRESS_SCHEME);
+ from_bcs::to_address(hash::sha3_256(bytes))
+}
+
+
+
+
+fun exists_at<T: key>(object: address): bool
+
+
+
+
+native fun exists_at<T: key>(object: address): bool;
+
+
+
+
+public fun object_address<T: key>(object: &object::Object<T>): address
+
+
+
+
+public fun object_address<T: key>(object: &Object<T>): address {
+ object.inner
+}
+
+
+
+
+public fun convert<X: key, Y: key>(object: object::Object<X>): object::Object<Y>
+
+
+
+
+public fun convert<X: key, Y: key>(object: Object<X>): Object<Y> {
+ address_to_object<Y>(object.inner)
+}
+
+
+
+
+public fun create_named_object(creator: &signer, seed: vector<u8>): object::ConstructorRef
+
+
+
+
+public fun create_named_object(creator: &signer, seed: vector<u8>): ConstructorRef {
+ let creator_address = signer::address_of(creator);
+ let obj_addr = create_object_address(&creator_address, seed);
+ create_object_internal(creator_address, obj_addr, false)
+}
+
+
+
+
+public(friend) fun create_user_derived_object(creator_address: address, derive_ref: &object::DeriveRef): object::ConstructorRef
+
+
+
+
+public(friend) fun create_user_derived_object(creator_address: address, derive_ref: &DeriveRef): ConstructorRef {
+ let obj_addr = create_user_derived_object_address(creator_address, derive_ref.self);
+ create_object_internal(creator_address, obj_addr, false)
+}
+
+
+
+
+public fun create_object(owner_address: address): object::ConstructorRef
+
+
+
+
+public fun create_object(owner_address: address): ConstructorRef {
+ let unique_address = transaction_context::generate_auid_address();
+ create_object_internal(owner_address, unique_address, true)
+}
+
+
+
+
+create_object
except the object to be created will be undeletable.
+
+
+public fun create_sticky_object(owner_address: address): object::ConstructorRef
+
+
+
+
+public fun create_sticky_object(owner_address: address): ConstructorRef {
+ let unique_address = transaction_context::generate_auid_address();
+ create_object_internal(owner_address, unique_address, false)
+}
+
+
+
+
+public(friend) fun create_sticky_object_at_address(owner_address: address, object_address: address): object::ConstructorRef
+
+
+
+
+public(friend) fun create_sticky_object_at_address(
+ owner_address: address,
+ object_address: address,
+): ConstructorRef {
+ create_object_internal(owner_address, object_address, false)
+}
+
+
+
+
+create_object
instead.
+Create a new object from a GUID generated by an account.
+As the GUID creation internally increments a counter, two transactions that executes
+create_object_from_account
function for the same creator run sequentially.
+Therefore, using create_object
method for creating objects is preferrable as it
+doesn't have the same bottlenecks.
+
+
+#[deprecated]
+public fun create_object_from_account(creator: &signer): object::ConstructorRef
+
+
+
+
+public fun create_object_from_account(creator: &signer): ConstructorRef {
+ let guid = account::create_guid(creator);
+ create_object_from_guid(signer::address_of(creator), guid)
+}
+
+
+
+
+create_object
instead.
+Create a new object from a GUID generated by an object.
+As the GUID creation internally increments a counter, two transactions that executes
+create_object_from_object
function for the same creator run sequentially.
+Therefore, using create_object
method for creating objects is preferrable as it
+doesn't have the same bottlenecks.
+
+
+#[deprecated]
+public fun create_object_from_object(creator: &signer): object::ConstructorRef
+
+
+
+
+public fun create_object_from_object(creator: &signer): ConstructorRef acquires ObjectCore {
+ let guid = create_guid(creator);
+ create_object_from_guid(signer::address_of(creator), guid)
+}
+
+
+
+
+fun create_object_from_guid(creator_address: address, guid: guid::GUID): object::ConstructorRef
+
+
+
+
+fun create_object_from_guid(creator_address: address, guid: guid::GUID): ConstructorRef {
+ let bytes = bcs::to_bytes(&guid);
+ vector::push_back(&mut bytes, OBJECT_FROM_GUID_ADDRESS_SCHEME);
+ let obj_addr = from_bcs::to_address(hash::sha3_256(bytes));
+ create_object_internal(creator_address, obj_addr, true)
+}
+
+
+
+
+fun create_object_internal(creator_address: address, object: address, can_delete: bool): object::ConstructorRef
+
+
+
+
+fun create_object_internal(
+ creator_address: address,
+ object: address,
+ can_delete: bool,
+): ConstructorRef {
+ assert!(!exists<ObjectCore>(object), error::already_exists(EOBJECT_EXISTS));
+
+ let object_signer = create_signer(object);
+ let guid_creation_num = INIT_GUID_CREATION_NUM;
+ let transfer_events_guid = guid::create(object, &mut guid_creation_num);
+
+ move_to(
+ &object_signer,
+ ObjectCore {
+ guid_creation_num,
+ owner: creator_address,
+ allow_ungated_transfer: true,
+ transfer_events: event::new_event_handle(transfer_events_guid),
+ },
+ );
+ ConstructorRef { self: object, can_delete }
+}
+
+
+
+
+public fun generate_delete_ref(ref: &object::ConstructorRef): object::DeleteRef
+
+
+
+
+public fun generate_delete_ref(ref: &ConstructorRef): DeleteRef {
+ assert!(ref.can_delete, error::permission_denied(ECANNOT_DELETE));
+ DeleteRef { self: ref.self }
+}
+
+
+
+
+public fun generate_extend_ref(ref: &object::ConstructorRef): object::ExtendRef
+
+
+
+
+public fun generate_extend_ref(ref: &ConstructorRef): ExtendRef {
+ ExtendRef { self: ref.self }
+}
+
+
+
+
+public fun generate_transfer_ref(ref: &object::ConstructorRef): object::TransferRef
+
+
+
+
+public fun generate_transfer_ref(ref: &ConstructorRef): TransferRef {
+ assert!(!exists<Untransferable>(ref.self), error::permission_denied(EOBJECT_NOT_TRANSFERRABLE));
+ TransferRef { self: ref.self }
+}
+
+
+
+
+public fun generate_derive_ref(ref: &object::ConstructorRef): object::DeriveRef
+
+
+
+
+public fun generate_derive_ref(ref: &ConstructorRef): DeriveRef {
+ DeriveRef { self: ref.self }
+}
+
+
+
+
+public fun generate_signer(ref: &object::ConstructorRef): signer
+
+
+
+
+public fun generate_signer(ref: &ConstructorRef): signer {
+ create_signer(ref.self)
+}
+
+
+
+
+public fun address_from_constructor_ref(ref: &object::ConstructorRef): address
+
+
+
+
+public fun address_from_constructor_ref(ref: &ConstructorRef): address {
+ ref.self
+}
+
+
+
+
+public fun object_from_constructor_ref<T: key>(ref: &object::ConstructorRef): object::Object<T>
+
+
+
+
+public fun object_from_constructor_ref<T: key>(ref: &ConstructorRef): Object<T> {
+ address_to_object<T>(ref.self)
+}
+
+
+
+
+public fun can_generate_delete_ref(ref: &object::ConstructorRef): bool
+
+
+
+
+public fun can_generate_delete_ref(ref: &ConstructorRef): bool {
+ ref.can_delete
+}
+
+
+
+
+public fun create_guid(object: &signer): guid::GUID
+
+
+
+
+public fun create_guid(object: &signer): guid::GUID acquires ObjectCore {
+ let addr = signer::address_of(object);
+ let object_data = borrow_global_mut<ObjectCore>(addr);
+ guid::create(addr, &mut object_data.guid_creation_num)
+}
+
+
+
+
+public fun new_event_handle<T: drop, store>(object: &signer): event::EventHandle<T>
+
+
+
+
+public fun new_event_handle<T: drop + store>(
+ object: &signer,
+): event::EventHandle<T> acquires ObjectCore {
+ event::new_event_handle(create_guid(object))
+}
+
+
+
+
+public fun address_from_delete_ref(ref: &object::DeleteRef): address
+
+
+
+
+public fun address_from_delete_ref(ref: &DeleteRef): address {
+ ref.self
+}
+
+
+
+
+public fun object_from_delete_ref<T: key>(ref: &object::DeleteRef): object::Object<T>
+
+
+
+
+public fun object_from_delete_ref<T: key>(ref: &DeleteRef): Object<T> {
+ address_to_object<T>(ref.self)
+}
+
+
+
+
+public fun delete(ref: object::DeleteRef)
+
+
+
+
+public fun delete(ref: DeleteRef) acquires Untransferable, ObjectCore {
+ let object_core = move_from<ObjectCore>(ref.self);
+ let ObjectCore {
+ guid_creation_num: _,
+ owner: _,
+ allow_ungated_transfer: _,
+ transfer_events,
+ } = object_core;
+
+ if (exists<Untransferable>(ref.self)) {
+ let Untransferable {} = move_from<Untransferable>(ref.self);
+ };
+
+ event::destroy_handle(transfer_events);
+}
+
+
+
+
+public fun generate_signer_for_extending(ref: &object::ExtendRef): signer
+
+
+
+
+public fun generate_signer_for_extending(ref: &ExtendRef): signer {
+ create_signer(ref.self)
+}
+
+
+
+
+public fun address_from_extend_ref(ref: &object::ExtendRef): address
+
+
+
+
+public fun address_from_extend_ref(ref: &ExtendRef): address {
+ ref.self
+}
+
+
+
+
+public fun disable_ungated_transfer(ref: &object::TransferRef)
+
+
+
+
+public fun disable_ungated_transfer(ref: &TransferRef) acquires ObjectCore {
+ let object = borrow_global_mut<ObjectCore>(ref.self);
+ object.allow_ungated_transfer = false;
+}
+
+
+
+
+public fun set_untransferable(ref: &object::ConstructorRef)
+
+
+
+
+public fun set_untransferable(ref: &ConstructorRef) acquires ObjectCore {
+ let object = borrow_global_mut<ObjectCore>(ref.self);
+ object.allow_ungated_transfer = false;
+ let object_signer = generate_signer(ref);
+ move_to(&object_signer, Untransferable {});
+}
+
+
+
+
+public fun enable_ungated_transfer(ref: &object::TransferRef)
+
+
+
+
+public fun enable_ungated_transfer(ref: &TransferRef) acquires ObjectCore {
+ assert!(!exists<Untransferable>(ref.self), error::permission_denied(EOBJECT_NOT_TRANSFERRABLE));
+ let object = borrow_global_mut<ObjectCore>(ref.self);
+ object.allow_ungated_transfer = true;
+}
+
+
+
+
+public fun generate_linear_transfer_ref(ref: &object::TransferRef): object::LinearTransferRef
+
+
+
+
+public fun generate_linear_transfer_ref(ref: &TransferRef): LinearTransferRef acquires ObjectCore {
+ assert!(!exists<Untransferable>(ref.self), error::permission_denied(EOBJECT_NOT_TRANSFERRABLE));
+ let owner = owner(Object<ObjectCore> { inner: ref.self });
+ LinearTransferRef {
+ self: ref.self,
+ owner,
+ }
+}
+
+
+
+
+public fun transfer_with_ref(ref: object::LinearTransferRef, to: address)
+
+
+
+
+public fun transfer_with_ref(ref: LinearTransferRef, to: address) acquires ObjectCore, TombStone {
+ assert!(!exists<Untransferable>(ref.self), error::permission_denied(EOBJECT_NOT_TRANSFERRABLE));
+
+ // Undo soft burn if present as we don't want the original owner to be able to reclaim by calling unburn later.
+ if (exists<TombStone>(ref.self)) {
+ let TombStone { original_owner: _ } = move_from<TombStone>(ref.self);
+ };
+
+ let object = borrow_global_mut<ObjectCore>(ref.self);
+ assert!(
+ object.owner == ref.owner,
+ error::permission_denied(ENOT_OBJECT_OWNER),
+ );
+ if (std::features::module_event_migration_enabled()) {
+ event::emit(
+ Transfer {
+ object: ref.self,
+ from: object.owner,
+ to,
+ },
+ );
+ };
+ event::emit_event(
+ &mut object.transfer_events,
+ TransferEvent {
+ object: ref.self,
+ from: object.owner,
+ to,
+ },
+ );
+ object.owner = to;
+}
+
+
+
+
+public entry fun transfer_call(owner: &signer, object: address, to: address)
+
+
+
+
+public entry fun transfer_call(
+ owner: &signer,
+ object: address,
+ to: address,
+) acquires ObjectCore {
+ transfer_raw(owner, object, to)
+}
+
+
+
+
+public entry fun transfer<T: key>(owner: &signer, object: object::Object<T>, to: address)
+
+
+
+
+public entry fun transfer<T: key>(
+ owner: &signer,
+ object: Object<T>,
+ to: address,
+) acquires ObjectCore {
+ transfer_raw(owner, object.inner, to)
+}
+
+
+
+
+public fun transfer_raw(owner: &signer, object: address, to: address)
+
+
+
+
+public fun transfer_raw(
+ owner: &signer,
+ object: address,
+ to: address,
+) acquires ObjectCore {
+ let owner_address = signer::address_of(owner);
+ verify_ungated_and_descendant(owner_address, object);
+ transfer_raw_inner(object, to);
+}
+
+
+
+
+fun transfer_raw_inner(object: address, to: address)
+
+
+
+
+inline fun transfer_raw_inner(object: address, to: address) acquires ObjectCore {
+ let object_core = borrow_global_mut<ObjectCore>(object);
+ if (object_core.owner != to) {
+ if (std::features::module_event_migration_enabled()) {
+ event::emit(
+ Transfer {
+ object,
+ from: object_core.owner,
+ to,
+ },
+ );
+ };
+ event::emit_event(
+ &mut object_core.transfer_events,
+ TransferEvent {
+ object,
+ from: object_core.owner,
+ to,
+ },
+ );
+ object_core.owner = to;
+ };
+}
+
+
+
+
+transfer
for more information.
+
+
+public entry fun transfer_to_object<O: key, T: key>(owner: &signer, object: object::Object<O>, to: object::Object<T>)
+
+
+
+
+public entry fun transfer_to_object<O: key, T: key>(
+ owner: &signer,
+ object: Object<O>,
+ to: Object<T>,
+) acquires ObjectCore {
+ transfer(owner, object, to.inner)
+}
+
+
+
+
+fun verify_ungated_and_descendant(owner: address, destination: address)
+
+
+
+
+fun verify_ungated_and_descendant(owner: address, destination: address) acquires ObjectCore {
+ let current_address = destination;
+ assert!(
+ exists<ObjectCore>(current_address),
+ error::not_found(EOBJECT_DOES_NOT_EXIST),
+ );
+
+ let object = borrow_global<ObjectCore>(current_address);
+ assert!(
+ object.allow_ungated_transfer,
+ error::permission_denied(ENO_UNGATED_TRANSFERS),
+ );
+
+ let current_address = object.owner;
+ let count = 0;
+ while (owner != current_address) {
+ count = count + 1;
+ assert!(count < MAXIMUM_OBJECT_NESTING, error::out_of_range(EMAXIMUM_NESTING));
+ // At this point, the first object exists and so the more likely case is that the
+ // object's owner is not an object. So we return a more sensible error.
+ assert!(
+ exists<ObjectCore>(current_address),
+ error::permission_denied(ENOT_OBJECT_OWNER),
+ );
+ let object = borrow_global<ObjectCore>(current_address);
+ assert!(
+ object.allow_ungated_transfer,
+ error::permission_denied(ENO_UNGATED_TRANSFERS),
+ );
+ current_address = object.owner;
+ };
+}
+
+
+
+
+public entry fun burn<T: key>(owner: &signer, object: object::Object<T>)
+
+
+
+
+public entry fun burn<T: key>(owner: &signer, object: Object<T>) acquires ObjectCore {
+ let original_owner = signer::address_of(owner);
+ assert!(is_owner(object, original_owner), error::permission_denied(ENOT_OBJECT_OWNER));
+ let object_addr = object.inner;
+ move_to(&create_signer(object_addr), TombStone { original_owner });
+ transfer_raw_inner(object_addr, BURN_ADDRESS);
+}
+
+
+
+
+public entry fun unburn<T: key>(original_owner: &signer, object: object::Object<T>)
+
+
+
+
+public entry fun unburn<T: key>(
+ original_owner: &signer,
+ object: Object<T>,
+) acquires TombStone, ObjectCore {
+ let object_addr = object.inner;
+ assert!(exists<TombStone>(object_addr), error::invalid_argument(EOBJECT_NOT_BURNT));
+
+ let TombStone { original_owner: original_owner_addr } = move_from<TombStone>(object_addr);
+ assert!(original_owner_addr == signer::address_of(original_owner), error::permission_denied(ENOT_OBJECT_OWNER));
+ transfer_raw_inner(object_addr, original_owner_addr);
+}
+
+
+
+
+public fun ungated_transfer_allowed<T: key>(object: object::Object<T>): bool
+
+
+
+
+public fun ungated_transfer_allowed<T: key>(object: Object<T>): bool acquires ObjectCore {
+ assert!(
+ exists<ObjectCore>(object.inner),
+ error::not_found(EOBJECT_DOES_NOT_EXIST),
+ );
+ borrow_global<ObjectCore>(object.inner).allow_ungated_transfer
+}
+
+
+
+
+public fun owner<T: key>(object: object::Object<T>): address
+
+
+
+
+public fun owner<T: key>(object: Object<T>): address acquires ObjectCore {
+ assert!(
+ exists<ObjectCore>(object.inner),
+ error::not_found(EOBJECT_DOES_NOT_EXIST),
+ );
+ borrow_global<ObjectCore>(object.inner).owner
+}
+
+
+
+
+public fun is_owner<T: key>(object: object::Object<T>, owner: address): bool
+
+
+
+
+public fun is_owner<T: key>(object: Object<T>, owner: address): bool acquires ObjectCore {
+ owner(object) == owner
+}
+
+
+
+
+public fun owns<T: key>(object: object::Object<T>, owner: address): bool
+
+
+
+
+public fun owns<T: key>(object: Object<T>, owner: address): bool acquires ObjectCore {
+ let current_address = object_address(&object);
+ if (current_address == owner) {
+ return true
+ };
+
+ assert!(
+ exists<ObjectCore>(current_address),
+ error::not_found(EOBJECT_DOES_NOT_EXIST),
+ );
+
+ let object = borrow_global<ObjectCore>(current_address);
+ let current_address = object.owner;
+
+ let count = 0;
+ while (owner != current_address) {
+ count = count + 1;
+ assert!(count < MAXIMUM_OBJECT_NESTING, error::out_of_range(EMAXIMUM_NESTING));
+ if (!exists<ObjectCore>(current_address)) {
+ return false
+ };
+
+ let object = borrow_global<ObjectCore>(current_address);
+ current_address = object.owner;
+ };
+ true
+}
+
+
+
+
+public fun root_owner<T: key>(object: object::Object<T>): address
+
+
+
+
+public fun root_owner<T: key>(object: Object<T>): address acquires ObjectCore {
+ let obj_owner = owner(object);
+ while (is_object(obj_owner)) {
+ obj_owner = owner(address_to_object<ObjectCore>(obj_owner));
+ };
+ obj_owner
+}
+
+
+
+
+No. | Requirement | Criticality | Implementation | Enforcement | +
---|---|---|---|---|
1 | +It's not possible to create an object twice on the same address. | +Critical | +The create_object_internal function includes an assertion to ensure that the object being created does not already exist at the specified address. | +Formally verified via create_object_internal. | +
2 | +Only its owner may transfer an object. | +Critical | +The transfer function mandates that the transaction be signed by the owner's address, ensuring that only the rightful owner may initiate the object transfer. | +Audited that it aborts if anyone other than the owner attempts to transfer. | +
3 | +The indirect owner of an object may transfer the object. | +Medium | +The owns function evaluates to true when the given address possesses either direct or indirect ownership of the specified object. | +Audited that it aborts if address transferring is not indirect owner. | +
4 | +Objects may never change the address which houses them. | +Low | +After creating an object, transfers to another owner may occur. However, the address which stores the object may not be changed. | +This is implied by high-level requirement 1. | +
5 | +If an ungated transfer is disabled on an object in an indirect ownership chain, a transfer should not occur. | +Medium | +Calling disable_ungated_transfer disables direct transfer, and only TransferRef may trigger transfers. The transfer_with_ref function is called. | +Formally verified via transfer_with_ref. | +
6 | +Object addresses must not overlap with other addresses in different domains. | +Critical | +The current addressing scheme with suffixes does not conflict with any existing addresses, such as resource accounts. The GUID space is explicitly separated to ensure this doesn't happen. | +This is true by construction if one correctly ensures the usage of INIT_GUID_CREATION_NUM during the creation of GUID. | +
pragma aborts_if_is_strict;
+
+
+
+
+
+
+
+
+fun spec_exists_at<T: key>(object: address): bool;
+
+
+
+
+
+
+### Function `address_to_object`
+
+
+public fun address_to_object<T: key>(object: address): object::Object<T>
+
+
+
+
+
+aborts_if !exists<ObjectCore>(object);
+aborts_if !spec_exists_at<T>(object);
+ensures result == Object<T> { inner: object };
+
+
+
+
+
+
+### Function `create_object_address`
+
+
+public fun create_object_address(source: &address, seed: vector<u8>): address
+
+
+
+
+
+pragma opaque;
+pragma aborts_if_is_strict = false;
+aborts_if [abstract] false;
+ensures [abstract] result == spec_create_object_address(source, seed);
+
+
+
+
+
+
+
+
+fun spec_create_user_derived_object_address_impl(source: address, derive_from: address): address;
+
+
+
+
+
+
+### Function `create_user_derived_object_address_impl`
+
+
+fun create_user_derived_object_address_impl(source: address, derive_from: address): address
+
+
+
+
+
+pragma opaque;
+ensures [abstract] result == spec_create_user_derived_object_address_impl(source, derive_from);
+
+
+
+
+
+
+### Function `create_user_derived_object_address`
+
+
+public fun create_user_derived_object_address(source: address, derive_from: address): address
+
+
+
+
+
+pragma opaque;
+pragma aborts_if_is_strict = false;
+aborts_if [abstract] false;
+ensures [abstract] result == spec_create_user_derived_object_address(source, derive_from);
+
+
+
+
+
+
+### Function `create_guid_object_address`
+
+
+public fun create_guid_object_address(source: address, creation_num: u64): address
+
+
+
+
+
+pragma opaque;
+pragma aborts_if_is_strict = false;
+aborts_if [abstract] false;
+ensures [abstract] result == spec_create_guid_object_address(source, creation_num);
+
+
+
+
+
+
+### Function `exists_at`
+
+
+fun exists_at<T: key>(object: address): bool
+
+
+
+
+
+pragma opaque;
+ensures [abstract] result == spec_exists_at<T>(object);
+
+
+
+
+
+
+### Function `object_address`
+
+
+public fun object_address<T: key>(object: &object::Object<T>): address
+
+
+
+
+
+aborts_if false;
+ensures result == object.inner;
+
+
+
+
+
+
+### Function `convert`
+
+
+public fun convert<X: key, Y: key>(object: object::Object<X>): object::Object<Y>
+
+
+
+
+
+aborts_if !exists<ObjectCore>(object.inner);
+aborts_if !spec_exists_at<Y>(object.inner);
+ensures result == Object<Y> { inner: object.inner };
+
+
+
+
+
+
+### Function `create_named_object`
+
+
+public fun create_named_object(creator: &signer, seed: vector<u8>): object::ConstructorRef
+
+
+
+
+
+let creator_address = signer::address_of(creator);
+let obj_addr = spec_create_object_address(creator_address, seed);
+aborts_if exists<ObjectCore>(obj_addr);
+ensures exists<ObjectCore>(obj_addr);
+ensures global<ObjectCore>(obj_addr) == ObjectCore {
+ guid_creation_num: INIT_GUID_CREATION_NUM + 1,
+ owner: creator_address,
+ allow_ungated_transfer: true,
+ transfer_events: event::EventHandle {
+ counter: 0,
+ guid: guid::GUID {
+ id: guid::ID {
+ creation_num: INIT_GUID_CREATION_NUM,
+ addr: obj_addr,
+ }
+ }
+ }
+};
+ensures result == ConstructorRef { self: obj_addr, can_delete: false };
+
+
+
+
+
+
+### Function `create_user_derived_object`
+
+
+public(friend) fun create_user_derived_object(creator_address: address, derive_ref: &object::DeriveRef): object::ConstructorRef
+
+
+
+
+
+let obj_addr = spec_create_user_derived_object_address(creator_address, derive_ref.self);
+aborts_if exists<ObjectCore>(obj_addr);
+ensures exists<ObjectCore>(obj_addr);
+ensures global<ObjectCore>(obj_addr) == ObjectCore {
+ guid_creation_num: INIT_GUID_CREATION_NUM + 1,
+ owner: creator_address,
+ allow_ungated_transfer: true,
+ transfer_events: event::EventHandle {
+ counter: 0,
+ guid: guid::GUID {
+ id: guid::ID {
+ creation_num: INIT_GUID_CREATION_NUM,
+ addr: obj_addr,
+ }
+ }
+ }
+};
+ensures result == ConstructorRef { self: obj_addr, can_delete: false };
+
+
+
+
+
+
+### Function `create_object`
+
+
+public fun create_object(owner_address: address): object::ConstructorRef
+
+
+
+
+
+pragma aborts_if_is_partial;
+let unique_address = transaction_context::spec_generate_unique_address();
+aborts_if exists<ObjectCore>(unique_address);
+ensures exists<ObjectCore>(unique_address);
+ensures global<ObjectCore>(unique_address) == ObjectCore {
+ guid_creation_num: INIT_GUID_CREATION_NUM + 1,
+ owner: owner_address,
+ allow_ungated_transfer: true,
+ transfer_events: event::EventHandle {
+ counter: 0,
+ guid: guid::GUID {
+ id: guid::ID {
+ creation_num: INIT_GUID_CREATION_NUM,
+ addr: unique_address,
+ }
+ }
+ }
+};
+ensures result == ConstructorRef { self: unique_address, can_delete: true };
+
+
+
+
+
+
+### Function `create_sticky_object`
+
+
+public fun create_sticky_object(owner_address: address): object::ConstructorRef
+
+
+
+
+
+pragma aborts_if_is_partial;
+let unique_address = transaction_context::spec_generate_unique_address();
+aborts_if exists<ObjectCore>(unique_address);
+ensures exists<ObjectCore>(unique_address);
+ensures global<ObjectCore>(unique_address) == ObjectCore {
+ guid_creation_num: INIT_GUID_CREATION_NUM + 1,
+ owner: owner_address,
+ allow_ungated_transfer: true,
+ transfer_events: event::EventHandle {
+ counter: 0,
+ guid: guid::GUID {
+ id: guid::ID {
+ creation_num: INIT_GUID_CREATION_NUM,
+ addr: unique_address,
+ }
+ }
+ }
+};
+ensures result == ConstructorRef { self: unique_address, can_delete: false };
+
+
+
+
+
+
+### Function `create_sticky_object_at_address`
+
+
+public(friend) fun create_sticky_object_at_address(owner_address: address, object_address: address): object::ConstructorRef
+
+
+
+
+
+pragma verify = false;
+
+
+
+
+
+
+### Function `create_object_from_account`
+
+
+#[deprecated]
+public fun create_object_from_account(creator: &signer): object::ConstructorRef
+
+
+
+
+
+aborts_if !exists<account::Account>(signer::address_of(creator));
+let object_data = global<account::Account>(signer::address_of(creator));
+aborts_if object_data.guid_creation_num + 1 > MAX_U64;
+aborts_if object_data.guid_creation_num + 1 >= account::MAX_GUID_CREATION_NUM;
+let creation_num = object_data.guid_creation_num;
+let addr = signer::address_of(creator);
+let guid = guid::GUID {
+ id: guid::ID {
+ creation_num,
+ addr,
+ }
+};
+let bytes_spec = bcs::to_bytes(guid);
+let bytes = concat(bytes_spec, vec<u8>(OBJECT_FROM_GUID_ADDRESS_SCHEME));
+let hash_bytes = hash::sha3_256(bytes);
+let obj_addr = from_bcs::deserialize<address>(hash_bytes);
+aborts_if exists<ObjectCore>(obj_addr);
+aborts_if !from_bcs::deserializable<address>(hash_bytes);
+ensures global<account::Account>(addr).guid_creation_num == old(
+ global<account::Account>(addr)
+).guid_creation_num + 1;
+ensures exists<ObjectCore>(obj_addr);
+ensures global<ObjectCore>(obj_addr) == ObjectCore {
+ guid_creation_num: INIT_GUID_CREATION_NUM + 1,
+ owner: addr,
+ allow_ungated_transfer: true,
+ transfer_events: event::EventHandle {
+ counter: 0,
+ guid: guid::GUID {
+ id: guid::ID {
+ creation_num: INIT_GUID_CREATION_NUM,
+ addr: obj_addr,
+ }
+ }
+ }
+};
+ensures result == ConstructorRef { self: obj_addr, can_delete: true };
+
+
+
+
+
+
+### Function `create_object_from_object`
+
+
+#[deprecated]
+public fun create_object_from_object(creator: &signer): object::ConstructorRef
+
+
+
+
+
+aborts_if !exists<ObjectCore>(signer::address_of(creator));
+let object_data = global<ObjectCore>(signer::address_of(creator));
+aborts_if object_data.guid_creation_num + 1 > MAX_U64;
+let creation_num = object_data.guid_creation_num;
+let addr = signer::address_of(creator);
+let guid = guid::GUID {
+ id: guid::ID {
+ creation_num,
+ addr,
+ }
+};
+let bytes_spec = bcs::to_bytes(guid);
+let bytes = concat(bytes_spec, vec<u8>(OBJECT_FROM_GUID_ADDRESS_SCHEME));
+let hash_bytes = hash::sha3_256(bytes);
+let obj_addr = from_bcs::deserialize<address>(hash_bytes);
+aborts_if exists<ObjectCore>(obj_addr);
+aborts_if !from_bcs::deserializable<address>(hash_bytes);
+ensures global<ObjectCore>(addr).guid_creation_num == old(global<ObjectCore>(addr)).guid_creation_num + 1;
+ensures exists<ObjectCore>(obj_addr);
+ensures global<ObjectCore>(obj_addr) == ObjectCore {
+ guid_creation_num: INIT_GUID_CREATION_NUM + 1,
+ owner: addr,
+ allow_ungated_transfer: true,
+ transfer_events: event::EventHandle {
+ counter: 0,
+ guid: guid::GUID {
+ id: guid::ID {
+ creation_num: INIT_GUID_CREATION_NUM,
+ addr: obj_addr,
+ }
+ }
+ }
+};
+ensures result == ConstructorRef { self: obj_addr, can_delete: true };
+
+
+
+
+
+
+### Function `create_object_from_guid`
+
+
+fun create_object_from_guid(creator_address: address, guid: guid::GUID): object::ConstructorRef
+
+
+
+
+
+let bytes_spec = bcs::to_bytes(guid);
+let bytes = concat(bytes_spec, vec<u8>(OBJECT_FROM_GUID_ADDRESS_SCHEME));
+let hash_bytes = hash::sha3_256(bytes);
+let obj_addr = from_bcs::deserialize<address>(hash_bytes);
+aborts_if exists<ObjectCore>(obj_addr);
+aborts_if !from_bcs::deserializable<address>(hash_bytes);
+ensures exists<ObjectCore>(obj_addr);
+ensures global<ObjectCore>(obj_addr) == ObjectCore {
+ guid_creation_num: INIT_GUID_CREATION_NUM + 1,
+ owner: creator_address,
+ allow_ungated_transfer: true,
+ transfer_events: event::EventHandle {
+ counter: 0,
+ guid: guid::GUID {
+ id: guid::ID {
+ creation_num: INIT_GUID_CREATION_NUM,
+ addr: obj_addr,
+ }
+ }
+ }
+};
+ensures result == ConstructorRef { self: obj_addr, can_delete: true };
+
+
+
+
+
+
+### Function `create_object_internal`
+
+
+fun create_object_internal(creator_address: address, object: address, can_delete: bool): object::ConstructorRef
+
+
+
+
+
+// This enforces high-level requirement 1:
+aborts_if exists<ObjectCore>(object);
+ensures exists<ObjectCore>(object);
+ensures global<ObjectCore>(object).guid_creation_num == INIT_GUID_CREATION_NUM + 1;
+ensures result == ConstructorRef { self: object, can_delete };
+
+
+
+
+
+
+### Function `generate_delete_ref`
+
+
+public fun generate_delete_ref(ref: &object::ConstructorRef): object::DeleteRef
+
+
+
+
+
+aborts_if !ref.can_delete;
+ensures result == DeleteRef { self: ref.self };
+
+
+
+
+
+
+### Function `generate_transfer_ref`
+
+
+public fun generate_transfer_ref(ref: &object::ConstructorRef): object::TransferRef
+
+
+
+
+
+aborts_if exists<Untransferable>(ref.self);
+ensures result == TransferRef {
+ self: ref.self,
+};
+
+
+
+
+
+
+### Function `object_from_constructor_ref`
+
+
+public fun object_from_constructor_ref<T: key>(ref: &object::ConstructorRef): object::Object<T>
+
+
+
+
+
+aborts_if !exists<ObjectCore>(ref.self);
+aborts_if !spec_exists_at<T>(ref.self);
+ensures result == Object<T> { inner: ref.self };
+
+
+
+
+
+
+### Function `create_guid`
+
+
+public fun create_guid(object: &signer): guid::GUID
+
+
+
+
+
+aborts_if !exists<ObjectCore>(signer::address_of(object));
+let object_data = global<ObjectCore>(signer::address_of(object));
+aborts_if object_data.guid_creation_num + 1 > MAX_U64;
+ensures result == guid::GUID {
+ id: guid::ID {
+ creation_num: object_data.guid_creation_num,
+ addr: signer::address_of(object)
+ }
+};
+
+
+
+
+
+
+### Function `new_event_handle`
+
+
+public fun new_event_handle<T: drop, store>(object: &signer): event::EventHandle<T>
+
+
+
+
+
+aborts_if !exists<ObjectCore>(signer::address_of(object));
+let object_data = global<ObjectCore>(signer::address_of(object));
+aborts_if object_data.guid_creation_num + 1 > MAX_U64;
+let guid = guid::GUID {
+ id: guid::ID {
+ creation_num: object_data.guid_creation_num,
+ addr: signer::address_of(object)
+ }
+};
+ensures result == event::EventHandle<T> {
+ counter: 0,
+ guid,
+};
+
+
+
+
+
+
+### Function `object_from_delete_ref`
+
+
+public fun object_from_delete_ref<T: key>(ref: &object::DeleteRef): object::Object<T>
+
+
+
+
+
+aborts_if !exists<ObjectCore>(ref.self);
+aborts_if !spec_exists_at<T>(ref.self);
+ensures result == Object<T> { inner: ref.self };
+
+
+
+
+
+
+### Function `delete`
+
+
+public fun delete(ref: object::DeleteRef)
+
+
+
+
+
+aborts_if !exists<ObjectCore>(ref.self);
+ensures !exists<ObjectCore>(ref.self);
+
+
+
+
+
+
+### Function `disable_ungated_transfer`
+
+
+public fun disable_ungated_transfer(ref: &object::TransferRef)
+
+
+
+
+
+aborts_if !exists<ObjectCore>(ref.self);
+ensures global<ObjectCore>(ref.self).allow_ungated_transfer == false;
+
+
+
+
+
+
+### Function `set_untransferable`
+
+
+public fun set_untransferable(ref: &object::ConstructorRef)
+
+
+
+
+
+aborts_if !exists<ObjectCore>(ref.self);
+aborts_if exists<Untransferable>(ref.self);
+ensures exists<Untransferable>(ref.self);
+ensures global<ObjectCore>(ref.self).allow_ungated_transfer == false;
+
+
+
+
+
+
+### Function `enable_ungated_transfer`
+
+
+public fun enable_ungated_transfer(ref: &object::TransferRef)
+
+
+
+
+
+aborts_if exists<Untransferable>(ref.self);
+aborts_if !exists<ObjectCore>(ref.self);
+ensures global<ObjectCore>(ref.self).allow_ungated_transfer == true;
+
+
+
+
+
+
+### Function `generate_linear_transfer_ref`
+
+
+public fun generate_linear_transfer_ref(ref: &object::TransferRef): object::LinearTransferRef
+
+
+
+
+
+aborts_if exists<Untransferable>(ref.self);
+aborts_if !exists<ObjectCore>(ref.self);
+let owner = global<ObjectCore>(ref.self).owner;
+ensures result == LinearTransferRef {
+ self: ref.self,
+ owner,
+};
+
+
+
+
+
+
+### Function `transfer_with_ref`
+
+
+public fun transfer_with_ref(ref: object::LinearTransferRef, to: address)
+
+
+
+
+
+aborts_if exists<Untransferable>(ref.self);
+let object = global<ObjectCore>(ref.self);
+aborts_if !exists<ObjectCore>(ref.self);
+// This enforces high-level requirement 5:
+aborts_if object.owner != ref.owner;
+ensures global<ObjectCore>(ref.self).owner == to;
+
+
+
+
+
+
+### Function `transfer_call`
+
+
+public entry fun transfer_call(owner: &signer, object: address, to: address)
+
+
+
+
+
+pragma aborts_if_is_partial;
+let owner_address = signer::address_of(owner);
+aborts_if !exists<ObjectCore>(object);
+aborts_if !global<ObjectCore>(object).allow_ungated_transfer;
+
+
+
+
+
+
+### Function `transfer`
+
+
+public entry fun transfer<T: key>(owner: &signer, object: object::Object<T>, to: address)
+
+
+
+
+
+pragma aborts_if_is_partial;
+let owner_address = signer::address_of(owner);
+let object_address = object.inner;
+aborts_if !exists<ObjectCore>(object_address);
+aborts_if !global<ObjectCore>(object_address).allow_ungated_transfer;
+
+
+
+
+
+
+### Function `transfer_raw`
+
+
+public fun transfer_raw(owner: &signer, object: address, to: address)
+
+
+
+
+
+pragma aborts_if_is_partial;
+let owner_address = signer::address_of(owner);
+aborts_if !exists<ObjectCore>(object);
+aborts_if !global<ObjectCore>(object).allow_ungated_transfer;
+
+
+
+
+
+
+### Function `transfer_to_object`
+
+
+public entry fun transfer_to_object<O: key, T: key>(owner: &signer, object: object::Object<O>, to: object::Object<T>)
+
+
+
+
+
+pragma aborts_if_is_partial;
+let owner_address = signer::address_of(owner);
+let object_address = object.inner;
+aborts_if !exists<ObjectCore>(object_address);
+aborts_if !global<ObjectCore>(object_address).allow_ungated_transfer;
+
+
+
+
+
+
+### Function `verify_ungated_and_descendant`
+
+
+fun verify_ungated_and_descendant(owner: address, destination: address)
+
+
+
+
+
+pragma aborts_if_is_partial;
+pragma unroll = MAXIMUM_OBJECT_NESTING;
+aborts_if !exists<ObjectCore>(destination);
+aborts_if !global<ObjectCore>(destination).allow_ungated_transfer;
+
+
+
+
+
+
+### Function `burn`
+
+
+public entry fun burn<T: key>(owner: &signer, object: object::Object<T>)
+
+
+
+
+
+pragma aborts_if_is_partial;
+let object_address = object.inner;
+aborts_if !exists<ObjectCore>(object_address);
+aborts_if owner(object) != signer::address_of(owner);
+aborts_if is_burnt(object);
+
+
+
+
+
+
+### Function `unburn`
+
+
+public entry fun unburn<T: key>(original_owner: &signer, object: object::Object<T>)
+
+
+
+
+
+pragma aborts_if_is_partial;
+let object_address = object.inner;
+aborts_if !exists<ObjectCore>(object_address);
+aborts_if !is_burnt(object);
+let tomb_stone = borrow_global<TombStone>(object_address);
+aborts_if tomb_stone.original_owner != signer::address_of(original_owner);
+
+
+
+
+
+
+### Function `ungated_transfer_allowed`
+
+
+public fun ungated_transfer_allowed<T: key>(object: object::Object<T>): bool
+
+
+
+
+
+aborts_if !exists<ObjectCore>(object.inner);
+ensures result == global<ObjectCore>(object.inner).allow_ungated_transfer;
+
+
+
+
+
+
+### Function `owner`
+
+
+public fun owner<T: key>(object: object::Object<T>): address
+
+
+
+
+
+aborts_if !exists<ObjectCore>(object.inner);
+ensures result == global<ObjectCore>(object.inner).owner;
+
+
+
+
+
+
+### Function `is_owner`
+
+
+public fun is_owner<T: key>(object: object::Object<T>, owner: address): bool
+
+
+
+
+
+aborts_if !exists<ObjectCore>(object.inner);
+ensures result == (global<ObjectCore>(object.inner).owner == owner);
+
+
+
+
+
+
+### Function `owns`
+
+
+public fun owns<T: key>(object: object::Object<T>, owner: address): bool
+
+
+
+
+
+pragma aborts_if_is_partial;
+let current_address_0 = object.inner;
+let object_0 = global<ObjectCore>(current_address_0);
+let current_address = object_0.owner;
+aborts_if object.inner != owner && !exists<ObjectCore>(object.inner);
+ensures current_address_0 == owner ==> result == true;
+
+
+
+
+
+
+### Function `root_owner`
+
+
+public fun root_owner<T: key>(object: object::Object<T>): address
+
+
+
+
+
+pragma aborts_if_is_partial;
+
+
+
+
+
+
+
+
+fun spec_create_object_address(source: address, seed: vector<u8>): address;
+
+
+
+
+
+
+
+
+fun spec_create_user_derived_object_address(source: address, derive_from: address): address;
+
+
+
+
+
+
+
+
+fun spec_create_guid_object_address(source: address, creation_num: u64): address;
+
+
+
+[move-book]: https://aptos.dev/move/book/SUMMARY
diff --git a/aptos-move/framework/aptos-framework/tests/compiler-v2-doc/object_code_deployment.md b/aptos-move/framework/aptos-framework/tests/compiler-v2-doc/object_code_deployment.md
new file mode 100644
index 0000000000000..210d0a1e6b892
--- /dev/null
+++ b/aptos-move/framework/aptos-framework/tests/compiler-v2-doc/object_code_deployment.md
@@ -0,0 +1,373 @@
+
+
+
+# Module `0x1::object_code_deployment`
+
+This module allows users to deploy, upgrade and freeze modules deployed to objects on-chain.
+This enables users to deploy modules to an object with a unique address each time they are published.
+This modules provides an alternative method to publish code on-chain, where code is deployed to objects rather than accounts.
+This is encouraged as it abstracts the necessary resources needed for deploying modules,
+along with the required authorization to upgrade and freeze modules.
+
+The functionalities of this module are as follows.
+
+Publishing modules flow:
+1. Create a new object with the address derived from the publisher address and the object seed.
+2. Publish the module passed in the function via metadata_serialized
and code
to the newly created object.
+3. Emits 'Publish' event with the address of the newly created object.
+4. Create a ManagingRefs
which stores the extend ref of the newly created object.
+Note: This is needed to upgrade the code as the signer must be generated to upgrade the existing code in an object.
+
+Upgrading modules flow:
+1. Assert the code_object
passed in the function is owned by the publisher
.
+2. Assert the code_object
passed in the function exists in global storage.
+2. Retrieve the ExtendRef
from the code_object
and generate the signer from this.
+3. Upgrade the module with the metadata_serialized
and code
passed in the function.
+4. Emits 'Upgrade' event with the address of the object with the upgraded code.
+Note: If the modules were deployed as immutable when calling publish
, the upgrade will fail.
+
+Freezing modules flow:
+1. Assert the code_object
passed in the function exists in global storage.
+2. Assert the code_object
passed in the function is owned by the publisher
.
+3. Mark all the modules in the code_object
as immutable.
+4. Emits 'Freeze' event with the address of the object with the frozen code.
+Note: There is no unfreeze function as this gives no benefit if the user can freeze/unfreeze modules at will.
+Once modules are marked as immutable, they cannot be made mutable again.
+
+
+- [Resource `ManagingRefs`](#0x1_object_code_deployment_ManagingRefs)
+- [Struct `Publish`](#0x1_object_code_deployment_Publish)
+- [Struct `Upgrade`](#0x1_object_code_deployment_Upgrade)
+- [Struct `Freeze`](#0x1_object_code_deployment_Freeze)
+- [Constants](#@Constants_0)
+- [Function `publish`](#0x1_object_code_deployment_publish)
+- [Function `object_seed`](#0x1_object_code_deployment_object_seed)
+- [Function `upgrade`](#0x1_object_code_deployment_upgrade)
+- [Function `freeze_code_object`](#0x1_object_code_deployment_freeze_code_object)
+
+
+use 0x1::account;
+use 0x1::bcs;
+use 0x1::code;
+use 0x1::error;
+use 0x1::event;
+use 0x1::features;
+use 0x1::object;
+use 0x1::signer;
+use 0x1::vector;
+
+
+
+
+
+
+## Resource `ManagingRefs`
+
+Internal struct, attached to the object, that holds Refs we need to manage the code deployment (i.e. upgrades).
+
+
+#[resource_group_member(#[group = 0x1::object::ObjectGroup])]
+struct ManagingRefs has key
+
+
+
+
+extend_ref: object::ExtendRef
+#[event]
+struct Publish has drop, store
+
+
+
+
+object_address: address
+#[event]
+struct Upgrade has drop, store
+
+
+
+
+object_address: address
+#[event]
+struct Freeze has drop, store
+
+
+
+
+object_address: address
+code_object
does not exist.
+
+
+const ECODE_OBJECT_DOES_NOT_EXIST: u64 = 3;
+
+
+
+
+
+
+Not the owner of the code_object
+
+
+const ENOT_CODE_OBJECT_OWNER: u64 = 2;
+
+
+
+
+
+
+Object code deployment feature not supported.
+
+
+const EOBJECT_CODE_DEPLOYMENT_NOT_SUPPORTED: u64 = 1;
+
+
+
+
+
+
+
+
+const OBJECT_CODE_DEPLOYMENT_DOMAIN_SEPARATOR: vector<u8> = [97, 112, 116, 111, 115, 95, 102, 114, 97, 109, 101, 119, 111, 114, 107, 58, 58, 111, 98, 106, 101, 99, 116, 95, 99, 111, 100, 101, 95, 100, 101, 112, 108, 111, 121, 109, 101, 110, 116];
+
+
+
+
+
+
+## Function `publish`
+
+Creates a new object with a unique address derived from the publisher address and the object seed.
+Publishes the code passed in the function to the newly created object.
+The caller must provide package metadata describing the package via metadata_serialized
and
+the code to be published via code
. This contains a vector of modules to be deployed on-chain.
+
+
+public entry fun publish(publisher: &signer, metadata_serialized: vector<u8>, code: vector<vector<u8>>)
+
+
+
+
+public entry fun publish(
+ publisher: &signer,
+ metadata_serialized: vector<u8>,
+ code: vector<vector<u8>>,
+) {
+ assert!(
+ features::is_object_code_deployment_enabled(),
+ error::unavailable(EOBJECT_CODE_DEPLOYMENT_NOT_SUPPORTED),
+ );
+
+ let publisher_address = signer::address_of(publisher);
+ let object_seed = object_seed(publisher_address);
+ let constructor_ref = &object::create_named_object(publisher, object_seed);
+ let code_signer = &object::generate_signer(constructor_ref);
+ code::publish_package_txn(code_signer, metadata_serialized, code);
+
+ event::emit(Publish { object_address: signer::address_of(code_signer), });
+
+ move_to(code_signer, ManagingRefs {
+ extend_ref: object::generate_extend_ref(constructor_ref),
+ });
+}
+
+
+
+
+fun object_seed(publisher: address): vector<u8>
+
+
+
+
+inline fun object_seed(publisher: address): vector<u8> {
+ let sequence_number = account::get_sequence_number(publisher) + 1;
+ let seeds = vector[];
+ vector::append(&mut seeds, bcs::to_bytes(&OBJECT_CODE_DEPLOYMENT_DOMAIN_SEPARATOR));
+ vector::append(&mut seeds, bcs::to_bytes(&sequence_number));
+ seeds
+}
+
+
+
+
+code_object
address with the new modules passed in code
,
+along with the metadata metadata_serialized
.
+Note: If the modules were deployed as immutable when calling publish
, the upgrade will fail.
+Requires the publisher to be the owner of the code_object
.
+
+
+public entry fun upgrade(publisher: &signer, metadata_serialized: vector<u8>, code: vector<vector<u8>>, code_object: object::Object<code::PackageRegistry>)
+
+
+
+
+public entry fun upgrade(
+ publisher: &signer,
+ metadata_serialized: vector<u8>,
+ code: vector<vector<u8>>,
+ code_object: Object<PackageRegistry>,
+) acquires ManagingRefs {
+ let publisher_address = signer::address_of(publisher);
+ assert!(
+ object::is_owner(code_object, publisher_address),
+ error::permission_denied(ENOT_CODE_OBJECT_OWNER),
+ );
+
+ let code_object_address = object::object_address(&code_object);
+ assert!(exists<ManagingRefs>(code_object_address), error::not_found(ECODE_OBJECT_DOES_NOT_EXIST));
+
+ let extend_ref = &borrow_global<ManagingRefs>(code_object_address).extend_ref;
+ let code_signer = &object::generate_signer_for_extending(extend_ref);
+ code::publish_package_txn(code_signer, metadata_serialized, code);
+
+ event::emit(Upgrade { object_address: signer::address_of(code_signer), });
+}
+
+
+
+
+code_object
should only have one package, as one package is deployed per object in this module.
+Requires the publisher
to be the owner of the code_object
.
+
+
+public entry fun freeze_code_object(publisher: &signer, code_object: object::Object<code::PackageRegistry>)
+
+
+
+
+public entry fun freeze_code_object(publisher: &signer, code_object: Object<PackageRegistry>) {
+ code::freeze_code_object(publisher, code_object);
+
+ event::emit(Freeze { object_address: object::object_address(&code_object), });
+}
+
+
+
+
+use 0x1::aggregator;
+use 0x1::aggregator_factory;
+use 0x1::error;
+use 0x1::option;
+
+
+
+
+
+
+## Struct `Integer`
+
+Wrapper around integer with a custom overflow limit. Supports add, subtract and read just like Aggregator
.
+
+
+struct Integer has store
+
+
+
+
+value: u128
+limit: u128
+struct OptionalAggregator has store
+
+
+
+
+aggregator: option::Option<aggregator::Aggregator>
+integer: option::Option<optional_aggregator::Integer>
+const EAGGREGATOR_OVERFLOW: u64 = 1;
+
+
+
+
+
+
+Aggregator feature is not supported. Raised by native code.
+
+
+const EAGGREGATOR_UNDERFLOW: u64 = 2;
+
+
+
+
+
+
+## Function `new_integer`
+
+Creates a new integer which overflows on exceeding a limit
.
+
+
+fun new_integer(limit: u128): optional_aggregator::Integer
+
+
+
+
+fun new_integer(limit: u128): Integer {
+ Integer {
+ value: 0,
+ limit,
+ }
+}
+
+
+
+
+value
to integer. Aborts on overflowing the limit.
+
+
+fun add_integer(integer: &mut optional_aggregator::Integer, value: u128)
+
+
+
+
+fun add_integer(integer: &mut Integer, value: u128) {
+ assert!(
+ value <= (integer.limit - integer.value),
+ error::out_of_range(EAGGREGATOR_OVERFLOW)
+ );
+ integer.value = integer.value + value;
+}
+
+
+
+
+value
from integer. Aborts on going below zero.
+
+
+fun sub_integer(integer: &mut optional_aggregator::Integer, value: u128)
+
+
+
+
+fun sub_integer(integer: &mut Integer, value: u128) {
+ assert!(value <= integer.value, error::out_of_range(EAGGREGATOR_UNDERFLOW));
+ integer.value = integer.value - value;
+}
+
+
+
+
+fun limit(integer: &optional_aggregator::Integer): u128
+
+
+
+
+fun limit(integer: &Integer): u128 {
+ integer.limit
+}
+
+
+
+
+fun read_integer(integer: &optional_aggregator::Integer): u128
+
+
+
+
+fun read_integer(integer: &Integer): u128 {
+ integer.value
+}
+
+
+
+
+fun destroy_integer(integer: optional_aggregator::Integer)
+
+
+
+
+fun destroy_integer(integer: Integer) {
+ let Integer { value: _, limit: _ } = integer;
+}
+
+
+
+
+public(friend) fun new(limit: u128, parallelizable: bool): optional_aggregator::OptionalAggregator
+
+
+
+
+public(friend) fun new(limit: u128, parallelizable: bool): OptionalAggregator {
+ if (parallelizable) {
+ OptionalAggregator {
+ aggregator: option::some(aggregator_factory::create_aggregator_internal(limit)),
+ integer: option::none(),
+ }
+ } else {
+ OptionalAggregator {
+ aggregator: option::none(),
+ integer: option::some(new_integer(limit)),
+ }
+ }
+}
+
+
+
+
+public fun switch(optional_aggregator: &mut optional_aggregator::OptionalAggregator)
+
+
+
+
+public fun switch(optional_aggregator: &mut OptionalAggregator) {
+ let value = read(optional_aggregator);
+ switch_and_zero_out(optional_aggregator);
+ add(optional_aggregator, value);
+}
+
+
+
+
+fun switch_and_zero_out(optional_aggregator: &mut optional_aggregator::OptionalAggregator)
+
+
+
+
+fun switch_and_zero_out(optional_aggregator: &mut OptionalAggregator) {
+ if (is_parallelizable(optional_aggregator)) {
+ switch_to_integer_and_zero_out(optional_aggregator);
+ } else {
+ switch_to_aggregator_and_zero_out(optional_aggregator);
+ }
+}
+
+
+
+
+fun switch_to_integer_and_zero_out(optional_aggregator: &mut optional_aggregator::OptionalAggregator): u128
+
+
+
+
+fun switch_to_integer_and_zero_out(
+ optional_aggregator: &mut OptionalAggregator
+): u128 {
+ let aggregator = option::extract(&mut optional_aggregator.aggregator);
+ let limit = aggregator::limit(&aggregator);
+ aggregator::destroy(aggregator);
+ let integer = new_integer(limit);
+ option::fill(&mut optional_aggregator.integer, integer);
+ limit
+}
+
+
+
+
+fun switch_to_aggregator_and_zero_out(optional_aggregator: &mut optional_aggregator::OptionalAggregator): u128
+
+
+
+
+fun switch_to_aggregator_and_zero_out(
+ optional_aggregator: &mut OptionalAggregator
+): u128 {
+ let integer = option::extract(&mut optional_aggregator.integer);
+ let limit = limit(&integer);
+ destroy_integer(integer);
+ let aggregator = aggregator_factory::create_aggregator_internal(limit);
+ option::fill(&mut optional_aggregator.aggregator, aggregator);
+ limit
+}
+
+
+
+
+public fun destroy(optional_aggregator: optional_aggregator::OptionalAggregator)
+
+
+
+
+public fun destroy(optional_aggregator: OptionalAggregator) {
+ if (is_parallelizable(&optional_aggregator)) {
+ destroy_optional_aggregator(optional_aggregator);
+ } else {
+ destroy_optional_integer(optional_aggregator);
+ }
+}
+
+
+
+
+fun destroy_optional_aggregator(optional_aggregator: optional_aggregator::OptionalAggregator): u128
+
+
+
+
+fun destroy_optional_aggregator(optional_aggregator: OptionalAggregator): u128 {
+ let OptionalAggregator { aggregator, integer } = optional_aggregator;
+ let limit = aggregator::limit(option::borrow(&aggregator));
+ aggregator::destroy(option::destroy_some(aggregator));
+ option::destroy_none(integer);
+ limit
+}
+
+
+
+
+fun destroy_optional_integer(optional_aggregator: optional_aggregator::OptionalAggregator): u128
+
+
+
+
+fun destroy_optional_integer(optional_aggregator: OptionalAggregator): u128 {
+ let OptionalAggregator { aggregator, integer } = optional_aggregator;
+ let limit = limit(option::borrow(&integer));
+ destroy_integer(option::destroy_some(integer));
+ option::destroy_none(aggregator);
+ limit
+}
+
+
+
+
+value
to optional aggregator, aborting on exceeding the limit
.
+
+
+public fun add(optional_aggregator: &mut optional_aggregator::OptionalAggregator, value: u128)
+
+
+
+
+public fun add(optional_aggregator: &mut OptionalAggregator, value: u128) {
+ if (option::is_some(&optional_aggregator.aggregator)) {
+ let aggregator = option::borrow_mut(&mut optional_aggregator.aggregator);
+ aggregator::add(aggregator, value);
+ } else {
+ let integer = option::borrow_mut(&mut optional_aggregator.integer);
+ add_integer(integer, value);
+ }
+}
+
+
+
+
+value
from optional aggregator, aborting on going below zero.
+
+
+public fun sub(optional_aggregator: &mut optional_aggregator::OptionalAggregator, value: u128)
+
+
+
+
+public fun sub(optional_aggregator: &mut OptionalAggregator, value: u128) {
+ if (option::is_some(&optional_aggregator.aggregator)) {
+ let aggregator = option::borrow_mut(&mut optional_aggregator.aggregator);
+ aggregator::sub(aggregator, value);
+ } else {
+ let integer = option::borrow_mut(&mut optional_aggregator.integer);
+ sub_integer(integer, value);
+ }
+}
+
+
+
+
+public fun read(optional_aggregator: &optional_aggregator::OptionalAggregator): u128
+
+
+
+
+public fun read(optional_aggregator: &OptionalAggregator): u128 {
+ if (option::is_some(&optional_aggregator.aggregator)) {
+ let aggregator = option::borrow(&optional_aggregator.aggregator);
+ aggregator::read(aggregator)
+ } else {
+ let integer = option::borrow(&optional_aggregator.integer);
+ read_integer(integer)
+ }
+}
+
+
+
+
+public fun is_parallelizable(optional_aggregator: &optional_aggregator::OptionalAggregator): bool
+
+
+
+
+public fun is_parallelizable(optional_aggregator: &OptionalAggregator): bool {
+ option::is_some(&optional_aggregator.aggregator)
+}
+
+
+
+
+No. | Requirement | Criticality | Implementation | Enforcement | +
---|---|---|---|---|
1 | +When creating a new integer instance, it guarantees that the limit assigned is a value passed into the function as an argument, and the value field becomes zero. | +High | +The new_integer function sets the limit field to the argument passed in, and the value field is set to zero. | +Formally verified via new_integer. | +
2 | +For a given integer instance it should always be possible to: (1) return the limit value of the integer resource, (2) return the current value stored in that particular instance, and (3) destroy the integer instance. | +Low | +The following functions should not abort if the Integer instance exists: limit(), read_integer(), destroy_integer(). | +Formally verified via: read_integer, limit, and destroy_integer. | +
3 | +Every successful switch must end with the aggregator type changed from non-parallelizable to parallelizable or vice versa. | +High | +The switch function run, if successful, should always change the aggregator type. | +Formally verified via switch_and_zero_out. | +
pragma verify = true;
+pragma aborts_if_is_strict;
+
+
+
+
+
+
+### Struct `OptionalAggregator`
+
+
+struct OptionalAggregator has store
+
+
+
+
+aggregator: option::Option<aggregator::Aggregator>
+integer: option::Option<optional_aggregator::Integer>
+invariant option::is_some(aggregator) <==> option::is_none(integer);
+invariant option::is_some(integer) <==> option::is_none(aggregator);
+invariant option::is_some(integer) ==> option::borrow(integer).value <= option::borrow(integer).limit;
+invariant option::is_some(aggregator) ==> aggregator::spec_aggregator_get_val(option::borrow(aggregator)) <=
+ aggregator::spec_get_limit(option::borrow(aggregator));
+
+
+
+
+
+
+### Function `new_integer`
+
+
+fun new_integer(limit: u128): optional_aggregator::Integer
+
+
+
+
+
+aborts_if false;
+ensures result.limit == limit;
+// This enforces high-level requirement 1:
+ensures result.value == 0;
+
+
+
+
+
+
+### Function `add_integer`
+
+
+fun add_integer(integer: &mut optional_aggregator::Integer, value: u128)
+
+
+
+Check for overflow.
+
+
+aborts_if value > (integer.limit - integer.value);
+aborts_if integer.value + value > MAX_U128;
+ensures integer.value <= integer.limit;
+ensures integer.value == old(integer.value) + value;
+
+
+
+
+
+
+### Function `sub_integer`
+
+
+fun sub_integer(integer: &mut optional_aggregator::Integer, value: u128)
+
+
+
+
+
+aborts_if value > integer.value;
+ensures integer.value == old(integer.value) - value;
+
+
+
+
+
+
+### Function `limit`
+
+
+fun limit(integer: &optional_aggregator::Integer): u128
+
+
+
+
+
+// This enforces high-level requirement 2:
+aborts_if false;
+
+
+
+
+
+
+### Function `read_integer`
+
+
+fun read_integer(integer: &optional_aggregator::Integer): u128
+
+
+
+
+
+// This enforces high-level requirement 2:
+aborts_if false;
+
+
+
+
+
+
+### Function `destroy_integer`
+
+
+fun destroy_integer(integer: optional_aggregator::Integer)
+
+
+
+
+
+// This enforces high-level requirement 2:
+aborts_if false;
+
+
+
+
+
+
+### Function `new`
+
+
+public(friend) fun new(limit: u128, parallelizable: bool): optional_aggregator::OptionalAggregator
+
+
+
+
+
+aborts_if parallelizable && !exists<aggregator_factory::AggregatorFactory>(@aptos_framework);
+ensures parallelizable ==> is_parallelizable(result);
+ensures !parallelizable ==> !is_parallelizable(result);
+ensures optional_aggregator_value(result) == 0;
+ensures optional_aggregator_value(result) <= optional_aggregator_limit(result);
+
+
+
+
+
+
+### Function `switch`
+
+
+public fun switch(optional_aggregator: &mut optional_aggregator::OptionalAggregator)
+
+
+
+
+
+let vec_ref = optional_aggregator.integer.vec;
+aborts_if is_parallelizable(optional_aggregator) && len(vec_ref) != 0;
+aborts_if !is_parallelizable(optional_aggregator) && len(vec_ref) == 0;
+aborts_if !is_parallelizable(optional_aggregator) && !exists<aggregator_factory::AggregatorFactory>(@aptos_framework);
+ensures optional_aggregator_value(optional_aggregator) == optional_aggregator_value(old(optional_aggregator));
+
+
+
+
+
+
+### Function `switch_and_zero_out`
+
+
+fun switch_and_zero_out(optional_aggregator: &mut optional_aggregator::OptionalAggregator)
+
+
+
+Optionlet vec_ref = optional_aggregator.integer.vec;
+aborts_if is_parallelizable(optional_aggregator) && len(vec_ref) != 0;
+aborts_if !is_parallelizable(optional_aggregator) && len(vec_ref) == 0;
+aborts_if !is_parallelizable(optional_aggregator) && !exists<aggregator_factory::AggregatorFactory>(@aptos_framework);
+// This enforces high-level requirement 3:
+ensures is_parallelizable(old(optional_aggregator)) ==> !is_parallelizable(optional_aggregator);
+ensures !is_parallelizable(old(optional_aggregator)) ==> is_parallelizable(optional_aggregator);
+ensures optional_aggregator_value(optional_aggregator) == 0;
+
+
+
+
+
+
+### Function `switch_to_integer_and_zero_out`
+
+
+fun switch_to_integer_and_zero_out(optional_aggregator: &mut optional_aggregator::OptionalAggregator): u128
+
+
+
+The aggregator exists and the integer dosex not exist when Switches from parallelizable to non-parallelizable implementation.
+
+
+let limit = aggregator::spec_get_limit(option::borrow(optional_aggregator.aggregator));
+aborts_if len(optional_aggregator.aggregator.vec) == 0;
+aborts_if len(optional_aggregator.integer.vec) != 0;
+ensures !is_parallelizable(optional_aggregator);
+ensures option::borrow(optional_aggregator.integer).limit == limit;
+ensures option::borrow(optional_aggregator.integer).value == 0;
+
+
+
+
+
+
+### Function `switch_to_aggregator_and_zero_out`
+
+
+fun switch_to_aggregator_and_zero_out(optional_aggregator: &mut optional_aggregator::OptionalAggregator): u128
+
+
+
+The integer exists and the aggregator does not exist when Switches from non-parallelizable to parallelizable implementation.
+The AggregatorFactory is under the @aptos_framework.
+
+
+let limit = option::borrow(optional_aggregator.integer).limit;
+aborts_if len(optional_aggregator.integer.vec) == 0;
+aborts_if !exists<aggregator_factory::AggregatorFactory>(@aptos_framework);
+aborts_if len(optional_aggregator.aggregator.vec) != 0;
+ensures is_parallelizable(optional_aggregator);
+ensures aggregator::spec_get_limit(option::borrow(optional_aggregator.aggregator)) == limit;
+ensures aggregator::spec_aggregator_get_val(option::borrow(optional_aggregator.aggregator)) == 0;
+
+
+
+
+
+
+### Function `destroy`
+
+
+public fun destroy(optional_aggregator: optional_aggregator::OptionalAggregator)
+
+
+
+
+
+aborts_if is_parallelizable(optional_aggregator) && len(optional_aggregator.integer.vec) != 0;
+aborts_if !is_parallelizable(optional_aggregator) && len(optional_aggregator.integer.vec) == 0;
+
+
+
+
+
+
+### Function `destroy_optional_aggregator`
+
+
+fun destroy_optional_aggregator(optional_aggregator: optional_aggregator::OptionalAggregator): u128
+
+
+
+The aggregator exists and the integer does not exist when destroy the aggregator.
+
+
+aborts_if len(optional_aggregator.aggregator.vec) == 0;
+aborts_if len(optional_aggregator.integer.vec) != 0;
+ensures result == aggregator::spec_get_limit(option::borrow(optional_aggregator.aggregator));
+
+
+
+
+
+
+### Function `destroy_optional_integer`
+
+
+fun destroy_optional_integer(optional_aggregator: optional_aggregator::OptionalAggregator): u128
+
+
+
+The integer exists and the aggregator does not exist when destroy the integer.
+
+
+aborts_if len(optional_aggregator.integer.vec) == 0;
+aborts_if len(optional_aggregator.aggregator.vec) != 0;
+ensures result == option::borrow(optional_aggregator.integer).limit;
+
+
+
+
+
+
+
+
+fun optional_aggregator_value(optional_aggregator: OptionalAggregator): u128 {
+ if (is_parallelizable(optional_aggregator)) {
+ aggregator::spec_aggregator_get_val(option::borrow(optional_aggregator.aggregator))
+ } else {
+ option::borrow(optional_aggregator.integer).value
+ }
+}
+
+
+
+
+
+
+
+
+fun optional_aggregator_limit(optional_aggregator: OptionalAggregator): u128 {
+ if (is_parallelizable(optional_aggregator)) {
+ aggregator::spec_get_limit(option::borrow(optional_aggregator.aggregator))
+ } else {
+ option::borrow(optional_aggregator.integer).limit
+ }
+}
+
+
+
+
+
+
+### Function `add`
+
+
+public fun add(optional_aggregator: &mut optional_aggregator::OptionalAggregator, value: u128)
+
+
+
+
+
+include AddAbortsIf;
+ensures ((optional_aggregator_value(optional_aggregator) == optional_aggregator_value(old(optional_aggregator)) + value));
+
+
+
+
+
+
+
+
+schema AddAbortsIf {
+ optional_aggregator: OptionalAggregator;
+ value: u128;
+ aborts_if is_parallelizable(optional_aggregator) && (aggregator::spec_aggregator_get_val(option::borrow(optional_aggregator.aggregator))
+ + value > aggregator::spec_get_limit(option::borrow(optional_aggregator.aggregator)));
+ aborts_if is_parallelizable(optional_aggregator) && (aggregator::spec_aggregator_get_val(option::borrow(optional_aggregator.aggregator))
+ + value > MAX_U128);
+ aborts_if !is_parallelizable(optional_aggregator) &&
+ (option::borrow(optional_aggregator.integer).value + value > MAX_U128);
+ aborts_if !is_parallelizable(optional_aggregator) &&
+ (value > (option::borrow(optional_aggregator.integer).limit - option::borrow(optional_aggregator.integer).value));
+}
+
+
+
+
+
+
+### Function `sub`
+
+
+public fun sub(optional_aggregator: &mut optional_aggregator::OptionalAggregator, value: u128)
+
+
+
+
+
+include SubAbortsIf;
+ensures ((optional_aggregator_value(optional_aggregator) == optional_aggregator_value(old(optional_aggregator)) - value));
+
+
+
+
+
+
+
+
+schema SubAbortsIf {
+ optional_aggregator: OptionalAggregator;
+ value: u128;
+ aborts_if is_parallelizable(optional_aggregator) && (aggregator::spec_aggregator_get_val(option::borrow(optional_aggregator.aggregator))
+ < value);
+ aborts_if !is_parallelizable(optional_aggregator) &&
+ (option::borrow(optional_aggregator.integer).value < value);
+}
+
+
+
+
+
+
+### Function `read`
+
+
+public fun read(optional_aggregator: &optional_aggregator::OptionalAggregator): u128
+
+
+
+
+
+ensures !is_parallelizable(optional_aggregator) ==> result == option::borrow(optional_aggregator.integer).value;
+ensures is_parallelizable(optional_aggregator) ==>
+ result == aggregator::spec_read(option::borrow(optional_aggregator.aggregator));
+
+
+
+[move-book]: https://aptos.dev/move/book/SUMMARY
diff --git a/aptos-move/framework/aptos-framework/tests/compiler-v2-doc/overview.md b/aptos-move/framework/aptos-framework/tests/compiler-v2-doc/overview.md
new file mode 100644
index 0000000000000..314baa3612ba9
--- /dev/null
+++ b/aptos-move/framework/aptos-framework/tests/compiler-v2-doc/overview.md
@@ -0,0 +1,76 @@
+
+
+
+# Aptos Framework
+
+
+This is the reference documentation of the Aptos framework.
+
+
+
+
+## Index
+
+
+- [`0x1::account`](account.md#0x1_account)
+- [`0x1::aggregator`](aggregator.md#0x1_aggregator)
+- [`0x1::aggregator_factory`](aggregator_factory.md#0x1_aggregator_factory)
+- [`0x1::aggregator_v2`](aggregator_v2.md#0x1_aggregator_v2)
+- [`0x1::aptos_account`](aptos_account.md#0x1_aptos_account)
+- [`0x1::aptos_coin`](aptos_coin.md#0x1_aptos_coin)
+- [`0x1::aptos_governance`](aptos_governance.md#0x1_aptos_governance)
+- [`0x1::block`](block.md#0x1_block)
+- [`0x1::chain_id`](chain_id.md#0x1_chain_id)
+- [`0x1::chain_status`](chain_status.md#0x1_chain_status)
+- [`0x1::code`](code.md#0x1_code)
+- [`0x1::coin`](coin.md#0x1_coin)
+- [`0x1::config_buffer`](config_buffer.md#0x1_config_buffer)
+- [`0x1::consensus_config`](consensus_config.md#0x1_consensus_config)
+- [`0x1::create_signer`](create_signer.md#0x1_create_signer)
+- [`0x1::delegation_pool`](delegation_pool.md#0x1_delegation_pool)
+- [`0x1::dispatchable_fungible_asset`](dispatchable_fungible_asset.md#0x1_dispatchable_fungible_asset)
+- [`0x1::dkg`](dkg.md#0x1_dkg)
+- [`0x1::event`](event.md#0x1_event)
+- [`0x1::execution_config`](execution_config.md#0x1_execution_config)
+- [`0x1::function_info`](function_info.md#0x1_function_info)
+- [`0x1::fungible_asset`](fungible_asset.md#0x1_fungible_asset)
+- [`0x1::gas_schedule`](gas_schedule.md#0x1_gas_schedule)
+- [`0x1::genesis`](genesis.md#0x1_genesis)
+- [`0x1::governance_proposal`](governance_proposal.md#0x1_governance_proposal)
+- [`0x1::guid`](guid.md#0x1_guid)
+- [`0x1::jwk_consensus_config`](jwk_consensus_config.md#0x1_jwk_consensus_config)
+- [`0x1::jwks`](jwks.md#0x1_jwks)
+- [`0x1::keyless_account`](keyless_account.md#0x1_keyless_account)
+- [`0x1::managed_coin`](managed_coin.md#0x1_managed_coin)
+- [`0x1::multisig_account`](multisig_account.md#0x1_multisig_account)
+- [`0x1::object`](object.md#0x1_object)
+- [`0x1::object_code_deployment`](object_code_deployment.md#0x1_object_code_deployment)
+- [`0x1::optional_aggregator`](optional_aggregator.md#0x1_optional_aggregator)
+- [`0x1::primary_fungible_store`](primary_fungible_store.md#0x1_primary_fungible_store)
+- [`0x1::randomness`](randomness.md#0x1_randomness)
+- [`0x1::randomness_api_v0_config`](randomness_api_v0_config.md#0x1_randomness_api_v0_config)
+- [`0x1::randomness_config`](randomness_config.md#0x1_randomness_config)
+- [`0x1::randomness_config_seqnum`](randomness_config_seqnum.md#0x1_randomness_config_seqnum)
+- [`0x1::reconfiguration`](reconfiguration.md#0x1_reconfiguration)
+- [`0x1::reconfiguration_state`](reconfiguration_state.md#0x1_reconfiguration_state)
+- [`0x1::reconfiguration_with_dkg`](reconfiguration_with_dkg.md#0x1_reconfiguration_with_dkg)
+- [`0x1::resource_account`](resource_account.md#0x1_resource_account)
+- [`0x1::stake`](stake.md#0x1_stake)
+- [`0x1::staking_config`](staking_config.md#0x1_staking_config)
+- [`0x1::staking_contract`](staking_contract.md#0x1_staking_contract)
+- [`0x1::staking_proxy`](staking_proxy.md#0x1_staking_proxy)
+- [`0x1::state_storage`](state_storage.md#0x1_state_storage)
+- [`0x1::storage_gas`](storage_gas.md#0x1_storage_gas)
+- [`0x1::system_addresses`](system_addresses.md#0x1_system_addresses)
+- [`0x1::timestamp`](timestamp.md#0x1_timestamp)
+- [`0x1::transaction_context`](transaction_context.md#0x1_transaction_context)
+- [`0x1::transaction_fee`](transaction_fee.md#0x1_transaction_fee)
+- [`0x1::transaction_validation`](transaction_validation.md#0x1_transaction_validation)
+- [`0x1::util`](util.md#0x1_util)
+- [`0x1::validator_consensus_info`](validator_consensus_info.md#0x1_validator_consensus_info)
+- [`0x1::version`](version.md#0x1_version)
+- [`0x1::vesting`](vesting.md#0x1_vesting)
+- [`0x1::voting`](voting.md#0x1_voting)
+
+
+[move-book]: https://aptos.dev/move/book/SUMMARY
diff --git a/aptos-move/framework/aptos-framework/tests/compiler-v2-doc/primary_fungible_store.md b/aptos-move/framework/aptos-framework/tests/compiler-v2-doc/primary_fungible_store.md
new file mode 100644
index 0000000000000..0ebfb73e5d659
--- /dev/null
+++ b/aptos-move/framework/aptos-framework/tests/compiler-v2-doc/primary_fungible_store.md
@@ -0,0 +1,955 @@
+
+
+
+# Module `0x1::primary_fungible_store`
+
+This module provides a way for creators of fungible assets to enable support for creating primary (deterministic)
+stores for their users. This is useful for assets that are meant to be used as a currency, as it allows users to
+easily create a store for their account and deposit/withdraw/transfer fungible assets to/from it.
+
+The transfer flow works as below:
+1. The sender calls transfer
on the fungible asset metadata object to transfer amount
of fungible asset to
+recipient
.
+2. The fungible asset metadata object calls ensure_primary_store_exists
to ensure that both the sender's and the
+recipient's primary stores exist. If either doesn't, it will be created.
+3. The fungible asset metadata object calls withdraw
on the sender's primary store to withdraw amount
of
+fungible asset from it. This emits a withdraw event.
+4. The fungible asset metadata object calls deposit
on the recipient's primary store to deposit amount
of
+fungible asset to it. This emits an deposit event.
+
+
+- [Resource `DeriveRefPod`](#0x1_primary_fungible_store_DeriveRefPod)
+- [Function `create_primary_store_enabled_fungible_asset`](#0x1_primary_fungible_store_create_primary_store_enabled_fungible_asset)
+- [Function `ensure_primary_store_exists`](#0x1_primary_fungible_store_ensure_primary_store_exists)
+- [Function `create_primary_store`](#0x1_primary_fungible_store_create_primary_store)
+- [Function `primary_store_address`](#0x1_primary_fungible_store_primary_store_address)
+- [Function `primary_store`](#0x1_primary_fungible_store_primary_store)
+- [Function `primary_store_exists`](#0x1_primary_fungible_store_primary_store_exists)
+- [Function `primary_store_address_inlined`](#0x1_primary_fungible_store_primary_store_address_inlined)
+- [Function `primary_store_inlined`](#0x1_primary_fungible_store_primary_store_inlined)
+- [Function `primary_store_exists_inlined`](#0x1_primary_fungible_store_primary_store_exists_inlined)
+- [Function `balance`](#0x1_primary_fungible_store_balance)
+- [Function `is_balance_at_least`](#0x1_primary_fungible_store_is_balance_at_least)
+- [Function `is_frozen`](#0x1_primary_fungible_store_is_frozen)
+- [Function `withdraw`](#0x1_primary_fungible_store_withdraw)
+- [Function `deposit`](#0x1_primary_fungible_store_deposit)
+- [Function `force_deposit`](#0x1_primary_fungible_store_force_deposit)
+- [Function `transfer`](#0x1_primary_fungible_store_transfer)
+- [Function `transfer_assert_minimum_deposit`](#0x1_primary_fungible_store_transfer_assert_minimum_deposit)
+- [Function `mint`](#0x1_primary_fungible_store_mint)
+- [Function `burn`](#0x1_primary_fungible_store_burn)
+- [Function `set_frozen_flag`](#0x1_primary_fungible_store_set_frozen_flag)
+- [Function `withdraw_with_ref`](#0x1_primary_fungible_store_withdraw_with_ref)
+- [Function `deposit_with_ref`](#0x1_primary_fungible_store_deposit_with_ref)
+- [Function `transfer_with_ref`](#0x1_primary_fungible_store_transfer_with_ref)
+- [Function `may_be_unburn`](#0x1_primary_fungible_store_may_be_unburn)
+- [Specification](#@Specification_0)
+ - [High-level Requirements](#high-level-req)
+ - [Module-level Specification](#module-level-spec)
+
+
+use 0x1::dispatchable_fungible_asset;
+use 0x1::fungible_asset;
+use 0x1::object;
+use 0x1::option;
+use 0x1::signer;
+use 0x1::string;
+
+
+
+
+
+
+## Resource `DeriveRefPod`
+
+A resource that holds the derive ref for the fungible asset metadata object. This is used to create primary
+stores for users with deterministic addresses so that users can easily deposit/withdraw/transfer fungible
+assets.
+
+
+#[resource_group_member(#[group = 0x1::object::ObjectGroup])]
+struct DeriveRefPod has key
+
+
+
+
+metadata_derive_ref: object::DeriveRef
+public fun create_primary_store_enabled_fungible_asset(constructor_ref: &object::ConstructorRef, maximum_supply: option::Option<u128>, name: string::String, symbol: string::String, decimals: u8, icon_uri: string::String, project_uri: string::String)
+
+
+
+
+public fun create_primary_store_enabled_fungible_asset(
+ constructor_ref: &ConstructorRef,
+ maximum_supply: Option<u128>,
+ name: String,
+ symbol: String,
+ decimals: u8,
+ icon_uri: String,
+ project_uri: String,
+) {
+ fungible_asset::add_fungibility(
+ constructor_ref,
+ maximum_supply,
+ name,
+ symbol,
+ decimals,
+ icon_uri,
+ project_uri,
+ );
+ let metadata_obj = &object::generate_signer(constructor_ref);
+ move_to(metadata_obj, DeriveRefPod {
+ metadata_derive_ref: object::generate_derive_ref(constructor_ref),
+ });
+}
+
+
+
+
+public fun ensure_primary_store_exists<T: key>(owner: address, metadata: object::Object<T>): object::Object<fungible_asset::FungibleStore>
+
+
+
+
+public fun ensure_primary_store_exists<T: key>(
+ owner: address,
+ metadata: Object<T>,
+): Object<FungibleStore> acquires DeriveRefPod {
+ let store_addr = primary_store_address(owner, metadata);
+ if (fungible_asset::store_exists(store_addr)) {
+ object::address_to_object(store_addr)
+ } else {
+ create_primary_store(owner, metadata)
+ }
+}
+
+
+
+
+public fun create_primary_store<T: key>(owner_addr: address, metadata: object::Object<T>): object::Object<fungible_asset::FungibleStore>
+
+
+
+
+public fun create_primary_store<T: key>(
+ owner_addr: address,
+ metadata: Object<T>,
+): Object<FungibleStore> acquires DeriveRefPod {
+ let metadata_addr = object::object_address(&metadata);
+ object::address_to_object<Metadata>(metadata_addr);
+ let derive_ref = &borrow_global<DeriveRefPod>(metadata_addr).metadata_derive_ref;
+ let constructor_ref = &object::create_user_derived_object(owner_addr, derive_ref);
+ // Disable ungated transfer as deterministic stores shouldn't be transferrable.
+ let transfer_ref = &object::generate_transfer_ref(constructor_ref);
+ object::disable_ungated_transfer(transfer_ref);
+
+ fungible_asset::create_store(constructor_ref, metadata)
+}
+
+
+
+
+#[view]
+public fun primary_store_address<T: key>(owner: address, metadata: object::Object<T>): address
+
+
+
+
+public fun primary_store_address<T: key>(owner: address, metadata: Object<T>): address {
+ let metadata_addr = object::object_address(&metadata);
+ object::create_user_derived_object_address(owner, metadata_addr)
+}
+
+
+
+
+#[view]
+public fun primary_store<T: key>(owner: address, metadata: object::Object<T>): object::Object<fungible_asset::FungibleStore>
+
+
+
+
+public fun primary_store<T: key>(owner: address, metadata: Object<T>): Object<FungibleStore> {
+ let store = primary_store_address(owner, metadata);
+ object::address_to_object<FungibleStore>(store)
+}
+
+
+
+
+#[view]
+public fun primary_store_exists<T: key>(account: address, metadata: object::Object<T>): bool
+
+
+
+
+public fun primary_store_exists<T: key>(account: address, metadata: Object<T>): bool {
+ fungible_asset::store_exists(primary_store_address(account, metadata))
+}
+
+
+
+
+public fun primary_store_address_inlined<T: key>(owner: address, metadata: object::Object<T>): address
+
+
+
+
+public inline fun primary_store_address_inlined<T: key>(owner: address, metadata: Object<T>): address {
+ let metadata_addr = object::object_address(&metadata);
+ object::create_user_derived_object_address(owner, metadata_addr)
+}
+
+
+
+
+public fun primary_store_inlined<T: key>(owner: address, metadata: object::Object<T>): object::Object<fungible_asset::FungibleStore>
+
+
+
+
+public inline fun primary_store_inlined<T: key>(owner: address, metadata: Object<T>): Object<FungibleStore> {
+ let store = primary_store_address_inlined(owner, metadata);
+ object::address_to_object(store)
+}
+
+
+
+
+public fun primary_store_exists_inlined<T: key>(account: address, metadata: object::Object<T>): bool
+
+
+
+
+public inline fun primary_store_exists_inlined<T: key>(account: address, metadata: Object<T>): bool {
+ fungible_asset::store_exists(primary_store_address_inlined(account, metadata))
+}
+
+
+
+
+account
's primary store.
+
+
+#[view]
+public fun balance<T: key>(account: address, metadata: object::Object<T>): u64
+
+
+
+
+public fun balance<T: key>(account: address, metadata: Object<T>): u64 {
+ if (primary_store_exists(account, metadata)) {
+ fungible_asset::balance(primary_store(account, metadata))
+ } else {
+ 0
+ }
+}
+
+
+
+
+#[view]
+public fun is_balance_at_least<T: key>(account: address, metadata: object::Object<T>, amount: u64): bool
+
+
+
+
+public fun is_balance_at_least<T: key>(account: address, metadata: Object<T>, amount: u64): bool {
+ if (primary_store_exists(account, metadata)) {
+ fungible_asset::is_balance_at_least(primary_store(account, metadata), amount)
+ } else {
+ amount == 0
+ }
+}
+
+
+
+
+#[view]
+public fun is_frozen<T: key>(account: address, metadata: object::Object<T>): bool
+
+
+
+
+public fun is_frozen<T: key>(account: address, metadata: Object<T>): bool {
+ if (primary_store_exists(account, metadata)) {
+ fungible_asset::is_frozen(primary_store(account, metadata))
+ } else {
+ false
+ }
+}
+
+
+
+
+amount
of fungible asset from the given account's primary store.
+
+
+public fun withdraw<T: key>(owner: &signer, metadata: object::Object<T>, amount: u64): fungible_asset::FungibleAsset
+
+
+
+
+public fun withdraw<T: key>(owner: &signer, metadata: Object<T>, amount: u64): FungibleAsset acquires DeriveRefPod {
+ let store = ensure_primary_store_exists(signer::address_of(owner), metadata);
+ // Check if the store object has been burnt or not. If so, unburn it first.
+ may_be_unburn(owner, store);
+ dispatchable_fungible_asset::withdraw(owner, store, amount)
+}
+
+
+
+
+fa
to the given account's primary store.
+
+
+public fun deposit(owner: address, fa: fungible_asset::FungibleAsset)
+
+
+
+
+public fun deposit(owner: address, fa: FungibleAsset) acquires DeriveRefPod {
+ let metadata = fungible_asset::asset_metadata(&fa);
+ let store = ensure_primary_store_exists(owner, metadata);
+ dispatchable_fungible_asset::deposit(store, fa);
+}
+
+
+
+
+fa
to the given account's primary store.
+
+
+public(friend) fun force_deposit(owner: address, fa: fungible_asset::FungibleAsset)
+
+
+
+
+public(friend) fun force_deposit(owner: address, fa: FungibleAsset) acquires DeriveRefPod {
+ let metadata = fungible_asset::asset_metadata(&fa);
+ let store = ensure_primary_store_exists(owner, metadata);
+ fungible_asset::deposit_internal(object::object_address(&store), fa);
+}
+
+
+
+
+amount
of fungible asset from sender's primary store to receiver's primary store.
+
+
+public entry fun transfer<T: key>(sender: &signer, metadata: object::Object<T>, recipient: address, amount: u64)
+
+
+
+
+public entry fun transfer<T: key>(
+ sender: &signer,
+ metadata: Object<T>,
+ recipient: address,
+ amount: u64,
+) acquires DeriveRefPod {
+ let sender_store = ensure_primary_store_exists(signer::address_of(sender), metadata);
+ // Check if the sender store object has been burnt or not. If so, unburn it first.
+ may_be_unburn(sender, sender_store);
+ let recipient_store = ensure_primary_store_exists(recipient, metadata);
+ dispatchable_fungible_asset::transfer(sender, sender_store, recipient_store, amount);
+}
+
+
+
+
+amount
of fungible asset from sender's primary store to receiver's primary store.
+Use the minimum deposit assertion api to make sure receipient will receive a minimum amount of fund.
+
+
+public entry fun transfer_assert_minimum_deposit<T: key>(sender: &signer, metadata: object::Object<T>, recipient: address, amount: u64, expected: u64)
+
+
+
+
+public entry fun transfer_assert_minimum_deposit<T: key>(
+ sender: &signer,
+ metadata: Object<T>,
+ recipient: address,
+ amount: u64,
+ expected: u64,
+) acquires DeriveRefPod {
+ let sender_store = ensure_primary_store_exists(signer::address_of(sender), metadata);
+ // Check if the sender store object has been burnt or not. If so, unburn it first.
+ may_be_unburn(sender, sender_store);
+ let recipient_store = ensure_primary_store_exists(recipient, metadata);
+ dispatchable_fungible_asset::transfer_assert_minimum_deposit(
+ sender,
+ sender_store,
+ recipient_store,
+ amount,
+ expected
+ );
+}
+
+
+
+
+owner
.
+
+
+public fun mint(mint_ref: &fungible_asset::MintRef, owner: address, amount: u64)
+
+
+
+
+public fun mint(mint_ref: &MintRef, owner: address, amount: u64) acquires DeriveRefPod {
+ let primary_store = ensure_primary_store_exists(owner, fungible_asset::mint_ref_metadata(mint_ref));
+ fungible_asset::mint_to(mint_ref, primary_store, amount);
+}
+
+
+
+
+owner
.
+
+
+public fun burn(burn_ref: &fungible_asset::BurnRef, owner: address, amount: u64)
+
+
+
+
+public fun burn(burn_ref: &BurnRef, owner: address, amount: u64) {
+ let primary_store = primary_store(owner, fungible_asset::burn_ref_metadata(burn_ref));
+ fungible_asset::burn_from(burn_ref, primary_store, amount);
+}
+
+
+
+
+owner
.
+
+
+public fun set_frozen_flag(transfer_ref: &fungible_asset::TransferRef, owner: address, frozen: bool)
+
+
+
+
+public fun set_frozen_flag(transfer_ref: &TransferRef, owner: address, frozen: bool) acquires DeriveRefPod {
+ let primary_store = ensure_primary_store_exists(owner, fungible_asset::transfer_ref_metadata(transfer_ref));
+ fungible_asset::set_frozen_flag(transfer_ref, primary_store, frozen);
+}
+
+
+
+
+owner
ignoring frozen flag.
+
+
+public fun withdraw_with_ref(transfer_ref: &fungible_asset::TransferRef, owner: address, amount: u64): fungible_asset::FungibleAsset
+
+
+
+
+public fun withdraw_with_ref(transfer_ref: &TransferRef, owner: address, amount: u64): FungibleAsset {
+ let from_primary_store = primary_store(owner, fungible_asset::transfer_ref_metadata(transfer_ref));
+ fungible_asset::withdraw_with_ref(transfer_ref, from_primary_store, amount)
+}
+
+
+
+
+owner
ignoring frozen flag.
+
+
+public fun deposit_with_ref(transfer_ref: &fungible_asset::TransferRef, owner: address, fa: fungible_asset::FungibleAsset)
+
+
+
+
+public fun deposit_with_ref(transfer_ref: &TransferRef, owner: address, fa: FungibleAsset) acquires DeriveRefPod {
+ let from_primary_store = ensure_primary_store_exists(
+ owner,
+ fungible_asset::transfer_ref_metadata(transfer_ref)
+ );
+ fungible_asset::deposit_with_ref(transfer_ref, from_primary_store, fa);
+}
+
+
+
+
+amount
of FA from the primary store of from
to that of to
ignoring frozen flag.
+
+
+public fun transfer_with_ref(transfer_ref: &fungible_asset::TransferRef, from: address, to: address, amount: u64)
+
+
+
+
+public fun transfer_with_ref(
+ transfer_ref: &TransferRef,
+ from: address,
+ to: address,
+ amount: u64
+) acquires DeriveRefPod {
+ let from_primary_store = primary_store(from, fungible_asset::transfer_ref_metadata(transfer_ref));
+ let to_primary_store = ensure_primary_store_exists(to, fungible_asset::transfer_ref_metadata(transfer_ref));
+ fungible_asset::transfer_with_ref(transfer_ref, from_primary_store, to_primary_store, amount);
+}
+
+
+
+
+fun may_be_unburn(owner: &signer, store: object::Object<fungible_asset::FungibleStore>)
+
+
+
+
+fun may_be_unburn(owner: &signer, store: Object<FungibleStore>) {
+ if (object::is_burnt(store)) {
+ object::unburn(owner, store);
+ };
+}
+
+
+
+
+No. | Requirement | Criticality | Implementation | Enforcement | +
---|---|---|---|---|
1 | +Creating a fungible asset with primary store support should initiate a derived reference and store it under the metadata object. | +Medium | +The function create_primary_store_enabled_fungible_asset makes an existing object, fungible, via the fungible_asset::add_fungibility function and initializes the DeriveRefPod resource by generating a DeriveRef for the object and then stores it under the object address. | +Audited that the DeriveRefPod has been properly initialized and stored under the metadata object. | +
2 | +Fetching and creating a primary fungible store of an asset should only succeed if the object supports primary store. | +Low | +The function create_primary_store is used to create a primary store by borrowing the DeriveRef resource from the object. In case the resource does not exist, creation will fail. The function ensure_primary_store_exists is used to fetch the primary store if it exists, otherwise it will create one via the create_primary function. | +Audited that it aborts if the DeriveRefPod doesn't exist. Audited that it aborts if the FungibleStore resource exists already under the object address. | +
3 | +It should be possible to create a primary store to hold a fungible asset. | +Medium | +The function create_primary_store borrows the DeriveRef resource from DeriveRefPod and then creates the store which is returned. | +Audited that it returns the newly created FungibleStore. | +
4 | +Fetching the balance or the frozen status of a primary store should never abort. | +Low | +The function balance returns the balance of the store, if the store exists, otherwise it returns 0. The function is_frozen returns the frozen flag of the fungible store, if the store exists, otherwise it returns false. | +Audited that the balance function returns the balance of the FungibleStore. Audited that the is_frozen function returns the frozen status of the FungibleStore resource. Audited that it never aborts. | +
5 | +The ability to withdraw, deposit, transfer, mint and burn should only be available for assets with primary store support. | +Medium | +The primary store is fetched before performing either of withdraw, deposit, transfer, mint, burn operation. If the FungibleStore resource doesn't exist the operation will fail. | +Audited that it aborts if the primary store FungibleStore doesn't exist. | +
6 | +The action of depositing a fungible asset of the same type as the store should never fail if the store is not frozen. | +Medium | +The function deposit fetches the owner's store, if it doesn't exist it will be created, and then deposits the fungible asset to it. The function deposit_with_ref fetches the owner's store, if it doesn't exist it will be created, and then deposit the fungible asset via the fungible_asset::deposit_with_ref function. Depositing fails if the metadata of the FungibleStore and FungibleAsset differs. | +Audited that it aborts if the store is frozen (deposit). Audited that the balance of the store is increased by the deposit amount (deposit, deposit_with_ref). Audited that it aborts if the metadata of the store and the asset differs (deposit, deposit_with_ref). | +
7 | +Withdrawing should only be allowed to the owner of an existing store with sufficient balance. | +Critical | +The withdraw function fetches the owner's store via the primary_store function and then calls fungible_asset::withdraw which validates the owner of the store, checks the frozen status and the balance of the store. The withdraw_with_ref function fetches the store of the owner via primary_store function and calls the fungible_asset::withdraw_with_ref which validates transfer_ref's metadata with the withdrawing stores metadata, and the balance of the store. | +Audited that it aborts if the owner doesn't own the store (withdraw). Audited that it aborts if the store is frozen (withdraw). Audited that it aborts if the transfer ref's metadata doesn't match the withdrawing store's metadata (withdraw_with_ref). Audited that it aborts if the store doesn't have sufficient balance. Audited that the store is not burned. Audited that the balance of the store is decreased by the amount withdrawn. | +
8 | +Only the fungible store owner is allowed to unburn a burned store. | +High | +The function may_be_unburn checks if the store is burned and then proceeds to call object::unburn which ensures that the owner of the object matches the address of the signer. | +Audited that the store is unburned successfully. | +
9 | +Only the owner of a primary store can transfer its balance to any recipient's primary store. | +High | +The function transfer fetches sender and recipient's primary stores, if the sender's store is burned it unburns the store and calls the fungile_asset::transfer to proceed with the transfer, which first withdraws the assets from the sender's store and then deposits to the recipient's store. The function transfer_with_ref fetches the sender's and recipient's stores and calls the fungible_asset::transfer_with_ref function which withdraws the asset with the ref from the sender and deposits the asset to the recipient with the ref. | +Audited the deposit and withdraw (transfer). Audited the deposit_with_ref and withdraw_with_ref (transfer_with_ref). Audited that the store balance of the sender is decreased by the specified amount and its added to the recipients store. (transfer, transfer_with_ref) Audited that the sender's store is not burned (transfer). | +
10 | +Minting an amount of assets to an unfrozen store is only allowed with a valid mint reference. | +High | +The mint function fetches the primary store and calls the fungible_asset::mint_to, which mints with MintRef's metadata which internally validates the amount and the increases the total supply of the asset. And the minted asset is deposited to the provided store by validating that the store is unfrozen and the store's metadata is the same as the depositing asset's metadata. | +Audited that it aborts if the amount is equal to 0. Audited that it aborts if the store is frozen. Audited that it aborts if the mint_ref's metadata is not the same as the store's metadata. Audited that the asset's total supply is increased by the amount minted. Audited that the balance of the store is increased by the minted amount. | +
11 | +Burning an amount of assets from an existing unfrozen store is only allowed with a valid burn reference. | +High | +The burn function fetches the primary store and calls the fungible_asset::burn_from function which withdraws the amount from the store while enforcing that the store has enough balance and burns the withdrawn asset after validating the asset's metadata and the BurnRef's metadata followed by decreasing the supply of the asset. | +Audited that it aborts if the metadata of the store is not same as the BurnRef's metadata. Audited that it aborts if the burning amount is 0. Audited that it aborts if the store doesn't have enough balance. Audited that it aborts if the asset's metadata is not same as the BurnRef's metadata. Audited that the total supply of the asset is decreased. Audited that the store's balance is reduced by the amount burned. | +
12 | +Setting the frozen flag of a store is only allowed with a valid reference. | +High | +The function set_frozen_flag fetches the primary store and calls fungible_asset::set_frozen_flag which validates the TransferRef's metadata with the store's metadata and then updates the frozen flag. | +Audited that it aborts if the store's metadata is not same as the TransferRef's metadata. Audited that the status of the frozen flag is updated correctly. | +
pragma verify = false;
+
+
+
+
+
+
+
+
+fun spec_primary_store_exists<T: key>(account: address, metadata: Object<T>): bool {
+ fungible_asset::store_exists(spec_primary_store_address(account, metadata))
+}
+
+
+
+
+
+
+
+
+fun spec_primary_store_address<T: key>(owner: address, metadata: Object<T>): address {
+ let metadata_addr = object::object_address(metadata);
+ object::spec_create_user_derived_object_address(owner, metadata_addr)
+}
+
+
+
+[move-book]: https://aptos.dev/move/book/SUMMARY
diff --git a/aptos-move/framework/aptos-framework/tests/compiler-v2-doc/randomness.md b/aptos-move/framework/aptos-framework/tests/compiler-v2-doc/randomness.md
new file mode 100644
index 0000000000000..692e0ed3a10b5
--- /dev/null
+++ b/aptos-move/framework/aptos-framework/tests/compiler-v2-doc/randomness.md
@@ -0,0 +1,1315 @@
+
+
+
+# Module `0x1::randomness`
+
+This module provides access to *instant* secure randomness generated by the Aptos validators, as documented in
+[AIP-41](https://github.com/aptos-foundation/AIPs/blob/main/aips/aip-41.md).
+
+Secure randomness means (1) the randomness cannot be predicted ahead of time by validators, developers or users
+and (2) the randomness cannot be biased in any way by validators, developers or users.
+
+Security holds under the same proof-of-stake assumption that secures the Aptos network.
+
+
+- [Resource `PerBlockRandomness`](#0x1_randomness_PerBlockRandomness)
+- [Struct `RandomnessGeneratedEvent`](#0x1_randomness_RandomnessGeneratedEvent)
+- [Resource `Ghost$var`](#0x1_randomness_Ghost$var)
+- [Constants](#@Constants_0)
+- [Function `initialize`](#0x1_randomness_initialize)
+- [Function `on_new_block`](#0x1_randomness_on_new_block)
+- [Function `next_32_bytes`](#0x1_randomness_next_32_bytes)
+- [Function `bytes`](#0x1_randomness_bytes)
+- [Function `u8_integer`](#0x1_randomness_u8_integer)
+- [Function `u16_integer`](#0x1_randomness_u16_integer)
+- [Function `u32_integer`](#0x1_randomness_u32_integer)
+- [Function `u64_integer`](#0x1_randomness_u64_integer)
+- [Function `u128_integer`](#0x1_randomness_u128_integer)
+- [Function `u256_integer`](#0x1_randomness_u256_integer)
+- [Function `u256_integer_internal`](#0x1_randomness_u256_integer_internal)
+- [Function `u8_range`](#0x1_randomness_u8_range)
+- [Function `u16_range`](#0x1_randomness_u16_range)
+- [Function `u32_range`](#0x1_randomness_u32_range)
+- [Function `u64_range`](#0x1_randomness_u64_range)
+- [Function `u64_range_internal`](#0x1_randomness_u64_range_internal)
+- [Function `u128_range`](#0x1_randomness_u128_range)
+- [Function `u256_range`](#0x1_randomness_u256_range)
+- [Function `permutation`](#0x1_randomness_permutation)
+- [Function `safe_add_mod`](#0x1_randomness_safe_add_mod)
+- [Function `fetch_and_increment_txn_counter`](#0x1_randomness_fetch_and_increment_txn_counter)
+- [Function `is_unbiasable`](#0x1_randomness_is_unbiasable)
+- [Specification](#@Specification_1)
+ - [Function `initialize`](#@Specification_1_initialize)
+ - [Function `on_new_block`](#@Specification_1_on_new_block)
+ - [Function `next_32_bytes`](#@Specification_1_next_32_bytes)
+ - [Function `u8_integer`](#@Specification_1_u8_integer)
+ - [Function `u16_integer`](#@Specification_1_u16_integer)
+ - [Function `u32_integer`](#@Specification_1_u32_integer)
+ - [Function `u64_integer`](#@Specification_1_u64_integer)
+ - [Function `u128_integer`](#@Specification_1_u128_integer)
+ - [Function `u256_integer`](#@Specification_1_u256_integer)
+ - [Function `u256_integer_internal`](#@Specification_1_u256_integer_internal)
+ - [Function `u8_range`](#@Specification_1_u8_range)
+ - [Function `u64_range`](#@Specification_1_u64_range)
+ - [Function `u256_range`](#@Specification_1_u256_range)
+ - [Function `permutation`](#@Specification_1_permutation)
+ - [Function `fetch_and_increment_txn_counter`](#@Specification_1_fetch_and_increment_txn_counter)
+ - [Function `is_unbiasable`](#@Specification_1_is_unbiasable)
+
+
+use 0x1::event;
+use 0x1::hash;
+use 0x1::option;
+use 0x1::system_addresses;
+use 0x1::transaction_context;
+use 0x1::vector;
+
+
+
+
+
+
+## Resource `PerBlockRandomness`
+
+32-byte randomness seed unique to every block.
+This resource is updated in every block prologue.
+
+
+struct PerBlockRandomness has drop, key
+
+
+
+
+epoch: u64
+round: u64
+seed: option::Option<vector<u8>>
+#[event]
+struct RandomnessGeneratedEvent has drop, store
+
+
+
+
+dummy_field: bool
+struct Ghost$var has copy, drop, store, key
+
+
+
+
+v: vector<u8>
+const MAX_U256: u256 = 115792089237316195423570985008687907853269984665640564039457584007913129639935;
+
+
+
+
+
+
+
+
+const DST: vector<u8> = [65, 80, 84, 79, 83, 95, 82, 65, 78, 68, 79, 77, 78, 69, 83, 83];
+
+
+
+
+
+
+Randomness APIs calls must originate from a private entry function with
+#[randomness]
annotation. Otherwise, malicious users can bias randomness result.
+
+
+const E_API_USE_IS_BIASIBLE: u64 = 1;
+
+
+
+
+
+
+## Function `initialize`
+
+Called in genesis.move.
+Must be called in tests to initialize the PerBlockRandomness
resource.
+
+
+public fun initialize(framework: &signer)
+
+
+
+
+public fun initialize(framework: &signer) {
+ system_addresses::assert_aptos_framework(framework);
+ if (!exists<PerBlockRandomness>(@aptos_framework)) {
+ move_to(framework, PerBlockRandomness {
+ epoch: 0,
+ round: 0,
+ seed: option::none(),
+ });
+ }
+}
+
+
+
+
+public(friend) fun on_new_block(vm: &signer, epoch: u64, round: u64, seed_for_new_block: option::Option<vector<u8>>)
+
+
+
+
+public(friend) fun on_new_block(vm: &signer, epoch: u64, round: u64, seed_for_new_block: Option<vector<u8>>) acquires PerBlockRandomness {
+ system_addresses::assert_vm(vm);
+ if (exists<PerBlockRandomness>(@aptos_framework)) {
+ let randomness = borrow_global_mut<PerBlockRandomness>(@aptos_framework);
+ randomness.epoch = epoch;
+ randomness.round = round;
+ randomness.seed = seed_for_new_block;
+ }
+}
+
+
+
+
+fun next_32_bytes(): vector<u8>
+
+
+
+
+fun next_32_bytes(): vector<u8> acquires PerBlockRandomness {
+ assert!(is_unbiasable(), E_API_USE_IS_BIASIBLE);
+
+ let input = DST;
+ let randomness = borrow_global<PerBlockRandomness>(@aptos_framework);
+ let seed = *option::borrow(&randomness.seed);
+
+ vector::append(&mut input, seed);
+ vector::append(&mut input, transaction_context::get_transaction_hash());
+ vector::append(&mut input, fetch_and_increment_txn_counter());
+ hash::sha3_256(input)
+}
+
+
+
+
+public fun bytes(n: u64): vector<u8>
+
+
+
+
+public fun bytes(n: u64): vector<u8> acquires PerBlockRandomness {
+ let v = vector[];
+ let c = 0;
+ while (c < n) {
+ let blob = next_32_bytes();
+ vector::append(&mut v, blob);
+
+ c = c + 32;
+ };
+
+ if (c > n) {
+ vector::trim(&mut v, n);
+ };
+
+ event::emit(RandomnessGeneratedEvent {});
+
+ v
+}
+
+
+
+
+public fun u8_integer(): u8
+
+
+
+
+public fun u8_integer(): u8 acquires PerBlockRandomness {
+ let raw = next_32_bytes();
+ let ret: u8 = vector::pop_back(&mut raw);
+
+ event::emit(RandomnessGeneratedEvent {});
+
+ ret
+}
+
+
+
+
+public fun u16_integer(): u16
+
+
+
+
+public fun u16_integer(): u16 acquires PerBlockRandomness {
+ let raw = next_32_bytes();
+ let i = 0;
+ let ret: u16 = 0;
+ while (i < 2) {
+ ret = ret * 256 + (vector::pop_back(&mut raw) as u16);
+ i = i + 1;
+ };
+
+ event::emit(RandomnessGeneratedEvent {});
+
+ ret
+}
+
+
+
+
+public fun u32_integer(): u32
+
+
+
+
+public fun u32_integer(): u32 acquires PerBlockRandomness {
+ let raw = next_32_bytes();
+ let i = 0;
+ let ret: u32 = 0;
+ while (i < 4) {
+ ret = ret * 256 + (vector::pop_back(&mut raw) as u32);
+ i = i + 1;
+ };
+
+ event::emit(RandomnessGeneratedEvent {});
+
+ ret
+}
+
+
+
+
+public fun u64_integer(): u64
+
+
+
+
+public fun u64_integer(): u64 acquires PerBlockRandomness {
+ let raw = next_32_bytes();
+ let i = 0;
+ let ret: u64 = 0;
+ while (i < 8) {
+ ret = ret * 256 + (vector::pop_back(&mut raw) as u64);
+ i = i + 1;
+ };
+
+ event::emit(RandomnessGeneratedEvent {});
+
+ ret
+}
+
+
+
+
+public fun u128_integer(): u128
+
+
+
+
+public fun u128_integer(): u128 acquires PerBlockRandomness {
+ let raw = next_32_bytes();
+ let i = 0;
+ let ret: u128 = 0;
+ while (i < 16) {
+ ret = ret * 256 + (vector::pop_back(&mut raw) as u128);
+ i = i + 1;
+ };
+
+ event::emit(RandomnessGeneratedEvent {});
+
+ ret
+}
+
+
+
+
+public fun u256_integer(): u256
+
+
+
+
+public fun u256_integer(): u256 acquires PerBlockRandomness {
+ event::emit(RandomnessGeneratedEvent {});
+ u256_integer_internal()
+}
+
+
+
+
+fun u256_integer_internal(): u256
+
+
+
+
+fun u256_integer_internal(): u256 acquires PerBlockRandomness {
+ let raw = next_32_bytes();
+ let i = 0;
+ let ret: u256 = 0;
+ while (i < 32) {
+ ret = ret * 256 + (vector::pop_back(&mut raw) as u256);
+ i = i + 1;
+ };
+ ret
+}
+
+
+
+
+public fun u8_range(min_incl: u8, max_excl: u8): u8
+
+
+
+
+public fun u8_range(min_incl: u8, max_excl: u8): u8 acquires PerBlockRandomness {
+ let range = ((max_excl - min_incl) as u256);
+ let sample = ((u256_integer_internal() % range) as u8);
+
+ event::emit(RandomnessGeneratedEvent {});
+
+ min_incl + sample
+}
+
+
+
+
+public fun u16_range(min_incl: u16, max_excl: u16): u16
+
+
+
+
+public fun u16_range(min_incl: u16, max_excl: u16): u16 acquires PerBlockRandomness {
+ let range = ((max_excl - min_incl) as u256);
+ let sample = ((u256_integer_internal() % range) as u16);
+
+ event::emit(RandomnessGeneratedEvent {});
+
+ min_incl + sample
+}
+
+
+
+
+public fun u32_range(min_incl: u32, max_excl: u32): u32
+
+
+
+
+public fun u32_range(min_incl: u32, max_excl: u32): u32 acquires PerBlockRandomness {
+ let range = ((max_excl - min_incl) as u256);
+ let sample = ((u256_integer_internal() % range) as u32);
+
+ event::emit(RandomnessGeneratedEvent {});
+
+ min_incl + sample
+}
+
+
+
+
+public fun u64_range(min_incl: u64, max_excl: u64): u64
+
+
+
+
+public fun u64_range(min_incl: u64, max_excl: u64): u64 acquires PerBlockRandomness {
+ event::emit(RandomnessGeneratedEvent {});
+
+ u64_range_internal(min_incl, max_excl)
+}
+
+
+
+
+public fun u64_range_internal(min_incl: u64, max_excl: u64): u64
+
+
+
+
+public fun u64_range_internal(min_incl: u64, max_excl: u64): u64 acquires PerBlockRandomness {
+ let range = ((max_excl - min_incl) as u256);
+ let sample = ((u256_integer_internal() % range) as u64);
+
+ min_incl + sample
+}
+
+
+
+
+public fun u128_range(min_incl: u128, max_excl: u128): u128
+
+
+
+
+public fun u128_range(min_incl: u128, max_excl: u128): u128 acquires PerBlockRandomness {
+ let range = ((max_excl - min_incl) as u256);
+ let sample = ((u256_integer_internal() % range) as u128);
+
+ event::emit(RandomnessGeneratedEvent {});
+
+ min_incl + sample
+}
+
+
+
+
+u256_integer()
+ rejection sampling.
+
+
+public fun u256_range(min_incl: u256, max_excl: u256): u256
+
+
+
+
+public fun u256_range(min_incl: u256, max_excl: u256): u256 acquires PerBlockRandomness {
+ let range = max_excl - min_incl;
+ let r0 = u256_integer_internal();
+ let r1 = u256_integer_internal();
+
+ // Will compute sample := (r0 + r1*2^256) % range.
+
+ let sample = r1 % range;
+ let i = 0;
+ while ({
+ spec {
+ invariant sample >= 0 && sample < max_excl - min_incl;
+ };
+ i < 256
+ }) {
+ sample = safe_add_mod(sample, sample, range);
+ i = i + 1;
+ };
+
+ let sample = safe_add_mod(sample, r0 % range, range);
+ spec {
+ assert sample >= 0 && sample < max_excl - min_incl;
+ };
+
+ event::emit(RandomnessGeneratedEvent {});
+
+ min_incl + sample
+}
+
+
+
+
+[0, 1, ..., n-1]
uniformly at random.
+If n is 0, returns the empty vector.
+
+
+public fun permutation(n: u64): vector<u64>
+
+
+
+
+public fun permutation(n: u64): vector<u64> acquires PerBlockRandomness {
+ let values = vector[];
+
+ if(n == 0) {
+ return vector[]
+ };
+
+ // Initialize into [0, 1, ..., n-1].
+ let i = 0;
+ while ({
+ spec {
+ invariant i <= n;
+ invariant len(values) == i;
+ };
+ i < n
+ }) {
+ std::vector::push_back(&mut values, i);
+ i = i + 1;
+ };
+ spec {
+ assert len(values) == n;
+ };
+
+ // Shuffle.
+ let tail = n - 1;
+ while ({
+ spec {
+ invariant tail >= 0 && tail < len(values);
+ };
+ tail > 0
+ }) {
+ let pop_position = u64_range_internal(0, tail + 1);
+ spec {
+ assert pop_position < len(values);
+ };
+ std::vector::swap(&mut values, pop_position, tail);
+ tail = tail - 1;
+ };
+
+ event::emit(RandomnessGeneratedEvent {});
+
+ values
+}
+
+
+
+
+(a + b) % m
, assuming m >= 1, 0 <= a < m, 0<= b < m
.
+
+
+fun safe_add_mod(a: u256, b: u256, m: u256): u256
+
+
+
+
+inline fun safe_add_mod(a: u256, b: u256, m: u256): u256 {
+ let neg_b = m - b;
+ if (a < neg_b) {
+ a + b
+ } else {
+ a - neg_b
+ }
+}
+
+
+
+
+E_API_USE_SUSCEPTIBLE_TO_TEST_AND_ABORT
if randomness is not unbiasable.
+
+
+fun fetch_and_increment_txn_counter(): vector<u8>
+
+
+
+
+native fun fetch_and_increment_txn_counter(): vector<u8>;
+
+
+
+
+#[randomness]
annotation.
+
+
+fun is_unbiasable(): bool
+
+
+
+
+native fun is_unbiasable(): bool;
+
+
+
+
+pragma verify = true;
+invariant [suspendable] chain_status::is_operating() ==> exists<PerBlockRandomness>(@aptos_framework);
+
+global var: vector<u8>;
+
+
+
+
+
+
+### Function `initialize`
+
+
+public fun initialize(framework: &signer)
+
+
+
+
+
+let framework_addr = signer::address_of(framework);
+aborts_if framework_addr != @aptos_framework;
+
+
+
+
+
+
+### Function `on_new_block`
+
+
+public(friend) fun on_new_block(vm: &signer, epoch: u64, round: u64, seed_for_new_block: option::Option<vector<u8>>)
+
+
+
+
+
+aborts_if signer::address_of(vm) != @vm;
+ensures exists<PerBlockRandomness>(@aptos_framework) ==> global<PerBlockRandomness>(@aptos_framework).seed == seed_for_new_block;
+ensures exists<PerBlockRandomness>(@aptos_framework) ==> global<PerBlockRandomness>(@aptos_framework).epoch == epoch;
+ensures exists<PerBlockRandomness>(@aptos_framework) ==> global<PerBlockRandomness>(@aptos_framework).round == round;
+
+
+
+
+
+
+### Function `next_32_bytes`
+
+
+fun next_32_bytes(): vector<u8>
+
+
+
+
+
+include NextBlobAbortsIf;
+let input = b"APTOS_RANDOMNESS";
+let randomness = global<PerBlockRandomness>(@aptos_framework);
+let seed = option::spec_borrow(randomness.seed);
+let txn_hash = transaction_context::spec_get_txn_hash();
+let txn_counter = spec_fetch_and_increment_txn_counter();
+ensures len(result) == 32;
+ensures result == hash::sha3_256(concat(concat(concat(input, seed), txn_hash), txn_counter));
+
+
+
+
+
+
+
+
+schema NextBlobAbortsIf {
+ let randomness = global<PerBlockRandomness>(@aptos_framework);
+ aborts_if option::spec_is_none(randomness.seed);
+ aborts_if !spec_is_unbiasable();
+ aborts_if !exists<PerBlockRandomness>(@aptos_framework);
+}
+
+
+
+
+
+
+### Function `u8_integer`
+
+
+public fun u8_integer(): u8
+
+
+
+
+
+include NextBlobAbortsIf;
+
+
+
+
+
+
+### Function `u16_integer`
+
+
+public fun u16_integer(): u16
+
+
+
+
+
+pragma unroll = 2;
+include NextBlobAbortsIf;
+
+
+
+
+
+
+### Function `u32_integer`
+
+
+public fun u32_integer(): u32
+
+
+
+
+
+pragma unroll = 4;
+include NextBlobAbortsIf;
+
+
+
+
+
+
+### Function `u64_integer`
+
+
+public fun u64_integer(): u64
+
+
+
+
+
+pragma unroll = 8;
+include NextBlobAbortsIf;
+
+
+
+
+
+
+### Function `u128_integer`
+
+
+public fun u128_integer(): u128
+
+
+
+
+
+pragma unroll = 16;
+include NextBlobAbortsIf;
+
+
+
+
+
+
+### Function `u256_integer`
+
+
+public fun u256_integer(): u256
+
+
+
+
+
+pragma verify_duration_estimate = 300;
+pragma unroll = 32;
+include NextBlobAbortsIf;
+ensures [abstract] result == spec_u256_integer();
+
+
+
+
+
+
+### Function `u256_integer_internal`
+
+
+fun u256_integer_internal(): u256
+
+
+
+
+
+pragma verify_duration_estimate = 300;
+pragma unroll = 32;
+include NextBlobAbortsIf;
+
+
+
+
+
+
+
+
+fun spec_u256_integer(): u256;
+
+
+
+
+
+
+### Function `u8_range`
+
+
+public fun u8_range(min_incl: u8, max_excl: u8): u8
+
+
+
+
+
+pragma verify_duration_estimate = 120;
+pragma opaque;
+include NextBlobAbortsIf;
+aborts_if min_incl >= max_excl;
+ensures result >= min_incl && result < max_excl;
+
+
+
+
+
+
+### Function `u64_range`
+
+
+public fun u64_range(min_incl: u64, max_excl: u64): u64
+
+
+
+
+
+pragma verify_duration_estimate = 120;
+include NextBlobAbortsIf;
+aborts_if min_incl >= max_excl;
+ensures result >= min_incl && result < max_excl;
+
+
+
+
+
+
+### Function `u256_range`
+
+
+public fun u256_range(min_incl: u256, max_excl: u256): u256
+
+
+
+
+
+pragma verify_duration_estimate = 120;
+include NextBlobAbortsIf;
+aborts_if min_incl >= max_excl;
+ensures result >= min_incl && result < max_excl;
+
+
+
+
+
+
+### Function `permutation`
+
+
+public fun permutation(n: u64): vector<u64>
+
+
+
+
+
+pragma aborts_if_is_partial;
+
+
+
+
+
+
+
+
+fun spec_safe_add_mod(a: u256, b: u256, m: u256): u256 {
+ if (a < m - b) {
+ a + b
+ } else {
+ a - (m - b)
+ }
+}
+
+
+
+
+
+
+### Function `fetch_and_increment_txn_counter`
+
+
+fun fetch_and_increment_txn_counter(): vector<u8>
+
+
+
+
+
+pragma opaque;
+aborts_if [abstract] false;
+ensures [abstract] result == spec_fetch_and_increment_txn_counter();
+
+
+
+
+
+
+
+
+fun spec_fetch_and_increment_txn_counter(): vector<u8>;
+
+
+
+
+
+
+### Function `is_unbiasable`
+
+
+fun is_unbiasable(): bool
+
+
+
+
+
+pragma opaque;
+aborts_if [abstract] false;
+ensures [abstract] result == spec_is_unbiasable();
+
+
+
+
+
+
+
+
+fun spec_is_unbiasable(): bool;
+
+
+
+[move-book]: https://aptos.dev/move/book/SUMMARY
diff --git a/aptos-move/framework/aptos-framework/tests/compiler-v2-doc/randomness_api_v0_config.md b/aptos-move/framework/aptos-framework/tests/compiler-v2-doc/randomness_api_v0_config.md
new file mode 100644
index 0000000000000..71b3ada332a5e
--- /dev/null
+++ b/aptos-move/framework/aptos-framework/tests/compiler-v2-doc/randomness_api_v0_config.md
@@ -0,0 +1,211 @@
+
+
+
+# Module `0x1::randomness_api_v0_config`
+
+
+
+- [Resource `RequiredGasDeposit`](#0x1_randomness_api_v0_config_RequiredGasDeposit)
+- [Resource `AllowCustomMaxGasFlag`](#0x1_randomness_api_v0_config_AllowCustomMaxGasFlag)
+- [Function `initialize`](#0x1_randomness_api_v0_config_initialize)
+- [Function `set_for_next_epoch`](#0x1_randomness_api_v0_config_set_for_next_epoch)
+- [Function `set_allow_max_gas_flag_for_next_epoch`](#0x1_randomness_api_v0_config_set_allow_max_gas_flag_for_next_epoch)
+- [Function `on_new_epoch`](#0x1_randomness_api_v0_config_on_new_epoch)
+- [Specification](#@Specification_0)
+
+
+use 0x1::chain_status;
+use 0x1::config_buffer;
+use 0x1::option;
+use 0x1::system_addresses;
+
+
+
+
+
+
+## Resource `RequiredGasDeposit`
+
+
+
+struct RequiredGasDeposit has drop, store, key
+
+
+
+
+gas_amount: option::Option<u64>
+max_gas
specified inside #[randomness()]
will be used as the required deposit.
+
+
+struct AllowCustomMaxGasFlag has drop, store, key
+
+
+
+
+value: bool
+fun initialize(framework: &signer, required_amount: randomness_api_v0_config::RequiredGasDeposit, allow_custom_max_gas_flag: randomness_api_v0_config::AllowCustomMaxGasFlag)
+
+
+
+
+fun initialize(framework: &signer, required_amount: RequiredGasDeposit, allow_custom_max_gas_flag: AllowCustomMaxGasFlag) {
+ system_addresses::assert_aptos_framework(framework);
+ chain_status::assert_genesis();
+ move_to(framework, required_amount);
+ move_to(framework, allow_custom_max_gas_flag);
+}
+
+
+
+
+RequiredGasDeposit
for the next epoch.
+
+
+public fun set_for_next_epoch(framework: &signer, gas_amount: option::Option<u64>)
+
+
+
+
+public fun set_for_next_epoch(framework: &signer, gas_amount: Option<u64>) {
+ system_addresses::assert_aptos_framework(framework);
+ config_buffer::upsert(RequiredGasDeposit { gas_amount });
+}
+
+
+
+
+AllowCustomMaxGasFlag
for the next epoch.
+
+
+public fun set_allow_max_gas_flag_for_next_epoch(framework: &signer, value: bool)
+
+
+
+
+public fun set_allow_max_gas_flag_for_next_epoch(framework: &signer, value: bool) {
+ system_addresses::assert_aptos_framework(framework);
+ config_buffer::upsert(AllowCustomMaxGasFlag { value } );
+}
+
+
+
+
+RequiredGasDeposit
, if there is any.
+
+
+public fun on_new_epoch(framework: &signer)
+
+
+
+
+public fun on_new_epoch(framework: &signer) acquires RequiredGasDeposit, AllowCustomMaxGasFlag {
+ system_addresses::assert_aptos_framework(framework);
+ if (config_buffer::does_exist<RequiredGasDeposit>()) {
+ let new_config = config_buffer::extract<RequiredGasDeposit>();
+ if (exists<RequiredGasDeposit>(@aptos_framework)) {
+ *borrow_global_mut<RequiredGasDeposit>(@aptos_framework) = new_config;
+ } else {
+ move_to(framework, new_config);
+ }
+ };
+ if (config_buffer::does_exist<AllowCustomMaxGasFlag>()) {
+ let new_config = config_buffer::extract<AllowCustomMaxGasFlag>();
+ if (exists<AllowCustomMaxGasFlag>(@aptos_framework)) {
+ *borrow_global_mut<AllowCustomMaxGasFlag>(@aptos_framework) = new_config;
+ } else {
+ move_to(framework, new_config);
+ }
+ }
+}
+
+
+
+
+pragma verify = false;
+
+
+
+[move-book]: https://aptos.dev/move/book/SUMMARY
diff --git a/aptos-move/framework/aptos-framework/tests/compiler-v2-doc/randomness_config.md b/aptos-move/framework/aptos-framework/tests/compiler-v2-doc/randomness_config.md
new file mode 100644
index 0000000000000..37299fe41b95f
--- /dev/null
+++ b/aptos-move/framework/aptos-framework/tests/compiler-v2-doc/randomness_config.md
@@ -0,0 +1,463 @@
+
+
+
+# Module `0x1::randomness_config`
+
+Structs and functions for on-chain randomness configurations.
+
+
+- [Resource `RandomnessConfig`](#0x1_randomness_config_RandomnessConfig)
+- [Struct `ConfigOff`](#0x1_randomness_config_ConfigOff)
+- [Struct `ConfigV1`](#0x1_randomness_config_ConfigV1)
+- [Struct `ConfigV2`](#0x1_randomness_config_ConfigV2)
+- [Constants](#@Constants_0)
+- [Function `initialize`](#0x1_randomness_config_initialize)
+- [Function `set_for_next_epoch`](#0x1_randomness_config_set_for_next_epoch)
+- [Function `on_new_epoch`](#0x1_randomness_config_on_new_epoch)
+- [Function `enabled`](#0x1_randomness_config_enabled)
+- [Function `new_off`](#0x1_randomness_config_new_off)
+- [Function `new_v1`](#0x1_randomness_config_new_v1)
+- [Function `new_v2`](#0x1_randomness_config_new_v2)
+- [Function `current`](#0x1_randomness_config_current)
+- [Specification](#@Specification_1)
+ - [Function `on_new_epoch`](#@Specification_1_on_new_epoch)
+ - [Function `current`](#@Specification_1_current)
+
+
+use 0x1::config_buffer;
+use 0x1::copyable_any;
+use 0x1::fixed_point64;
+use 0x1::string;
+use 0x1::system_addresses;
+
+
+
+
+
+
+## Resource `RandomnessConfig`
+
+The configuration of the on-chain randomness feature.
+
+
+struct RandomnessConfig has copy, drop, store, key
+
+
+
+
+variant: copyable_any::Any
+Any
.
+ Currently the variant type is one of the following.
+ - ConfigOff
+ - ConfigV1
+struct ConfigOff has copy, drop, store
+
+
+
+
+dummy_field: bool
+struct ConfigV1 has copy, drop, store
+
+
+
+
+secrecy_threshold: fixed_point64::FixedPoint64
+subset_power / total_power <= secrecy_threshold
,
+reconstruction_threshold: fixed_point64::FixedPoint64
+subset_power / total_power > reconstruction_threshold
.
+struct ConfigV2 has copy, drop, store
+
+
+
+
+secrecy_threshold: fixed_point64::FixedPoint64
+subset_power / total_power <= secrecy_threshold
,
+reconstruction_threshold: fixed_point64::FixedPoint64
+subset_power / total_power > reconstruction_threshold
.
+fast_path_secrecy_threshold: fixed_point64::FixedPoint64
+subset_power / total_power <= fast_path_secrecy_threshold
,
+const EINVALID_CONFIG_VARIANT: u64 = 1;
+
+
+
+
+
+
+## Function `initialize`
+
+Initialize the configuration. Used in genesis or governance.
+
+
+public fun initialize(framework: &signer, config: randomness_config::RandomnessConfig)
+
+
+
+
+public fun initialize(framework: &signer, config: RandomnessConfig) {
+ system_addresses::assert_aptos_framework(framework);
+ if (!exists<RandomnessConfig>(@aptos_framework)) {
+ move_to(framework, config)
+ }
+}
+
+
+
+
+public fun set_for_next_epoch(framework: &signer, new_config: randomness_config::RandomnessConfig)
+
+
+
+
+public fun set_for_next_epoch(framework: &signer, new_config: RandomnessConfig) {
+ system_addresses::assert_aptos_framework(framework);
+ config_buffer::upsert(new_config);
+}
+
+
+
+
+RandomnessConfig
, if there is any.
+
+
+public(friend) fun on_new_epoch(framework: &signer)
+
+
+
+
+public(friend) fun on_new_epoch(framework: &signer) acquires RandomnessConfig {
+ system_addresses::assert_aptos_framework(framework);
+ if (config_buffer::does_exist<RandomnessConfig>()) {
+ let new_config = config_buffer::extract<RandomnessConfig>();
+ if (exists<RandomnessConfig>(@aptos_framework)) {
+ *borrow_global_mut<RandomnessConfig>(@aptos_framework) = new_config;
+ } else {
+ move_to(framework, new_config);
+ }
+ }
+}
+
+
+
+
+DKGManager
, RandManager
, BlockMetadataExt
) is enabled.
+
+NOTE: this returning true does not mean randomness will run.
+The feature works if and only if consensus_config::validator_txn_enabled() && randomness_config::enabled()
.
+
+
+public fun enabled(): bool
+
+
+
+
+public fun enabled(): bool acquires RandomnessConfig {
+ if (exists<RandomnessConfig>(@aptos_framework)) {
+ let config = borrow_global<RandomnessConfig>(@aptos_framework);
+ let variant_type_name = *string::bytes(copyable_any::type_name(&config.variant));
+ variant_type_name != b"0x1::randomness_config::ConfigOff"
+ } else {
+ false
+ }
+}
+
+
+
+
+ConfigOff
variant.
+
+
+public fun new_off(): randomness_config::RandomnessConfig
+
+
+
+
+public fun new_off(): RandomnessConfig {
+ RandomnessConfig {
+ variant: copyable_any::pack( ConfigOff {} )
+ }
+}
+
+
+
+
+ConfigV1
variant.
+
+
+public fun new_v1(secrecy_threshold: fixed_point64::FixedPoint64, reconstruction_threshold: fixed_point64::FixedPoint64): randomness_config::RandomnessConfig
+
+
+
+
+public fun new_v1(secrecy_threshold: FixedPoint64, reconstruction_threshold: FixedPoint64): RandomnessConfig {
+ RandomnessConfig {
+ variant: copyable_any::pack( ConfigV1 {
+ secrecy_threshold,
+ reconstruction_threshold
+ } )
+ }
+}
+
+
+
+
+ConfigV2
variant.
+
+
+public fun new_v2(secrecy_threshold: fixed_point64::FixedPoint64, reconstruction_threshold: fixed_point64::FixedPoint64, fast_path_secrecy_threshold: fixed_point64::FixedPoint64): randomness_config::RandomnessConfig
+
+
+
+
+public fun new_v2(
+ secrecy_threshold: FixedPoint64,
+ reconstruction_threshold: FixedPoint64,
+ fast_path_secrecy_threshold: FixedPoint64,
+): RandomnessConfig {
+ RandomnessConfig {
+ variant: copyable_any::pack( ConfigV2 {
+ secrecy_threshold,
+ reconstruction_threshold,
+ fast_path_secrecy_threshold,
+ } )
+ }
+}
+
+
+
+
+public fun current(): randomness_config::RandomnessConfig
+
+
+
+
+public fun current(): RandomnessConfig acquires RandomnessConfig {
+ if (exists<RandomnessConfig>(@aptos_framework)) {
+ *borrow_global<RandomnessConfig>(@aptos_framework)
+ } else {
+ new_off()
+ }
+}
+
+
+
+
+public(friend) fun on_new_epoch(framework: &signer)
+
+
+
+
+
+requires @aptos_framework == std::signer::address_of(framework);
+include config_buffer::OnNewEpochRequirement<RandomnessConfig>;
+aborts_if false;
+
+
+
+
+
+
+### Function `current`
+
+
+public fun current(): randomness_config::RandomnessConfig
+
+
+
+
+
+aborts_if false;
+
+
+
+[move-book]: https://aptos.dev/move/book/SUMMARY
diff --git a/aptos-move/framework/aptos-framework/tests/compiler-v2-doc/randomness_config_seqnum.md b/aptos-move/framework/aptos-framework/tests/compiler-v2-doc/randomness_config_seqnum.md
new file mode 100644
index 0000000000000..204660ce96813
--- /dev/null
+++ b/aptos-move/framework/aptos-framework/tests/compiler-v2-doc/randomness_config_seqnum.md
@@ -0,0 +1,170 @@
+
+
+
+# Module `0x1::randomness_config_seqnum`
+
+Randomness stall recovery utils.
+
+When randomness generation is stuck due to a bug, the chain is also stuck. Below is the recovery procedure.
+1. Ensure more than 2/3 stakes are stuck at the same version.
+1. Every validator restarts with randomness_override_seq_num
set to X+1
in the node config file,
+where X
is the current RandomnessConfigSeqNum
on chain.
+1. The chain should then be unblocked.
+1. Once the bug is fixed and the binary + framework have been patched,
+a governance proposal is needed to set RandomnessConfigSeqNum
to be X+2
.
+
+
+- [Resource `RandomnessConfigSeqNum`](#0x1_randomness_config_seqnum_RandomnessConfigSeqNum)
+- [Function `set_for_next_epoch`](#0x1_randomness_config_seqnum_set_for_next_epoch)
+- [Function `initialize`](#0x1_randomness_config_seqnum_initialize)
+- [Function `on_new_epoch`](#0x1_randomness_config_seqnum_on_new_epoch)
+- [Specification](#@Specification_0)
+ - [Function `on_new_epoch`](#@Specification_0_on_new_epoch)
+
+
+use 0x1::config_buffer;
+use 0x1::system_addresses;
+
+
+
+
+
+
+## Resource `RandomnessConfigSeqNum`
+
+If this seqnum is smaller than a validator local override, the on-chain RandomnessConfig
will be ignored.
+Useful in a chain recovery from randomness stall.
+
+
+struct RandomnessConfigSeqNum has drop, store, key
+
+
+
+
+seq_num: u64
+RandomnessConfigSeqNum
.
+Used when re-enable randomness after an emergency randomness disable via local override.
+
+
+public fun set_for_next_epoch(framework: &signer, seq_num: u64)
+
+
+
+
+public fun set_for_next_epoch(framework: &signer, seq_num: u64) {
+ system_addresses::assert_aptos_framework(framework);
+ config_buffer::upsert(RandomnessConfigSeqNum { seq_num });
+}
+
+
+
+
+public fun initialize(framework: &signer)
+
+
+
+
+public fun initialize(framework: &signer) {
+ system_addresses::assert_aptos_framework(framework);
+ if (!exists<RandomnessConfigSeqNum>(@aptos_framework)) {
+ move_to(framework, RandomnessConfigSeqNum { seq_num: 0 })
+ }
+}
+
+
+
+
+RandomnessConfig
, if there is any.
+
+
+public(friend) fun on_new_epoch(framework: &signer)
+
+
+
+
+public(friend) fun on_new_epoch(framework: &signer) acquires RandomnessConfigSeqNum {
+ system_addresses::assert_aptos_framework(framework);
+ if (config_buffer::does_exist<RandomnessConfigSeqNum>()) {
+ let new_config = config_buffer::extract<RandomnessConfigSeqNum>();
+ if (exists<RandomnessConfigSeqNum>(@aptos_framework)) {
+ *borrow_global_mut<RandomnessConfigSeqNum>(@aptos_framework) = new_config;
+ } else {
+ move_to(framework, new_config);
+ }
+ }
+}
+
+
+
+
+public(friend) fun on_new_epoch(framework: &signer)
+
+
+
+
+
+requires @aptos_framework == std::signer::address_of(framework);
+include config_buffer::OnNewEpochRequirement<RandomnessConfigSeqNum>;
+aborts_if false;
+
+
+
+[move-book]: https://aptos.dev/move/book/SUMMARY
diff --git a/aptos-move/framework/aptos-framework/tests/compiler-v2-doc/reconfiguration.md b/aptos-move/framework/aptos-framework/tests/compiler-v2-doc/reconfiguration.md
new file mode 100644
index 0000000000000..545dfef452233
--- /dev/null
+++ b/aptos-move/framework/aptos-framework/tests/compiler-v2-doc/reconfiguration.md
@@ -0,0 +1,798 @@
+
+
+
+# Module `0x1::reconfiguration`
+
+Publishes configuration information for validators, and issues reconfiguration events
+to synchronize configuration changes for the validators.
+
+
+- [Struct `NewEpochEvent`](#0x1_reconfiguration_NewEpochEvent)
+- [Struct `NewEpoch`](#0x1_reconfiguration_NewEpoch)
+- [Resource `Configuration`](#0x1_reconfiguration_Configuration)
+- [Resource `DisableReconfiguration`](#0x1_reconfiguration_DisableReconfiguration)
+- [Constants](#@Constants_0)
+- [Function `initialize`](#0x1_reconfiguration_initialize)
+- [Function `disable_reconfiguration`](#0x1_reconfiguration_disable_reconfiguration)
+- [Function `enable_reconfiguration`](#0x1_reconfiguration_enable_reconfiguration)
+- [Function `reconfiguration_enabled`](#0x1_reconfiguration_reconfiguration_enabled)
+- [Function `reconfigure`](#0x1_reconfiguration_reconfigure)
+- [Function `last_reconfiguration_time`](#0x1_reconfiguration_last_reconfiguration_time)
+- [Function `current_epoch`](#0x1_reconfiguration_current_epoch)
+- [Function `emit_genesis_reconfiguration_event`](#0x1_reconfiguration_emit_genesis_reconfiguration_event)
+- [Specification](#@Specification_1)
+ - [High-level Requirements](#high-level-req)
+ - [Module-level Specification](#module-level-spec)
+ - [Function `initialize`](#@Specification_1_initialize)
+ - [Function `disable_reconfiguration`](#@Specification_1_disable_reconfiguration)
+ - [Function `enable_reconfiguration`](#@Specification_1_enable_reconfiguration)
+ - [Function `reconfiguration_enabled`](#@Specification_1_reconfiguration_enabled)
+ - [Function `reconfigure`](#@Specification_1_reconfigure)
+ - [Function `last_reconfiguration_time`](#@Specification_1_last_reconfiguration_time)
+ - [Function `current_epoch`](#@Specification_1_current_epoch)
+ - [Function `emit_genesis_reconfiguration_event`](#@Specification_1_emit_genesis_reconfiguration_event)
+
+
+use 0x1::account;
+use 0x1::chain_status;
+use 0x1::error;
+use 0x1::event;
+use 0x1::features;
+use 0x1::reconfiguration_state;
+use 0x1::signer;
+use 0x1::stake;
+use 0x1::storage_gas;
+use 0x1::system_addresses;
+use 0x1::timestamp;
+use 0x1::transaction_fee;
+
+
+
+
+
+
+## Struct `NewEpochEvent`
+
+Event that signals consensus to start a new epoch,
+with new configuration information. This is also called a
+"reconfiguration event"
+
+
+#[event]
+struct NewEpochEvent has drop, store
+
+
+
+
+epoch: u64
+#[event]
+struct NewEpoch has drop, store
+
+
+
+
+epoch: u64
+struct Configuration has key
+
+
+
+
+epoch: u64
+last_reconfiguration_time: u64
+events: event::EventHandle<reconfiguration::NewEpochEvent>
+struct DisableReconfiguration has key
+
+
+
+
+dummy_field: bool
+Reconfiguration
resource is in an invalid state
+
+
+const ECONFIG: u64 = 2;
+
+
+
+
+
+
+The Configuration
resource is in an invalid state
+
+
+const ECONFIGURATION: u64 = 1;
+
+
+
+
+
+
+An invalid block time was encountered.
+
+
+const EINVALID_BLOCK_TIME: u64 = 4;
+
+
+
+
+
+
+An invalid block time was encountered.
+
+
+const EINVALID_GUID_FOR_EVENT: u64 = 5;
+
+
+
+
+
+
+A ModifyConfigCapability
is in a different state than was expected
+
+
+const EMODIFY_CAPABILITY: u64 = 3;
+
+
+
+
+
+
+## Function `initialize`
+
+Only called during genesis.
+Publishes Configuration
resource. Can only be invoked by aptos framework account, and only a single time in Genesis.
+
+
+public(friend) fun initialize(aptos_framework: &signer)
+
+
+
+
+public(friend) fun initialize(aptos_framework: &signer) {
+ system_addresses::assert_aptos_framework(aptos_framework);
+
+ // assert it matches `new_epoch_event_key()`, otherwise the event can't be recognized
+ assert!(account::get_guid_next_creation_num(signer::address_of(aptos_framework)) == 2, error::invalid_state(EINVALID_GUID_FOR_EVENT));
+ move_to<Configuration>(
+ aptos_framework,
+ Configuration {
+ epoch: 0,
+ last_reconfiguration_time: 0,
+ events: account::new_event_handle<NewEpochEvent>(aptos_framework),
+ }
+ );
+}
+
+
+
+
+fun disable_reconfiguration(aptos_framework: &signer)
+
+
+
+
+fun disable_reconfiguration(aptos_framework: &signer) {
+ system_addresses::assert_aptos_framework(aptos_framework);
+ assert!(reconfiguration_enabled(), error::invalid_state(ECONFIGURATION));
+ move_to(aptos_framework, DisableReconfiguration {})
+}
+
+
+
+
+fun enable_reconfiguration(aptos_framework: &signer)
+
+
+
+
+fun enable_reconfiguration(aptos_framework: &signer) acquires DisableReconfiguration {
+ system_addresses::assert_aptos_framework(aptos_framework);
+
+ assert!(!reconfiguration_enabled(), error::invalid_state(ECONFIGURATION));
+ DisableReconfiguration {} = move_from<DisableReconfiguration>(signer::address_of(aptos_framework));
+}
+
+
+
+
+fun reconfiguration_enabled(): bool
+
+
+
+
+fun reconfiguration_enabled(): bool {
+ !exists<DisableReconfiguration>(@aptos_framework)
+}
+
+
+
+
+public(friend) fun reconfigure()
+
+
+
+
+public(friend) fun reconfigure() acquires Configuration {
+ // Do not do anything if genesis has not finished.
+ if (chain_status::is_genesis() || timestamp::now_microseconds() == 0 || !reconfiguration_enabled()) {
+ return
+ };
+
+ let config_ref = borrow_global_mut<Configuration>(@aptos_framework);
+ let current_time = timestamp::now_microseconds();
+
+ // Do not do anything if a reconfiguration event is already emitted within this transaction.
+ //
+ // This is OK because:
+ // - The time changes in every non-empty block
+ // - A block automatically ends after a transaction that emits a reconfiguration event, which is guaranteed by
+ // VM spec that all transactions comming after a reconfiguration transaction will be returned as Retry
+ // status.
+ // - Each transaction must emit at most one reconfiguration event
+ //
+ // Thus, this check ensures that a transaction that does multiple "reconfiguration required" actions emits only
+ // one reconfiguration event.
+ //
+ if (current_time == config_ref.last_reconfiguration_time) {
+ return
+ };
+
+ reconfiguration_state::on_reconfig_start();
+
+ // Reconfiguration "forces the block" to end, as mentioned above. Therefore, we must process the collected fees
+ // explicitly so that staking can distribute them.
+ //
+ // This also handles the case when a validator is removed due to the governance proposal. In particular, removing
+ // the validator causes a reconfiguration. We explicitly process fees, i.e. we drain aggregatable coin and populate
+ // the fees table, prior to calling `on_new_epoch()`. That call, in turn, distributes transaction fees for all active
+ // and pending_inactive validators, which include any validator that is to be removed.
+ if (features::collect_and_distribute_gas_fees()) {
+ // All transactions after reconfiguration are Retry. Therefore, when the next
+ // block starts and tries to assign/burn collected fees it will be just 0 and
+ // nothing will be assigned.
+ transaction_fee::process_collected_fees();
+ };
+
+ // Call stake to compute the new validator set and distribute rewards and transaction fees.
+ stake::on_new_epoch();
+ storage_gas::on_reconfig();
+
+ assert!(current_time > config_ref.last_reconfiguration_time, error::invalid_state(EINVALID_BLOCK_TIME));
+ config_ref.last_reconfiguration_time = current_time;
+ spec {
+ assume config_ref.epoch + 1 <= MAX_U64;
+ };
+ config_ref.epoch = config_ref.epoch + 1;
+
+ if (std::features::module_event_migration_enabled()) {
+ event::emit(
+ NewEpoch {
+ epoch: config_ref.epoch,
+ },
+ );
+ };
+ event::emit_event<NewEpochEvent>(
+ &mut config_ref.events,
+ NewEpochEvent {
+ epoch: config_ref.epoch,
+ },
+ );
+
+ reconfiguration_state::on_reconfig_finish();
+}
+
+
+
+
+public fun last_reconfiguration_time(): u64
+
+
+
+
+public fun last_reconfiguration_time(): u64 acquires Configuration {
+ borrow_global<Configuration>(@aptos_framework).last_reconfiguration_time
+}
+
+
+
+
+public fun current_epoch(): u64
+
+
+
+
+public fun current_epoch(): u64 acquires Configuration {
+ borrow_global<Configuration>(@aptos_framework).epoch
+}
+
+
+
+
+NewEpochEvent
event. This function will be invoked by genesis directly to generate the very first
+reconfiguration event.
+
+
+fun emit_genesis_reconfiguration_event()
+
+
+
+
+fun emit_genesis_reconfiguration_event() acquires Configuration {
+ let config_ref = borrow_global_mut<Configuration>(@aptos_framework);
+ assert!(config_ref.epoch == 0 && config_ref.last_reconfiguration_time == 0, error::invalid_state(ECONFIGURATION));
+ config_ref.epoch = 1;
+
+ if (std::features::module_event_migration_enabled()) {
+ event::emit(
+ NewEpoch {
+ epoch: config_ref.epoch,
+ },
+ );
+ };
+ event::emit_event<NewEpochEvent>(
+ &mut config_ref.events,
+ NewEpochEvent {
+ epoch: config_ref.epoch,
+ },
+ );
+}
+
+
+
+
+No. | Requirement | Criticality | Implementation | Enforcement | +
---|---|---|---|---|
1 | +The Configuration resource is stored under the Aptos framework account with initial values upon module's initialization. | +Medium | +The Configuration resource may only be initialized with specific values and published under the aptos_framework account. | +Formally verified via initialize. | +
2 | +The reconfiguration status may be determined at any time without causing an abort, indicating whether or not the system allows reconfiguration. | +Low | +The reconfiguration_enabled function will never abort and always returns a boolean value that accurately represents whether the system allows reconfiguration. | +Formally verified via reconfiguration_enabled. | +
3 | +For each reconfiguration, the epoch value (config_ref.epoch) increases by 1, and one 'NewEpochEvent' is emitted. | +Critical | +After reconfiguration, the reconfigure() function increases the epoch value of the configuration by one and increments the counter of the NewEpochEvent's EventHandle by one. | +Audited that these two values remain in sync. | +
4 | +Reconfiguration is possible only if genesis has started and reconfiguration is enabled. Also, the last reconfiguration must not be the current time, returning early without further actions otherwise. | +High | +The reconfigure() function may only execute to perform successful reconfiguration when genesis has started and when reconfiguration is enabled. Without satisfying both conditions, the function returns early without executing any further actions. | +Formally verified via reconfigure. | +
5 | +Consecutive reconfigurations without the passage of time are not permitted. | +High | +The reconfigure() function enforces the restriction that reconfiguration may only be performed when the current time is not equal to the last_reconfiguration_time. | +Formally verified via reconfigure. | +
pragma verify = true;
+pragma aborts_if_is_strict;
+invariant [suspendable] chain_status::is_operating() ==> exists<Configuration>(@aptos_framework);
+invariant [suspendable] chain_status::is_operating() ==>
+ (timestamp::spec_now_microseconds() >= last_reconfiguration_time());
+
+
+
+Make sure the signer address is @aptos_framework.
+
+
+
+
+
+schema AbortsIfNotAptosFramework {
+ aptos_framework: &signer;
+ let addr = signer::address_of(aptos_framework);
+ aborts_if !system_addresses::is_aptos_framework_address(addr);
+}
+
+
+
+
+
+
+### Function `initialize`
+
+
+public(friend) fun initialize(aptos_framework: &signer)
+
+
+
+Address @aptos_framework must exist resource Account and Configuration.
+Already exists in framework account.
+Guid_creation_num should be 2 according to logic.
+
+
+include AbortsIfNotAptosFramework;
+let addr = signer::address_of(aptos_framework);
+let post config = global<Configuration>(@aptos_framework);
+requires exists<Account>(addr);
+aborts_if !(global<Account>(addr).guid_creation_num == 2);
+aborts_if exists<Configuration>(@aptos_framework);
+// This enforces high-level requirement 1:
+ensures exists<Configuration>(@aptos_framework);
+ensures config.epoch == 0 && config.last_reconfiguration_time == 0;
+ensures config.events == event::EventHandle<NewEpochEvent> {
+ counter: 0,
+ guid: guid::GUID {
+ id: guid::ID {
+ creation_num: 2,
+ addr: @aptos_framework
+ }
+ }
+};
+
+
+
+
+
+
+### Function `disable_reconfiguration`
+
+
+fun disable_reconfiguration(aptos_framework: &signer)
+
+
+
+
+
+include AbortsIfNotAptosFramework;
+aborts_if exists<DisableReconfiguration>(@aptos_framework);
+ensures exists<DisableReconfiguration>(@aptos_framework);
+
+
+
+
+
+
+### Function `enable_reconfiguration`
+
+
+fun enable_reconfiguration(aptos_framework: &signer)
+
+
+
+Make sure the caller is admin and check the resource DisableReconfiguration.
+
+
+include AbortsIfNotAptosFramework;
+aborts_if !exists<DisableReconfiguration>(@aptos_framework);
+ensures !exists<DisableReconfiguration>(@aptos_framework);
+
+
+
+
+
+
+### Function `reconfiguration_enabled`
+
+
+fun reconfiguration_enabled(): bool
+
+
+
+
+
+// This enforces high-level requirement 2:
+aborts_if false;
+ensures result == !exists<DisableReconfiguration>(@aptos_framework);
+
+
+
+
+
+
+### Function `reconfigure`
+
+
+public(friend) fun reconfigure()
+
+
+
+
+
+pragma verify = true;
+pragma verify_duration_estimate = 600;
+requires exists<stake::ValidatorFees>(@aptos_framework);
+let success = !(chain_status::is_genesis() || timestamp::spec_now_microseconds() == 0 || !reconfiguration_enabled())
+ && timestamp::spec_now_microseconds() != global<Configuration>(@aptos_framework).last_reconfiguration_time;
+include features::spec_periodical_reward_rate_decrease_enabled() ==> staking_config::StakingRewardsConfigEnabledRequirement;
+include success ==> aptos_coin::ExistsAptosCoin;
+include transaction_fee::RequiresCollectedFeesPerValueLeqBlockAptosSupply;
+aborts_if false;
+ensures success ==> global<Configuration>(@aptos_framework).epoch == old(global<Configuration>(@aptos_framework).epoch) + 1;
+ensures success ==> global<Configuration>(@aptos_framework).last_reconfiguration_time == timestamp::spec_now_microseconds();
+// This enforces high-level requirement 4 and high-level requirement 5:
+ensures !success ==> global<Configuration>(@aptos_framework).epoch == old(global<Configuration>(@aptos_framework).epoch);
+
+
+
+
+
+
+### Function `last_reconfiguration_time`
+
+
+public fun last_reconfiguration_time(): u64
+
+
+
+
+
+aborts_if !exists<Configuration>(@aptos_framework);
+ensures result == global<Configuration>(@aptos_framework).last_reconfiguration_time;
+
+
+
+
+
+
+### Function `current_epoch`
+
+
+public fun current_epoch(): u64
+
+
+
+
+
+aborts_if !exists<Configuration>(@aptos_framework);
+ensures result == global<Configuration>(@aptos_framework).epoch;
+
+
+
+
+
+
+### Function `emit_genesis_reconfiguration_event`
+
+
+fun emit_genesis_reconfiguration_event()
+
+
+
+When genesis_event emit the epoch and the last_reconfiguration_time
.
+Should equal to 0
+
+
+aborts_if !exists<Configuration>(@aptos_framework);
+let config_ref = global<Configuration>(@aptos_framework);
+aborts_if !(config_ref.epoch == 0 && config_ref.last_reconfiguration_time == 0);
+ensures global<Configuration>(@aptos_framework).epoch == 1;
+
+
+
+[move-book]: https://aptos.dev/move/book/SUMMARY
diff --git a/aptos-move/framework/aptos-framework/tests/compiler-v2-doc/reconfiguration_state.md b/aptos-move/framework/aptos-framework/tests/compiler-v2-doc/reconfiguration_state.md
new file mode 100644
index 0000000000000..f10d45d9f5b62
--- /dev/null
+++ b/aptos-move/framework/aptos-framework/tests/compiler-v2-doc/reconfiguration_state.md
@@ -0,0 +1,573 @@
+
+
+
+# Module `0x1::reconfiguration_state`
+
+Reconfiguration meta-state resources and util functions.
+
+WARNING: reconfiguration_state::initialize()
is required before RECONFIGURE_WITH_DKG
can be enabled.
+
+
+- [Resource `State`](#0x1_reconfiguration_state_State)
+- [Struct `StateInactive`](#0x1_reconfiguration_state_StateInactive)
+- [Struct `StateActive`](#0x1_reconfiguration_state_StateActive)
+- [Constants](#@Constants_0)
+- [Function `is_initialized`](#0x1_reconfiguration_state_is_initialized)
+- [Function `initialize`](#0x1_reconfiguration_state_initialize)
+- [Function `initialize_for_testing`](#0x1_reconfiguration_state_initialize_for_testing)
+- [Function `is_in_progress`](#0x1_reconfiguration_state_is_in_progress)
+- [Function `on_reconfig_start`](#0x1_reconfiguration_state_on_reconfig_start)
+- [Function `start_time_secs`](#0x1_reconfiguration_state_start_time_secs)
+- [Function `on_reconfig_finish`](#0x1_reconfiguration_state_on_reconfig_finish)
+- [Specification](#@Specification_1)
+ - [Resource `State`](#@Specification_1_State)
+ - [Function `initialize`](#@Specification_1_initialize)
+ - [Function `initialize_for_testing`](#@Specification_1_initialize_for_testing)
+ - [Function `is_in_progress`](#@Specification_1_is_in_progress)
+ - [Function `on_reconfig_start`](#@Specification_1_on_reconfig_start)
+ - [Function `start_time_secs`](#@Specification_1_start_time_secs)
+
+
+use 0x1::copyable_any;
+use 0x1::error;
+use 0x1::string;
+use 0x1::system_addresses;
+use 0x1::timestamp;
+
+
+
+
+
+
+## Resource `State`
+
+Reconfiguration drivers update this resources to notify other modules of some reconfiguration state.
+
+
+struct State has key
+
+
+
+
+variant: copyable_any::Any
+Any
.
+ Currently the variant type is one of the following.
+ - ReconfigStateInactive
+ - ReconfigStateActive
+struct StateInactive has copy, drop, store
+
+
+
+
+dummy_field: bool
+struct StateActive has copy, drop, store
+
+
+
+
+start_time_secs: u64
+const ERECONFIG_NOT_IN_PROGRESS: u64 = 1;
+
+
+
+
+
+
+## Function `is_initialized`
+
+
+
+public fun is_initialized(): bool
+
+
+
+
+public fun is_initialized(): bool {
+ exists<State>(@aptos_framework)
+}
+
+
+
+
+public fun initialize(fx: &signer)
+
+
+
+
+public fun initialize(fx: &signer) {
+ system_addresses::assert_aptos_framework(fx);
+ if (!exists<State>(@aptos_framework)) {
+ move_to(fx, State {
+ variant: copyable_any::pack(StateInactive {})
+ })
+ }
+}
+
+
+
+
+public fun initialize_for_testing(fx: &signer)
+
+
+
+
+public fun initialize_for_testing(fx: &signer) {
+ initialize(fx)
+}
+
+
+
+
+public(friend) fun is_in_progress(): bool
+
+
+
+
+public(friend) fun is_in_progress(): bool acquires State {
+ if (!exists<State>(@aptos_framework)) {
+ return false
+ };
+
+ let state = borrow_global<State>(@aptos_framework);
+ let variant_type_name = *string::bytes(copyable_any::type_name(&state.variant));
+ variant_type_name == b"0x1::reconfiguration_state::StateActive"
+}
+
+
+
+
+stake.move
, needs this info).
+
+
+public(friend) fun on_reconfig_start()
+
+
+
+
+public(friend) fun on_reconfig_start() acquires State {
+ if (exists<State>(@aptos_framework)) {
+ let state = borrow_global_mut<State>(@aptos_framework);
+ let variant_type_name = *string::bytes(copyable_any::type_name(&state.variant));
+ if (variant_type_name == b"0x1::reconfiguration_state::StateInactive") {
+ state.variant = copyable_any::pack(StateActive {
+ start_time_secs: timestamp::now_seconds()
+ });
+ }
+ };
+}
+
+
+
+
+public(friend) fun start_time_secs(): u64
+
+
+
+
+public(friend) fun start_time_secs(): u64 acquires State {
+ let state = borrow_global<State>(@aptos_framework);
+ let variant_type_name = *string::bytes(copyable_any::type_name(&state.variant));
+ if (variant_type_name == b"0x1::reconfiguration_state::StateActive") {
+ let active = copyable_any::unpack<StateActive>(state.variant);
+ active.start_time_secs
+ } else {
+ abort(error::invalid_state(ERECONFIG_NOT_IN_PROGRESS))
+ }
+}
+
+
+
+
+public(friend) fun on_reconfig_finish()
+
+
+
+
+public(friend) fun on_reconfig_finish() acquires State {
+ if (exists<State>(@aptos_framework)) {
+ let state = borrow_global_mut<State>(@aptos_framework);
+ let variant_type_name = *string::bytes(copyable_any::type_name(&state.variant));
+ if (variant_type_name == b"0x1::reconfiguration_state::StateActive") {
+ state.variant = copyable_any::pack(StateInactive {});
+ } else {
+ abort(error::invalid_state(ERECONFIG_NOT_IN_PROGRESS))
+ }
+ }
+}
+
+
+
+
+invariant [suspendable] chain_status::is_operating() ==> exists<State>(@aptos_framework);
+
+
+
+
+
+
+### Resource `State`
+
+
+struct State has key
+
+
+
+
+variant: copyable_any::Any
+Any
.
+ Currently the variant type is one of the following.
+ - ReconfigStateInactive
+ - ReconfigStateActive
+invariant copyable_any::type_name(variant).bytes == b"0x1::reconfiguration_state::StateActive" ||
+ copyable_any::type_name(variant).bytes == b"0x1::reconfiguration_state::StateInactive";
+invariant copyable_any::type_name(variant).bytes == b"0x1::reconfiguration_state::StateActive"
+ ==> from_bcs::deserializable<StateActive>(variant.data);
+invariant copyable_any::type_name(variant).bytes == b"0x1::reconfiguration_state::StateInactive"
+ ==> from_bcs::deserializable<StateInactive>(variant.data);
+invariant copyable_any::type_name(variant).bytes == b"0x1::reconfiguration_state::StateActive" ==>
+ type_info::type_name<StateActive>() == variant.type_name;
+invariant copyable_any::type_name(variant).bytes == b"0x1::reconfiguration_state::StateInactive" ==>
+ type_info::type_name<StateInactive>() == variant.type_name;
+
+
+
+
+
+
+### Function `initialize`
+
+
+public fun initialize(fx: &signer)
+
+
+
+
+
+aborts_if signer::address_of(fx) != @aptos_framework;
+let post post_state = global<State>(@aptos_framework);
+ensures exists<State>(@aptos_framework);
+ensures !exists<State>(@aptos_framework) ==> from_bcs::deserializable<StateInactive>(post_state.variant.data);
+
+
+
+
+
+
+### Function `initialize_for_testing`
+
+
+public fun initialize_for_testing(fx: &signer)
+
+
+
+
+
+aborts_if signer::address_of(fx) != @aptos_framework;
+
+
+
+
+
+
+### Function `is_in_progress`
+
+
+public(friend) fun is_in_progress(): bool
+
+
+
+
+
+aborts_if false;
+
+
+
+
+
+
+
+
+fun spec_is_in_progress(): bool {
+ if (!exists<State>(@aptos_framework)) {
+ false
+ } else {
+ copyable_any::type_name(global<State>(@aptos_framework).variant).bytes == b"0x1::reconfiguration_state::StateActive"
+ }
+}
+
+
+
+
+
+
+### Function `on_reconfig_start`
+
+
+public(friend) fun on_reconfig_start()
+
+
+
+
+
+aborts_if false;
+requires exists<timestamp::CurrentTimeMicroseconds>(@aptos_framework);
+let state = Any {
+ type_name: type_info::type_name<StateActive>(),
+ data: bcs::serialize(StateActive {
+ start_time_secs: timestamp::spec_now_seconds()
+ })
+};
+let pre_state = global<State>(@aptos_framework);
+let post post_state = global<State>(@aptos_framework);
+ensures (exists<State>(@aptos_framework) && copyable_any::type_name(pre_state.variant).bytes
+ == b"0x1::reconfiguration_state::StateInactive") ==> copyable_any::type_name(post_state.variant).bytes
+ == b"0x1::reconfiguration_state::StateActive";
+ensures (exists<State>(@aptos_framework) && copyable_any::type_name(pre_state.variant).bytes
+ == b"0x1::reconfiguration_state::StateInactive") ==> post_state.variant == state;
+ensures (exists<State>(@aptos_framework) && copyable_any::type_name(pre_state.variant).bytes
+ == b"0x1::reconfiguration_state::StateInactive") ==> from_bcs::deserializable<StateActive>(post_state.variant.data);
+
+
+
+
+
+
+### Function `start_time_secs`
+
+
+public(friend) fun start_time_secs(): u64
+
+
+
+
+
+include StartTimeSecsAbortsIf;
+
+
+
+
+
+
+
+
+fun spec_start_time_secs(): u64 {
+ use aptos_std::from_bcs;
+ let state = global<State>(@aptos_framework);
+ from_bcs::deserialize<StateActive>(state.variant.data).start_time_secs
+}
+
+
+
+
+
+
+
+
+schema StartTimeSecsRequirement {
+ requires exists<State>(@aptos_framework);
+ requires copyable_any::type_name(global<State>(@aptos_framework).variant).bytes
+ == b"0x1::reconfiguration_state::StateActive";
+ include UnpackRequiresStateActive {
+ x: global<State>(@aptos_framework).variant
+ };
+}
+
+
+
+
+
+
+
+
+schema UnpackRequiresStateActive {
+ x: Any;
+ requires type_info::type_name<StateActive>() == x.type_name && from_bcs::deserializable<StateActive>(x.data);
+}
+
+
+
+
+
+
+
+
+schema StartTimeSecsAbortsIf {
+ aborts_if !exists<State>(@aptos_framework);
+ include copyable_any::type_name(global<State>(@aptos_framework).variant).bytes
+ == b"0x1::reconfiguration_state::StateActive" ==>
+ copyable_any::UnpackAbortsIf<StateActive> {
+ x: global<State>(@aptos_framework).variant
+ };
+ aborts_if copyable_any::type_name(global<State>(@aptos_framework).variant).bytes
+ != b"0x1::reconfiguration_state::StateActive";
+}
+
+
+
+[move-book]: https://aptos.dev/move/book/SUMMARY
diff --git a/aptos-move/framework/aptos-framework/tests/compiler-v2-doc/reconfiguration_with_dkg.md b/aptos-move/framework/aptos-framework/tests/compiler-v2-doc/reconfiguration_with_dkg.md
new file mode 100644
index 0000000000000..85b4f1dbc74e2
--- /dev/null
+++ b/aptos-move/framework/aptos-framework/tests/compiler-v2-doc/reconfiguration_with_dkg.md
@@ -0,0 +1,251 @@
+
+
+
+# Module `0x1::reconfiguration_with_dkg`
+
+Reconfiguration with DKG helper functions.
+
+
+- [Function `try_start`](#0x1_reconfiguration_with_dkg_try_start)
+- [Function `finish`](#0x1_reconfiguration_with_dkg_finish)
+- [Function `finish_with_dkg_result`](#0x1_reconfiguration_with_dkg_finish_with_dkg_result)
+- [Specification](#@Specification_0)
+ - [Function `try_start`](#@Specification_0_try_start)
+ - [Function `finish`](#@Specification_0_finish)
+ - [Function `finish_with_dkg_result`](#@Specification_0_finish_with_dkg_result)
+
+
+use 0x1::consensus_config;
+use 0x1::dkg;
+use 0x1::execution_config;
+use 0x1::features;
+use 0x1::gas_schedule;
+use 0x1::jwk_consensus_config;
+use 0x1::jwks;
+use 0x1::keyless_account;
+use 0x1::option;
+use 0x1::randomness_api_v0_config;
+use 0x1::randomness_config;
+use 0x1::randomness_config_seqnum;
+use 0x1::reconfiguration;
+use 0x1::reconfiguration_state;
+use 0x1::stake;
+use 0x1::system_addresses;
+use 0x1::validator_consensus_info;
+use 0x1::version;
+
+
+
+
+
+
+## Function `try_start`
+
+Trigger a reconfiguration with DKG.
+Do nothing if one is already in progress.
+
+
+public(friend) fun try_start()
+
+
+
+
+public(friend) fun try_start() {
+ let incomplete_dkg_session = dkg::incomplete_session();
+ if (option::is_some(&incomplete_dkg_session)) {
+ let session = option::borrow(&incomplete_dkg_session);
+ if (dkg::session_dealer_epoch(session) == reconfiguration::current_epoch()) {
+ return
+ }
+ };
+ reconfiguration_state::on_reconfig_start();
+ let cur_epoch = reconfiguration::current_epoch();
+ dkg::start(
+ cur_epoch,
+ randomness_config::current(),
+ stake::cur_validator_consensus_infos(),
+ stake::next_validator_consensus_infos(),
+ );
+}
+
+
+
+
+reconfiguration::reconfigure()
).
+Re-enable validator set changes.
+Run the default reconfiguration to enter the new epoch.
+
+
+public(friend) fun finish(framework: &signer)
+
+
+
+
+public(friend) fun finish(framework: &signer) {
+ system_addresses::assert_aptos_framework(framework);
+ dkg::try_clear_incomplete_session(framework);
+ consensus_config::on_new_epoch(framework);
+ execution_config::on_new_epoch(framework);
+ gas_schedule::on_new_epoch(framework);
+ std::version::on_new_epoch(framework);
+ features::on_new_epoch(framework);
+ jwk_consensus_config::on_new_epoch(framework);
+ jwks::on_new_epoch(framework);
+ keyless_account::on_new_epoch(framework);
+ randomness_config_seqnum::on_new_epoch(framework);
+ randomness_config::on_new_epoch(framework);
+ randomness_api_v0_config::on_new_epoch(framework);
+ reconfiguration::reconfigure();
+}
+
+
+
+
+fun finish_with_dkg_result(account: &signer, dkg_result: vector<u8>)
+
+
+
+
+fun finish_with_dkg_result(account: &signer, dkg_result: vector<u8>) {
+ dkg::finish(dkg_result);
+ finish(account);
+}
+
+
+
+
+pragma verify = true;
+
+
+
+
+
+
+### Function `try_start`
+
+
+public(friend) fun try_start()
+
+
+
+
+
+pragma verify_duration_estimate = 120;
+requires exists<reconfiguration::Configuration>(@aptos_framework);
+requires chain_status::is_operating();
+include stake::ResourceRequirement;
+include stake::GetReconfigStartTimeRequirement;
+include features::spec_periodical_reward_rate_decrease_enabled(
+) ==> staking_config::StakingRewardsConfigEnabledRequirement;
+aborts_if false;
+pragma verify_duration_estimate = 600;
+
+
+
+
+
+
+### Function `finish`
+
+
+public(friend) fun finish(framework: &signer)
+
+
+
+
+
+pragma verify_duration_estimate = 1500;
+include FinishRequirement;
+aborts_if false;
+
+
+
+
+
+
+
+
+schema FinishRequirement {
+ framework: signer;
+ requires signer::address_of(framework) == @aptos_framework;
+ requires chain_status::is_operating();
+ requires exists<CoinInfo<AptosCoin>>(@aptos_framework);
+ include staking_config::StakingRewardsConfigRequirement;
+ requires exists<stake::ValidatorFees>(@aptos_framework);
+ include transaction_fee::RequiresCollectedFeesPerValueLeqBlockAptosSupply;
+ requires exists<features::Features>(@std);
+ include config_buffer::OnNewEpochRequirement<version::Version>;
+ include config_buffer::OnNewEpochRequirement<gas_schedule::GasScheduleV2>;
+ include config_buffer::OnNewEpochRequirement<execution_config::ExecutionConfig>;
+ include config_buffer::OnNewEpochRequirement<consensus_config::ConsensusConfig>;
+ include config_buffer::OnNewEpochRequirement<jwks::SupportedOIDCProviders>;
+ include config_buffer::OnNewEpochRequirement<randomness_config::RandomnessConfig>;
+ include config_buffer::OnNewEpochRequirement<randomness_config_seqnum::RandomnessConfigSeqNum>;
+ include config_buffer::OnNewEpochRequirement<randomness_api_v0_config::AllowCustomMaxGasFlag>;
+ include config_buffer::OnNewEpochRequirement<randomness_api_v0_config::RequiredGasDeposit>;
+ include config_buffer::OnNewEpochRequirement<jwk_consensus_config::JWKConsensusConfig>;
+ include config_buffer::OnNewEpochRequirement<keyless_account::Configuration>;
+ include config_buffer::OnNewEpochRequirement<keyless_account::Groth16VerificationKey>;
+}
+
+
+
+
+
+
+### Function `finish_with_dkg_result`
+
+
+fun finish_with_dkg_result(account: &signer, dkg_result: vector<u8>)
+
+
+
+
+
+pragma verify_duration_estimate = 1500;
+include FinishRequirement {
+ framework: account
+};
+requires dkg::has_incomplete_session();
+aborts_if false;
+
+
+
+[move-book]: https://aptos.dev/move/book/SUMMARY
diff --git a/aptos-move/framework/aptos-framework/tests/compiler-v2-doc/resource_account.md b/aptos-move/framework/aptos-framework/tests/compiler-v2-doc/resource_account.md
new file mode 100644
index 0000000000000..4df4d4691ced3
--- /dev/null
+++ b/aptos-move/framework/aptos-framework/tests/compiler-v2-doc/resource_account.md
@@ -0,0 +1,636 @@
+
+
+
+# Module `0x1::resource_account`
+
+A resource account is used to manage resources independent of an account managed by a user.
+This contains several utilities to make using resource accounts more effective.
+
+
+
+
+### Resource Accounts to manage liquidity pools
+
+
+A dev wishing to use resource accounts for a liquidity pool, would likely do the following:
+
+1. Create a new account using resource_account::create_resource_account
. This creates the
+account, stores the signer_cap
within a resource_account::Container
, and rotates the key to
+the current account's authentication key or a provided authentication key.
+2. Define the liquidity pool module's address to be the same as the resource account.
+3. Construct a package-publishing transaction for the resource account using the
+authentication key used in step 1.
+4. In the liquidity pool module's init_module
function, call retrieve_resource_account_cap
+which will retrieve the signer_cap
and rotate the resource account's authentication key to
+0x0
, effectively locking it off.
+5. When adding a new coin, the liquidity pool will load the capability and hence the signer
to
+register and store new LiquidityCoin
resources.
+
+Code snippets to help:
+
+```
+fun init_module(resource_account: &signer) {
+let dev_address = @DEV_ADDR;
+let signer_cap = retrieve_resource_account_cap(resource_account, dev_address);
+let lp = LiquidityPoolInfo { signer_cap: signer_cap, ... };
+move_to(resource_account, lp);
+}
+```
+
+Later on during a coin registration:
+```
+public fun add_coinresource_account::create_resource_account_and_publish_package
.
+This creates the account and publishes the package for that account.
+2. At a later point in time, the account creator can move the signer capability to the module.
+
+```
+struct MyModuleResource has key {
+...
+resource_signer_cap: Optionuse 0x1::account;
+use 0x1::aptos_coin;
+use 0x1::code;
+use 0x1::coin;
+use 0x1::error;
+use 0x1::signer;
+use 0x1::simple_map;
+use 0x1::vector;
+
+
+
+
+
+
+## Resource `Container`
+
+
+
+struct Container has key
+
+
+
+
+store: simple_map::SimpleMap<address, account::SignerCapability>
+const ZERO_AUTH_KEY: vector<u8> = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0];
+
+
+
+
+
+
+Container resource not found in account
+
+
+const ECONTAINER_NOT_PUBLISHED: u64 = 1;
+
+
+
+
+
+
+The resource account was not created by the specified source account
+
+
+const EUNAUTHORIZED_NOT_OWNER: u64 = 2;
+
+
+
+
+
+
+## Function `create_resource_account`
+
+Creates a new resource account and rotates the authentication key to either
+the optional auth key if it is non-empty (though auth keys are 32-bytes)
+or the source accounts current auth key.
+
+
+public entry fun create_resource_account(origin: &signer, seed: vector<u8>, optional_auth_key: vector<u8>)
+
+
+
+
+public entry fun create_resource_account(
+ origin: &signer,
+ seed: vector<u8>,
+ optional_auth_key: vector<u8>,
+) acquires Container {
+ let (resource, resource_signer_cap) = account::create_resource_account(origin, seed);
+ rotate_account_authentication_key_and_store_capability(
+ origin,
+ resource,
+ resource_signer_cap,
+ optional_auth_key,
+ );
+}
+
+
+
+
+Coin<AptosCoin>
.
+
+
+public entry fun create_resource_account_and_fund(origin: &signer, seed: vector<u8>, optional_auth_key: vector<u8>, fund_amount: u64)
+
+
+
+
+public entry fun create_resource_account_and_fund(
+ origin: &signer,
+ seed: vector<u8>,
+ optional_auth_key: vector<u8>,
+ fund_amount: u64,
+) acquires Container {
+ let (resource, resource_signer_cap) = account::create_resource_account(origin, seed);
+ coin::register<AptosCoin>(&resource);
+ coin::transfer<AptosCoin>(origin, signer::address_of(&resource), fund_amount);
+ rotate_account_authentication_key_and_store_capability(
+ origin,
+ resource,
+ resource_signer_cap,
+ optional_auth_key,
+ );
+}
+
+
+
+
+public entry fun create_resource_account_and_publish_package(origin: &signer, seed: vector<u8>, metadata_serialized: vector<u8>, code: vector<vector<u8>>)
+
+
+
+
+public entry fun create_resource_account_and_publish_package(
+ origin: &signer,
+ seed: vector<u8>,
+ metadata_serialized: vector<u8>,
+ code: vector<vector<u8>>,
+) acquires Container {
+ let (resource, resource_signer_cap) = account::create_resource_account(origin, seed);
+ aptos_framework::code::publish_package_txn(&resource, metadata_serialized, code);
+ rotate_account_authentication_key_and_store_capability(
+ origin,
+ resource,
+ resource_signer_cap,
+ ZERO_AUTH_KEY,
+ );
+}
+
+
+
+
+fun rotate_account_authentication_key_and_store_capability(origin: &signer, resource: signer, resource_signer_cap: account::SignerCapability, optional_auth_key: vector<u8>)
+
+
+
+
+fun rotate_account_authentication_key_and_store_capability(
+ origin: &signer,
+ resource: signer,
+ resource_signer_cap: account::SignerCapability,
+ optional_auth_key: vector<u8>,
+) acquires Container {
+ let origin_addr = signer::address_of(origin);
+ if (!exists<Container>(origin_addr)) {
+ move_to(origin, Container { store: simple_map::create() })
+ };
+
+ let container = borrow_global_mut<Container>(origin_addr);
+ let resource_addr = signer::address_of(&resource);
+ simple_map::add(&mut container.store, resource_addr, resource_signer_cap);
+
+ let auth_key = if (vector::is_empty(&optional_auth_key)) {
+ account::get_authentication_key(origin_addr)
+ } else {
+ optional_auth_key
+ };
+ account::rotate_authentication_key_internal(&resource, auth_key);
+}
+
+
+
+
+public fun retrieve_resource_account_cap(resource: &signer, source_addr: address): account::SignerCapability
+
+
+
+
+public fun retrieve_resource_account_cap(
+ resource: &signer,
+ source_addr: address,
+): account::SignerCapability acquires Container {
+ assert!(exists<Container>(source_addr), error::not_found(ECONTAINER_NOT_PUBLISHED));
+
+ let resource_addr = signer::address_of(resource);
+ let (resource_signer_cap, empty_container) = {
+ let container = borrow_global_mut<Container>(source_addr);
+ assert!(
+ simple_map::contains_key(&container.store, &resource_addr),
+ error::invalid_argument(EUNAUTHORIZED_NOT_OWNER)
+ );
+ let (_resource_addr, signer_cap) = simple_map::remove(&mut container.store, &resource_addr);
+ (signer_cap, simple_map::length(&container.store) == 0)
+ };
+
+ if (empty_container) {
+ let container = move_from(source_addr);
+ let Container { store } = container;
+ simple_map::destroy_empty(store);
+ };
+
+ account::rotate_authentication_key_internal(resource, ZERO_AUTH_KEY);
+ resource_signer_cap
+}
+
+
+
+
+No. | Requirement | Criticality | Implementation | Enforcement | +
---|---|---|---|---|
1 | +The length of the authentication key must be 32 bytes. | +Medium | +The rotate_authentication_key_internal function ensures that the authentication key passed to it is of 32 bytes. | +Formally verified via RotateAccountAuthenticationKeyAndStoreCapabilityAbortsIf. | +
2 | +The Container structure must exist in the origin account in order to rotate the authentication key of a resource account and to store its signer capability. | +High | +The rotate_account_authentication_key_and_store_capability function makes sure the Container structure exists under the origin account. | +Formally verified via rotate_account_authentication_key_and_store_capability. | +
3 | +The resource account is registered for the Aptos coin. | +High | +The create_resource_account_and_fund ensures the newly created resource account is registered to receive the AptosCoin. | +Formally verified via create_resource_account_and_fund. | +
4 | +It is not possible to store two capabilities for the same resource address. | +Medium | +The rotate_account_authentication_key_and_store_capability will abort if the resource signer capability for the given resource address already exists in container.store. | +Formally verified via rotate_account_authentication_key_and_store_capability. | +
5 | +If provided, the optional authentication key is used for key rotation. | +Low | +The rotate_account_authentication_key_and_store_capability function will use optional_auth_key if it is provided as a parameter. | +Formally verified via rotate_account_authentication_key_and_store_capability. | +
6 | +The container stores the resource accounts' signer capabilities. | +Low | +retrieve_resource_account_cap will abort if there is no Container structure assigned to source_addr. | +Formally verified via retreive_resource_account_cap. | +
7 | +Resource account may retrieve the signer capability if it was previously added to its container. | +High | +retrieve_resource_account_cap will abort if the container of source_addr doesn't store the signer capability for the given resource. | +Formally verified via retrieve_resource_account_cap. | +
8 | +Retrieving the last signer capability from the container must result in the container being removed. | +Low | +retrieve_resource_account_cap will remove the container if the retrieved signer_capability was the last one stored under it. | +Formally verified via retrieve_resource_account_cap. | +
pragma verify = true;
+pragma aborts_if_is_strict;
+
+
+
+
+
+
+### Function `create_resource_account`
+
+
+public entry fun create_resource_account(origin: &signer, seed: vector<u8>, optional_auth_key: vector<u8>)
+
+
+
+
+
+let source_addr = signer::address_of(origin);
+let resource_addr = account::spec_create_resource_address(source_addr, seed);
+include RotateAccountAuthenticationKeyAndStoreCapabilityAbortsIfWithoutAccountLimit;
+
+
+
+
+
+
+### Function `create_resource_account_and_fund`
+
+
+public entry fun create_resource_account_and_fund(origin: &signer, seed: vector<u8>, optional_auth_key: vector<u8>, fund_amount: u64)
+
+
+
+
+
+pragma verify = false;
+let source_addr = signer::address_of(origin);
+let resource_addr = account::spec_create_resource_address(source_addr, seed);
+let coin_store_resource = global<coin::CoinStore<AptosCoin>>(resource_addr);
+include aptos_account::WithdrawAbortsIf<AptosCoin>{from: origin, amount: fund_amount};
+include aptos_account::GuidAbortsIf<AptosCoin>{to: resource_addr};
+include RotateAccountAuthenticationKeyAndStoreCapabilityAbortsIfWithoutAccountLimit;
+aborts_if coin::spec_is_account_registered<AptosCoin>(resource_addr) && coin_store_resource.frozen;
+// This enforces high-level requirement 3:
+ensures exists<aptos_framework::coin::CoinStore<AptosCoin>>(resource_addr);
+
+
+
+
+
+
+### Function `create_resource_account_and_publish_package`
+
+
+public entry fun create_resource_account_and_publish_package(origin: &signer, seed: vector<u8>, metadata_serialized: vector<u8>, code: vector<vector<u8>>)
+
+
+
+
+
+pragma verify = false;
+let source_addr = signer::address_of(origin);
+let resource_addr = account::spec_create_resource_address(source_addr, seed);
+let optional_auth_key = ZERO_AUTH_KEY;
+include RotateAccountAuthenticationKeyAndStoreCapabilityAbortsIfWithoutAccountLimit;
+
+
+
+
+
+
+### Function `rotate_account_authentication_key_and_store_capability`
+
+
+fun rotate_account_authentication_key_and_store_capability(origin: &signer, resource: signer, resource_signer_cap: account::SignerCapability, optional_auth_key: vector<u8>)
+
+
+
+
+
+let resource_addr = signer::address_of(resource);
+// This enforces high-level requirement 1:
+include RotateAccountAuthenticationKeyAndStoreCapabilityAbortsIf;
+// This enforces high-level requirement 2:
+ensures exists<Container>(signer::address_of(origin));
+// This enforces high-level requirement 5:
+ensures vector::length(optional_auth_key) != 0 ==>
+ global<aptos_framework::account::Account>(resource_addr).authentication_key == optional_auth_key;
+
+
+
+
+
+
+
+
+schema RotateAccountAuthenticationKeyAndStoreCapabilityAbortsIf {
+ origin: signer;
+ resource_addr: address;
+ optional_auth_key: vector<u8>;
+ let source_addr = signer::address_of(origin);
+ let container = global<Container>(source_addr);
+ let get = len(optional_auth_key) == 0;
+ aborts_if get && !exists<Account>(source_addr);
+ // This enforces high-level requirement 4:
+ aborts_if exists<Container>(source_addr) && simple_map::spec_contains_key(container.store, resource_addr);
+ aborts_if get && !(exists<Account>(resource_addr) && len(global<Account>(source_addr).authentication_key) == 32);
+ aborts_if !get && !(exists<Account>(resource_addr) && len(optional_auth_key) == 32);
+ ensures simple_map::spec_contains_key(global<Container>(source_addr).store, resource_addr);
+ ensures exists<Container>(source_addr);
+}
+
+
+
+
+
+
+
+
+schema RotateAccountAuthenticationKeyAndStoreCapabilityAbortsIfWithoutAccountLimit {
+ source_addr: address;
+ optional_auth_key: vector<u8>;
+ resource_addr: address;
+ let container = global<Container>(source_addr);
+ let get = len(optional_auth_key) == 0;
+ let account = global<account::Account>(source_addr);
+ requires source_addr != resource_addr;
+ aborts_if len(ZERO_AUTH_KEY) != 32;
+ include account::exists_at(resource_addr) ==> account::CreateResourceAccountAbortsIf;
+ include !account::exists_at(resource_addr) ==> account::CreateAccountAbortsIf {addr: resource_addr};
+ aborts_if get && !exists<account::Account>(source_addr);
+ aborts_if exists<Container>(source_addr) && simple_map::spec_contains_key(container.store, resource_addr);
+ aborts_if get && len(global<account::Account>(source_addr).authentication_key) != 32;
+ aborts_if !get && len(optional_auth_key) != 32;
+ ensures simple_map::spec_contains_key(global<Container>(source_addr).store, resource_addr);
+ ensures exists<Container>(source_addr);
+}
+
+
+
+
+
+
+### Function `retrieve_resource_account_cap`
+
+
+public fun retrieve_resource_account_cap(resource: &signer, source_addr: address): account::SignerCapability
+
+
+
+
+
+// This enforces high-level requirement 6:
+aborts_if !exists<Container>(source_addr);
+let resource_addr = signer::address_of(resource);
+let container = global<Container>(source_addr);
+// This enforces high-level requirement 7:
+aborts_if !simple_map::spec_contains_key(container.store, resource_addr);
+aborts_if !exists<account::Account>(resource_addr);
+// This enforces high-level requirement 8:
+ensures simple_map::spec_contains_key(old(global<Container>(source_addr)).store, resource_addr) &&
+ simple_map::spec_len(old(global<Container>(source_addr)).store) == 1 ==> !exists<Container>(source_addr);
+ensures exists<Container>(source_addr) ==> !simple_map::spec_contains_key(global<Container>(source_addr).store, resource_addr);
+
+
+
+[move-book]: https://aptos.dev/move/book/SUMMARY
diff --git a/aptos-move/framework/aptos-framework/tests/compiler-v2-doc/stake.md b/aptos-move/framework/aptos-framework/tests/compiler-v2-doc/stake.md
new file mode 100644
index 0000000000000..865c0df8233c7
--- /dev/null
+++ b/aptos-move/framework/aptos-framework/tests/compiler-v2-doc/stake.md
@@ -0,0 +1,6105 @@
+
+
+
+# Module `0x1::stake`
+
+
+Validator lifecycle:
+1. Prepare a validator node set up and call stake::initialize_validator
+2. Once ready to deposit stake (or have funds assigned by a staking service in exchange for ownership capability),
+call stake::add_stake (or *_with_cap versions if called from the staking service)
+3. Call stake::join_validator_set (or _with_cap version) to join the active validator set. Changes are effective in
+the next epoch.
+4. Validate and gain rewards. The stake will automatically be locked up for a fixed duration (set by governance) and
+automatically renewed at expiration.
+5. At any point, if the validator operator wants to update the consensus key or network/fullnode addresses, they can
+call stake::rotate_consensus_key and stake::update_network_and_fullnode_addresses. Similar to changes to stake, the
+changes to consensus key/network/fullnode addresses are only effective in the next epoch.
+6. Validator can request to unlock their stake at any time. However, their stake will only become withdrawable when
+their current lockup expires. This can be at most as long as the fixed lockup duration.
+7. After exiting, the validator can either explicitly leave the validator set by calling stake::leave_validator_set
+or if their stake drops below the min required, they would get removed at the end of the epoch.
+8. Validator can always rejoin the validator set by going through steps 2-3 again.
+9. An owner can always switch operators by calling stake::set_operator.
+10. An owner can always switch designated voter by calling stake::set_designated_voter.
+
+
+- [Resource `OwnerCapability`](#0x1_stake_OwnerCapability)
+- [Resource `StakePool`](#0x1_stake_StakePool)
+- [Resource `ValidatorConfig`](#0x1_stake_ValidatorConfig)
+- [Struct `ValidatorInfo`](#0x1_stake_ValidatorInfo)
+- [Resource `ValidatorSet`](#0x1_stake_ValidatorSet)
+- [Resource `AptosCoinCapabilities`](#0x1_stake_AptosCoinCapabilities)
+- [Struct `IndividualValidatorPerformance`](#0x1_stake_IndividualValidatorPerformance)
+- [Resource `ValidatorPerformance`](#0x1_stake_ValidatorPerformance)
+- [Struct `RegisterValidatorCandidateEvent`](#0x1_stake_RegisterValidatorCandidateEvent)
+- [Struct `RegisterValidatorCandidate`](#0x1_stake_RegisterValidatorCandidate)
+- [Struct `SetOperatorEvent`](#0x1_stake_SetOperatorEvent)
+- [Struct `SetOperator`](#0x1_stake_SetOperator)
+- [Struct `AddStakeEvent`](#0x1_stake_AddStakeEvent)
+- [Struct `AddStake`](#0x1_stake_AddStake)
+- [Struct `ReactivateStakeEvent`](#0x1_stake_ReactivateStakeEvent)
+- [Struct `ReactivateStake`](#0x1_stake_ReactivateStake)
+- [Struct `RotateConsensusKeyEvent`](#0x1_stake_RotateConsensusKeyEvent)
+- [Struct `RotateConsensusKey`](#0x1_stake_RotateConsensusKey)
+- [Struct `UpdateNetworkAndFullnodeAddressesEvent`](#0x1_stake_UpdateNetworkAndFullnodeAddressesEvent)
+- [Struct `UpdateNetworkAndFullnodeAddresses`](#0x1_stake_UpdateNetworkAndFullnodeAddresses)
+- [Struct `IncreaseLockupEvent`](#0x1_stake_IncreaseLockupEvent)
+- [Struct `IncreaseLockup`](#0x1_stake_IncreaseLockup)
+- [Struct `JoinValidatorSetEvent`](#0x1_stake_JoinValidatorSetEvent)
+- [Struct `JoinValidatorSet`](#0x1_stake_JoinValidatorSet)
+- [Struct `DistributeRewardsEvent`](#0x1_stake_DistributeRewardsEvent)
+- [Struct `DistributeRewards`](#0x1_stake_DistributeRewards)
+- [Struct `UnlockStakeEvent`](#0x1_stake_UnlockStakeEvent)
+- [Struct `UnlockStake`](#0x1_stake_UnlockStake)
+- [Struct `WithdrawStakeEvent`](#0x1_stake_WithdrawStakeEvent)
+- [Struct `WithdrawStake`](#0x1_stake_WithdrawStake)
+- [Struct `LeaveValidatorSetEvent`](#0x1_stake_LeaveValidatorSetEvent)
+- [Struct `LeaveValidatorSet`](#0x1_stake_LeaveValidatorSet)
+- [Resource `ValidatorFees`](#0x1_stake_ValidatorFees)
+- [Resource `AllowedValidators`](#0x1_stake_AllowedValidators)
+- [Resource `Ghost$ghost_valid_perf`](#0x1_stake_Ghost$ghost_valid_perf)
+- [Resource `Ghost$ghost_proposer_idx`](#0x1_stake_Ghost$ghost_proposer_idx)
+- [Resource `Ghost$ghost_active_num`](#0x1_stake_Ghost$ghost_active_num)
+- [Resource `Ghost$ghost_pending_inactive_num`](#0x1_stake_Ghost$ghost_pending_inactive_num)
+- [Constants](#@Constants_0)
+- [Function `initialize_validator_fees`](#0x1_stake_initialize_validator_fees)
+- [Function `add_transaction_fee`](#0x1_stake_add_transaction_fee)
+- [Function `get_lockup_secs`](#0x1_stake_get_lockup_secs)
+- [Function `get_remaining_lockup_secs`](#0x1_stake_get_remaining_lockup_secs)
+- [Function `get_stake`](#0x1_stake_get_stake)
+- [Function `get_validator_state`](#0x1_stake_get_validator_state)
+- [Function `get_current_epoch_voting_power`](#0x1_stake_get_current_epoch_voting_power)
+- [Function `get_delegated_voter`](#0x1_stake_get_delegated_voter)
+- [Function `get_operator`](#0x1_stake_get_operator)
+- [Function `get_owned_pool_address`](#0x1_stake_get_owned_pool_address)
+- [Function `get_validator_index`](#0x1_stake_get_validator_index)
+- [Function `get_current_epoch_proposal_counts`](#0x1_stake_get_current_epoch_proposal_counts)
+- [Function `get_validator_config`](#0x1_stake_get_validator_config)
+- [Function `stake_pool_exists`](#0x1_stake_stake_pool_exists)
+- [Function `initialize`](#0x1_stake_initialize)
+- [Function `store_aptos_coin_mint_cap`](#0x1_stake_store_aptos_coin_mint_cap)
+- [Function `remove_validators`](#0x1_stake_remove_validators)
+- [Function `initialize_stake_owner`](#0x1_stake_initialize_stake_owner)
+- [Function `initialize_validator`](#0x1_stake_initialize_validator)
+- [Function `initialize_owner`](#0x1_stake_initialize_owner)
+- [Function `extract_owner_cap`](#0x1_stake_extract_owner_cap)
+- [Function `deposit_owner_cap`](#0x1_stake_deposit_owner_cap)
+- [Function `destroy_owner_cap`](#0x1_stake_destroy_owner_cap)
+- [Function `set_operator`](#0x1_stake_set_operator)
+- [Function `set_operator_with_cap`](#0x1_stake_set_operator_with_cap)
+- [Function `set_delegated_voter`](#0x1_stake_set_delegated_voter)
+- [Function `set_delegated_voter_with_cap`](#0x1_stake_set_delegated_voter_with_cap)
+- [Function `add_stake`](#0x1_stake_add_stake)
+- [Function `add_stake_with_cap`](#0x1_stake_add_stake_with_cap)
+- [Function `reactivate_stake`](#0x1_stake_reactivate_stake)
+- [Function `reactivate_stake_with_cap`](#0x1_stake_reactivate_stake_with_cap)
+- [Function `rotate_consensus_key`](#0x1_stake_rotate_consensus_key)
+- [Function `update_network_and_fullnode_addresses`](#0x1_stake_update_network_and_fullnode_addresses)
+- [Function `increase_lockup`](#0x1_stake_increase_lockup)
+- [Function `increase_lockup_with_cap`](#0x1_stake_increase_lockup_with_cap)
+- [Function `join_validator_set`](#0x1_stake_join_validator_set)
+- [Function `join_validator_set_internal`](#0x1_stake_join_validator_set_internal)
+- [Function `unlock`](#0x1_stake_unlock)
+- [Function `unlock_with_cap`](#0x1_stake_unlock_with_cap)
+- [Function `withdraw`](#0x1_stake_withdraw)
+- [Function `withdraw_with_cap`](#0x1_stake_withdraw_with_cap)
+- [Function `leave_validator_set`](#0x1_stake_leave_validator_set)
+- [Function `is_current_epoch_validator`](#0x1_stake_is_current_epoch_validator)
+- [Function `update_performance_statistics`](#0x1_stake_update_performance_statistics)
+- [Function `on_new_epoch`](#0x1_stake_on_new_epoch)
+- [Function `cur_validator_consensus_infos`](#0x1_stake_cur_validator_consensus_infos)
+- [Function `next_validator_consensus_infos`](#0x1_stake_next_validator_consensus_infos)
+- [Function `validator_consensus_infos_from_validator_set`](#0x1_stake_validator_consensus_infos_from_validator_set)
+- [Function `addresses_from_validator_infos`](#0x1_stake_addresses_from_validator_infos)
+- [Function `update_stake_pool`](#0x1_stake_update_stake_pool)
+- [Function `get_reconfig_start_time_secs`](#0x1_stake_get_reconfig_start_time_secs)
+- [Function `calculate_rewards_amount`](#0x1_stake_calculate_rewards_amount)
+- [Function `distribute_rewards`](#0x1_stake_distribute_rewards)
+- [Function `append`](#0x1_stake_append)
+- [Function `find_validator`](#0x1_stake_find_validator)
+- [Function `generate_validator_info`](#0x1_stake_generate_validator_info)
+- [Function `get_next_epoch_voting_power`](#0x1_stake_get_next_epoch_voting_power)
+- [Function `update_voting_power_increase`](#0x1_stake_update_voting_power_increase)
+- [Function `assert_stake_pool_exists`](#0x1_stake_assert_stake_pool_exists)
+- [Function `configure_allowed_validators`](#0x1_stake_configure_allowed_validators)
+- [Function `is_allowed`](#0x1_stake_is_allowed)
+- [Function `assert_owner_cap_exists`](#0x1_stake_assert_owner_cap_exists)
+- [Function `assert_reconfig_not_in_progress`](#0x1_stake_assert_reconfig_not_in_progress)
+- [Specification](#@Specification_1)
+ - [High-level Requirements](#high-level-req)
+ - [Module-level Specification](#module-level-spec)
+ - [Resource `ValidatorSet`](#@Specification_1_ValidatorSet)
+ - [Function `initialize_validator_fees`](#@Specification_1_initialize_validator_fees)
+ - [Function `add_transaction_fee`](#@Specification_1_add_transaction_fee)
+ - [Function `get_validator_state`](#@Specification_1_get_validator_state)
+ - [Function `initialize`](#@Specification_1_initialize)
+ - [Function `remove_validators`](#@Specification_1_remove_validators)
+ - [Function `initialize_stake_owner`](#@Specification_1_initialize_stake_owner)
+ - [Function `initialize_validator`](#@Specification_1_initialize_validator)
+ - [Function `extract_owner_cap`](#@Specification_1_extract_owner_cap)
+ - [Function `deposit_owner_cap`](#@Specification_1_deposit_owner_cap)
+ - [Function `set_operator_with_cap`](#@Specification_1_set_operator_with_cap)
+ - [Function `set_delegated_voter_with_cap`](#@Specification_1_set_delegated_voter_with_cap)
+ - [Function `add_stake`](#@Specification_1_add_stake)
+ - [Function `add_stake_with_cap`](#@Specification_1_add_stake_with_cap)
+ - [Function `reactivate_stake_with_cap`](#@Specification_1_reactivate_stake_with_cap)
+ - [Function `rotate_consensus_key`](#@Specification_1_rotate_consensus_key)
+ - [Function `update_network_and_fullnode_addresses`](#@Specification_1_update_network_and_fullnode_addresses)
+ - [Function `increase_lockup_with_cap`](#@Specification_1_increase_lockup_with_cap)
+ - [Function `join_validator_set`](#@Specification_1_join_validator_set)
+ - [Function `unlock_with_cap`](#@Specification_1_unlock_with_cap)
+ - [Function `withdraw`](#@Specification_1_withdraw)
+ - [Function `leave_validator_set`](#@Specification_1_leave_validator_set)
+ - [Function `is_current_epoch_validator`](#@Specification_1_is_current_epoch_validator)
+ - [Function `update_performance_statistics`](#@Specification_1_update_performance_statistics)
+ - [Function `on_new_epoch`](#@Specification_1_on_new_epoch)
+ - [Function `next_validator_consensus_infos`](#@Specification_1_next_validator_consensus_infos)
+ - [Function `validator_consensus_infos_from_validator_set`](#@Specification_1_validator_consensus_infos_from_validator_set)
+ - [Function `update_stake_pool`](#@Specification_1_update_stake_pool)
+ - [Function `get_reconfig_start_time_secs`](#@Specification_1_get_reconfig_start_time_secs)
+ - [Function `calculate_rewards_amount`](#@Specification_1_calculate_rewards_amount)
+ - [Function `distribute_rewards`](#@Specification_1_distribute_rewards)
+ - [Function `append`](#@Specification_1_append)
+ - [Function `find_validator`](#@Specification_1_find_validator)
+ - [Function `update_voting_power_increase`](#@Specification_1_update_voting_power_increase)
+ - [Function `assert_stake_pool_exists`](#@Specification_1_assert_stake_pool_exists)
+ - [Function `configure_allowed_validators`](#@Specification_1_configure_allowed_validators)
+ - [Function `assert_owner_cap_exists`](#@Specification_1_assert_owner_cap_exists)
+
+
+use 0x1::account;
+use 0x1::aptos_coin;
+use 0x1::bls12381;
+use 0x1::chain_status;
+use 0x1::coin;
+use 0x1::error;
+use 0x1::event;
+use 0x1::features;
+use 0x1::fixed_point64;
+use 0x1::math64;
+use 0x1::option;
+use 0x1::reconfiguration_state;
+use 0x1::signer;
+use 0x1::staking_config;
+use 0x1::system_addresses;
+use 0x1::table;
+use 0x1::timestamp;
+use 0x1::validator_consensus_info;
+use 0x1::vector;
+
+
+
+
+
+
+## Resource `OwnerCapability`
+
+Capability that represents ownership and can be used to control the validator and the associated stake pool.
+Having this be separate from the signer for the account that the validator resources are hosted at allows
+modules to have control over a validator.
+
+
+struct OwnerCapability has store, key
+
+
+
+
+pool_address: address
+struct StakePool has key
+
+
+
+
+active: coin::Coin<aptos_coin::AptosCoin>
+inactive: coin::Coin<aptos_coin::AptosCoin>
+pending_active: coin::Coin<aptos_coin::AptosCoin>
+pending_inactive: coin::Coin<aptos_coin::AptosCoin>
+locked_until_secs: u64
+operator_address: address
+delegated_voter: address
+initialize_validator_events: event::EventHandle<stake::RegisterValidatorCandidateEvent>
+set_operator_events: event::EventHandle<stake::SetOperatorEvent>
+add_stake_events: event::EventHandle<stake::AddStakeEvent>
+reactivate_stake_events: event::EventHandle<stake::ReactivateStakeEvent>
+rotate_consensus_key_events: event::EventHandle<stake::RotateConsensusKeyEvent>
+update_network_and_fullnode_addresses_events: event::EventHandle<stake::UpdateNetworkAndFullnodeAddressesEvent>
+increase_lockup_events: event::EventHandle<stake::IncreaseLockupEvent>
+join_validator_set_events: event::EventHandle<stake::JoinValidatorSetEvent>
+distribute_rewards_events: event::EventHandle<stake::DistributeRewardsEvent>
+unlock_stake_events: event::EventHandle<stake::UnlockStakeEvent>
+withdraw_stake_events: event::EventHandle<stake::WithdrawStakeEvent>
+leave_validator_set_events: event::EventHandle<stake::LeaveValidatorSetEvent>
+struct ValidatorConfig has copy, drop, store, key
+
+
+
+
+consensus_pubkey: vector<u8>
+network_addresses: vector<u8>
+fullnode_addresses: vector<u8>
+validator_index: u64
+struct ValidatorInfo has copy, drop, store
+
+
+
+
+addr: address
+voting_power: u64
+config: stake::ValidatorConfig
+struct ValidatorSet has copy, drop, store, key
+
+
+
+
+consensus_scheme: u8
+active_validators: vector<stake::ValidatorInfo>
+pending_inactive: vector<stake::ValidatorInfo>
+pending_active: vector<stake::ValidatorInfo>
+total_voting_power: u128
+total_joining_power: u128
+struct AptosCoinCapabilities has key
+
+
+
+
+mint_cap: coin::MintCapability<aptos_coin::AptosCoin>
+struct IndividualValidatorPerformance has drop, store
+
+
+
+
+successful_proposals: u64
+failed_proposals: u64
+struct ValidatorPerformance has key
+
+
+
+
+validators: vector<stake::IndividualValidatorPerformance>
+struct RegisterValidatorCandidateEvent has drop, store
+
+
+
+
+pool_address: address
+#[event]
+struct RegisterValidatorCandidate has drop, store
+
+
+
+
+pool_address: address
+struct SetOperatorEvent has drop, store
+
+
+
+
+pool_address: address
+old_operator: address
+new_operator: address
+#[event]
+struct SetOperator has drop, store
+
+
+
+
+pool_address: address
+old_operator: address
+new_operator: address
+struct AddStakeEvent has drop, store
+
+
+
+
+pool_address: address
+amount_added: u64
+#[event]
+struct AddStake has drop, store
+
+
+
+
+pool_address: address
+amount_added: u64
+struct ReactivateStakeEvent has drop, store
+
+
+
+
+pool_address: address
+amount: u64
+#[event]
+struct ReactivateStake has drop, store
+
+
+
+
+pool_address: address
+amount: u64
+struct RotateConsensusKeyEvent has drop, store
+
+
+
+
+pool_address: address
+old_consensus_pubkey: vector<u8>
+new_consensus_pubkey: vector<u8>
+#[event]
+struct RotateConsensusKey has drop, store
+
+
+
+
+pool_address: address
+old_consensus_pubkey: vector<u8>
+new_consensus_pubkey: vector<u8>
+struct UpdateNetworkAndFullnodeAddressesEvent has drop, store
+
+
+
+
+pool_address: address
+old_network_addresses: vector<u8>
+new_network_addresses: vector<u8>
+old_fullnode_addresses: vector<u8>
+new_fullnode_addresses: vector<u8>
+#[event]
+struct UpdateNetworkAndFullnodeAddresses has drop, store
+
+
+
+
+pool_address: address
+old_network_addresses: vector<u8>
+new_network_addresses: vector<u8>
+old_fullnode_addresses: vector<u8>
+new_fullnode_addresses: vector<u8>
+struct IncreaseLockupEvent has drop, store
+
+
+
+
+pool_address: address
+old_locked_until_secs: u64
+new_locked_until_secs: u64
+#[event]
+struct IncreaseLockup has drop, store
+
+
+
+
+pool_address: address
+old_locked_until_secs: u64
+new_locked_until_secs: u64
+struct JoinValidatorSetEvent has drop, store
+
+
+
+
+pool_address: address
+#[event]
+struct JoinValidatorSet has drop, store
+
+
+
+
+pool_address: address
+struct DistributeRewardsEvent has drop, store
+
+
+
+
+pool_address: address
+rewards_amount: u64
+#[event]
+struct DistributeRewards has drop, store
+
+
+
+
+pool_address: address
+rewards_amount: u64
+struct UnlockStakeEvent has drop, store
+
+
+
+
+pool_address: address
+amount_unlocked: u64
+#[event]
+struct UnlockStake has drop, store
+
+
+
+
+pool_address: address
+amount_unlocked: u64
+struct WithdrawStakeEvent has drop, store
+
+
+
+
+pool_address: address
+amount_withdrawn: u64
+#[event]
+struct WithdrawStake has drop, store
+
+
+
+
+pool_address: address
+amount_withdrawn: u64
+struct LeaveValidatorSetEvent has drop, store
+
+
+
+
+pool_address: address
+#[event]
+struct LeaveValidatorSet has drop, store
+
+
+
+
+pool_address: address
+struct ValidatorFees has key
+
+
+
+
+fees_table: table::Table<address, coin::Coin<aptos_coin::AptosCoin>>
+struct AllowedValidators has key
+
+
+
+
+accounts: vector<address>
+struct Ghost$ghost_valid_perf has copy, drop, store, key
+
+
+
+
+v: stake::ValidatorPerformance
+struct Ghost$ghost_proposer_idx has copy, drop, store, key
+
+
+
+
+v: option::Option<u64>
+struct Ghost$ghost_active_num has copy, drop, store, key
+
+
+
+
+v: u64
+struct Ghost$ghost_pending_inactive_num has copy, drop, store, key
+
+
+
+
+v: u64
+const MAX_U64: u128 = 18446744073709551615;
+
+
+
+
+
+
+Account is already registered as a validator candidate.
+
+
+const EALREADY_REGISTERED: u64 = 8;
+
+
+
+
+
+
+Limit the maximum value of rewards_rate
in order to avoid any arithmetic overflow.
+
+
+const MAX_REWARDS_RATE: u64 = 1000000;
+
+
+
+
+
+
+Account is already a validator or pending validator.
+
+
+const EALREADY_ACTIVE_VALIDATOR: u64 = 4;
+
+
+
+
+
+
+Table to store collected transaction fees for each validator already exists.
+
+
+const EFEES_TABLE_ALREADY_EXISTS: u64 = 19;
+
+
+
+
+
+
+Validator is not defined in the ACL of entities allowed to be validators
+
+
+const EINELIGIBLE_VALIDATOR: u64 = 17;
+
+
+
+
+
+
+Cannot update stake pool's lockup to earlier than current lockup.
+
+
+const EINVALID_LOCKUP: u64 = 18;
+
+
+
+
+
+
+Invalid consensus public key
+
+
+const EINVALID_PUBLIC_KEY: u64 = 11;
+
+
+
+
+
+
+Can't remove last validator.
+
+
+const ELAST_VALIDATOR: u64 = 6;
+
+
+
+
+
+
+Account does not have the right operator capability.
+
+
+const ENOT_OPERATOR: u64 = 9;
+
+
+
+
+
+
+Account is not a validator.
+
+
+const ENOT_VALIDATOR: u64 = 5;
+
+
+
+
+
+
+Validators cannot join or leave post genesis on this test network.
+
+
+const ENO_POST_GENESIS_VALIDATOR_SET_CHANGE_ALLOWED: u64 = 10;
+
+
+
+
+
+
+An account cannot own more than one owner capability.
+
+
+const EOWNER_CAP_ALREADY_EXISTS: u64 = 16;
+
+
+
+
+
+
+Owner capability does not exist at the provided account.
+
+
+const EOWNER_CAP_NOT_FOUND: u64 = 15;
+
+
+
+
+
+
+Validator set change temporarily disabled because of in-progress reconfiguration.
+
+
+const ERECONFIGURATION_IN_PROGRESS: u64 = 20;
+
+
+
+
+
+
+Total stake exceeds maximum allowed.
+
+
+const ESTAKE_EXCEEDS_MAX: u64 = 7;
+
+
+
+
+
+
+Stake pool does not exist at the provided pool address.
+
+
+const ESTAKE_POOL_DOES_NOT_EXIST: u64 = 14;
+
+
+
+
+
+
+Too much stake to join validator set.
+
+
+const ESTAKE_TOO_HIGH: u64 = 3;
+
+
+
+
+
+
+Not enough stake to join validator set.
+
+
+const ESTAKE_TOO_LOW: u64 = 2;
+
+
+
+
+
+
+Validator Config not published.
+
+
+const EVALIDATOR_CONFIG: u64 = 1;
+
+
+
+
+
+
+Validator set exceeds the limit
+
+
+const EVALIDATOR_SET_TOO_LARGE: u64 = 12;
+
+
+
+
+
+
+Voting power increase has exceeded the limit for this current epoch.
+
+
+const EVOTING_POWER_INCREASE_EXCEEDS_LIMIT: u64 = 13;
+
+
+
+
+
+
+Limit the maximum size to u16::max, it's the current limit of the bitvec
+https://github.com/aptos-labs/aptos-core/blob/main/crates/aptos-bitvec/src/lib.rs#L20
+
+
+const MAX_VALIDATOR_SET_SIZE: u64 = 65536;
+
+
+
+
+
+
+
+
+const VALIDATOR_STATUS_ACTIVE: u64 = 2;
+
+
+
+
+
+
+
+
+const VALIDATOR_STATUS_INACTIVE: u64 = 4;
+
+
+
+
+
+
+Validator status enum. We can switch to proper enum later once Move supports it.
+
+
+const VALIDATOR_STATUS_PENDING_ACTIVE: u64 = 1;
+
+
+
+
+
+
+
+
+const VALIDATOR_STATUS_PENDING_INACTIVE: u64 = 3;
+
+
+
+
+
+
+## Function `initialize_validator_fees`
+
+Initializes the resource storing information about collected transaction fees per validator.
+Used by transaction_fee.move
to initialize fee collection and distribution.
+
+
+public(friend) fun initialize_validator_fees(aptos_framework: &signer)
+
+
+
+
+public(friend) fun initialize_validator_fees(aptos_framework: &signer) {
+ system_addresses::assert_aptos_framework(aptos_framework);
+ assert!(
+ !exists<ValidatorFees>(@aptos_framework),
+ error::already_exists(EFEES_TABLE_ALREADY_EXISTS)
+ );
+ move_to(aptos_framework, ValidatorFees { fees_table: table::new() });
+}
+
+
+
+
+public(friend) fun add_transaction_fee(validator_addr: address, fee: coin::Coin<aptos_coin::AptosCoin>)
+
+
+
+
+public(friend) fun add_transaction_fee(validator_addr: address, fee: Coin<AptosCoin>) acquires ValidatorFees {
+ let fees_table = &mut borrow_global_mut<ValidatorFees>(@aptos_framework).fees_table;
+ if (table::contains(fees_table, validator_addr)) {
+ let collected_fee = table::borrow_mut(fees_table, validator_addr);
+ coin::merge(collected_fee, fee);
+ } else {
+ table::add(fees_table, validator_addr, fee);
+ }
+}
+
+
+
+
+pool_address
.
+This will throw an error if there's no stake pool at pool_address
.
+
+
+#[view]
+public fun get_lockup_secs(pool_address: address): u64
+
+
+
+
+public fun get_lockup_secs(pool_address: address): u64 acquires StakePool {
+ assert_stake_pool_exists(pool_address);
+ borrow_global<StakePool>(pool_address).locked_until_secs
+}
+
+
+
+
+pool_address
.
+This will throw an error if there's no stake pool at pool_address
.
+
+
+#[view]
+public fun get_remaining_lockup_secs(pool_address: address): u64
+
+
+
+
+public fun get_remaining_lockup_secs(pool_address: address): u64 acquires StakePool {
+ assert_stake_pool_exists(pool_address);
+ let lockup_time = borrow_global<StakePool>(pool_address).locked_until_secs;
+ if (lockup_time <= timestamp::now_seconds()) {
+ 0
+ } else {
+ lockup_time - timestamp::now_seconds()
+ }
+}
+
+
+
+
+pool_address
(whether the validator is active or not).
+The returned amounts are for (active, inactive, pending_active, pending_inactive) stake respectively.
+
+
+#[view]
+public fun get_stake(pool_address: address): (u64, u64, u64, u64)
+
+
+
+
+public fun get_stake(pool_address: address): (u64, u64, u64, u64) acquires StakePool {
+ assert_stake_pool_exists(pool_address);
+ let stake_pool = borrow_global<StakePool>(pool_address);
+ (
+ coin::value(&stake_pool.active),
+ coin::value(&stake_pool.inactive),
+ coin::value(&stake_pool.pending_active),
+ coin::value(&stake_pool.pending_inactive),
+ )
+}
+
+
+
+
+#[view]
+public fun get_validator_state(pool_address: address): u64
+
+
+
+
+public fun get_validator_state(pool_address: address): u64 acquires ValidatorSet {
+ let validator_set = borrow_global<ValidatorSet>(@aptos_framework);
+ if (option::is_some(&find_validator(&validator_set.pending_active, pool_address))) {
+ VALIDATOR_STATUS_PENDING_ACTIVE
+ } else if (option::is_some(&find_validator(&validator_set.active_validators, pool_address))) {
+ VALIDATOR_STATUS_ACTIVE
+ } else if (option::is_some(&find_validator(&validator_set.pending_inactive, pool_address))) {
+ VALIDATOR_STATUS_PENDING_INACTIVE
+ } else {
+ VALIDATOR_STATUS_INACTIVE
+ }
+}
+
+
+
+
+#[view]
+public fun get_current_epoch_voting_power(pool_address: address): u64
+
+
+
+
+public fun get_current_epoch_voting_power(pool_address: address): u64 acquires StakePool, ValidatorSet {
+ assert_stake_pool_exists(pool_address);
+ let validator_state = get_validator_state(pool_address);
+ // Both active and pending inactive validators can still vote in the current epoch.
+ if (validator_state == VALIDATOR_STATUS_ACTIVE || validator_state == VALIDATOR_STATUS_PENDING_INACTIVE) {
+ let active_stake = coin::value(&borrow_global<StakePool>(pool_address).active);
+ let pending_inactive_stake = coin::value(&borrow_global<StakePool>(pool_address).pending_inactive);
+ active_stake + pending_inactive_stake
+ } else {
+ 0
+ }
+}
+
+
+
+
+pool_address
.
+
+
+#[view]
+public fun get_delegated_voter(pool_address: address): address
+
+
+
+
+public fun get_delegated_voter(pool_address: address): address acquires StakePool {
+ assert_stake_pool_exists(pool_address);
+ borrow_global<StakePool>(pool_address).delegated_voter
+}
+
+
+
+
+pool_address
.
+
+
+#[view]
+public fun get_operator(pool_address: address): address
+
+
+
+
+public fun get_operator(pool_address: address): address acquires StakePool {
+ assert_stake_pool_exists(pool_address);
+ borrow_global<StakePool>(pool_address).operator_address
+}
+
+
+
+
+owner_cap
.
+
+
+public fun get_owned_pool_address(owner_cap: &stake::OwnerCapability): address
+
+
+
+
+public fun get_owned_pool_address(owner_cap: &OwnerCapability): address {
+ owner_cap.pool_address
+}
+
+
+
+
+pool_address
.
+
+
+#[view]
+public fun get_validator_index(pool_address: address): u64
+
+
+
+
+public fun get_validator_index(pool_address: address): u64 acquires ValidatorConfig {
+ assert_stake_pool_exists(pool_address);
+ borrow_global<ValidatorConfig>(pool_address).validator_index
+}
+
+
+
+
+#[view]
+public fun get_current_epoch_proposal_counts(validator_index: u64): (u64, u64)
+
+
+
+
+public fun get_current_epoch_proposal_counts(validator_index: u64): (u64, u64) acquires ValidatorPerformance {
+ let validator_performances = &borrow_global<ValidatorPerformance>(@aptos_framework).validators;
+ let validator_performance = vector::borrow(validator_performances, validator_index);
+ (validator_performance.successful_proposals, validator_performance.failed_proposals)
+}
+
+
+
+
+#[view]
+public fun get_validator_config(pool_address: address): (vector<u8>, vector<u8>, vector<u8>)
+
+
+
+
+public fun get_validator_config(
+ pool_address: address
+): (vector<u8>, vector<u8>, vector<u8>) acquires ValidatorConfig {
+ assert_stake_pool_exists(pool_address);
+ let validator_config = borrow_global<ValidatorConfig>(pool_address);
+ (validator_config.consensus_pubkey, validator_config.network_addresses, validator_config.fullnode_addresses)
+}
+
+
+
+
+#[view]
+public fun stake_pool_exists(addr: address): bool
+
+
+
+
+public fun stake_pool_exists(addr: address): bool {
+ exists<StakePool>(addr)
+}
+
+
+
+
+public(friend) fun initialize(aptos_framework: &signer)
+
+
+
+
+public(friend) fun initialize(aptos_framework: &signer) {
+ system_addresses::assert_aptos_framework(aptos_framework);
+
+ move_to(aptos_framework, ValidatorSet {
+ consensus_scheme: 0,
+ active_validators: vector::empty(),
+ pending_active: vector::empty(),
+ pending_inactive: vector::empty(),
+ total_voting_power: 0,
+ total_joining_power: 0,
+ });
+
+ move_to(aptos_framework, ValidatorPerformance {
+ validators: vector::empty(),
+ });
+}
+
+
+
+
+public(friend) fun store_aptos_coin_mint_cap(aptos_framework: &signer, mint_cap: coin::MintCapability<aptos_coin::AptosCoin>)
+
+
+
+
+public(friend) fun store_aptos_coin_mint_cap(aptos_framework: &signer, mint_cap: MintCapability<AptosCoin>) {
+ system_addresses::assert_aptos_framework(aptos_framework);
+ move_to(aptos_framework, AptosCoinCapabilities { mint_cap })
+}
+
+
+
+
+public fun remove_validators(aptos_framework: &signer, validators: &vector<address>)
+
+
+
+
+public fun remove_validators(
+ aptos_framework: &signer,
+ validators: &vector<address>,
+) acquires ValidatorSet {
+ assert_reconfig_not_in_progress();
+ system_addresses::assert_aptos_framework(aptos_framework);
+ let validator_set = borrow_global_mut<ValidatorSet>(@aptos_framework);
+ let active_validators = &mut validator_set.active_validators;
+ let pending_inactive = &mut validator_set.pending_inactive;
+ spec {
+ update ghost_active_num = len(active_validators);
+ update ghost_pending_inactive_num = len(pending_inactive);
+ };
+ let len_validators = vector::length(validators);
+ let i = 0;
+ // Remove each validator from the validator set.
+ while ({
+ spec {
+ invariant i <= len_validators;
+ invariant spec_validators_are_initialized(active_validators);
+ invariant spec_validator_indices_are_valid(active_validators);
+ invariant spec_validators_are_initialized(pending_inactive);
+ invariant spec_validator_indices_are_valid(pending_inactive);
+ invariant ghost_active_num + ghost_pending_inactive_num == len(active_validators) + len(pending_inactive);
+ };
+ i < len_validators
+ }) {
+ let validator = *vector::borrow(validators, i);
+ let validator_index = find_validator(active_validators, validator);
+ if (option::is_some(&validator_index)) {
+ let validator_info = vector::swap_remove(active_validators, *option::borrow(&validator_index));
+ vector::push_back(pending_inactive, validator_info);
+ spec {
+ update ghost_active_num = ghost_active_num - 1;
+ update ghost_pending_inactive_num = ghost_pending_inactive_num + 1;
+ };
+ };
+ i = i + 1;
+ };
+}
+
+
+
+
+public entry fun initialize_stake_owner(owner: &signer, initial_stake_amount: u64, operator: address, voter: address)
+
+
+
+
+public entry fun initialize_stake_owner(
+ owner: &signer,
+ initial_stake_amount: u64,
+ operator: address,
+ voter: address,
+) acquires AllowedValidators, OwnerCapability, StakePool, ValidatorSet {
+ initialize_owner(owner);
+ move_to(owner, ValidatorConfig {
+ consensus_pubkey: vector::empty(),
+ network_addresses: vector::empty(),
+ fullnode_addresses: vector::empty(),
+ validator_index: 0,
+ });
+
+ if (initial_stake_amount > 0) {
+ add_stake(owner, initial_stake_amount);
+ };
+
+ let account_address = signer::address_of(owner);
+ if (account_address != operator) {
+ set_operator(owner, operator)
+ };
+ if (account_address != voter) {
+ set_delegated_voter(owner, voter)
+ };
+}
+
+
+
+
+public entry fun initialize_validator(account: &signer, consensus_pubkey: vector<u8>, proof_of_possession: vector<u8>, network_addresses: vector<u8>, fullnode_addresses: vector<u8>)
+
+
+
+
+public entry fun initialize_validator(
+ account: &signer,
+ consensus_pubkey: vector<u8>,
+ proof_of_possession: vector<u8>,
+ network_addresses: vector<u8>,
+ fullnode_addresses: vector<u8>,
+) acquires AllowedValidators {
+ // Checks the public key has a valid proof-of-possession to prevent rogue-key attacks.
+ let pubkey_from_pop = &mut bls12381::public_key_from_bytes_with_pop(
+ consensus_pubkey,
+ &proof_of_possession_from_bytes(proof_of_possession)
+ );
+ assert!(option::is_some(pubkey_from_pop), error::invalid_argument(EINVALID_PUBLIC_KEY));
+
+ initialize_owner(account);
+ move_to(account, ValidatorConfig {
+ consensus_pubkey,
+ network_addresses,
+ fullnode_addresses,
+ validator_index: 0,
+ });
+}
+
+
+
+
+fun initialize_owner(owner: &signer)
+
+
+
+
+fun initialize_owner(owner: &signer) acquires AllowedValidators {
+ let owner_address = signer::address_of(owner);
+ assert!(is_allowed(owner_address), error::not_found(EINELIGIBLE_VALIDATOR));
+ assert!(!stake_pool_exists(owner_address), error::already_exists(EALREADY_REGISTERED));
+
+ move_to(owner, StakePool {
+ active: coin::zero<AptosCoin>(),
+ pending_active: coin::zero<AptosCoin>(),
+ pending_inactive: coin::zero<AptosCoin>(),
+ inactive: coin::zero<AptosCoin>(),
+ locked_until_secs: 0,
+ operator_address: owner_address,
+ delegated_voter: owner_address,
+ // Events.
+ initialize_validator_events: account::new_event_handle<RegisterValidatorCandidateEvent>(owner),
+ set_operator_events: account::new_event_handle<SetOperatorEvent>(owner),
+ add_stake_events: account::new_event_handle<AddStakeEvent>(owner),
+ reactivate_stake_events: account::new_event_handle<ReactivateStakeEvent>(owner),
+ rotate_consensus_key_events: account::new_event_handle<RotateConsensusKeyEvent>(owner),
+ update_network_and_fullnode_addresses_events: account::new_event_handle<UpdateNetworkAndFullnodeAddressesEvent>(
+ owner
+ ),
+ increase_lockup_events: account::new_event_handle<IncreaseLockupEvent>(owner),
+ join_validator_set_events: account::new_event_handle<JoinValidatorSetEvent>(owner),
+ distribute_rewards_events: account::new_event_handle<DistributeRewardsEvent>(owner),
+ unlock_stake_events: account::new_event_handle<UnlockStakeEvent>(owner),
+ withdraw_stake_events: account::new_event_handle<WithdrawStakeEvent>(owner),
+ leave_validator_set_events: account::new_event_handle<LeaveValidatorSetEvent>(owner),
+ });
+
+ move_to(owner, OwnerCapability { pool_address: owner_address });
+}
+
+
+
+
+public fun extract_owner_cap(owner: &signer): stake::OwnerCapability
+
+
+
+
+public fun extract_owner_cap(owner: &signer): OwnerCapability acquires OwnerCapability {
+ let owner_address = signer::address_of(owner);
+ assert_owner_cap_exists(owner_address);
+ move_from<OwnerCapability>(owner_address)
+}
+
+
+
+
+owner_cap
into account
. This requires account
to not already have ownership of another
+staking pool.
+
+
+public fun deposit_owner_cap(owner: &signer, owner_cap: stake::OwnerCapability)
+
+
+
+
+public fun deposit_owner_cap(owner: &signer, owner_cap: OwnerCapability) {
+ assert!(!exists<OwnerCapability>(signer::address_of(owner)), error::not_found(EOWNER_CAP_ALREADY_EXISTS));
+ move_to(owner, owner_cap);
+}
+
+
+
+
+owner_cap
.
+
+
+public fun destroy_owner_cap(owner_cap: stake::OwnerCapability)
+
+
+
+
+public fun destroy_owner_cap(owner_cap: OwnerCapability) {
+ let OwnerCapability { pool_address: _ } = owner_cap;
+}
+
+
+
+
+public entry fun set_operator(owner: &signer, new_operator: address)
+
+
+
+
+public entry fun set_operator(owner: &signer, new_operator: address) acquires OwnerCapability, StakePool {
+ let owner_address = signer::address_of(owner);
+ assert_owner_cap_exists(owner_address);
+ let ownership_cap = borrow_global<OwnerCapability>(owner_address);
+ set_operator_with_cap(ownership_cap, new_operator);
+}
+
+
+
+
+public fun set_operator_with_cap(owner_cap: &stake::OwnerCapability, new_operator: address)
+
+
+
+
+public fun set_operator_with_cap(owner_cap: &OwnerCapability, new_operator: address) acquires StakePool {
+ let pool_address = owner_cap.pool_address;
+ assert_stake_pool_exists(pool_address);
+ let stake_pool = borrow_global_mut<StakePool>(pool_address);
+ let old_operator = stake_pool.operator_address;
+ stake_pool.operator_address = new_operator;
+
+ if (std::features::module_event_migration_enabled()) {
+ event::emit(
+ SetOperator {
+ pool_address,
+ old_operator,
+ new_operator,
+ },
+ );
+ };
+
+ event::emit_event(
+ &mut stake_pool.set_operator_events,
+ SetOperatorEvent {
+ pool_address,
+ old_operator,
+ new_operator,
+ },
+ );
+}
+
+
+
+
+public entry fun set_delegated_voter(owner: &signer, new_voter: address)
+
+
+
+
+public entry fun set_delegated_voter(owner: &signer, new_voter: address) acquires OwnerCapability, StakePool {
+ let owner_address = signer::address_of(owner);
+ assert_owner_cap_exists(owner_address);
+ let ownership_cap = borrow_global<OwnerCapability>(owner_address);
+ set_delegated_voter_with_cap(ownership_cap, new_voter);
+}
+
+
+
+
+public fun set_delegated_voter_with_cap(owner_cap: &stake::OwnerCapability, new_voter: address)
+
+
+
+
+public fun set_delegated_voter_with_cap(owner_cap: &OwnerCapability, new_voter: address) acquires StakePool {
+ let pool_address = owner_cap.pool_address;
+ assert_stake_pool_exists(pool_address);
+ let stake_pool = borrow_global_mut<StakePool>(pool_address);
+ stake_pool.delegated_voter = new_voter;
+}
+
+
+
+
+amount
of coins from the account
owning the StakePool.
+
+
+public entry fun add_stake(owner: &signer, amount: u64)
+
+
+
+
+public entry fun add_stake(owner: &signer, amount: u64) acquires OwnerCapability, StakePool, ValidatorSet {
+ let owner_address = signer::address_of(owner);
+ assert_owner_cap_exists(owner_address);
+ let ownership_cap = borrow_global<OwnerCapability>(owner_address);
+ add_stake_with_cap(ownership_cap, coin::withdraw<AptosCoin>(owner, amount));
+}
+
+
+
+
+coins
into pool_address
. this requires the corresponding owner_cap
to be passed in.
+
+
+public fun add_stake_with_cap(owner_cap: &stake::OwnerCapability, coins: coin::Coin<aptos_coin::AptosCoin>)
+
+
+
+
+public fun add_stake_with_cap(owner_cap: &OwnerCapability, coins: Coin<AptosCoin>) acquires StakePool, ValidatorSet {
+ assert_reconfig_not_in_progress();
+ let pool_address = owner_cap.pool_address;
+ assert_stake_pool_exists(pool_address);
+
+ let amount = coin::value(&coins);
+ if (amount == 0) {
+ coin::destroy_zero(coins);
+ return
+ };
+
+ // Only track and validate voting power increase for active and pending_active validator.
+ // Pending_inactive validator will be removed from the validator set in the next epoch.
+ // Inactive validator's total stake will be tracked when they join the validator set.
+ let validator_set = borrow_global_mut<ValidatorSet>(@aptos_framework);
+ // Search directly rather using get_validator_state to save on unnecessary loops.
+ if (option::is_some(&find_validator(&validator_set.active_validators, pool_address)) ||
+ option::is_some(&find_validator(&validator_set.pending_active, pool_address))) {
+ update_voting_power_increase(amount);
+ };
+
+ // Add to pending_active if it's a current validator because the stake is not counted until the next epoch.
+ // Otherwise, the delegation can be added to active directly as the validator is also activated in the epoch.
+ let stake_pool = borrow_global_mut<StakePool>(pool_address);
+ if (is_current_epoch_validator(pool_address)) {
+ coin::merge<AptosCoin>(&mut stake_pool.pending_active, coins);
+ } else {
+ coin::merge<AptosCoin>(&mut stake_pool.active, coins);
+ };
+
+ let (_, maximum_stake) = staking_config::get_required_stake(&staking_config::get());
+ let voting_power = get_next_epoch_voting_power(stake_pool);
+ assert!(voting_power <= maximum_stake, error::invalid_argument(ESTAKE_EXCEEDS_MAX));
+
+ if (std::features::module_event_migration_enabled()) {
+ event::emit(
+ AddStake {
+ pool_address,
+ amount_added: amount,
+ },
+ );
+ };
+ event::emit_event(
+ &mut stake_pool.add_stake_events,
+ AddStakeEvent {
+ pool_address,
+ amount_added: amount,
+ },
+ );
+}
+
+
+
+
+amount
of coins from pending_inactive to active.
+
+
+public entry fun reactivate_stake(owner: &signer, amount: u64)
+
+
+
+
+public entry fun reactivate_stake(owner: &signer, amount: u64) acquires OwnerCapability, StakePool {
+ assert_reconfig_not_in_progress();
+ let owner_address = signer::address_of(owner);
+ assert_owner_cap_exists(owner_address);
+ let ownership_cap = borrow_global<OwnerCapability>(owner_address);
+ reactivate_stake_with_cap(ownership_cap, amount);
+}
+
+
+
+
+public fun reactivate_stake_with_cap(owner_cap: &stake::OwnerCapability, amount: u64)
+
+
+
+
+public fun reactivate_stake_with_cap(owner_cap: &OwnerCapability, amount: u64) acquires StakePool {
+ assert_reconfig_not_in_progress();
+ let pool_address = owner_cap.pool_address;
+ assert_stake_pool_exists(pool_address);
+
+ // Cap the amount to reactivate by the amount in pending_inactive.
+ let stake_pool = borrow_global_mut<StakePool>(pool_address);
+ let total_pending_inactive = coin::value(&stake_pool.pending_inactive);
+ amount = min(amount, total_pending_inactive);
+
+ // Since this does not count as a voting power change (pending inactive still counts as voting power in the
+ // current epoch), stake can be immediately moved from pending inactive to active.
+ // We also don't need to check voting power increase as there's none.
+ let reactivated_coins = coin::extract(&mut stake_pool.pending_inactive, amount);
+ coin::merge(&mut stake_pool.active, reactivated_coins);
+
+ if (std::features::module_event_migration_enabled()) {
+ event::emit(
+ ReactivateStake {
+ pool_address,
+ amount,
+ },
+ );
+ };
+ event::emit_event(
+ &mut stake_pool.reactivate_stake_events,
+ ReactivateStakeEvent {
+ pool_address,
+ amount,
+ },
+ );
+}
+
+
+
+
+public entry fun rotate_consensus_key(operator: &signer, pool_address: address, new_consensus_pubkey: vector<u8>, proof_of_possession: vector<u8>)
+
+
+
+
+public entry fun rotate_consensus_key(
+ operator: &signer,
+ pool_address: address,
+ new_consensus_pubkey: vector<u8>,
+ proof_of_possession: vector<u8>,
+) acquires StakePool, ValidatorConfig {
+ assert_reconfig_not_in_progress();
+ assert_stake_pool_exists(pool_address);
+
+ let stake_pool = borrow_global_mut<StakePool>(pool_address);
+ assert!(signer::address_of(operator) == stake_pool.operator_address, error::unauthenticated(ENOT_OPERATOR));
+
+ assert!(exists<ValidatorConfig>(pool_address), error::not_found(EVALIDATOR_CONFIG));
+ let validator_info = borrow_global_mut<ValidatorConfig>(pool_address);
+ let old_consensus_pubkey = validator_info.consensus_pubkey;
+ // Checks the public key has a valid proof-of-possession to prevent rogue-key attacks.
+ let pubkey_from_pop = &mut bls12381::public_key_from_bytes_with_pop(
+ new_consensus_pubkey,
+ &proof_of_possession_from_bytes(proof_of_possession)
+ );
+ assert!(option::is_some(pubkey_from_pop), error::invalid_argument(EINVALID_PUBLIC_KEY));
+ validator_info.consensus_pubkey = new_consensus_pubkey;
+
+ if (std::features::module_event_migration_enabled()) {
+ event::emit(
+ RotateConsensusKey {
+ pool_address,
+ old_consensus_pubkey,
+ new_consensus_pubkey,
+ },
+ );
+ };
+ event::emit_event(
+ &mut stake_pool.rotate_consensus_key_events,
+ RotateConsensusKeyEvent {
+ pool_address,
+ old_consensus_pubkey,
+ new_consensus_pubkey,
+ },
+ );
+}
+
+
+
+
+public entry fun update_network_and_fullnode_addresses(operator: &signer, pool_address: address, new_network_addresses: vector<u8>, new_fullnode_addresses: vector<u8>)
+
+
+
+
+public entry fun update_network_and_fullnode_addresses(
+ operator: &signer,
+ pool_address: address,
+ new_network_addresses: vector<u8>,
+ new_fullnode_addresses: vector<u8>,
+) acquires StakePool, ValidatorConfig {
+ assert_reconfig_not_in_progress();
+ assert_stake_pool_exists(pool_address);
+ let stake_pool = borrow_global_mut<StakePool>(pool_address);
+ assert!(signer::address_of(operator) == stake_pool.operator_address, error::unauthenticated(ENOT_OPERATOR));
+ assert!(exists<ValidatorConfig>(pool_address), error::not_found(EVALIDATOR_CONFIG));
+ let validator_info = borrow_global_mut<ValidatorConfig>(pool_address);
+ let old_network_addresses = validator_info.network_addresses;
+ validator_info.network_addresses = new_network_addresses;
+ let old_fullnode_addresses = validator_info.fullnode_addresses;
+ validator_info.fullnode_addresses = new_fullnode_addresses;
+
+ if (std::features::module_event_migration_enabled()) {
+ event::emit(
+ UpdateNetworkAndFullnodeAddresses {
+ pool_address,
+ old_network_addresses,
+ new_network_addresses,
+ old_fullnode_addresses,
+ new_fullnode_addresses,
+ },
+ );
+ };
+ event::emit_event(
+ &mut stake_pool.update_network_and_fullnode_addresses_events,
+ UpdateNetworkAndFullnodeAddressesEvent {
+ pool_address,
+ old_network_addresses,
+ new_network_addresses,
+ old_fullnode_addresses,
+ new_fullnode_addresses,
+ },
+ );
+
+}
+
+
+
+
+public entry fun increase_lockup(owner: &signer)
+
+
+
+
+public entry fun increase_lockup(owner: &signer) acquires OwnerCapability, StakePool {
+ let owner_address = signer::address_of(owner);
+ assert_owner_cap_exists(owner_address);
+ let ownership_cap = borrow_global<OwnerCapability>(owner_address);
+ increase_lockup_with_cap(ownership_cap);
+}
+
+
+
+
+public fun increase_lockup_with_cap(owner_cap: &stake::OwnerCapability)
+
+
+
+
+public fun increase_lockup_with_cap(owner_cap: &OwnerCapability) acquires StakePool {
+ let pool_address = owner_cap.pool_address;
+ assert_stake_pool_exists(pool_address);
+ let config = staking_config::get();
+
+ let stake_pool = borrow_global_mut<StakePool>(pool_address);
+ let old_locked_until_secs = stake_pool.locked_until_secs;
+ let new_locked_until_secs = timestamp::now_seconds() + staking_config::get_recurring_lockup_duration(&config);
+ assert!(old_locked_until_secs < new_locked_until_secs, error::invalid_argument(EINVALID_LOCKUP));
+ stake_pool.locked_until_secs = new_locked_until_secs;
+
+ if (std::features::module_event_migration_enabled()) {
+ event::emit(
+ IncreaseLockup {
+ pool_address,
+ old_locked_until_secs,
+ new_locked_until_secs,
+ },
+ );
+ };
+ event::emit_event(
+ &mut stake_pool.increase_lockup_events,
+ IncreaseLockupEvent {
+ pool_address,
+ old_locked_until_secs,
+ new_locked_until_secs,
+ },
+ );
+}
+
+
+
+
+public entry fun join_validator_set(operator: &signer, pool_address: address)
+
+
+
+
+public entry fun join_validator_set(
+ operator: &signer,
+ pool_address: address
+) acquires StakePool, ValidatorConfig, ValidatorSet {
+ assert!(
+ staking_config::get_allow_validator_set_change(&staking_config::get()),
+ error::invalid_argument(ENO_POST_GENESIS_VALIDATOR_SET_CHANGE_ALLOWED),
+ );
+
+ join_validator_set_internal(operator, pool_address);
+}
+
+
+
+
+pool_address
join the validator set. Can only be called after calling initialize_validator
.
+If the validator has the required stake (more than minimum and less than maximum allowed), they will be
+added to the pending_active queue. All validators in this queue will be added to the active set when the next
+epoch starts (eligibility will be rechecked).
+
+This internal version can only be called by the Genesis module during Genesis.
+
+
+public(friend) fun join_validator_set_internal(operator: &signer, pool_address: address)
+
+
+
+
+public(friend) fun join_validator_set_internal(
+ operator: &signer,
+ pool_address: address
+) acquires StakePool, ValidatorConfig, ValidatorSet {
+ assert_reconfig_not_in_progress();
+ assert_stake_pool_exists(pool_address);
+ let stake_pool = borrow_global_mut<StakePool>(pool_address);
+ assert!(signer::address_of(operator) == stake_pool.operator_address, error::unauthenticated(ENOT_OPERATOR));
+ assert!(
+ get_validator_state(pool_address) == VALIDATOR_STATUS_INACTIVE,
+ error::invalid_state(EALREADY_ACTIVE_VALIDATOR),
+ );
+
+ let config = staking_config::get();
+ let (minimum_stake, maximum_stake) = staking_config::get_required_stake(&config);
+ let voting_power = get_next_epoch_voting_power(stake_pool);
+ assert!(voting_power >= minimum_stake, error::invalid_argument(ESTAKE_TOO_LOW));
+ assert!(voting_power <= maximum_stake, error::invalid_argument(ESTAKE_TOO_HIGH));
+
+ // Track and validate voting power increase.
+ update_voting_power_increase(voting_power);
+
+ // Add validator to pending_active, to be activated in the next epoch.
+ let validator_config = borrow_global_mut<ValidatorConfig>(pool_address);
+ assert!(!vector::is_empty(&validator_config.consensus_pubkey), error::invalid_argument(EINVALID_PUBLIC_KEY));
+
+ // Validate the current validator set size has not exceeded the limit.
+ let validator_set = borrow_global_mut<ValidatorSet>(@aptos_framework);
+ vector::push_back(
+ &mut validator_set.pending_active,
+ generate_validator_info(pool_address, stake_pool, *validator_config)
+ );
+ let validator_set_size = vector::length(&validator_set.active_validators) + vector::length(
+ &validator_set.pending_active
+ );
+ assert!(validator_set_size <= MAX_VALIDATOR_SET_SIZE, error::invalid_argument(EVALIDATOR_SET_TOO_LARGE));
+
+ if (std::features::module_event_migration_enabled()) {
+ event::emit(JoinValidatorSet { pool_address });
+ };
+ event::emit_event(
+ &mut stake_pool.join_validator_set_events,
+ JoinValidatorSetEvent { pool_address },
+ );
+}
+
+
+
+
+public entry fun unlock(owner: &signer, amount: u64)
+
+
+
+
+public entry fun unlock(owner: &signer, amount: u64) acquires OwnerCapability, StakePool {
+ assert_reconfig_not_in_progress();
+ let owner_address = signer::address_of(owner);
+ assert_owner_cap_exists(owner_address);
+ let ownership_cap = borrow_global<OwnerCapability>(owner_address);
+ unlock_with_cap(amount, ownership_cap);
+}
+
+
+
+
+amount
from the active stake. Only possible if the lockup has expired.
+
+
+public fun unlock_with_cap(amount: u64, owner_cap: &stake::OwnerCapability)
+
+
+
+
+public fun unlock_with_cap(amount: u64, owner_cap: &OwnerCapability) acquires StakePool {
+ assert_reconfig_not_in_progress();
+ // Short-circuit if amount to unlock is 0 so we don't emit events.
+ if (amount == 0) {
+ return
+ };
+
+ // Unlocked coins are moved to pending_inactive. When the current lockup cycle expires, they will be moved into
+ // inactive in the earliest possible epoch transition.
+ let pool_address = owner_cap.pool_address;
+ assert_stake_pool_exists(pool_address);
+ let stake_pool = borrow_global_mut<StakePool>(pool_address);
+ // Cap amount to unlock by maximum active stake.
+ let amount = min(amount, coin::value(&stake_pool.active));
+ let unlocked_stake = coin::extract(&mut stake_pool.active, amount);
+ coin::merge<AptosCoin>(&mut stake_pool.pending_inactive, unlocked_stake);
+
+ if (std::features::module_event_migration_enabled()) {
+ event::emit(
+ UnlockStake {
+ pool_address,
+ amount_unlocked: amount,
+ },
+ );
+ };
+ event::emit_event(
+ &mut stake_pool.unlock_stake_events,
+ UnlockStakeEvent {
+ pool_address,
+ amount_unlocked: amount,
+ },
+ );
+}
+
+
+
+
+account
's inactive stake.
+
+
+public entry fun withdraw(owner: &signer, withdraw_amount: u64)
+
+
+
+
+public entry fun withdraw(
+ owner: &signer,
+ withdraw_amount: u64
+) acquires OwnerCapability, StakePool, ValidatorSet {
+ let owner_address = signer::address_of(owner);
+ assert_owner_cap_exists(owner_address);
+ let ownership_cap = borrow_global<OwnerCapability>(owner_address);
+ let coins = withdraw_with_cap(ownership_cap, withdraw_amount);
+ coin::deposit<AptosCoin>(owner_address, coins);
+}
+
+
+
+
+pool_address
's inactive stake with the corresponding owner_cap
.
+
+
+public fun withdraw_with_cap(owner_cap: &stake::OwnerCapability, withdraw_amount: u64): coin::Coin<aptos_coin::AptosCoin>
+
+
+
+
+public fun withdraw_with_cap(
+ owner_cap: &OwnerCapability,
+ withdraw_amount: u64
+): Coin<AptosCoin> acquires StakePool, ValidatorSet {
+ assert_reconfig_not_in_progress();
+ let pool_address = owner_cap.pool_address;
+ assert_stake_pool_exists(pool_address);
+ let stake_pool = borrow_global_mut<StakePool>(pool_address);
+ // There's an edge case where a validator unlocks their stake and leaves the validator set before
+ // the stake is fully unlocked (the current lockup cycle has not expired yet).
+ // This can leave their stake stuck in pending_inactive even after the current lockup cycle expires.
+ if (get_validator_state(pool_address) == VALIDATOR_STATUS_INACTIVE &&
+ timestamp::now_seconds() >= stake_pool.locked_until_secs) {
+ let pending_inactive_stake = coin::extract_all(&mut stake_pool.pending_inactive);
+ coin::merge(&mut stake_pool.inactive, pending_inactive_stake);
+ };
+
+ // Cap withdraw amount by total inactive coins.
+ withdraw_amount = min(withdraw_amount, coin::value(&stake_pool.inactive));
+ if (withdraw_amount == 0) return coin::zero<AptosCoin>();
+
+ if (std::features::module_event_migration_enabled()) {
+ event::emit(
+ WithdrawStake {
+ pool_address,
+ amount_withdrawn: withdraw_amount,
+ },
+ );
+ };
+ event::emit_event(
+ &mut stake_pool.withdraw_stake_events,
+ WithdrawStakeEvent {
+ pool_address,
+ amount_withdrawn: withdraw_amount,
+ },
+ );
+
+ coin::extract(&mut stake_pool.inactive, withdraw_amount)
+}
+
+
+
+
+pool_address
leave the validator set. The validator is only actually removed from the set when
+the next epoch starts.
+The last validator in the set cannot leave. This is an edge case that should never happen as long as the network
+is still operational.
+
+Can only be called by the operator of the validator/staking pool.
+
+
+public entry fun leave_validator_set(operator: &signer, pool_address: address)
+
+
+
+
+public entry fun leave_validator_set(
+ operator: &signer,
+ pool_address: address
+) acquires StakePool, ValidatorSet {
+ assert_reconfig_not_in_progress();
+ let config = staking_config::get();
+ assert!(
+ staking_config::get_allow_validator_set_change(&config),
+ error::invalid_argument(ENO_POST_GENESIS_VALIDATOR_SET_CHANGE_ALLOWED),
+ );
+
+ assert_stake_pool_exists(pool_address);
+ let stake_pool = borrow_global_mut<StakePool>(pool_address);
+ // Account has to be the operator.
+ assert!(signer::address_of(operator) == stake_pool.operator_address, error::unauthenticated(ENOT_OPERATOR));
+
+ let validator_set = borrow_global_mut<ValidatorSet>(@aptos_framework);
+ // If the validator is still pending_active, directly kick the validator out.
+ let maybe_pending_active_index = find_validator(&validator_set.pending_active, pool_address);
+ if (option::is_some(&maybe_pending_active_index)) {
+ vector::swap_remove(
+ &mut validator_set.pending_active, option::extract(&mut maybe_pending_active_index));
+
+ // Decrease the voting power increase as the pending validator's voting power was added when they requested
+ // to join. Now that they changed their mind, their voting power should not affect the joining limit of this
+ // epoch.
+ let validator_stake = (get_next_epoch_voting_power(stake_pool) as u128);
+ // total_joining_power should be larger than validator_stake but just in case there has been a small
+ // rounding error somewhere that can lead to an underflow, we still want to allow this transaction to
+ // succeed.
+ if (validator_set.total_joining_power > validator_stake) {
+ validator_set.total_joining_power = validator_set.total_joining_power - validator_stake;
+ } else {
+ validator_set.total_joining_power = 0;
+ };
+ } else {
+ // Validate that the validator is already part of the validator set.
+ let maybe_active_index = find_validator(&validator_set.active_validators, pool_address);
+ assert!(option::is_some(&maybe_active_index), error::invalid_state(ENOT_VALIDATOR));
+ let validator_info = vector::swap_remove(
+ &mut validator_set.active_validators, option::extract(&mut maybe_active_index));
+ assert!(vector::length(&validator_set.active_validators) > 0, error::invalid_state(ELAST_VALIDATOR));
+ vector::push_back(&mut validator_set.pending_inactive, validator_info);
+
+ if (std::features::module_event_migration_enabled()) {
+ event::emit(LeaveValidatorSet { pool_address });
+ };
+ event::emit_event(
+ &mut stake_pool.leave_validator_set_events,
+ LeaveValidatorSetEvent {
+ pool_address,
+ },
+ );
+ };
+}
+
+
+
+
+public fun is_current_epoch_validator(pool_address: address): bool
+
+
+
+
+public fun is_current_epoch_validator(pool_address: address): bool acquires ValidatorSet {
+ assert_stake_pool_exists(pool_address);
+ let validator_state = get_validator_state(pool_address);
+ validator_state == VALIDATOR_STATUS_ACTIVE || validator_state == VALIDATOR_STATUS_PENDING_INACTIVE
+}
+
+
+
+
+public(friend) fun update_performance_statistics(proposer_index: option::Option<u64>, failed_proposer_indices: vector<u64>)
+
+
+
+
+public(friend) fun update_performance_statistics(
+ proposer_index: Option<u64>,
+ failed_proposer_indices: vector<u64>
+) acquires ValidatorPerformance {
+ // Validator set cannot change until the end of the epoch, so the validator index in arguments should
+ // match with those of the validators in ValidatorPerformance resource.
+ let validator_perf = borrow_global_mut<ValidatorPerformance>(@aptos_framework);
+ let validator_len = vector::length(&validator_perf.validators);
+
+ spec {
+ update ghost_valid_perf = validator_perf;
+ update ghost_proposer_idx = proposer_index;
+ };
+ // proposer_index is an option because it can be missing (for NilBlocks)
+ if (option::is_some(&proposer_index)) {
+ let cur_proposer_index = option::extract(&mut proposer_index);
+ // Here, and in all other vector::borrow, skip any validator indices that are out of bounds,
+ // this ensures that this function doesn't abort if there are out of bounds errors.
+ if (cur_proposer_index < validator_len) {
+ let validator = vector::borrow_mut(&mut validator_perf.validators, cur_proposer_index);
+ spec {
+ assume validator.successful_proposals + 1 <= MAX_U64;
+ };
+ validator.successful_proposals = validator.successful_proposals + 1;
+ };
+ };
+
+ let f = 0;
+ let f_len = vector::length(&failed_proposer_indices);
+ while ({
+ spec {
+ invariant len(validator_perf.validators) == validator_len;
+ invariant (option::spec_is_some(ghost_proposer_idx) && option::spec_borrow(
+ ghost_proposer_idx
+ ) < validator_len) ==>
+ (validator_perf.validators[option::spec_borrow(ghost_proposer_idx)].successful_proposals ==
+ ghost_valid_perf.validators[option::spec_borrow(ghost_proposer_idx)].successful_proposals + 1);
+ };
+ f < f_len
+ }) {
+ let validator_index = *vector::borrow(&failed_proposer_indices, f);
+ if (validator_index < validator_len) {
+ let validator = vector::borrow_mut(&mut validator_perf.validators, validator_index);
+ spec {
+ assume validator.failed_proposals + 1 <= MAX_U64;
+ };
+ validator.failed_proposals = validator.failed_proposals + 1;
+ };
+ f = f + 1;
+ };
+}
+
+
+
+
+public(friend) fun on_new_epoch()
+
+
+
+
+public(friend) fun on_new_epoch(
+) acquires StakePool, AptosCoinCapabilities, ValidatorConfig, ValidatorPerformance, ValidatorSet, ValidatorFees {
+ let validator_set = borrow_global_mut<ValidatorSet>(@aptos_framework);
+ let config = staking_config::get();
+ let validator_perf = borrow_global_mut<ValidatorPerformance>(@aptos_framework);
+
+ // Process pending stake and distribute transaction fees and rewards for each currently active validator.
+ vector::for_each_ref(&validator_set.active_validators, |validator| {
+ let validator: &ValidatorInfo = validator;
+ update_stake_pool(validator_perf, validator.addr, &config);
+ });
+
+ // Process pending stake and distribute transaction fees and rewards for each currently pending_inactive validator
+ // (requested to leave but not removed yet).
+ vector::for_each_ref(&validator_set.pending_inactive, |validator| {
+ let validator: &ValidatorInfo = validator;
+ update_stake_pool(validator_perf, validator.addr, &config);
+ });
+
+ // Activate currently pending_active validators.
+ append(&mut validator_set.active_validators, &mut validator_set.pending_active);
+
+ // Officially deactivate all pending_inactive validators. They will now no longer receive rewards.
+ validator_set.pending_inactive = vector::empty();
+
+ // Update active validator set so that network address/public key change takes effect.
+ // Moreover, recalculate the total voting power, and deactivate the validator whose
+ // voting power is less than the minimum required stake.
+ let next_epoch_validators = vector::empty();
+ let (minimum_stake, _) = staking_config::get_required_stake(&config);
+ let vlen = vector::length(&validator_set.active_validators);
+ let total_voting_power = 0;
+ let i = 0;
+ while ({
+ spec {
+ invariant spec_validators_are_initialized(next_epoch_validators);
+ invariant i <= vlen;
+ };
+ i < vlen
+ }) {
+ let old_validator_info = vector::borrow_mut(&mut validator_set.active_validators, i);
+ let pool_address = old_validator_info.addr;
+ let validator_config = borrow_global_mut<ValidatorConfig>(pool_address);
+ let stake_pool = borrow_global_mut<StakePool>(pool_address);
+ let new_validator_info = generate_validator_info(pool_address, stake_pool, *validator_config);
+
+ // A validator needs at least the min stake required to join the validator set.
+ if (new_validator_info.voting_power >= minimum_stake) {
+ spec {
+ assume total_voting_power + new_validator_info.voting_power <= MAX_U128;
+ };
+ total_voting_power = total_voting_power + (new_validator_info.voting_power as u128);
+ vector::push_back(&mut next_epoch_validators, new_validator_info);
+ };
+ i = i + 1;
+ };
+
+ validator_set.active_validators = next_epoch_validators;
+ validator_set.total_voting_power = total_voting_power;
+ validator_set.total_joining_power = 0;
+
+ // Update validator indices, reset performance scores, and renew lockups.
+ validator_perf.validators = vector::empty();
+ let recurring_lockup_duration_secs = staking_config::get_recurring_lockup_duration(&config);
+ let vlen = vector::length(&validator_set.active_validators);
+ let validator_index = 0;
+ while ({
+ spec {
+ invariant spec_validators_are_initialized(validator_set.active_validators);
+ invariant len(validator_set.pending_active) == 0;
+ invariant len(validator_set.pending_inactive) == 0;
+ invariant 0 <= validator_index && validator_index <= vlen;
+ invariant vlen == len(validator_set.active_validators);
+ invariant forall i in 0..validator_index:
+ global<ValidatorConfig>(validator_set.active_validators[i].addr).validator_index < validator_index;
+ invariant forall i in 0..validator_index:
+ validator_set.active_validators[i].config.validator_index < validator_index;
+ invariant len(validator_perf.validators) == validator_index;
+ };
+ validator_index < vlen
+ }) {
+ let validator_info = vector::borrow_mut(&mut validator_set.active_validators, validator_index);
+ validator_info.config.validator_index = validator_index;
+ let validator_config = borrow_global_mut<ValidatorConfig>(validator_info.addr);
+ validator_config.validator_index = validator_index;
+
+ vector::push_back(&mut validator_perf.validators, IndividualValidatorPerformance {
+ successful_proposals: 0,
+ failed_proposals: 0,
+ });
+
+ // Automatically renew a validator's lockup for validators that will still be in the validator set in the
+ // next epoch.
+ let stake_pool = borrow_global_mut<StakePool>(validator_info.addr);
+ let now_secs = timestamp::now_seconds();
+ let reconfig_start_secs = if (chain_status::is_operating()) {
+ get_reconfig_start_time_secs()
+ } else {
+ now_secs
+ };
+ if (stake_pool.locked_until_secs <= reconfig_start_secs) {
+ spec {
+ assume now_secs + recurring_lockup_duration_secs <= MAX_U64;
+ };
+ stake_pool.locked_until_secs = now_secs + recurring_lockup_duration_secs;
+ };
+
+ validator_index = validator_index + 1;
+ };
+
+ if (features::periodical_reward_rate_decrease_enabled()) {
+ // Update rewards rate after reward distribution.
+ staking_config::calculate_and_save_latest_epoch_rewards_rate();
+ };
+}
+
+
+
+
+ValidatorConsensusInfo
of each current validator, sorted by current validator index.
+
+
+public fun cur_validator_consensus_infos(): vector<validator_consensus_info::ValidatorConsensusInfo>
+
+
+
+
+public fun cur_validator_consensus_infos(): vector<ValidatorConsensusInfo> acquires ValidatorSet {
+ let validator_set = borrow_global<ValidatorSet>(@aptos_framework);
+ validator_consensus_infos_from_validator_set(validator_set)
+}
+
+
+
+
+public fun next_validator_consensus_infos(): vector<validator_consensus_info::ValidatorConsensusInfo>
+
+
+
+
+public fun next_validator_consensus_infos(): vector<ValidatorConsensusInfo> acquires ValidatorSet, ValidatorPerformance, StakePool, ValidatorFees, ValidatorConfig {
+ // Init.
+ let cur_validator_set = borrow_global<ValidatorSet>(@aptos_framework);
+ let staking_config = staking_config::get();
+ let validator_perf = borrow_global<ValidatorPerformance>(@aptos_framework);
+ let (minimum_stake, _) = staking_config::get_required_stake(&staking_config);
+ let (rewards_rate, rewards_rate_denominator) = staking_config::get_reward_rate(&staking_config);
+
+ // Compute new validator set.
+ let new_active_validators = vector[];
+ let num_new_actives = 0;
+ let candidate_idx = 0;
+ let new_total_power = 0;
+ let num_cur_actives = vector::length(&cur_validator_set.active_validators);
+ let num_cur_pending_actives = vector::length(&cur_validator_set.pending_active);
+ spec {
+ assume num_cur_actives + num_cur_pending_actives <= MAX_U64;
+ };
+ let num_candidates = num_cur_actives + num_cur_pending_actives;
+ while ({
+ spec {
+ invariant candidate_idx <= num_candidates;
+ invariant spec_validators_are_initialized(new_active_validators);
+ invariant len(new_active_validators) == num_new_actives;
+ invariant forall i in 0..len(new_active_validators):
+ new_active_validators[i].config.validator_index == i;
+ invariant num_new_actives <= candidate_idx;
+ invariant spec_validators_are_initialized(new_active_validators);
+ };
+ candidate_idx < num_candidates
+ }) {
+ let candidate_in_current_validator_set = candidate_idx < num_cur_actives;
+ let candidate = if (candidate_idx < num_cur_actives) {
+ vector::borrow(&cur_validator_set.active_validators, candidate_idx)
+ } else {
+ vector::borrow(&cur_validator_set.pending_active, candidate_idx - num_cur_actives)
+ };
+ let stake_pool = borrow_global<StakePool>(candidate.addr);
+ let cur_active = coin::value(&stake_pool.active);
+ let cur_pending_active = coin::value(&stake_pool.pending_active);
+ let cur_pending_inactive = coin::value(&stake_pool.pending_inactive);
+
+ let cur_reward = if (candidate_in_current_validator_set && cur_active > 0) {
+ spec {
+ assert candidate.config.validator_index < len(validator_perf.validators);
+ };
+ let cur_perf = vector::borrow(&validator_perf.validators, candidate.config.validator_index);
+ spec {
+ assume cur_perf.successful_proposals + cur_perf.failed_proposals <= MAX_U64;
+ };
+ calculate_rewards_amount(cur_active, cur_perf.successful_proposals, cur_perf.successful_proposals + cur_perf.failed_proposals, rewards_rate, rewards_rate_denominator)
+ } else {
+ 0
+ };
+
+ let cur_fee = 0;
+ if (features::collect_and_distribute_gas_fees()) {
+ let fees_table = &borrow_global<ValidatorFees>(@aptos_framework).fees_table;
+ if (table::contains(fees_table, candidate.addr)) {
+ let fee_coin = table::borrow(fees_table, candidate.addr);
+ cur_fee = coin::value(fee_coin);
+ }
+ };
+
+ let lockup_expired = get_reconfig_start_time_secs() >= stake_pool.locked_until_secs;
+ spec {
+ assume cur_active + cur_pending_active + cur_reward + cur_fee <= MAX_U64;
+ assume cur_active + cur_pending_inactive + cur_pending_active + cur_reward + cur_fee <= MAX_U64;
+ };
+ let new_voting_power =
+ cur_active
+ + if (lockup_expired) { 0 } else { cur_pending_inactive }
+ + cur_pending_active
+ + cur_reward + cur_fee;
+
+ if (new_voting_power >= minimum_stake) {
+ let config = *borrow_global<ValidatorConfig>(candidate.addr);
+ config.validator_index = num_new_actives;
+ let new_validator_info = ValidatorInfo {
+ addr: candidate.addr,
+ voting_power: new_voting_power,
+ config,
+ };
+
+ // Update ValidatorSet.
+ spec {
+ assume new_total_power + new_voting_power <= MAX_U128;
+ };
+ new_total_power = new_total_power + (new_voting_power as u128);
+ vector::push_back(&mut new_active_validators, new_validator_info);
+ num_new_actives = num_new_actives + 1;
+
+ };
+ candidate_idx = candidate_idx + 1;
+ };
+
+ let new_validator_set = ValidatorSet {
+ consensus_scheme: cur_validator_set.consensus_scheme,
+ active_validators: new_active_validators,
+ pending_inactive: vector[],
+ pending_active: vector[],
+ total_voting_power: new_total_power,
+ total_joining_power: 0,
+ };
+
+ validator_consensus_infos_from_validator_set(&new_validator_set)
+}
+
+
+
+
+fun validator_consensus_infos_from_validator_set(validator_set: &stake::ValidatorSet): vector<validator_consensus_info::ValidatorConsensusInfo>
+
+
+
+
+fun validator_consensus_infos_from_validator_set(validator_set: &ValidatorSet): vector<ValidatorConsensusInfo> {
+ let validator_consensus_infos = vector[];
+
+ let num_active = vector::length(&validator_set.active_validators);
+ let num_pending_inactive = vector::length(&validator_set.pending_inactive);
+ spec {
+ assume num_active + num_pending_inactive <= MAX_U64;
+ };
+ let total = num_active + num_pending_inactive;
+
+ // Pre-fill the return value with dummy values.
+ let idx = 0;
+ while ({
+ spec {
+ invariant idx <= len(validator_set.active_validators) + len(validator_set.pending_inactive);
+ invariant len(validator_consensus_infos) == idx;
+ invariant len(validator_consensus_infos) <= len(validator_set.active_validators) + len(validator_set.pending_inactive);
+ };
+ idx < total
+ }) {
+ vector::push_back(&mut validator_consensus_infos, validator_consensus_info::default());
+ idx = idx + 1;
+ };
+ spec {
+ assert len(validator_consensus_infos) == len(validator_set.active_validators) + len(validator_set.pending_inactive);
+ assert spec_validator_indices_are_valid_config(validator_set.active_validators,
+ len(validator_set.active_validators) + len(validator_set.pending_inactive));
+ };
+
+ vector::for_each_ref(&validator_set.active_validators, |obj| {
+ let vi: &ValidatorInfo = obj;
+ spec {
+ assume len(validator_consensus_infos) == len(validator_set.active_validators) + len(validator_set.pending_inactive);
+ assert vi.config.validator_index < len(validator_consensus_infos);
+ };
+ let vci = vector::borrow_mut(&mut validator_consensus_infos, vi.config.validator_index);
+ *vci = validator_consensus_info::new(
+ vi.addr,
+ vi.config.consensus_pubkey,
+ vi.voting_power
+ );
+ spec {
+ assert len(validator_consensus_infos) == len(validator_set.active_validators) + len(validator_set.pending_inactive);
+ };
+ });
+
+ vector::for_each_ref(&validator_set.pending_inactive, |obj| {
+ let vi: &ValidatorInfo = obj;
+ spec {
+ assume len(validator_consensus_infos) == len(validator_set.active_validators) + len(validator_set.pending_inactive);
+ assert vi.config.validator_index < len(validator_consensus_infos);
+ };
+ let vci = vector::borrow_mut(&mut validator_consensus_infos, vi.config.validator_index);
+ *vci = validator_consensus_info::new(
+ vi.addr,
+ vi.config.consensus_pubkey,
+ vi.voting_power
+ );
+ spec {
+ assert len(validator_consensus_infos) == len(validator_set.active_validators) + len(validator_set.pending_inactive);
+ };
+ });
+
+ validator_consensus_infos
+}
+
+
+
+
+fun addresses_from_validator_infos(infos: &vector<stake::ValidatorInfo>): vector<address>
+
+
+
+
+fun addresses_from_validator_infos(infos: &vector<ValidatorInfo>): vector<address> {
+ vector::map_ref(infos, |obj| {
+ let info: &ValidatorInfo = obj;
+ info.addr
+ })
+}
+
+
+
+
+commit == true
.
+
+1. distribute transaction fees to active/pending_inactive delegations
+2. distribute rewards to active/pending_inactive delegations
+3. process pending_active, pending_inactive correspondingly
+This function shouldn't abort.
+
+
+fun update_stake_pool(validator_perf: &stake::ValidatorPerformance, pool_address: address, staking_config: &staking_config::StakingConfig)
+
+
+
+
+fun update_stake_pool(
+ validator_perf: &ValidatorPerformance,
+ pool_address: address,
+ staking_config: &StakingConfig,
+) acquires StakePool, AptosCoinCapabilities, ValidatorConfig, ValidatorFees {
+ let stake_pool = borrow_global_mut<StakePool>(pool_address);
+ let validator_config = borrow_global<ValidatorConfig>(pool_address);
+ let cur_validator_perf = vector::borrow(&validator_perf.validators, validator_config.validator_index);
+ let num_successful_proposals = cur_validator_perf.successful_proposals;
+ spec {
+ // The following addition should not overflow because `num_total_proposals` cannot be larger than 86400,
+ // the maximum number of proposals in a day (1 proposal per second).
+ assume cur_validator_perf.successful_proposals + cur_validator_perf.failed_proposals <= MAX_U64;
+ };
+ let num_total_proposals = cur_validator_perf.successful_proposals + cur_validator_perf.failed_proposals;
+ let (rewards_rate, rewards_rate_denominator) = staking_config::get_reward_rate(staking_config);
+ let rewards_active = distribute_rewards(
+ &mut stake_pool.active,
+ num_successful_proposals,
+ num_total_proposals,
+ rewards_rate,
+ rewards_rate_denominator
+ );
+ let rewards_pending_inactive = distribute_rewards(
+ &mut stake_pool.pending_inactive,
+ num_successful_proposals,
+ num_total_proposals,
+ rewards_rate,
+ rewards_rate_denominator
+ );
+ spec {
+ assume rewards_active + rewards_pending_inactive <= MAX_U64;
+ };
+ let rewards_amount = rewards_active + rewards_pending_inactive;
+ // Pending active stake can now be active.
+ coin::merge(&mut stake_pool.active, coin::extract_all(&mut stake_pool.pending_active));
+
+ // Additionally, distribute transaction fees.
+ if (features::collect_and_distribute_gas_fees()) {
+ let fees_table = &mut borrow_global_mut<ValidatorFees>(@aptos_framework).fees_table;
+ if (table::contains(fees_table, pool_address)) {
+ let coin = table::remove(fees_table, pool_address);
+ coin::merge(&mut stake_pool.active, coin);
+ };
+ };
+
+ // Pending inactive stake is only fully unlocked and moved into inactive if the current lockup cycle has expired
+ let current_lockup_expiration = stake_pool.locked_until_secs;
+ if (get_reconfig_start_time_secs() >= current_lockup_expiration) {
+ coin::merge(
+ &mut stake_pool.inactive,
+ coin::extract_all(&mut stake_pool.pending_inactive),
+ );
+ };
+
+ if (std::features::module_event_migration_enabled()) {
+ event::emit(DistributeRewards { pool_address, rewards_amount });
+ };
+ event::emit_event(
+ &mut stake_pool.distribute_rewards_events,
+ DistributeRewardsEvent {
+ pool_address,
+ rewards_amount,
+ },
+ );
+}
+
+
+
+
+fun get_reconfig_start_time_secs(): u64
+
+
+
+
+fun get_reconfig_start_time_secs(): u64 {
+ if (reconfiguration_state::is_initialized()) {
+ reconfiguration_state::start_time_secs()
+ } else {
+ timestamp::now_seconds()
+ }
+}
+
+
+
+
+fun calculate_rewards_amount(stake_amount: u64, num_successful_proposals: u64, num_total_proposals: u64, rewards_rate: u64, rewards_rate_denominator: u64): u64
+
+
+
+
+fun calculate_rewards_amount(
+ stake_amount: u64,
+ num_successful_proposals: u64,
+ num_total_proposals: u64,
+ rewards_rate: u64,
+ rewards_rate_denominator: u64,
+): u64 {
+ spec {
+ // The following condition must hold because
+ // (1) num_successful_proposals <= num_total_proposals, and
+ // (2) `num_total_proposals` cannot be larger than 86400, the maximum number of proposals
+ // in a day (1 proposal per second), and `num_total_proposals` is reset to 0 every epoch.
+ assume num_successful_proposals * MAX_REWARDS_RATE <= MAX_U64;
+ };
+ // The rewards amount is equal to (stake amount * rewards rate * performance multiplier).
+ // We do multiplication in u128 before division to avoid the overflow and minimize the rounding error.
+ let rewards_numerator = (stake_amount as u128) * (rewards_rate as u128) * (num_successful_proposals as u128);
+ let rewards_denominator = (rewards_rate_denominator as u128) * (num_total_proposals as u128);
+ if (rewards_denominator > 0) {
+ ((rewards_numerator / rewards_denominator) as u64)
+ } else {
+ 0
+ }
+}
+
+
+
+
+stake
and num_successful_votes
.
+
+
+fun distribute_rewards(stake: &mut coin::Coin<aptos_coin::AptosCoin>, num_successful_proposals: u64, num_total_proposals: u64, rewards_rate: u64, rewards_rate_denominator: u64): u64
+
+
+
+
+fun distribute_rewards(
+ stake: &mut Coin<AptosCoin>,
+ num_successful_proposals: u64,
+ num_total_proposals: u64,
+ rewards_rate: u64,
+ rewards_rate_denominator: u64,
+): u64 acquires AptosCoinCapabilities {
+ let stake_amount = coin::value(stake);
+ let rewards_amount = if (stake_amount > 0) {
+ calculate_rewards_amount(
+ stake_amount,
+ num_successful_proposals,
+ num_total_proposals,
+ rewards_rate,
+ rewards_rate_denominator
+ )
+ } else {
+ 0
+ };
+ if (rewards_amount > 0) {
+ let mint_cap = &borrow_global<AptosCoinCapabilities>(@aptos_framework).mint_cap;
+ let rewards = coin::mint(rewards_amount, mint_cap);
+ coin::merge(stake, rewards);
+ };
+ rewards_amount
+}
+
+
+
+
+fun append<T>(v1: &mut vector<T>, v2: &mut vector<T>)
+
+
+
+
+fun append<T>(v1: &mut vector<T>, v2: &mut vector<T>) {
+ while (!vector::is_empty(v2)) {
+ vector::push_back(v1, vector::pop_back(v2));
+ }
+}
+
+
+
+
+fun find_validator(v: &vector<stake::ValidatorInfo>, addr: address): option::Option<u64>
+
+
+
+
+fun find_validator(v: &vector<ValidatorInfo>, addr: address): Option<u64> {
+ let i = 0;
+ let len = vector::length(v);
+ while ({
+ spec {
+ invariant !(exists j in 0..i: v[j].addr == addr);
+ };
+ i < len
+ }) {
+ if (vector::borrow(v, i).addr == addr) {
+ return option::some(i)
+ };
+ i = i + 1;
+ };
+ option::none()
+}
+
+
+
+
+fun generate_validator_info(addr: address, stake_pool: &stake::StakePool, config: stake::ValidatorConfig): stake::ValidatorInfo
+
+
+
+
+fun generate_validator_info(addr: address, stake_pool: &StakePool, config: ValidatorConfig): ValidatorInfo {
+ let voting_power = get_next_epoch_voting_power(stake_pool);
+ ValidatorInfo {
+ addr,
+ voting_power,
+ config,
+ }
+}
+
+
+
+
+fun get_next_epoch_voting_power(stake_pool: &stake::StakePool): u64
+
+
+
+
+fun get_next_epoch_voting_power(stake_pool: &StakePool): u64 {
+ let value_pending_active = coin::value(&stake_pool.pending_active);
+ let value_active = coin::value(&stake_pool.active);
+ let value_pending_inactive = coin::value(&stake_pool.pending_inactive);
+ spec {
+ assume value_pending_active + value_active + value_pending_inactive <= MAX_U64;
+ };
+ value_pending_active + value_active + value_pending_inactive
+}
+
+
+
+
+fun update_voting_power_increase(increase_amount: u64)
+
+
+
+
+fun update_voting_power_increase(increase_amount: u64) acquires ValidatorSet {
+ let validator_set = borrow_global_mut<ValidatorSet>(@aptos_framework);
+ let voting_power_increase_limit =
+ (staking_config::get_voting_power_increase_limit(&staking_config::get()) as u128);
+ validator_set.total_joining_power = validator_set.total_joining_power + (increase_amount as u128);
+
+ // Only validator voting power increase if the current validator set's voting power > 0.
+ if (validator_set.total_voting_power > 0) {
+ assert!(
+ validator_set.total_joining_power <= validator_set.total_voting_power * voting_power_increase_limit / 100,
+ error::invalid_argument(EVOTING_POWER_INCREASE_EXCEEDS_LIMIT),
+ );
+ }
+}
+
+
+
+
+fun assert_stake_pool_exists(pool_address: address)
+
+
+
+
+fun assert_stake_pool_exists(pool_address: address) {
+ assert!(stake_pool_exists(pool_address), error::invalid_argument(ESTAKE_POOL_DOES_NOT_EXIST));
+}
+
+
+
+
+public fun configure_allowed_validators(aptos_framework: &signer, accounts: vector<address>)
+
+
+
+
+public fun configure_allowed_validators(
+ aptos_framework: &signer,
+ accounts: vector<address>
+) acquires AllowedValidators {
+ let aptos_framework_address = signer::address_of(aptos_framework);
+ system_addresses::assert_aptos_framework(aptos_framework);
+ if (!exists<AllowedValidators>(aptos_framework_address)) {
+ move_to(aptos_framework, AllowedValidators { accounts });
+ } else {
+ let allowed = borrow_global_mut<AllowedValidators>(aptos_framework_address);
+ allowed.accounts = accounts;
+ }
+}
+
+
+
+
+fun is_allowed(account: address): bool
+
+
+
+
+fun is_allowed(account: address): bool acquires AllowedValidators {
+ if (!exists<AllowedValidators>(@aptos_framework)) {
+ true
+ } else {
+ let allowed = borrow_global<AllowedValidators>(@aptos_framework);
+ vector::contains(&allowed.accounts, &account)
+ }
+}
+
+
+
+
+fun assert_owner_cap_exists(owner: address)
+
+
+
+
+fun assert_owner_cap_exists(owner: address) {
+ assert!(exists<OwnerCapability>(owner), error::not_found(EOWNER_CAP_NOT_FOUND));
+}
+
+
+
+
+fun assert_reconfig_not_in_progress()
+
+
+
+
+fun assert_reconfig_not_in_progress() {
+ assert!(!reconfiguration_state::is_in_progress(), error::invalid_state(ERECONFIGURATION_IN_PROGRESS));
+}
+
+
+
+
+No. | Requirement | Criticality | Implementation | Enforcement | +
---|---|---|---|---|
1 | +The validator set resource stores consensus information for each validator. The consensus scheme remains consistent across all validators within the set. | +Low | +The consensus_scheme attribute within ValidatorSet initializes with the value zero during the module's initialization and its value remains unchanged afterward. | +Formally verified by the data invariant of ValidatorSet. | +
2 | +The owner of a validator is immutable. | +Low | +During the initialization of a validator, the owner attribute becomes the signer's address. This assignment establishes the signer as the owner and controller of the validator entity. Subsequently, the owner attribute remains unchanged throughout the validator's lifespan, maintaining its assigned value without any modifications. | +Formally verified in the schema ValidatorOwnerNoChange. | +
3 | +The total staked value in the stake pool should remain constant, excluding operations related to adding and withdrawing. | +Low | +The total staked value (AptosCoin) of a stake pool is grouped by: active, inactive, pending_active, and pending_inactive. The stake value remains constant except during the execution of the add_stake_with_cap or withdraw_with_cap functions or on_new_epoch (which distributes the reward). | +Formally specified in the schema StakedValueNoChange. | +
4 | +During each epoch, the following operations should be consistently performed without aborting: rewards distribution, validator activation/deactivation, updates to validator sets and voting power, and renewal of lockups. | +Low | +The on_new_epoch function is triggered at each epoch boundary to perform distribution of the transaction fee, updates to active/inactive stakes, updates to pending active/inactive validators and adjusts voting power of the validators without aborting. | +Formally verified via on_new_epoch. This also requires a manual review to verify the state updates of the stake pool. | +
pragma verify = true;
+invariant [suspendable] exists<ValidatorSet>(@aptos_framework) ==> validator_set_is_valid();
+invariant [suspendable] chain_status::is_operating() ==> exists<AptosCoinCapabilities>(@aptos_framework);
+invariant [suspendable] chain_status::is_operating() ==> exists<ValidatorPerformance>(@aptos_framework);
+invariant [suspendable] chain_status::is_operating() ==> exists<ValidatorSet>(@aptos_framework);
+apply ValidatorOwnerNoChange to *;
+apply ValidatorNotChangeDuringReconfig to * except on_new_epoch;
+apply StakePoolNotChangeDuringReconfig to * except on_new_epoch, update_stake_pool;
+
+global ghost_valid_perf: ValidatorPerformance;
+
+global ghost_proposer_idx: Option<u64>;
+
+global ghost_active_num: u64;
+
+global ghost_pending_inactive_num: u64;
+
+
+
+
+
+
+### Resource `ValidatorSet`
+
+
+struct ValidatorSet has copy, drop, store, key
+
+
+
+
+consensus_scheme: u8
+active_validators: vector<stake::ValidatorInfo>
+pending_inactive: vector<stake::ValidatorInfo>
+pending_active: vector<stake::ValidatorInfo>
+total_voting_power: u128
+total_joining_power: u128
+// This enforces high-level requirement 1:
+invariant consensus_scheme == 0;
+
+
+
+
+
+
+
+
+schema ValidatorNotChangeDuringReconfig {
+ ensures (reconfiguration_state::spec_is_in_progress() && old(exists<ValidatorSet>(@aptos_framework))) ==>
+ old(global<ValidatorSet>(@aptos_framework)) == global<ValidatorSet>(@aptos_framework);
+}
+
+
+
+
+
+
+
+
+schema StakePoolNotChangeDuringReconfig {
+ ensures forall a: address where old(exists<StakePool>(a)): reconfiguration_state::spec_is_in_progress() ==>
+ (old(global<StakePool>(a).pending_inactive) == global<StakePool>(a).pending_inactive &&
+ old(global<StakePool>(a).pending_active) == global<StakePool>(a).pending_active &&
+ old(global<StakePool>(a).inactive) == global<StakePool>(a).inactive &&
+ old(global<StakePool>(a).active) == global<StakePool>(a).active);
+}
+
+
+
+
+
+
+
+
+schema ValidatorOwnerNoChange {
+ // This enforces high-level requirement 2:
+ ensures forall addr: address where old(exists<OwnerCapability>(addr)):
+ old(global<OwnerCapability>(addr)).pool_address == global<OwnerCapability>(addr).pool_address;
+}
+
+
+
+
+
+
+
+
+schema StakedValueNochange {
+ pool_address: address;
+ let stake_pool = global<StakePool>(pool_address);
+ let post post_stake_pool = global<StakePool>(pool_address);
+ // This enforces high-level requirement 3:
+ ensures stake_pool.active.value + stake_pool.inactive.value + stake_pool.pending_active.value + stake_pool.pending_inactive.value ==
+ post_stake_pool.active.value + post_stake_pool.inactive.value + post_stake_pool.pending_active.value + post_stake_pool.pending_inactive.value;
+}
+
+
+
+
+
+
+
+
+fun validator_set_is_valid(): bool {
+ let validator_set = global<ValidatorSet>(@aptos_framework);
+ validator_set_is_valid_impl(validator_set)
+}
+
+
+
+
+
+
+
+
+fun validator_set_is_valid_impl(validator_set: ValidatorSet): bool {
+ spec_validators_are_initialized(validator_set.active_validators) &&
+ spec_validators_are_initialized(validator_set.pending_inactive) &&
+ spec_validators_are_initialized(validator_set.pending_active) &&
+ spec_validator_indices_are_valid(validator_set.active_validators) &&
+ spec_validator_indices_are_valid(validator_set.pending_inactive)
+ && spec_validator_indices_active_pending_inactive(validator_set)
+}
+
+
+
+
+
+
+### Function `initialize_validator_fees`
+
+
+public(friend) fun initialize_validator_fees(aptos_framework: &signer)
+
+
+
+
+
+let aptos_addr = signer::address_of(aptos_framework);
+aborts_if !system_addresses::is_aptos_framework_address(aptos_addr);
+aborts_if exists<ValidatorFees>(aptos_addr);
+ensures exists<ValidatorFees>(aptos_addr);
+
+
+
+
+
+
+### Function `add_transaction_fee`
+
+
+public(friend) fun add_transaction_fee(validator_addr: address, fee: coin::Coin<aptos_coin::AptosCoin>)
+
+
+
+
+
+aborts_if !exists<ValidatorFees>(@aptos_framework);
+let fees_table = global<ValidatorFees>(@aptos_framework).fees_table;
+let post post_fees_table = global<ValidatorFees>(@aptos_framework).fees_table;
+let collected_fee = table::spec_get(fees_table, validator_addr);
+let post post_collected_fee = table::spec_get(post_fees_table, validator_addr);
+ensures if (table::spec_contains(fees_table, validator_addr)) {
+ post_collected_fee.value == collected_fee.value + fee.value
+} else {
+ table::spec_contains(post_fees_table, validator_addr) &&
+ table::spec_get(post_fees_table, validator_addr) == fee
+};
+
+
+
+
+
+
+### Function `get_validator_state`
+
+
+#[view]
+public fun get_validator_state(pool_address: address): u64
+
+
+
+
+
+aborts_if !exists<ValidatorSet>(@aptos_framework);
+let validator_set = global<ValidatorSet>(@aptos_framework);
+ensures result == VALIDATOR_STATUS_PENDING_ACTIVE ==> spec_contains(validator_set.pending_active, pool_address);
+ensures result == VALIDATOR_STATUS_ACTIVE ==> spec_contains(validator_set.active_validators, pool_address);
+ensures result == VALIDATOR_STATUS_PENDING_INACTIVE ==> spec_contains(validator_set.pending_inactive, pool_address);
+ensures result == VALIDATOR_STATUS_INACTIVE ==> (
+ !spec_contains(validator_set.pending_active, pool_address)
+ && !spec_contains(validator_set.active_validators, pool_address)
+ && !spec_contains(validator_set.pending_inactive, pool_address)
+);
+
+
+
+
+
+
+### Function `initialize`
+
+
+public(friend) fun initialize(aptos_framework: &signer)
+
+
+
+
+
+pragma disable_invariants_in_body;
+let aptos_addr = signer::address_of(aptos_framework);
+aborts_if !system_addresses::is_aptos_framework_address(aptos_addr);
+aborts_if exists<ValidatorSet>(aptos_addr);
+aborts_if exists<ValidatorPerformance>(aptos_addr);
+ensures exists<ValidatorSet>(aptos_addr);
+ensures global<ValidatorSet>(aptos_addr).consensus_scheme == 0;
+ensures exists<ValidatorPerformance>(aptos_addr);
+
+
+
+
+
+
+### Function `remove_validators`
+
+
+public fun remove_validators(aptos_framework: &signer, validators: &vector<address>)
+
+
+
+
+
+requires chain_status::is_operating();
+let validator_set = global<ValidatorSet>(@aptos_framework);
+let post post_validator_set = global<ValidatorSet>(@aptos_framework);
+let active_validators = validator_set.active_validators;
+let post post_active_validators = post_validator_set.active_validators;
+let pending_inactive_validators = validator_set.pending_inactive;
+let post post_pending_inactive_validators = post_validator_set.pending_inactive;
+invariant len(active_validators) > 0;
+ensures len(active_validators) + len(pending_inactive_validators) == len(post_active_validators)
+ + len(post_pending_inactive_validators);
+
+
+
+
+
+
+### Function `initialize_stake_owner`
+
+
+public entry fun initialize_stake_owner(owner: &signer, initial_stake_amount: u64, operator: address, voter: address)
+
+
+
+
+
+pragma verify_duration_estimate = 120;
+include ResourceRequirement;
+let addr = signer::address_of(owner);
+ensures global<ValidatorConfig>(addr) == ValidatorConfig {
+ consensus_pubkey: vector::empty(),
+ network_addresses: vector::empty(),
+ fullnode_addresses: vector::empty(),
+ validator_index: 0,
+};
+ensures global<OwnerCapability>(addr) == OwnerCapability { pool_address: addr };
+let post stakepool = global<StakePool>(addr);
+let post active = stakepool.active.value;
+let post pending_active = stakepool.pending_active.value;
+ensures spec_is_current_epoch_validator(addr) ==>
+ pending_active == initial_stake_amount;
+ensures !spec_is_current_epoch_validator(addr) ==>
+ active == initial_stake_amount;
+
+
+
+
+
+
+### Function `initialize_validator`
+
+
+public entry fun initialize_validator(account: &signer, consensus_pubkey: vector<u8>, proof_of_possession: vector<u8>, network_addresses: vector<u8>, fullnode_addresses: vector<u8>)
+
+
+
+
+
+let pubkey_from_pop = bls12381::spec_public_key_from_bytes_with_pop(
+ consensus_pubkey,
+ proof_of_possession_from_bytes(proof_of_possession)
+);
+aborts_if !option::spec_is_some(pubkey_from_pop);
+let addr = signer::address_of(account);
+let post_addr = signer::address_of(account);
+let allowed = global<AllowedValidators>(@aptos_framework);
+aborts_if exists<ValidatorConfig>(addr);
+aborts_if exists<AllowedValidators>(@aptos_framework) && !vector::spec_contains(allowed.accounts, addr);
+aborts_if stake_pool_exists(addr);
+aborts_if exists<OwnerCapability>(addr);
+aborts_if !exists<account::Account>(addr);
+aborts_if global<account::Account>(addr).guid_creation_num + 12 > MAX_U64;
+aborts_if global<account::Account>(addr).guid_creation_num + 12 >= account::MAX_GUID_CREATION_NUM;
+ensures exists<StakePool>(post_addr);
+ensures global<OwnerCapability>(post_addr) == OwnerCapability { pool_address: post_addr };
+ensures global<ValidatorConfig>(post_addr) == ValidatorConfig {
+ consensus_pubkey,
+ network_addresses,
+ fullnode_addresses,
+ validator_index: 0,
+};
+
+
+
+
+
+
+### Function `extract_owner_cap`
+
+
+public fun extract_owner_cap(owner: &signer): stake::OwnerCapability
+
+
+
+
+
+pragma verify_duration_estimate = 300;
+let owner_address = signer::address_of(owner);
+aborts_if !exists<OwnerCapability>(owner_address);
+ensures !exists<OwnerCapability>(owner_address);
+
+
+
+
+
+
+### Function `deposit_owner_cap`
+
+
+public fun deposit_owner_cap(owner: &signer, owner_cap: stake::OwnerCapability)
+
+
+
+
+
+let owner_address = signer::address_of(owner);
+aborts_if exists<OwnerCapability>(owner_address);
+ensures exists<OwnerCapability>(owner_address);
+ensures global<OwnerCapability>(owner_address) == owner_cap;
+
+
+
+
+
+
+### Function `set_operator_with_cap`
+
+
+public fun set_operator_with_cap(owner_cap: &stake::OwnerCapability, new_operator: address)
+
+
+
+
+
+let pool_address = owner_cap.pool_address;
+let post post_stake_pool = global<StakePool>(pool_address);
+modifies global<StakePool>(pool_address);
+include StakedValueNochange;
+ensures post_stake_pool.operator_address == new_operator;
+
+
+
+
+
+
+### Function `set_delegated_voter_with_cap`
+
+
+public fun set_delegated_voter_with_cap(owner_cap: &stake::OwnerCapability, new_voter: address)
+
+
+
+
+
+let pool_address = owner_cap.pool_address;
+let post post_stake_pool = global<StakePool>(pool_address);
+include StakedValueNochange;
+aborts_if !exists<StakePool>(pool_address);
+modifies global<StakePool>(pool_address);
+ensures post_stake_pool.delegated_voter == new_voter;
+
+
+
+
+
+
+### Function `add_stake`
+
+
+public entry fun add_stake(owner: &signer, amount: u64)
+
+
+
+
+
+pragma verify_duration_estimate = 120;
+pragma aborts_if_is_partial;
+aborts_if reconfiguration_state::spec_is_in_progress();
+include ResourceRequirement;
+include AddStakeAbortsIfAndEnsures;
+
+
+
+
+
+
+### Function `add_stake_with_cap`
+
+
+public fun add_stake_with_cap(owner_cap: &stake::OwnerCapability, coins: coin::Coin<aptos_coin::AptosCoin>)
+
+
+
+
+
+pragma disable_invariants_in_body;
+pragma verify_duration_estimate = 300;
+include ResourceRequirement;
+let amount = coins.value;
+aborts_if reconfiguration_state::spec_is_in_progress();
+include AddStakeWithCapAbortsIfAndEnsures { amount };
+
+
+
+
+
+
+### Function `reactivate_stake_with_cap`
+
+
+public fun reactivate_stake_with_cap(owner_cap: &stake::OwnerCapability, amount: u64)
+
+
+
+
+
+let pool_address = owner_cap.pool_address;
+include StakedValueNochange;
+aborts_if reconfiguration_state::spec_is_in_progress();
+aborts_if !stake_pool_exists(pool_address);
+let pre_stake_pool = global<StakePool>(pool_address);
+let post stake_pool = global<StakePool>(pool_address);
+modifies global<StakePool>(pool_address);
+let min_amount = aptos_std::math64::min(amount, pre_stake_pool.pending_inactive.value);
+ensures stake_pool.pending_inactive.value == pre_stake_pool.pending_inactive.value - min_amount;
+ensures stake_pool.active.value == pre_stake_pool.active.value + min_amount;
+
+
+
+
+
+
+### Function `rotate_consensus_key`
+
+
+public entry fun rotate_consensus_key(operator: &signer, pool_address: address, new_consensus_pubkey: vector<u8>, proof_of_possession: vector<u8>)
+
+
+
+
+
+let pre_stake_pool = global<StakePool>(pool_address);
+let post validator_info = global<ValidatorConfig>(pool_address);
+aborts_if reconfiguration_state::spec_is_in_progress();
+aborts_if !exists<StakePool>(pool_address);
+aborts_if signer::address_of(operator) != pre_stake_pool.operator_address;
+aborts_if !exists<ValidatorConfig>(pool_address);
+let pubkey_from_pop = bls12381::spec_public_key_from_bytes_with_pop(
+ new_consensus_pubkey,
+ proof_of_possession_from_bytes(proof_of_possession)
+);
+aborts_if !option::spec_is_some(pubkey_from_pop);
+modifies global<ValidatorConfig>(pool_address);
+include StakedValueNochange;
+ensures validator_info.consensus_pubkey == new_consensus_pubkey;
+
+
+
+
+
+
+### Function `update_network_and_fullnode_addresses`
+
+
+public entry fun update_network_and_fullnode_addresses(operator: &signer, pool_address: address, new_network_addresses: vector<u8>, new_fullnode_addresses: vector<u8>)
+
+
+
+
+
+let pre_stake_pool = global<StakePool>(pool_address);
+let post validator_info = global<ValidatorConfig>(pool_address);
+modifies global<ValidatorConfig>(pool_address);
+include StakedValueNochange;
+aborts_if reconfiguration_state::spec_is_in_progress();
+aborts_if !exists<StakePool>(pool_address);
+aborts_if !exists<ValidatorConfig>(pool_address);
+aborts_if signer::address_of(operator) != pre_stake_pool.operator_address;
+ensures validator_info.network_addresses == new_network_addresses;
+ensures validator_info.fullnode_addresses == new_fullnode_addresses;
+
+
+
+
+
+
+### Function `increase_lockup_with_cap`
+
+
+public fun increase_lockup_with_cap(owner_cap: &stake::OwnerCapability)
+
+
+
+
+
+let config = global<staking_config::StakingConfig>(@aptos_framework);
+let pool_address = owner_cap.pool_address;
+let pre_stake_pool = global<StakePool>(pool_address);
+let post stake_pool = global<StakePool>(pool_address);
+let now_seconds = timestamp::spec_now_seconds();
+let lockup = config.recurring_lockup_duration_secs;
+modifies global<StakePool>(pool_address);
+include StakedValueNochange;
+aborts_if !exists<StakePool>(pool_address);
+aborts_if pre_stake_pool.locked_until_secs >= lockup + now_seconds;
+aborts_if lockup + now_seconds > MAX_U64;
+aborts_if !exists<timestamp::CurrentTimeMicroseconds>(@aptos_framework);
+aborts_if !exists<staking_config::StakingConfig>(@aptos_framework);
+ensures stake_pool.locked_until_secs == lockup + now_seconds;
+
+
+
+
+
+
+### Function `join_validator_set`
+
+
+public entry fun join_validator_set(operator: &signer, pool_address: address)
+
+
+
+
+
+pragma disable_invariants_in_body;
+aborts_if !staking_config::get_allow_validator_set_change(staking_config::get());
+aborts_if !exists<StakePool>(pool_address);
+aborts_if !exists<ValidatorConfig>(pool_address);
+aborts_if !exists<StakingConfig>(@aptos_framework);
+aborts_if !exists<ValidatorSet>(@aptos_framework);
+aborts_if reconfiguration_state::spec_is_in_progress();
+let stake_pool = global<StakePool>(pool_address);
+let validator_set = global<ValidatorSet>(@aptos_framework);
+let post p_validator_set = global<ValidatorSet>(@aptos_framework);
+aborts_if signer::address_of(operator) != stake_pool.operator_address;
+aborts_if option::spec_is_some(spec_find_validator(validator_set.active_validators, pool_address)) ||
+ option::spec_is_some(spec_find_validator(validator_set.pending_inactive, pool_address)) ||
+ option::spec_is_some(spec_find_validator(validator_set.pending_active, pool_address));
+let config = staking_config::get();
+let voting_power = get_next_epoch_voting_power(stake_pool);
+let minimum_stake = config.minimum_stake;
+let maximum_stake = config.maximum_stake;
+aborts_if voting_power < minimum_stake;
+aborts_if voting_power >maximum_stake;
+let validator_config = global<ValidatorConfig>(pool_address);
+aborts_if vector::is_empty(validator_config.consensus_pubkey);
+let validator_set_size = vector::length(validator_set.active_validators) + vector::length(validator_set.pending_active) + 1;
+aborts_if validator_set_size > MAX_VALIDATOR_SET_SIZE;
+let voting_power_increase_limit = (staking_config::get_voting_power_increase_limit(config) as u128);
+aborts_if (validator_set.total_joining_power + (voting_power as u128)) > MAX_U128;
+aborts_if validator_set.total_voting_power * voting_power_increase_limit > MAX_U128;
+aborts_if validator_set.total_voting_power > 0 &&
+ (validator_set.total_joining_power + (voting_power as u128)) * 100 > validator_set.total_voting_power * voting_power_increase_limit;
+let post p_validator_info = ValidatorInfo {
+ addr: pool_address,
+ voting_power,
+ config: validator_config,
+};
+ensures validator_set.total_joining_power + voting_power == p_validator_set.total_joining_power;
+ensures vector::spec_contains(p_validator_set.pending_active, p_validator_info);
+
+
+
+
+
+
+### Function `unlock_with_cap`
+
+
+public fun unlock_with_cap(amount: u64, owner_cap: &stake::OwnerCapability)
+
+
+
+
+
+pragma verify_duration_estimate = 300;
+let pool_address = owner_cap.pool_address;
+let pre_stake_pool = global<StakePool>(pool_address);
+let post stake_pool = global<StakePool>(pool_address);
+aborts_if reconfiguration_state::spec_is_in_progress();
+aborts_if amount != 0 && !exists<StakePool>(pool_address);
+modifies global<StakePool>(pool_address);
+include StakedValueNochange;
+let min_amount = aptos_std::math64::min(amount,pre_stake_pool.active.value);
+ensures stake_pool.active.value == pre_stake_pool.active.value - min_amount;
+ensures stake_pool.pending_inactive.value == pre_stake_pool.pending_inactive.value + min_amount;
+
+
+
+
+
+
+### Function `withdraw`
+
+
+public entry fun withdraw(owner: &signer, withdraw_amount: u64)
+
+
+
+
+
+pragma verify = false;
+aborts_if reconfiguration_state::spec_is_in_progress();
+let addr = signer::address_of(owner);
+let ownership_cap = global<OwnerCapability>(addr);
+let pool_address = ownership_cap.pool_address;
+let stake_pool = global<StakePool>(pool_address);
+aborts_if !exists<OwnerCapability>(addr);
+aborts_if !exists<StakePool>(pool_address);
+aborts_if !exists<ValidatorSet>(@aptos_framework);
+let validator_set = global<ValidatorSet>(@aptos_framework);
+let bool_find_validator = !option::spec_is_some(spec_find_validator(validator_set.active_validators, pool_address)) &&
+ !option::spec_is_some(spec_find_validator(validator_set.pending_inactive, pool_address)) &&
+ !option::spec_is_some(spec_find_validator(validator_set.pending_active, pool_address));
+aborts_if bool_find_validator && !exists<timestamp::CurrentTimeMicroseconds>(@aptos_framework);
+let new_withdraw_amount_1 = min(withdraw_amount, stake_pool.inactive.value + stake_pool.pending_inactive.value);
+let new_withdraw_amount_2 = min(withdraw_amount, stake_pool.inactive.value);
+aborts_if bool_find_validator && timestamp::now_seconds() > stake_pool.locked_until_secs &&
+ new_withdraw_amount_1 > 0 && stake_pool.inactive.value + stake_pool.pending_inactive.value < new_withdraw_amount_1;
+aborts_if !(bool_find_validator && exists<timestamp::CurrentTimeMicroseconds>(@aptos_framework)) &&
+ new_withdraw_amount_2 > 0 && stake_pool.inactive.value < new_withdraw_amount_2;
+aborts_if !exists<coin::CoinStore<AptosCoin>>(addr);
+include coin::DepositAbortsIf<AptosCoin>{account_addr: addr};
+let coin_store = global<coin::CoinStore<AptosCoin>>(addr);
+let post p_coin_store = global<coin::CoinStore<AptosCoin>>(addr);
+ensures bool_find_validator && timestamp::now_seconds() > stake_pool.locked_until_secs
+ && exists<account::Account>(addr) && exists<coin::CoinStore<AptosCoin>>(addr) ==>
+ coin_store.coin.value + new_withdraw_amount_1 == p_coin_store.coin.value;
+ensures !(bool_find_validator && exists<timestamp::CurrentTimeMicroseconds>(@aptos_framework))
+ && exists<account::Account>(addr) && exists<coin::CoinStore<AptosCoin>>(addr) ==>
+ coin_store.coin.value + new_withdraw_amount_2 == p_coin_store.coin.value;
+
+
+
+
+
+
+### Function `leave_validator_set`
+
+
+public entry fun leave_validator_set(operator: &signer, pool_address: address)
+
+
+
+
+
+pragma disable_invariants_in_body;
+requires chain_status::is_operating();
+aborts_if reconfiguration_state::spec_is_in_progress();
+let config = staking_config::get();
+aborts_if !staking_config::get_allow_validator_set_change(config);
+aborts_if !exists<StakePool>(pool_address);
+aborts_if !exists<ValidatorSet>(@aptos_framework);
+aborts_if !exists<staking_config::StakingConfig>(@aptos_framework);
+let stake_pool = global<StakePool>(pool_address);
+aborts_if signer::address_of(operator) != stake_pool.operator_address;
+let validator_set = global<ValidatorSet>(@aptos_framework);
+let validator_find_bool = option::spec_is_some(spec_find_validator(validator_set.pending_active, pool_address));
+let active_validators = validator_set.active_validators;
+let pending_active = validator_set.pending_active;
+let post post_validator_set = global<ValidatorSet>(@aptos_framework);
+let post post_active_validators = post_validator_set.active_validators;
+let pending_inactive_validators = validator_set.pending_inactive;
+let post post_pending_inactive_validators = post_validator_set.pending_inactive;
+ensures len(active_validators) + len(pending_inactive_validators) == len(post_active_validators)
+ + len(post_pending_inactive_validators);
+aborts_if !validator_find_bool && !option::spec_is_some(spec_find_validator(active_validators, pool_address));
+aborts_if !validator_find_bool && vector::length(validator_set.active_validators) <= option::spec_borrow(spec_find_validator(active_validators, pool_address));
+aborts_if !validator_find_bool && vector::length(validator_set.active_validators) < 2;
+aborts_if validator_find_bool && vector::length(validator_set.pending_active) <= option::spec_borrow(spec_find_validator(pending_active, pool_address));
+let post p_validator_set = global<ValidatorSet>(@aptos_framework);
+let validator_stake = (get_next_epoch_voting_power(stake_pool) as u128);
+ensures validator_find_bool && validator_set.total_joining_power > validator_stake ==>
+ p_validator_set.total_joining_power == validator_set.total_joining_power - validator_stake;
+ensures !validator_find_bool ==> !option::spec_is_some(spec_find_validator(p_validator_set.pending_active, pool_address));
+
+
+
+
+
+
+### Function `is_current_epoch_validator`
+
+
+public fun is_current_epoch_validator(pool_address: address): bool
+
+
+
+
+
+include ResourceRequirement;
+aborts_if !spec_has_stake_pool(pool_address);
+ensures result == spec_is_current_epoch_validator(pool_address);
+
+
+
+
+
+
+### Function `update_performance_statistics`
+
+
+public(friend) fun update_performance_statistics(proposer_index: option::Option<u64>, failed_proposer_indices: vector<u64>)
+
+
+
+
+
+requires chain_status::is_operating();
+aborts_if false;
+let validator_perf = global<ValidatorPerformance>(@aptos_framework);
+let post post_validator_perf = global<ValidatorPerformance>(@aptos_framework);
+let validator_len = len(validator_perf.validators);
+ensures (option::spec_is_some(ghost_proposer_idx) && option::spec_borrow(ghost_proposer_idx) < validator_len) ==>
+ (post_validator_perf.validators[option::spec_borrow(ghost_proposer_idx)].successful_proposals ==
+ validator_perf.validators[option::spec_borrow(ghost_proposer_idx)].successful_proposals + 1);
+
+
+
+
+
+
+### Function `on_new_epoch`
+
+
+public(friend) fun on_new_epoch()
+
+
+
+
+
+pragma verify = false;
+pragma disable_invariants_in_body;
+include ResourceRequirement;
+include GetReconfigStartTimeRequirement;
+include staking_config::StakingRewardsConfigRequirement;
+include aptos_framework::aptos_coin::ExistsAptosCoin;
+// This enforces high-level requirement 4:
+aborts_if false;
+
+
+
+
+
+
+### Function `next_validator_consensus_infos`
+
+
+public fun next_validator_consensus_infos(): vector<validator_consensus_info::ValidatorConsensusInfo>
+
+
+
+
+
+pragma verify_duration_estimate = 300;
+aborts_if false;
+include ResourceRequirement;
+include GetReconfigStartTimeRequirement;
+include features::spec_periodical_reward_rate_decrease_enabled() ==> staking_config::StakingRewardsConfigEnabledRequirement;
+
+
+
+
+
+
+### Function `validator_consensus_infos_from_validator_set`
+
+
+fun validator_consensus_infos_from_validator_set(validator_set: &stake::ValidatorSet): vector<validator_consensus_info::ValidatorConsensusInfo>
+
+
+
+
+
+aborts_if false;
+invariant spec_validator_indices_are_valid_config(validator_set.active_validators,
+ len(validator_set.active_validators) + len(validator_set.pending_inactive));
+invariant len(validator_set.pending_inactive) == 0 ||
+ spec_validator_indices_are_valid_config(validator_set.pending_inactive,
+ len(validator_set.active_validators) + len(validator_set.pending_inactive));
+
+
+
+
+
+
+
+
+schema AddStakeWithCapAbortsIfAndEnsures {
+ owner_cap: OwnerCapability;
+ amount: u64;
+ let pool_address = owner_cap.pool_address;
+ aborts_if !exists<StakePool>(pool_address);
+ let config = global<staking_config::StakingConfig>(@aptos_framework);
+ let validator_set = global<ValidatorSet>(@aptos_framework);
+ let voting_power_increase_limit = config.voting_power_increase_limit;
+ let post post_validator_set = global<ValidatorSet>(@aptos_framework);
+ let update_voting_power_increase = amount != 0 && (spec_contains(validator_set.active_validators, pool_address)
+ || spec_contains(validator_set.pending_active, pool_address));
+ aborts_if update_voting_power_increase && validator_set.total_joining_power + amount > MAX_U128;
+ ensures update_voting_power_increase ==> post_validator_set.total_joining_power == validator_set.total_joining_power + amount;
+ aborts_if update_voting_power_increase && validator_set.total_voting_power > 0
+ && validator_set.total_voting_power * voting_power_increase_limit > MAX_U128;
+ aborts_if update_voting_power_increase && validator_set.total_voting_power > 0
+ && validator_set.total_joining_power + amount > validator_set.total_voting_power * voting_power_increase_limit / 100;
+ let stake_pool = global<StakePool>(pool_address);
+ let post post_stake_pool = global<StakePool>(pool_address);
+ let value_pending_active = stake_pool.pending_active.value;
+ let value_active = stake_pool.active.value;
+ ensures amount != 0 && spec_is_current_epoch_validator(pool_address) ==> post_stake_pool.pending_active.value == value_pending_active + amount;
+ ensures amount != 0 && !spec_is_current_epoch_validator(pool_address) ==> post_stake_pool.active.value == value_active + amount;
+ let maximum_stake = config.maximum_stake;
+ let value_pending_inactive = stake_pool.pending_inactive.value;
+ let next_epoch_voting_power = value_pending_active + value_active + value_pending_inactive;
+ let voting_power = next_epoch_voting_power + amount;
+ aborts_if amount != 0 && voting_power > MAX_U64;
+ aborts_if amount != 0 && voting_power > maximum_stake;
+}
+
+
+
+
+
+
+
+
+schema AddStakeAbortsIfAndEnsures {
+ owner: signer;
+ amount: u64;
+ let owner_address = signer::address_of(owner);
+ aborts_if !exists<OwnerCapability>(owner_address);
+ let owner_cap = global<OwnerCapability>(owner_address);
+ include AddStakeWithCapAbortsIfAndEnsures { owner_cap };
+}
+
+
+
+
+
+
+
+
+fun spec_is_allowed(account: address): bool {
+ if (!exists<AllowedValidators>(@aptos_framework)) {
+ true
+ } else {
+ let allowed = global<AllowedValidators>(@aptos_framework);
+ contains(allowed.accounts, account)
+ }
+}
+
+
+
+
+
+
+
+
+fun spec_find_validator(v: vector<ValidatorInfo>, addr: address): Option<u64>;
+
+
+
+
+
+
+
+
+fun spec_validators_are_initialized(validators: vector<ValidatorInfo>): bool {
+ forall i in 0..len(validators):
+ spec_has_stake_pool(validators[i].addr) &&
+ spec_has_validator_config(validators[i].addr)
+}
+
+
+
+
+
+
+
+
+fun spec_validators_are_initialized_addrs(addrs: vector<address>): bool {
+ forall i in 0..len(addrs):
+ spec_has_stake_pool(addrs[i]) &&
+ spec_has_validator_config(addrs[i])
+}
+
+
+
+
+
+
+
+
+fun spec_validator_indices_are_valid(validators: vector<ValidatorInfo>): bool {
+ spec_validator_indices_are_valid_addr(validators, spec_validator_index_upper_bound()) &&
+ spec_validator_indices_are_valid_config(validators, spec_validator_index_upper_bound())
+}
+
+
+
+
+
+
+
+
+fun spec_validator_indices_are_valid_addr(validators: vector<ValidatorInfo>, upper_bound: u64): bool {
+ forall i in 0..len(validators):
+ global<ValidatorConfig>(validators[i].addr).validator_index < upper_bound
+}
+
+
+
+
+
+
+
+
+fun spec_validator_indices_are_valid_config(validators: vector<ValidatorInfo>, upper_bound: u64): bool {
+ forall i in 0..len(validators):
+ validators[i].config.validator_index < upper_bound
+}
+
+
+
+
+
+
+
+
+fun spec_validator_indices_active_pending_inactive(validator_set: ValidatorSet): bool {
+ len(validator_set.pending_inactive) + len(validator_set.active_validators) == spec_validator_index_upper_bound()
+}
+
+
+
+
+
+
+
+
+fun spec_validator_index_upper_bound(): u64 {
+ len(global<ValidatorPerformance>(@aptos_framework).validators)
+}
+
+
+
+
+
+
+
+
+fun spec_has_stake_pool(a: address): bool {
+ exists<StakePool>(a)
+}
+
+
+
+
+
+
+
+
+fun spec_has_validator_config(a: address): bool {
+ exists<ValidatorConfig>(a)
+}
+
+
+
+
+
+
+
+
+fun spec_rewards_amount(
+ stake_amount: u64,
+ num_successful_proposals: u64,
+ num_total_proposals: u64,
+ rewards_rate: u64,
+ rewards_rate_denominator: u64,
+): u64;
+
+
+
+
+
+
+
+
+fun spec_contains(validators: vector<ValidatorInfo>, addr: address): bool {
+ exists i in 0..len(validators): validators[i].addr == addr
+}
+
+
+
+
+
+
+
+
+fun spec_is_current_epoch_validator(pool_address: address): bool {
+ let validator_set = global<ValidatorSet>(@aptos_framework);
+ !spec_contains(validator_set.pending_active, pool_address)
+ && (spec_contains(validator_set.active_validators, pool_address)
+ || spec_contains(validator_set.pending_inactive, pool_address))
+}
+
+
+
+
+
+
+
+
+schema ResourceRequirement {
+ requires exists<AptosCoinCapabilities>(@aptos_framework);
+ requires exists<ValidatorPerformance>(@aptos_framework);
+ requires exists<ValidatorSet>(@aptos_framework);
+ requires exists<StakingConfig>(@aptos_framework);
+ requires exists<StakingRewardsConfig>(@aptos_framework) || !features::spec_periodical_reward_rate_decrease_enabled();
+ requires exists<timestamp::CurrentTimeMicroseconds>(@aptos_framework);
+ requires exists<ValidatorFees>(@aptos_framework);
+}
+
+
+
+
+
+
+
+
+fun spec_get_reward_rate_1(config: StakingConfig): num {
+ if (features::spec_periodical_reward_rate_decrease_enabled()) {
+ let epoch_rewards_rate = global<staking_config::StakingRewardsConfig>(@aptos_framework).rewards_rate;
+ if (epoch_rewards_rate.value == 0) {
+ 0
+ } else {
+ let denominator_0 = aptos_std::fixed_point64::spec_divide_u128(staking_config::MAX_REWARDS_RATE, epoch_rewards_rate);
+ let denominator = if (denominator_0 > MAX_U64) {
+ MAX_U64
+ } else {
+ denominator_0
+ };
+ let nominator = aptos_std::fixed_point64::spec_multiply_u128(denominator, epoch_rewards_rate);
+ nominator
+ }
+ } else {
+ config.rewards_rate
+ }
+}
+
+
+
+
+
+
+
+
+fun spec_get_reward_rate_2(config: StakingConfig): num {
+ if (features::spec_periodical_reward_rate_decrease_enabled()) {
+ let epoch_rewards_rate = global<staking_config::StakingRewardsConfig>(@aptos_framework).rewards_rate;
+ if (epoch_rewards_rate.value == 0) {
+ 1
+ } else {
+ let denominator_0 = aptos_std::fixed_point64::spec_divide_u128(staking_config::MAX_REWARDS_RATE, epoch_rewards_rate);
+ let denominator = if (denominator_0 > MAX_U64) {
+ MAX_U64
+ } else {
+ denominator_0
+ };
+ denominator
+ }
+ } else {
+ config.rewards_rate_denominator
+ }
+}
+
+
+
+
+
+
+### Function `update_stake_pool`
+
+
+fun update_stake_pool(validator_perf: &stake::ValidatorPerformance, pool_address: address, staking_config: &staking_config::StakingConfig)
+
+
+
+
+
+pragma verify_duration_estimate = 300;
+include ResourceRequirement;
+include GetReconfigStartTimeRequirement;
+include staking_config::StakingRewardsConfigRequirement;
+include UpdateStakePoolAbortsIf;
+let stake_pool = global<StakePool>(pool_address);
+let validator_config = global<ValidatorConfig>(pool_address);
+let cur_validator_perf = validator_perf.validators[validator_config.validator_index];
+let num_successful_proposals = cur_validator_perf.successful_proposals;
+let num_total_proposals = cur_validator_perf.successful_proposals + cur_validator_perf.failed_proposals;
+let rewards_rate = spec_get_reward_rate_1(staking_config);
+let rewards_rate_denominator = spec_get_reward_rate_2(staking_config);
+let rewards_amount_1 = if (stake_pool.active.value > 0) {
+ spec_rewards_amount(stake_pool.active.value, num_successful_proposals, num_total_proposals, rewards_rate, rewards_rate_denominator)
+} else {
+ 0
+};
+let rewards_amount_2 = if (stake_pool.pending_inactive.value > 0) {
+ spec_rewards_amount(stake_pool.pending_inactive.value, num_successful_proposals, num_total_proposals, rewards_rate, rewards_rate_denominator)
+} else {
+ 0
+};
+let post post_stake_pool = global<StakePool>(pool_address);
+let post post_active_value = post_stake_pool.active.value;
+let post post_pending_inactive_value = post_stake_pool.pending_inactive.value;
+let fees_table = global<ValidatorFees>(@aptos_framework).fees_table;
+let post post_fees_table = global<ValidatorFees>(@aptos_framework).fees_table;
+let post post_inactive_value = post_stake_pool.inactive.value;
+ensures post_stake_pool.pending_active.value == 0;
+ensures if (features::spec_is_enabled(features::COLLECT_AND_DISTRIBUTE_GAS_FEES) && table::spec_contains(fees_table, pool_address)) {
+ !table::spec_contains(post_fees_table, pool_address) &&
+ post_active_value == stake_pool.active.value + rewards_amount_1 + stake_pool.pending_active.value + table::spec_get(fees_table, pool_address).value
+} else {
+ post_active_value == stake_pool.active.value + rewards_amount_1 + stake_pool.pending_active.value
+};
+ensures if (spec_get_reconfig_start_time_secs() >= stake_pool.locked_until_secs) {
+ post_pending_inactive_value == 0 &&
+ post_inactive_value == stake_pool.inactive.value + stake_pool.pending_inactive.value + rewards_amount_2
+} else {
+ post_pending_inactive_value == stake_pool.pending_inactive.value + rewards_amount_2
+};
+
+
+
+
+
+
+
+
+schema UpdateStakePoolAbortsIf {
+ pool_address: address;
+ validator_perf: ValidatorPerformance;
+ aborts_if !exists<StakePool>(pool_address);
+ aborts_if !exists<ValidatorConfig>(pool_address);
+ aborts_if global<ValidatorConfig>(pool_address).validator_index >= len(validator_perf.validators);
+ let aptos_addr = type_info::type_of<AptosCoin>().account_address;
+ aborts_if !exists<ValidatorFees>(aptos_addr);
+ let stake_pool = global<StakePool>(pool_address);
+ include DistributeRewardsAbortsIf {stake: stake_pool.active};
+ include DistributeRewardsAbortsIf {stake: stake_pool.pending_inactive};
+}
+
+
+
+
+
+
+### Function `get_reconfig_start_time_secs`
+
+
+fun get_reconfig_start_time_secs(): u64
+
+
+
+
+
+include GetReconfigStartTimeRequirement;
+
+
+
+
+
+
+
+
+schema GetReconfigStartTimeRequirement {
+ requires exists<timestamp::CurrentTimeMicroseconds>(@aptos_framework);
+ include reconfiguration_state::StartTimeSecsRequirement;
+}
+
+
+
+
+
+
+
+
+fun spec_get_reconfig_start_time_secs(): u64 {
+ if (exists<reconfiguration_state::State>(@aptos_framework)) {
+ reconfiguration_state::spec_start_time_secs()
+ } else {
+ timestamp::spec_now_seconds()
+ }
+}
+
+
+
+
+
+
+### Function `calculate_rewards_amount`
+
+
+fun calculate_rewards_amount(stake_amount: u64, num_successful_proposals: u64, num_total_proposals: u64, rewards_rate: u64, rewards_rate_denominator: u64): u64
+
+
+
+
+
+pragma opaque;
+pragma verify_duration_estimate = 300;
+requires rewards_rate <= MAX_REWARDS_RATE;
+requires rewards_rate_denominator > 0;
+requires rewards_rate <= rewards_rate_denominator;
+requires num_successful_proposals <= num_total_proposals;
+ensures [concrete] (rewards_rate_denominator * num_total_proposals == 0) ==> result == 0;
+ensures [concrete] (rewards_rate_denominator * num_total_proposals > 0) ==> {
+ let amount = ((stake_amount * rewards_rate * num_successful_proposals) /
+ (rewards_rate_denominator * num_total_proposals));
+ result == amount
+};
+aborts_if false;
+ensures [abstract] result == spec_rewards_amount(
+ stake_amount,
+ num_successful_proposals,
+ num_total_proposals,
+ rewards_rate,
+ rewards_rate_denominator);
+
+
+
+
+
+
+### Function `distribute_rewards`
+
+
+fun distribute_rewards(stake: &mut coin::Coin<aptos_coin::AptosCoin>, num_successful_proposals: u64, num_total_proposals: u64, rewards_rate: u64, rewards_rate_denominator: u64): u64
+
+
+
+
+
+include ResourceRequirement;
+requires rewards_rate <= MAX_REWARDS_RATE;
+requires rewards_rate_denominator > 0;
+requires rewards_rate <= rewards_rate_denominator;
+requires num_successful_proposals <= num_total_proposals;
+include DistributeRewardsAbortsIf;
+ensures old(stake.value) > 0 ==>
+ result == spec_rewards_amount(
+ old(stake.value),
+ num_successful_proposals,
+ num_total_proposals,
+ rewards_rate,
+ rewards_rate_denominator);
+ensures old(stake.value) > 0 ==>
+ stake.value == old(stake.value) + spec_rewards_amount(
+ old(stake.value),
+ num_successful_proposals,
+ num_total_proposals,
+ rewards_rate,
+ rewards_rate_denominator);
+ensures old(stake.value) == 0 ==> result == 0;
+ensures old(stake.value) == 0 ==> stake.value == old(stake.value);
+
+
+
+
+
+
+
+
+schema DistributeRewardsAbortsIf {
+ stake: Coin<AptosCoin>;
+ num_successful_proposals: num;
+ num_total_proposals: num;
+ rewards_rate: num;
+ rewards_rate_denominator: num;
+ let stake_amount = coin::value(stake);
+ let rewards_amount = if (stake_amount > 0) {
+ spec_rewards_amount(stake_amount, num_successful_proposals, num_total_proposals, rewards_rate, rewards_rate_denominator)
+ } else {
+ 0
+ };
+ let amount = rewards_amount;
+ let addr = type_info::type_of<AptosCoin>().account_address;
+ aborts_if (rewards_amount > 0) && !exists<coin::CoinInfo<AptosCoin>>(addr);
+ modifies global<coin::CoinInfo<AptosCoin>>(addr);
+ include (rewards_amount > 0) ==> coin::CoinAddAbortsIf<AptosCoin> { amount: amount };
+}
+
+
+
+
+
+
+### Function `append`
+
+
+fun append<T>(v1: &mut vector<T>, v2: &mut vector<T>)
+
+
+
+
+
+pragma opaque, verify = false;
+aborts_if false;
+ensures len(v1) == old(len(v1) + len(v2));
+ensures len(v2) == 0;
+ensures (forall i in 0..old(len(v1)): v1[i] == old(v1[i]));
+ensures (forall i in old(len(v1))..len(v1): v1[i] == old(v2[len(v2) - (i - len(v1)) - 1]));
+
+
+
+
+
+
+### Function `find_validator`
+
+
+fun find_validator(v: &vector<stake::ValidatorInfo>, addr: address): option::Option<u64>
+
+
+
+
+
+pragma opaque;
+aborts_if false;
+ensures option::is_none(result) ==> (forall i in 0..len(v): v[i].addr != addr);
+ensures option::is_some(result) ==> v[option::borrow(result)].addr == addr;
+ensures option::is_some(result) ==> spec_contains(v, addr);
+ensures [abstract] result == spec_find_validator(v,addr);
+
+
+
+
+
+
+### Function `update_voting_power_increase`
+
+
+fun update_voting_power_increase(increase_amount: u64)
+
+
+
+
+
+requires !reconfiguration_state::spec_is_in_progress();
+aborts_if !exists<ValidatorSet>(@aptos_framework);
+aborts_if !exists<staking_config::StakingConfig>(@aptos_framework);
+let aptos = @aptos_framework;
+let pre_validator_set = global<ValidatorSet>(aptos);
+let post validator_set = global<ValidatorSet>(aptos);
+let staking_config = global<staking_config::StakingConfig>(aptos);
+let voting_power_increase_limit = staking_config.voting_power_increase_limit;
+aborts_if pre_validator_set.total_joining_power + increase_amount > MAX_U128;
+aborts_if pre_validator_set.total_voting_power > 0 && pre_validator_set.total_voting_power * voting_power_increase_limit > MAX_U128;
+aborts_if pre_validator_set.total_voting_power > 0 &&
+ pre_validator_set.total_joining_power + increase_amount > pre_validator_set.total_voting_power * voting_power_increase_limit / 100;
+ensures validator_set.total_voting_power > 0 ==>
+ validator_set.total_joining_power <= validator_set.total_voting_power * voting_power_increase_limit / 100;
+ensures validator_set.total_joining_power == pre_validator_set.total_joining_power + increase_amount;
+
+
+
+
+
+
+### Function `assert_stake_pool_exists`
+
+
+fun assert_stake_pool_exists(pool_address: address)
+
+
+
+
+
+aborts_if !stake_pool_exists(pool_address);
+
+
+
+
+
+
+### Function `configure_allowed_validators`
+
+
+public fun configure_allowed_validators(aptos_framework: &signer, accounts: vector<address>)
+
+
+
+
+
+let aptos_framework_address = signer::address_of(aptos_framework);
+aborts_if !system_addresses::is_aptos_framework_address(aptos_framework_address);
+let post allowed = global<AllowedValidators>(aptos_framework_address);
+ensures allowed.accounts == accounts;
+
+
+
+
+
+
+### Function `assert_owner_cap_exists`
+
+
+fun assert_owner_cap_exists(owner: address)
+
+
+
+
+
+aborts_if !exists<OwnerCapability>(owner);
+
+
+
+[move-book]: https://aptos.dev/move/book/SUMMARY
diff --git a/aptos-move/framework/aptos-framework/tests/compiler-v2-doc/staking_config.md b/aptos-move/framework/aptos-framework/tests/compiler-v2-doc/staking_config.md
new file mode 100644
index 0000000000000..f053716b0e392
--- /dev/null
+++ b/aptos-move/framework/aptos-framework/tests/compiler-v2-doc/staking_config.md
@@ -0,0 +1,1595 @@
+
+
+
+# Module `0x1::staking_config`
+
+Provides the configuration for staking and rewards
+
+
+- [Resource `StakingConfig`](#0x1_staking_config_StakingConfig)
+- [Resource `StakingRewardsConfig`](#0x1_staking_config_StakingRewardsConfig)
+- [Constants](#@Constants_0)
+- [Function `initialize`](#0x1_staking_config_initialize)
+- [Function `reward_rate`](#0x1_staking_config_reward_rate)
+- [Function `initialize_rewards`](#0x1_staking_config_initialize_rewards)
+- [Function `get`](#0x1_staking_config_get)
+- [Function `get_allow_validator_set_change`](#0x1_staking_config_get_allow_validator_set_change)
+- [Function `get_required_stake`](#0x1_staking_config_get_required_stake)
+- [Function `get_recurring_lockup_duration`](#0x1_staking_config_get_recurring_lockup_duration)
+- [Function `get_reward_rate`](#0x1_staking_config_get_reward_rate)
+- [Function `get_voting_power_increase_limit`](#0x1_staking_config_get_voting_power_increase_limit)
+- [Function `calculate_and_save_latest_epoch_rewards_rate`](#0x1_staking_config_calculate_and_save_latest_epoch_rewards_rate)
+- [Function `calculate_and_save_latest_rewards_config`](#0x1_staking_config_calculate_and_save_latest_rewards_config)
+- [Function `update_required_stake`](#0x1_staking_config_update_required_stake)
+- [Function `update_recurring_lockup_duration_secs`](#0x1_staking_config_update_recurring_lockup_duration_secs)
+- [Function `update_rewards_rate`](#0x1_staking_config_update_rewards_rate)
+- [Function `update_rewards_config`](#0x1_staking_config_update_rewards_config)
+- [Function `update_voting_power_increase_limit`](#0x1_staking_config_update_voting_power_increase_limit)
+- [Function `validate_required_stake`](#0x1_staking_config_validate_required_stake)
+- [Function `validate_rewards_config`](#0x1_staking_config_validate_rewards_config)
+- [Specification](#@Specification_1)
+ - [High-level Requirements](#high-level-req)
+ - [Module-level Specification](#module-level-spec)
+ - [Resource `StakingConfig`](#@Specification_1_StakingConfig)
+ - [Resource `StakingRewardsConfig`](#@Specification_1_StakingRewardsConfig)
+ - [Function `initialize`](#@Specification_1_initialize)
+ - [Function `reward_rate`](#@Specification_1_reward_rate)
+ - [Function `initialize_rewards`](#@Specification_1_initialize_rewards)
+ - [Function `get`](#@Specification_1_get)
+ - [Function `get_reward_rate`](#@Specification_1_get_reward_rate)
+ - [Function `calculate_and_save_latest_epoch_rewards_rate`](#@Specification_1_calculate_and_save_latest_epoch_rewards_rate)
+ - [Function `calculate_and_save_latest_rewards_config`](#@Specification_1_calculate_and_save_latest_rewards_config)
+ - [Function `update_required_stake`](#@Specification_1_update_required_stake)
+ - [Function `update_recurring_lockup_duration_secs`](#@Specification_1_update_recurring_lockup_duration_secs)
+ - [Function `update_rewards_rate`](#@Specification_1_update_rewards_rate)
+ - [Function `update_rewards_config`](#@Specification_1_update_rewards_config)
+ - [Function `update_voting_power_increase_limit`](#@Specification_1_update_voting_power_increase_limit)
+ - [Function `validate_required_stake`](#@Specification_1_validate_required_stake)
+ - [Function `validate_rewards_config`](#@Specification_1_validate_rewards_config)
+
+
+use 0x1::error;
+use 0x1::features;
+use 0x1::fixed_point64;
+use 0x1::math_fixed64;
+use 0x1::system_addresses;
+use 0x1::timestamp;
+
+
+
+
+
+
+## Resource `StakingConfig`
+
+Validator set configurations that will be stored with the @aptos_framework account.
+
+
+struct StakingConfig has copy, drop, key
+
+
+
+
+minimum_stake: u64
+maximum_stake: u64
+recurring_lockup_duration_secs: u64
+allow_validator_set_change: bool
+rewards_rate: u64
+rewards_rate_denominator: u64
+voting_power_increase_limit: u64
+struct StakingRewardsConfig has copy, drop, key
+
+
+
+
+rewards_rate: fixed_point64::FixedPoint64
+min_rewards_rate: fixed_point64::FixedPoint64
+rewards_rate_period_in_secs: u64
+last_rewards_rate_period_start_in_secs: u64
+rewards_rate_decrease_rate: fixed_point64::FixedPoint64
+const MAX_U64: u128 = 18446744073709551615;
+
+
+
+
+
+
+Denominator of number in basis points. 1 bps(basis points) = 0.01%.
+
+
+const BPS_DENOMINATOR: u64 = 10000;
+
+
+
+
+
+
+The function has been deprecated.
+
+
+const EDEPRECATED_FUNCTION: u64 = 10;
+
+
+
+
+
+
+The function is disabled or hasn't been enabled.
+
+
+const EDISABLED_FUNCTION: u64 = 11;
+
+
+
+
+
+
+Specified start time of last rewards rate period is invalid, which must be not late than the current timestamp.
+
+
+const EINVALID_LAST_REWARDS_RATE_PERIOD_START: u64 = 7;
+
+
+
+
+
+
+Specified min rewards rate is invalid, which must be within [0, rewards_rate].
+
+
+const EINVALID_MIN_REWARDS_RATE: u64 = 6;
+
+
+
+
+
+
+Specified rewards rate is invalid, which must be within [0, MAX_REWARDS_RATE].
+
+
+const EINVALID_REWARDS_RATE: u64 = 5;
+
+
+
+
+
+
+Specified rewards rate decrease rate is invalid, which must be not greater than BPS_DENOMINATOR.
+
+
+const EINVALID_REWARDS_RATE_DECREASE_RATE: u64 = 8;
+
+
+
+
+
+
+Specified rewards rate period is invalid. It must be larger than 0 and cannot be changed if configured.
+
+
+const EINVALID_REWARDS_RATE_PERIOD: u64 = 9;
+
+
+
+
+
+
+Specified stake range is invalid. Max must be greater than min.
+
+
+const EINVALID_STAKE_RANGE: u64 = 3;
+
+
+
+
+
+
+The voting power increase limit percentage must be within (0, 50].
+
+
+const EINVALID_VOTING_POWER_INCREASE_LIMIT: u64 = 4;
+
+
+
+
+
+
+Stake lockup duration cannot be zero.
+
+
+const EZERO_LOCKUP_DURATION: u64 = 1;
+
+
+
+
+
+
+Reward rate denominator cannot be zero.
+
+
+const EZERO_REWARDS_RATE_DENOMINATOR: u64 = 2;
+
+
+
+
+
+
+Limit the maximum value of rewards_rate
in order to avoid any arithmetic overflow.
+
+
+const MAX_REWARDS_RATE: u64 = 1000000;
+
+
+
+
+
+
+1 year => 365 * 24 * 60 * 60
+
+
+const ONE_YEAR_IN_SECS: u64 = 31536000;
+
+
+
+
+
+
+## Function `initialize`
+
+Only called during genesis.
+
+
+public(friend) fun initialize(aptos_framework: &signer, minimum_stake: u64, maximum_stake: u64, recurring_lockup_duration_secs: u64, allow_validator_set_change: bool, rewards_rate: u64, rewards_rate_denominator: u64, voting_power_increase_limit: u64)
+
+
+
+
+public(friend) fun initialize(
+ aptos_framework: &signer,
+ minimum_stake: u64,
+ maximum_stake: u64,
+ recurring_lockup_duration_secs: u64,
+ allow_validator_set_change: bool,
+ rewards_rate: u64,
+ rewards_rate_denominator: u64,
+ voting_power_increase_limit: u64,
+) {
+ system_addresses::assert_aptos_framework(aptos_framework);
+
+ // This can fail genesis but is necessary so that any misconfigurations can be corrected before genesis succeeds
+ validate_required_stake(minimum_stake, maximum_stake);
+
+ assert!(recurring_lockup_duration_secs > 0, error::invalid_argument(EZERO_LOCKUP_DURATION));
+ assert!(
+ rewards_rate_denominator > 0,
+ error::invalid_argument(EZERO_REWARDS_RATE_DENOMINATOR),
+ );
+ assert!(
+ voting_power_increase_limit > 0 && voting_power_increase_limit <= 50,
+ error::invalid_argument(EINVALID_VOTING_POWER_INCREASE_LIMIT),
+ );
+
+ // `rewards_rate` which is the numerator is limited to be `<= MAX_REWARDS_RATE` in order to avoid the arithmetic
+ // overflow in the rewards calculation. `rewards_rate_denominator` can be adjusted to get the desired rewards
+ // rate (i.e., rewards_rate / rewards_rate_denominator).
+ assert!(rewards_rate <= MAX_REWARDS_RATE, error::invalid_argument(EINVALID_REWARDS_RATE));
+
+ // We assert that (rewards_rate / rewards_rate_denominator <= 1).
+ assert!(rewards_rate <= rewards_rate_denominator, error::invalid_argument(EINVALID_REWARDS_RATE));
+
+ move_to(aptos_framework, StakingConfig {
+ minimum_stake,
+ maximum_stake,
+ recurring_lockup_duration_secs,
+ allow_validator_set_change,
+ rewards_rate,
+ rewards_rate_denominator,
+ voting_power_increase_limit,
+ });
+}
+
+
+
+
+#[view]
+public fun reward_rate(): (u64, u64)
+
+
+
+
+public fun reward_rate(): (u64, u64) acquires StakingRewardsConfig, StakingConfig {
+ get_reward_rate(borrow_global<StakingConfig>(@aptos_framework))
+}
+
+
+
+
+public fun initialize_rewards(aptos_framework: &signer, rewards_rate: fixed_point64::FixedPoint64, min_rewards_rate: fixed_point64::FixedPoint64, rewards_rate_period_in_secs: u64, last_rewards_rate_period_start_in_secs: u64, rewards_rate_decrease_rate: fixed_point64::FixedPoint64)
+
+
+
+
+public fun initialize_rewards(
+ aptos_framework: &signer,
+ rewards_rate: FixedPoint64,
+ min_rewards_rate: FixedPoint64,
+ rewards_rate_period_in_secs: u64,
+ last_rewards_rate_period_start_in_secs: u64,
+ rewards_rate_decrease_rate: FixedPoint64,
+) {
+ system_addresses::assert_aptos_framework(aptos_framework);
+
+ validate_rewards_config(
+ rewards_rate,
+ min_rewards_rate,
+ rewards_rate_period_in_secs,
+ rewards_rate_decrease_rate,
+ );
+ assert!(
+ timestamp::now_seconds() >= last_rewards_rate_period_start_in_secs,
+ error::invalid_argument(EINVALID_LAST_REWARDS_RATE_PERIOD_START)
+ );
+
+ move_to(aptos_framework, StakingRewardsConfig {
+ rewards_rate,
+ min_rewards_rate,
+ rewards_rate_period_in_secs,
+ last_rewards_rate_period_start_in_secs,
+ rewards_rate_decrease_rate,
+ });
+}
+
+
+
+
+public fun get(): staking_config::StakingConfig
+
+
+
+
+public fun get(): StakingConfig acquires StakingConfig {
+ *borrow_global<StakingConfig>(@aptos_framework)
+}
+
+
+
+
+public fun get_allow_validator_set_change(config: &staking_config::StakingConfig): bool
+
+
+
+
+public fun get_allow_validator_set_change(config: &StakingConfig): bool {
+ config.allow_validator_set_change
+}
+
+
+
+
+public fun get_required_stake(config: &staking_config::StakingConfig): (u64, u64)
+
+
+
+
+public fun get_required_stake(config: &StakingConfig): (u64, u64) {
+ (config.minimum_stake, config.maximum_stake)
+}
+
+
+
+
+public fun get_recurring_lockup_duration(config: &staking_config::StakingConfig): u64
+
+
+
+
+public fun get_recurring_lockup_duration(config: &StakingConfig): u64 {
+ config.recurring_lockup_duration_secs
+}
+
+
+
+
+public fun get_reward_rate(config: &staking_config::StakingConfig): (u64, u64)
+
+
+
+
+public fun get_reward_rate(config: &StakingConfig): (u64, u64) acquires StakingRewardsConfig {
+ if (features::periodical_reward_rate_decrease_enabled()) {
+ let epoch_rewards_rate = borrow_global<StakingRewardsConfig>(@aptos_framework).rewards_rate;
+ if (fixed_point64::is_zero(epoch_rewards_rate)) {
+ (0u64, 1u64)
+ } else {
+ // Maximize denominator for higher precision.
+ // Restriction: nominator <= MAX_REWARDS_RATE && denominator <= MAX_U64
+ let denominator = fixed_point64::divide_u128((MAX_REWARDS_RATE as u128), epoch_rewards_rate);
+ if (denominator > MAX_U64) {
+ denominator = MAX_U64
+ };
+ let nominator = (fixed_point64::multiply_u128(denominator, epoch_rewards_rate) as u64);
+ (nominator, (denominator as u64))
+ }
+ } else {
+ (config.rewards_rate, config.rewards_rate_denominator)
+ }
+}
+
+
+
+
+public fun get_voting_power_increase_limit(config: &staking_config::StakingConfig): u64
+
+
+
+
+public fun get_voting_power_increase_limit(config: &StakingConfig): u64 {
+ config.voting_power_increase_limit
+}
+
+
+
+
+public(friend) fun calculate_and_save_latest_epoch_rewards_rate(): fixed_point64::FixedPoint64
+
+
+
+
+public(friend) fun calculate_and_save_latest_epoch_rewards_rate(): FixedPoint64 acquires StakingRewardsConfig {
+ assert!(features::periodical_reward_rate_decrease_enabled(), error::invalid_state(EDISABLED_FUNCTION));
+ let staking_rewards_config = calculate_and_save_latest_rewards_config();
+ staking_rewards_config.rewards_rate
+}
+
+
+
+
+fun calculate_and_save_latest_rewards_config(): staking_config::StakingRewardsConfig
+
+
+
+
+fun calculate_and_save_latest_rewards_config(): StakingRewardsConfig acquires StakingRewardsConfig {
+ let staking_rewards_config = borrow_global_mut<StakingRewardsConfig>(@aptos_framework);
+ let current_time_in_secs = timestamp::now_seconds();
+ assert!(
+ current_time_in_secs >= staking_rewards_config.last_rewards_rate_period_start_in_secs,
+ error::invalid_argument(EINVALID_LAST_REWARDS_RATE_PERIOD_START)
+ );
+ if (current_time_in_secs - staking_rewards_config.last_rewards_rate_period_start_in_secs < staking_rewards_config.rewards_rate_period_in_secs) {
+ return *staking_rewards_config
+ };
+ // Rewards rate decrease rate cannot be greater than 100%. Otherwise rewards rate will be negative.
+ assert!(
+ fixed_point64::ceil(staking_rewards_config.rewards_rate_decrease_rate) <= 1,
+ error::invalid_argument(EINVALID_REWARDS_RATE_DECREASE_RATE)
+ );
+ let new_rate = math_fixed64::mul_div(
+ staking_rewards_config.rewards_rate,
+ fixed_point64::sub(
+ fixed_point64::create_from_u128(1),
+ staking_rewards_config.rewards_rate_decrease_rate,
+ ),
+ fixed_point64::create_from_u128(1),
+ );
+ new_rate = fixed_point64::max(new_rate, staking_rewards_config.min_rewards_rate);
+
+ staking_rewards_config.rewards_rate = new_rate;
+ staking_rewards_config.last_rewards_rate_period_start_in_secs =
+ staking_rewards_config.last_rewards_rate_period_start_in_secs +
+ staking_rewards_config.rewards_rate_period_in_secs;
+ return *staking_rewards_config
+}
+
+
+
+
+public fun update_required_stake(aptos_framework: &signer, minimum_stake: u64, maximum_stake: u64)
+
+
+
+
+public fun update_required_stake(
+ aptos_framework: &signer,
+ minimum_stake: u64,
+ maximum_stake: u64,
+) acquires StakingConfig {
+ system_addresses::assert_aptos_framework(aptos_framework);
+ validate_required_stake(minimum_stake, maximum_stake);
+
+ let staking_config = borrow_global_mut<StakingConfig>(@aptos_framework);
+ staking_config.minimum_stake = minimum_stake;
+ staking_config.maximum_stake = maximum_stake;
+}
+
+
+
+
+public fun update_recurring_lockup_duration_secs(aptos_framework: &signer, new_recurring_lockup_duration_secs: u64)
+
+
+
+
+public fun update_recurring_lockup_duration_secs(
+ aptos_framework: &signer,
+ new_recurring_lockup_duration_secs: u64,
+) acquires StakingConfig {
+ assert!(new_recurring_lockup_duration_secs > 0, error::invalid_argument(EZERO_LOCKUP_DURATION));
+ system_addresses::assert_aptos_framework(aptos_framework);
+
+ let staking_config = borrow_global_mut<StakingConfig>(@aptos_framework);
+ staking_config.recurring_lockup_duration_secs = new_recurring_lockup_duration_secs;
+}
+
+
+
+
+public fun update_rewards_rate(aptos_framework: &signer, new_rewards_rate: u64, new_rewards_rate_denominator: u64)
+
+
+
+
+public fun update_rewards_rate(
+ aptos_framework: &signer,
+ new_rewards_rate: u64,
+ new_rewards_rate_denominator: u64,
+) acquires StakingConfig {
+ assert!(!features::periodical_reward_rate_decrease_enabled(), error::invalid_state(EDEPRECATED_FUNCTION));
+ system_addresses::assert_aptos_framework(aptos_framework);
+ assert!(
+ new_rewards_rate_denominator > 0,
+ error::invalid_argument(EZERO_REWARDS_RATE_DENOMINATOR),
+ );
+ // `rewards_rate` which is the numerator is limited to be `<= MAX_REWARDS_RATE` in order to avoid the arithmetic
+ // overflow in the rewards calculation. `rewards_rate_denominator` can be adjusted to get the desired rewards
+ // rate (i.e., rewards_rate / rewards_rate_denominator).
+ assert!(new_rewards_rate <= MAX_REWARDS_RATE, error::invalid_argument(EINVALID_REWARDS_RATE));
+
+ // We assert that (rewards_rate / rewards_rate_denominator <= 1).
+ assert!(new_rewards_rate <= new_rewards_rate_denominator, error::invalid_argument(EINVALID_REWARDS_RATE));
+
+ let staking_config = borrow_global_mut<StakingConfig>(@aptos_framework);
+ staking_config.rewards_rate = new_rewards_rate;
+ staking_config.rewards_rate_denominator = new_rewards_rate_denominator;
+}
+
+
+
+
+public fun update_rewards_config(aptos_framework: &signer, rewards_rate: fixed_point64::FixedPoint64, min_rewards_rate: fixed_point64::FixedPoint64, rewards_rate_period_in_secs: u64, rewards_rate_decrease_rate: fixed_point64::FixedPoint64)
+
+
+
+
+public fun update_rewards_config(
+ aptos_framework: &signer,
+ rewards_rate: FixedPoint64,
+ min_rewards_rate: FixedPoint64,
+ rewards_rate_period_in_secs: u64,
+ rewards_rate_decrease_rate: FixedPoint64,
+) acquires StakingRewardsConfig {
+ system_addresses::assert_aptos_framework(aptos_framework);
+
+ validate_rewards_config(
+ rewards_rate,
+ min_rewards_rate,
+ rewards_rate_period_in_secs,
+ rewards_rate_decrease_rate,
+ );
+
+ let staking_rewards_config = borrow_global_mut<StakingRewardsConfig>(@aptos_framework);
+ // Currently rewards_rate_period_in_secs is not allowed to be changed because this could bring complicated
+ // logics. At the moment the argument is just a placeholder for future use.
+ assert!(
+ rewards_rate_period_in_secs == staking_rewards_config.rewards_rate_period_in_secs,
+ error::invalid_argument(EINVALID_REWARDS_RATE_PERIOD),
+ );
+ staking_rewards_config.rewards_rate = rewards_rate;
+ staking_rewards_config.min_rewards_rate = min_rewards_rate;
+ staking_rewards_config.rewards_rate_period_in_secs = rewards_rate_period_in_secs;
+ staking_rewards_config.rewards_rate_decrease_rate = rewards_rate_decrease_rate;
+}
+
+
+
+
+public fun update_voting_power_increase_limit(aptos_framework: &signer, new_voting_power_increase_limit: u64)
+
+
+
+
+public fun update_voting_power_increase_limit(
+ aptos_framework: &signer,
+ new_voting_power_increase_limit: u64,
+) acquires StakingConfig {
+ system_addresses::assert_aptos_framework(aptos_framework);
+ assert!(
+ new_voting_power_increase_limit > 0 && new_voting_power_increase_limit <= 50,
+ error::invalid_argument(EINVALID_VOTING_POWER_INCREASE_LIMIT),
+ );
+
+ let staking_config = borrow_global_mut<StakingConfig>(@aptos_framework);
+ staking_config.voting_power_increase_limit = new_voting_power_increase_limit;
+}
+
+
+
+
+fun validate_required_stake(minimum_stake: u64, maximum_stake: u64)
+
+
+
+
+fun validate_required_stake(minimum_stake: u64, maximum_stake: u64) {
+ assert!(minimum_stake <= maximum_stake && maximum_stake > 0, error::invalid_argument(EINVALID_STAKE_RANGE));
+}
+
+
+
+
+fun validate_rewards_config(rewards_rate: fixed_point64::FixedPoint64, min_rewards_rate: fixed_point64::FixedPoint64, rewards_rate_period_in_secs: u64, rewards_rate_decrease_rate: fixed_point64::FixedPoint64)
+
+
+
+
+fun validate_rewards_config(
+ rewards_rate: FixedPoint64,
+ min_rewards_rate: FixedPoint64,
+ rewards_rate_period_in_secs: u64,
+ rewards_rate_decrease_rate: FixedPoint64,
+) {
+ // Bound rewards rate to avoid arithmetic overflow.
+ assert!(
+ less_or_equal(rewards_rate, fixed_point64::create_from_u128((1u128))),
+ error::invalid_argument(EINVALID_REWARDS_RATE)
+ );
+ assert!(
+ less_or_equal(min_rewards_rate, rewards_rate),
+ error::invalid_argument(EINVALID_MIN_REWARDS_RATE)
+ );
+ // Rewards rate decrease rate cannot be greater than 100%. Otherwise rewards rate will be negative.
+ assert!(
+ fixed_point64::ceil(rewards_rate_decrease_rate) <= 1,
+ error::invalid_argument(EINVALID_REWARDS_RATE_DECREASE_RATE)
+ );
+ // This field, rewards_rate_period_in_secs must be greater than 0.
+ // TODO: rewards_rate_period_in_secs should be longer than the epoch duration but reading epoch duration causes a circular dependency.
+ assert!(
+ rewards_rate_period_in_secs > 0,
+ error::invalid_argument(EINVALID_REWARDS_RATE_PERIOD),
+ );
+}
+
+
+
+
+No. | Requirement | Criticality | Implementation | Enforcement | +
---|---|---|---|---|
1 | +The ability to initialize the staking config and staking rewards resources, as well as the ability to update the staking config and staking rewards should only be available to the Aptos framework account. | +Medium | +The function initialize and initialize_rewards are used to initialize the StakingConfig and StakingRewardConfig resources. Updating the resources, can be done using the update_required_stake, update_recurring_lockup_duration_secs, update_rewards_rate, update_rewards_config, update_voting_power_increase_limit functions, which ensure that the signer is aptos_framework using the assert_aptos_framework function. | +Verified via initialize, initialize_rewards, update_required_stake, update_recurring_lockup_duration_secs, update_rewards_rate, update_rewards_config, and update_voting_power_increase_limit. | +
2 | +The voting power increase, in a staking config resource, should always be greater than 0 and less or equal to 50. | +High | +During the initialization and update of the staking config, the value of voting_power_increase_limit is ensured to be in the range of (0 to 50]. | +Ensured via initialize and update_voting_power_increase_limit. Formally verified via StakingConfig. | +
3 | +The recurring lockup duration, in a staking config resource, should always be greater than 0. | +Medium | +During the initialization and update of the staking config, the value of recurring_lockup_duration_secs is ensured to be greater than 0. | +Ensured via initialize and update_recurring_lockup_duration_secs. Formally verified via StakingConfig. | +
4 | +The calculation of rewards should not be possible if the last reward rate period just started. | +High | +The function calculate_and_save_latest_rewards_config ensures that last_rewards_rate_period_start_in_secs is greater or equal to the current timestamp. | +Formally verified in StakingRewardsConfigEnabledRequirement. | +
5 | +The rewards rate should always be less than or equal to 100%. | +High | +When initializing and updating the rewards rate, it is ensured that the rewards_rate is less or equal to MAX_REWARDS_RATE, otherwise rewards rate will be negative. | +Verified via StakingConfig. | +
6 | +The reward rate's denominator should never be 0. | +High | +While initializing and updating the rewards rate, rewards_rate_denominator is ensured to be greater than 0. | +Verified via StakingConfig. | +
7 | +The reward rate's nominator and dominator ratio should always be less or equal to 1. | +High | +When initializing and updating the rewards rate, it is ensured that rewards_rate is less or equal to rewards_rate_denominator. | +Verified via StakingConfig. | +
invariant [suspendable] chain_status::is_operating() ==> exists<StakingConfig>(@aptos_framework);
+pragma verify = true;
+pragma aborts_if_is_strict;
+
+
+
+
+
+
+### Resource `StakingConfig`
+
+
+struct StakingConfig has copy, drop, key
+
+
+
+
+minimum_stake: u64
+maximum_stake: u64
+recurring_lockup_duration_secs: u64
+allow_validator_set_change: bool
+rewards_rate: u64
+rewards_rate_denominator: u64
+voting_power_increase_limit: u64
+// This enforces high-level requirement 5:
+invariant rewards_rate <= MAX_REWARDS_RATE;
+// This enforces high-level requirement 6:
+invariant rewards_rate_denominator > 0;
+// This enforces high-level requirement 7:
+invariant rewards_rate <= rewards_rate_denominator;
+// This enforces high-level requirement 3:
+invariant recurring_lockup_duration_secs > 0;
+// This enforces high-level requirement 2:
+invariant voting_power_increase_limit > 0 && voting_power_increase_limit <= 50;
+
+
+
+
+
+
+### Resource `StakingRewardsConfig`
+
+
+struct StakingRewardsConfig has copy, drop, key
+
+
+
+
+rewards_rate: fixed_point64::FixedPoint64
+min_rewards_rate: fixed_point64::FixedPoint64
+rewards_rate_period_in_secs: u64
+last_rewards_rate_period_start_in_secs: u64
+rewards_rate_decrease_rate: fixed_point64::FixedPoint64
+invariant fixed_point64::spec_less_or_equal(
+ rewards_rate,
+ fixed_point64::spec_create_from_u128((1u128)));
+invariant fixed_point64::spec_less_or_equal(min_rewards_rate, rewards_rate);
+invariant rewards_rate_period_in_secs > 0;
+invariant fixed_point64::spec_ceil(rewards_rate_decrease_rate) <= 1;
+
+
+
+
+
+
+### Function `initialize`
+
+
+public(friend) fun initialize(aptos_framework: &signer, minimum_stake: u64, maximum_stake: u64, recurring_lockup_duration_secs: u64, allow_validator_set_change: bool, rewards_rate: u64, rewards_rate_denominator: u64, voting_power_increase_limit: u64)
+
+
+
+Caller must be @aptos_framework.
+The maximum_stake must be greater than maximum_stake in the range of Specified stake and the maximum_stake greater than zero.
+The rewards_rate_denominator must greater than zero.
+Only this %0-%50 of current total voting power is allowed to join the validator set in each epoch.
+The rewards_rate
which is the numerator is limited to be <= MAX_REWARDS_RATE
in order to avoid the arithmetic overflow in the rewards calculation.
+rewards_rate/rewards_rate_denominator <= 1.
+StakingConfig does not exist under the aptos_framework before creating it.
+
+
+let addr = signer::address_of(aptos_framework);
+// This enforces high-level requirement 1:
+aborts_if addr != @aptos_framework;
+aborts_if minimum_stake > maximum_stake || maximum_stake == 0;
+// This enforces high-level requirement 3:
+aborts_if recurring_lockup_duration_secs == 0;
+aborts_if rewards_rate_denominator == 0;
+// This enforces high-level requirement 2:
+aborts_if voting_power_increase_limit == 0 || voting_power_increase_limit > 50;
+aborts_if rewards_rate > MAX_REWARDS_RATE;
+aborts_if rewards_rate > rewards_rate_denominator;
+aborts_if exists<StakingConfig>(addr);
+ensures exists<StakingConfig>(addr);
+
+
+
+
+
+
+### Function `reward_rate`
+
+
+#[view]
+public fun reward_rate(): (u64, u64)
+
+
+
+
+
+let config = global<StakingConfig>(@aptos_framework);
+aborts_if !exists<StakingConfig>(@aptos_framework);
+include StakingRewardsConfigRequirement;
+ensures (features::spec_periodical_reward_rate_decrease_enabled() &&
+ (global<StakingRewardsConfig>(@aptos_framework).rewards_rate.value as u64) != 0) ==>
+ result_1 <= MAX_REWARDS_RATE && result_2 <= MAX_U64;
+
+
+
+
+
+
+### Function `initialize_rewards`
+
+
+public fun initialize_rewards(aptos_framework: &signer, rewards_rate: fixed_point64::FixedPoint64, min_rewards_rate: fixed_point64::FixedPoint64, rewards_rate_period_in_secs: u64, last_rewards_rate_period_start_in_secs: u64, rewards_rate_decrease_rate: fixed_point64::FixedPoint64)
+
+
+
+Caller must be @aptos_framework.
+last_rewards_rate_period_start_in_secs cannot be later than now.
+Abort at any condition in StakingRewardsConfigValidationAborts.
+StakingRewardsConfig does not exist under the aptos_framework before creating it.
+
+
+pragma verify_duration_estimate = 120;
+requires exists<timestamp::CurrentTimeMicroseconds>(@aptos_framework);
+let addr = signer::address_of(aptos_framework);
+// This enforces high-level requirement 1:
+aborts_if addr != @aptos_framework;
+aborts_if last_rewards_rate_period_start_in_secs > timestamp::spec_now_seconds();
+include StakingRewardsConfigValidationAbortsIf;
+aborts_if exists<StakingRewardsConfig>(addr);
+ensures exists<StakingRewardsConfig>(addr);
+
+
+
+
+
+
+### Function `get`
+
+
+public fun get(): staking_config::StakingConfig
+
+
+
+
+
+aborts_if !exists<StakingConfig>(@aptos_framework);
+
+
+
+
+
+
+### Function `get_reward_rate`
+
+
+public fun get_reward_rate(config: &staking_config::StakingConfig): (u64, u64)
+
+
+
+
+
+include StakingRewardsConfigRequirement;
+ensures (features::spec_periodical_reward_rate_decrease_enabled() &&
+ (global<StakingRewardsConfig>(@aptos_framework).rewards_rate.value as u64) != 0) ==>
+ result_1 <= MAX_REWARDS_RATE && result_2 <= MAX_U64;
+
+
+
+
+
+
+### Function `calculate_and_save_latest_epoch_rewards_rate`
+
+
+public(friend) fun calculate_and_save_latest_epoch_rewards_rate(): fixed_point64::FixedPoint64
+
+
+
+
+
+pragma verify_duration_estimate = 120;
+aborts_if !exists<StakingRewardsConfig>(@aptos_framework);
+aborts_if !features::spec_periodical_reward_rate_decrease_enabled();
+include StakingRewardsConfigRequirement;
+
+
+
+
+
+
+### Function `calculate_and_save_latest_rewards_config`
+
+
+fun calculate_and_save_latest_rewards_config(): staking_config::StakingRewardsConfig
+
+
+
+
+
+pragma verify_duration_estimate = 120;
+requires features::spec_periodical_reward_rate_decrease_enabled();
+include StakingRewardsConfigRequirement;
+aborts_if !exists<StakingRewardsConfig>(@aptos_framework);
+
+
+
+
+
+
+### Function `update_required_stake`
+
+
+public fun update_required_stake(aptos_framework: &signer, minimum_stake: u64, maximum_stake: u64)
+
+
+
+Caller must be @aptos_framework.
+The maximum_stake must be greater than maximum_stake in the range of Specified stake and the maximum_stake greater than zero.
+The StakingConfig is under @aptos_framework.
+
+
+let addr = signer::address_of(aptos_framework);
+// This enforces high-level requirement 1:
+aborts_if addr != @aptos_framework;
+aborts_if minimum_stake > maximum_stake || maximum_stake == 0;
+aborts_if !exists<StakingConfig>(@aptos_framework);
+ensures global<StakingConfig>(@aptos_framework).minimum_stake == minimum_stake &&
+ global<StakingConfig>(@aptos_framework).maximum_stake == maximum_stake;
+
+
+
+
+
+
+### Function `update_recurring_lockup_duration_secs`
+
+
+public fun update_recurring_lockup_duration_secs(aptos_framework: &signer, new_recurring_lockup_duration_secs: u64)
+
+
+
+Caller must be @aptos_framework.
+The new_recurring_lockup_duration_secs must greater than zero.
+The StakingConfig is under @aptos_framework.
+
+
+let addr = signer::address_of(aptos_framework);
+// This enforces high-level requirement 1:
+aborts_if addr != @aptos_framework;
+// This enforces high-level requirement 3:
+aborts_if new_recurring_lockup_duration_secs == 0;
+aborts_if !exists<StakingConfig>(@aptos_framework);
+ensures global<StakingConfig>(@aptos_framework).recurring_lockup_duration_secs == new_recurring_lockup_duration_secs;
+
+
+
+
+
+
+### Function `update_rewards_rate`
+
+
+public fun update_rewards_rate(aptos_framework: &signer, new_rewards_rate: u64, new_rewards_rate_denominator: u64)
+
+
+
+Caller must be @aptos_framework.
+The new_rewards_rate_denominator must greater than zero.
+The StakingConfig is under @aptos_framework.
+The rewards_rate
which is the numerator is limited to be <= MAX_REWARDS_RATE
in order to avoid the arithmetic overflow in the rewards calculation.
+rewards_rate/rewards_rate_denominator <= 1.
+
+
+aborts_if features::spec_periodical_reward_rate_decrease_enabled();
+let addr = signer::address_of(aptos_framework);
+// This enforces high-level requirement 1:
+aborts_if addr != @aptos_framework;
+aborts_if new_rewards_rate_denominator == 0;
+aborts_if !exists<StakingConfig>(@aptos_framework);
+aborts_if new_rewards_rate > MAX_REWARDS_RATE;
+aborts_if new_rewards_rate > new_rewards_rate_denominator;
+let post staking_config = global<StakingConfig>(@aptos_framework);
+ensures staking_config.rewards_rate == new_rewards_rate;
+ensures staking_config.rewards_rate_denominator == new_rewards_rate_denominator;
+
+
+
+
+
+
+### Function `update_rewards_config`
+
+
+public fun update_rewards_config(aptos_framework: &signer, rewards_rate: fixed_point64::FixedPoint64, min_rewards_rate: fixed_point64::FixedPoint64, rewards_rate_period_in_secs: u64, rewards_rate_decrease_rate: fixed_point64::FixedPoint64)
+
+
+
+Caller must be @aptos_framework.
+StakingRewardsConfig is under the @aptos_framework.
+
+
+pragma verify_duration_estimate = 120;
+include StakingRewardsConfigRequirement;
+let addr = signer::address_of(aptos_framework);
+// This enforces high-level requirement 1:
+aborts_if addr != @aptos_framework;
+aborts_if global<StakingRewardsConfig>(@aptos_framework).rewards_rate_period_in_secs != rewards_rate_period_in_secs;
+include StakingRewardsConfigValidationAbortsIf;
+aborts_if !exists<StakingRewardsConfig>(addr);
+let post staking_rewards_config = global<StakingRewardsConfig>(@aptos_framework);
+ensures staking_rewards_config.rewards_rate == rewards_rate;
+ensures staking_rewards_config.min_rewards_rate == min_rewards_rate;
+ensures staking_rewards_config.rewards_rate_period_in_secs == rewards_rate_period_in_secs;
+ensures staking_rewards_config.rewards_rate_decrease_rate == rewards_rate_decrease_rate;
+
+
+
+
+
+
+### Function `update_voting_power_increase_limit`
+
+
+public fun update_voting_power_increase_limit(aptos_framework: &signer, new_voting_power_increase_limit: u64)
+
+
+
+Caller must be @aptos_framework.
+Only this %0-%50 of current total voting power is allowed to join the validator set in each epoch.
+The StakingConfig is under @aptos_framework.
+
+
+let addr = signer::address_of(aptos_framework);
+// This enforces high-level requirement 1:
+aborts_if addr != @aptos_framework;
+// This enforces high-level requirement 2:
+aborts_if new_voting_power_increase_limit == 0 || new_voting_power_increase_limit > 50;
+aborts_if !exists<StakingConfig>(@aptos_framework);
+ensures global<StakingConfig>(@aptos_framework).voting_power_increase_limit == new_voting_power_increase_limit;
+
+
+
+
+
+
+### Function `validate_required_stake`
+
+
+fun validate_required_stake(minimum_stake: u64, maximum_stake: u64)
+
+
+
+The maximum_stake must be greater than maximum_stake in the range of Specified stake and the maximum_stake greater than zero.
+
+
+aborts_if minimum_stake > maximum_stake || maximum_stake == 0;
+
+
+
+
+
+
+### Function `validate_rewards_config`
+
+
+fun validate_rewards_config(rewards_rate: fixed_point64::FixedPoint64, min_rewards_rate: fixed_point64::FixedPoint64, rewards_rate_period_in_secs: u64, rewards_rate_decrease_rate: fixed_point64::FixedPoint64)
+
+
+
+Abort at any condition in StakingRewardsConfigValidationAborts.
+
+
+include StakingRewardsConfigValidationAbortsIf;
+
+
+
+rewards_rate must be within [0, 1].
+min_rewards_rate must be not greater than rewards_rate.
+rewards_rate_period_in_secs must be greater than 0.
+rewards_rate_decrease_rate must be within [0,1].
+
+
+
+
+
+schema StakingRewardsConfigValidationAbortsIf {
+ rewards_rate: FixedPoint64;
+ min_rewards_rate: FixedPoint64;
+ rewards_rate_period_in_secs: u64;
+ rewards_rate_decrease_rate: FixedPoint64;
+ aborts_if fixed_point64::spec_greater(
+ rewards_rate,
+ fixed_point64::spec_create_from_u128((1u128)));
+ aborts_if fixed_point64::spec_greater(min_rewards_rate, rewards_rate);
+ aborts_if rewards_rate_period_in_secs == 0;
+ aborts_if fixed_point64::spec_ceil(rewards_rate_decrease_rate) > 1;
+}
+
+
+
+
+
+
+
+
+schema StakingRewardsConfigRequirement {
+ requires exists<timestamp::CurrentTimeMicroseconds>(@aptos_framework);
+ include features::spec_periodical_reward_rate_decrease_enabled() ==> StakingRewardsConfigEnabledRequirement;
+}
+
+
+
+
+
+
+
+
+schema StakingRewardsConfigEnabledRequirement {
+ requires exists<StakingRewardsConfig>(@aptos_framework);
+ let staking_rewards_config = global<StakingRewardsConfig>(@aptos_framework);
+ let rewards_rate = staking_rewards_config.rewards_rate;
+ let min_rewards_rate = staking_rewards_config.min_rewards_rate;
+ let rewards_rate_period_in_secs = staking_rewards_config.rewards_rate_period_in_secs;
+ let last_rewards_rate_period_start_in_secs = staking_rewards_config.last_rewards_rate_period_start_in_secs;
+ let rewards_rate_decrease_rate = staking_rewards_config.rewards_rate_decrease_rate;
+ requires fixed_point64::spec_less_or_equal(
+ rewards_rate,
+ fixed_point64::spec_create_from_u128((1u128)));
+ requires fixed_point64::spec_less_or_equal(min_rewards_rate, rewards_rate);
+ requires rewards_rate_period_in_secs > 0;
+ // This enforces high-level requirement 4:
+ requires last_rewards_rate_period_start_in_secs <= timestamp::spec_now_seconds();
+ requires fixed_point64::spec_ceil(rewards_rate_decrease_rate) <= 1;
+}
+
+
+
+[move-book]: https://aptos.dev/move/book/SUMMARY
diff --git a/aptos-move/framework/aptos-framework/tests/compiler-v2-doc/staking_contract.md b/aptos-move/framework/aptos-framework/tests/compiler-v2-doc/staking_contract.md
new file mode 100644
index 0000000000000..e57893b01b4d1
--- /dev/null
+++ b/aptos-move/framework/aptos-framework/tests/compiler-v2-doc/staking_contract.md
@@ -0,0 +1,3581 @@
+
+
+
+# Module `0x1::staking_contract`
+
+Allow stakers and operators to enter a staking contract with reward sharing.
+The main accounting logic in a staking contract consists of 2 parts:
+1. Tracks how much commission needs to be paid out to the operator. This is tracked with an increasing principal
+amount that's updated every time the operator requests commission, the staker withdraws funds, or the staker
+switches operators.
+2. Distributions of funds to operators (commissions) and stakers (stake withdrawals) use the shares model provided
+by the pool_u64 to track shares that increase in price as the stake pool accumulates rewards.
+
+Example flow:
+1. A staker creates a staking contract with an operator by calling create_staking_contract() with 100 coins of
+initial stake and commission = 10%. This means the operator will receive 10% of any accumulated rewards. A new stake
+pool will be created and hosted in a separate account that's controlled by the staking contract.
+2. The operator sets up a validator node and, once ready, joins the validator set by calling stake::join_validator_set
+3. After some time, the stake pool gains rewards and now has 150 coins.
+4. Operator can now call request_commission. 10% of (150 - 100) = 5 coins will be unlocked from the stake pool. The
+staker's principal is now updated from 100 to 145 (150 coins - 5 coins of commission). The pending distribution pool
+has 5 coins total and the operator owns all 5 shares of it.
+5. Some more time has passed. The pool now has 50 more coins in rewards and a total balance of 195. The operator
+calls request_commission again. Since the previous 5 coins have now become withdrawable, it'll be deposited into the
+operator's account first. Their new commission will be 10% of (195 coins - 145 principal) = 5 coins. Principal is
+updated to be 190 (195 - 5). Pending distribution pool has 5 coins and operator owns all 5 shares.
+6. Staker calls unlock_stake to unlock 50 coins of stake, which gets added to the pending distribution pool. Based
+on shares math, staker will be owning 50 shares and operator still owns 5 shares of the 55-coin pending distribution
+pool.
+7. Some time passes and the 55 coins become fully withdrawable from the stake pool. Due to accumulated rewards, the
+55 coins become 70 coins. Calling distribute() distributes 6 coins to the operator and 64 coins to the validator.
+
+
+- [Struct `StakingGroupContainer`](#0x1_staking_contract_StakingGroupContainer)
+- [Struct `StakingContract`](#0x1_staking_contract_StakingContract)
+- [Resource `Store`](#0x1_staking_contract_Store)
+- [Resource `BeneficiaryForOperator`](#0x1_staking_contract_BeneficiaryForOperator)
+- [Struct `UpdateCommissionEvent`](#0x1_staking_contract_UpdateCommissionEvent)
+- [Struct `UpdateCommission`](#0x1_staking_contract_UpdateCommission)
+- [Resource `StakingGroupUpdateCommissionEvent`](#0x1_staking_contract_StakingGroupUpdateCommissionEvent)
+- [Struct `CreateStakingContract`](#0x1_staking_contract_CreateStakingContract)
+- [Struct `UpdateVoter`](#0x1_staking_contract_UpdateVoter)
+- [Struct `ResetLockup`](#0x1_staking_contract_ResetLockup)
+- [Struct `AddStake`](#0x1_staking_contract_AddStake)
+- [Struct `RequestCommission`](#0x1_staking_contract_RequestCommission)
+- [Struct `UnlockStake`](#0x1_staking_contract_UnlockStake)
+- [Struct `SwitchOperator`](#0x1_staking_contract_SwitchOperator)
+- [Struct `AddDistribution`](#0x1_staking_contract_AddDistribution)
+- [Struct `Distribute`](#0x1_staking_contract_Distribute)
+- [Struct `SetBeneficiaryForOperator`](#0x1_staking_contract_SetBeneficiaryForOperator)
+- [Struct `CreateStakingContractEvent`](#0x1_staking_contract_CreateStakingContractEvent)
+- [Struct `UpdateVoterEvent`](#0x1_staking_contract_UpdateVoterEvent)
+- [Struct `ResetLockupEvent`](#0x1_staking_contract_ResetLockupEvent)
+- [Struct `AddStakeEvent`](#0x1_staking_contract_AddStakeEvent)
+- [Struct `RequestCommissionEvent`](#0x1_staking_contract_RequestCommissionEvent)
+- [Struct `UnlockStakeEvent`](#0x1_staking_contract_UnlockStakeEvent)
+- [Struct `SwitchOperatorEvent`](#0x1_staking_contract_SwitchOperatorEvent)
+- [Struct `AddDistributionEvent`](#0x1_staking_contract_AddDistributionEvent)
+- [Struct `DistributeEvent`](#0x1_staking_contract_DistributeEvent)
+- [Constants](#@Constants_0)
+- [Function `stake_pool_address`](#0x1_staking_contract_stake_pool_address)
+- [Function `last_recorded_principal`](#0x1_staking_contract_last_recorded_principal)
+- [Function `commission_percentage`](#0x1_staking_contract_commission_percentage)
+- [Function `staking_contract_amounts`](#0x1_staking_contract_staking_contract_amounts)
+- [Function `pending_distribution_counts`](#0x1_staking_contract_pending_distribution_counts)
+- [Function `staking_contract_exists`](#0x1_staking_contract_staking_contract_exists)
+- [Function `beneficiary_for_operator`](#0x1_staking_contract_beneficiary_for_operator)
+- [Function `get_expected_stake_pool_address`](#0x1_staking_contract_get_expected_stake_pool_address)
+- [Function `create_staking_contract`](#0x1_staking_contract_create_staking_contract)
+- [Function `create_staking_contract_with_coins`](#0x1_staking_contract_create_staking_contract_with_coins)
+- [Function `add_stake`](#0x1_staking_contract_add_stake)
+- [Function `update_voter`](#0x1_staking_contract_update_voter)
+- [Function `reset_lockup`](#0x1_staking_contract_reset_lockup)
+- [Function `update_commision`](#0x1_staking_contract_update_commision)
+- [Function `request_commission`](#0x1_staking_contract_request_commission)
+- [Function `request_commission_internal`](#0x1_staking_contract_request_commission_internal)
+- [Function `unlock_stake`](#0x1_staking_contract_unlock_stake)
+- [Function `unlock_rewards`](#0x1_staking_contract_unlock_rewards)
+- [Function `switch_operator_with_same_commission`](#0x1_staking_contract_switch_operator_with_same_commission)
+- [Function `switch_operator`](#0x1_staking_contract_switch_operator)
+- [Function `set_beneficiary_for_operator`](#0x1_staking_contract_set_beneficiary_for_operator)
+- [Function `distribute`](#0x1_staking_contract_distribute)
+- [Function `distribute_internal`](#0x1_staking_contract_distribute_internal)
+- [Function `assert_staking_contract_exists`](#0x1_staking_contract_assert_staking_contract_exists)
+- [Function `add_distribution`](#0x1_staking_contract_add_distribution)
+- [Function `get_staking_contract_amounts_internal`](#0x1_staking_contract_get_staking_contract_amounts_internal)
+- [Function `create_stake_pool`](#0x1_staking_contract_create_stake_pool)
+- [Function `update_distribution_pool`](#0x1_staking_contract_update_distribution_pool)
+- [Function `create_resource_account_seed`](#0x1_staking_contract_create_resource_account_seed)
+- [Function `new_staking_contracts_holder`](#0x1_staking_contract_new_staking_contracts_holder)
+- [Specification](#@Specification_1)
+ - [High-level Requirements](#high-level-req)
+ - [Module-level Specification](#module-level-spec)
+ - [Function `stake_pool_address`](#@Specification_1_stake_pool_address)
+ - [Function `last_recorded_principal`](#@Specification_1_last_recorded_principal)
+ - [Function `commission_percentage`](#@Specification_1_commission_percentage)
+ - [Function `staking_contract_amounts`](#@Specification_1_staking_contract_amounts)
+ - [Function `pending_distribution_counts`](#@Specification_1_pending_distribution_counts)
+ - [Function `staking_contract_exists`](#@Specification_1_staking_contract_exists)
+ - [Function `beneficiary_for_operator`](#@Specification_1_beneficiary_for_operator)
+ - [Function `create_staking_contract`](#@Specification_1_create_staking_contract)
+ - [Function `create_staking_contract_with_coins`](#@Specification_1_create_staking_contract_with_coins)
+ - [Function `add_stake`](#@Specification_1_add_stake)
+ - [Function `update_voter`](#@Specification_1_update_voter)
+ - [Function `reset_lockup`](#@Specification_1_reset_lockup)
+ - [Function `update_commision`](#@Specification_1_update_commision)
+ - [Function `request_commission`](#@Specification_1_request_commission)
+ - [Function `request_commission_internal`](#@Specification_1_request_commission_internal)
+ - [Function `unlock_stake`](#@Specification_1_unlock_stake)
+ - [Function `unlock_rewards`](#@Specification_1_unlock_rewards)
+ - [Function `switch_operator_with_same_commission`](#@Specification_1_switch_operator_with_same_commission)
+ - [Function `switch_operator`](#@Specification_1_switch_operator)
+ - [Function `set_beneficiary_for_operator`](#@Specification_1_set_beneficiary_for_operator)
+ - [Function `distribute`](#@Specification_1_distribute)
+ - [Function `distribute_internal`](#@Specification_1_distribute_internal)
+ - [Function `assert_staking_contract_exists`](#@Specification_1_assert_staking_contract_exists)
+ - [Function `add_distribution`](#@Specification_1_add_distribution)
+ - [Function `get_staking_contract_amounts_internal`](#@Specification_1_get_staking_contract_amounts_internal)
+ - [Function `create_stake_pool`](#@Specification_1_create_stake_pool)
+ - [Function `update_distribution_pool`](#@Specification_1_update_distribution_pool)
+ - [Function `new_staking_contracts_holder`](#@Specification_1_new_staking_contracts_holder)
+
+
+use 0x1::account;
+use 0x1::aptos_account;
+use 0x1::aptos_coin;
+use 0x1::bcs;
+use 0x1::coin;
+use 0x1::error;
+use 0x1::event;
+use 0x1::features;
+use 0x1::pool_u64;
+use 0x1::signer;
+use 0x1::simple_map;
+use 0x1::stake;
+use 0x1::staking_config;
+use 0x1::vector;
+
+
+
+
+
+
+## Struct `StakingGroupContainer`
+
+
+
+#[resource_group(#[scope = module_])]
+struct StakingGroupContainer
+
+
+
+
+dummy_field: bool
+struct StakingContract has store
+
+
+
+
+principal: u64
+pool_address: address
+owner_cap: stake::OwnerCapability
+commission_percentage: u64
+distribution_pool: pool_u64::Pool
+signer_cap: account::SignerCapability
+struct Store has key
+
+
+
+
+staking_contracts: simple_map::SimpleMap<address, staking_contract::StakingContract>
+create_staking_contract_events: event::EventHandle<staking_contract::CreateStakingContractEvent>
+update_voter_events: event::EventHandle<staking_contract::UpdateVoterEvent>
+reset_lockup_events: event::EventHandle<staking_contract::ResetLockupEvent>
+add_stake_events: event::EventHandle<staking_contract::AddStakeEvent>
+request_commission_events: event::EventHandle<staking_contract::RequestCommissionEvent>
+unlock_stake_events: event::EventHandle<staking_contract::UnlockStakeEvent>
+switch_operator_events: event::EventHandle<staking_contract::SwitchOperatorEvent>
+add_distribution_events: event::EventHandle<staking_contract::AddDistributionEvent>
+distribute_events: event::EventHandle<staking_contract::DistributeEvent>
+struct BeneficiaryForOperator has key
+
+
+
+
+beneficiary_for_operator: address
+struct UpdateCommissionEvent has drop, store
+
+
+
+
+staker: address
+operator: address
+old_commission_percentage: u64
+new_commission_percentage: u64
+#[event]
+struct UpdateCommission has drop, store
+
+
+
+
+staker: address
+operator: address
+old_commission_percentage: u64
+new_commission_percentage: u64
+#[resource_group_member(#[group = 0x1::staking_contract::StakingGroupContainer])]
+struct StakingGroupUpdateCommissionEvent has key
+
+
+
+
+update_commission_events: event::EventHandle<staking_contract::UpdateCommissionEvent>
+#[event]
+struct CreateStakingContract has drop, store
+
+
+
+
+operator: address
+voter: address
+pool_address: address
+principal: u64
+commission_percentage: u64
+#[event]
+struct UpdateVoter has drop, store
+
+
+
+
+operator: address
+pool_address: address
+old_voter: address
+new_voter: address
+#[event]
+struct ResetLockup has drop, store
+
+
+
+
+operator: address
+pool_address: address
+#[event]
+struct AddStake has drop, store
+
+
+
+
+operator: address
+pool_address: address
+amount: u64
+#[event]
+struct RequestCommission has drop, store
+
+
+
+
+operator: address
+pool_address: address
+accumulated_rewards: u64
+commission_amount: u64
+#[event]
+struct UnlockStake has drop, store
+
+
+
+
+operator: address
+pool_address: address
+amount: u64
+commission_paid: u64
+#[event]
+struct SwitchOperator has drop, store
+
+
+
+
+old_operator: address
+new_operator: address
+pool_address: address
+#[event]
+struct AddDistribution has drop, store
+
+
+
+
+operator: address
+pool_address: address
+amount: u64
+#[event]
+struct Distribute has drop, store
+
+
+
+
+operator: address
+pool_address: address
+recipient: address
+amount: u64
+#[event]
+struct SetBeneficiaryForOperator has drop, store
+
+
+
+
+operator: address
+old_beneficiary: address
+new_beneficiary: address
+struct CreateStakingContractEvent has drop, store
+
+
+
+
+operator: address
+voter: address
+pool_address: address
+principal: u64
+commission_percentage: u64
+struct UpdateVoterEvent has drop, store
+
+
+
+
+operator: address
+pool_address: address
+old_voter: address
+new_voter: address
+struct ResetLockupEvent has drop, store
+
+
+
+
+operator: address
+pool_address: address
+struct AddStakeEvent has drop, store
+
+
+
+
+operator: address
+pool_address: address
+amount: u64
+struct RequestCommissionEvent has drop, store
+
+
+
+
+operator: address
+pool_address: address
+accumulated_rewards: u64
+commission_amount: u64
+struct UnlockStakeEvent has drop, store
+
+
+
+
+operator: address
+pool_address: address
+amount: u64
+commission_paid: u64
+struct SwitchOperatorEvent has drop, store
+
+
+
+
+old_operator: address
+new_operator: address
+pool_address: address
+struct AddDistributionEvent has drop, store
+
+
+
+
+operator: address
+pool_address: address
+amount: u64
+struct DistributeEvent has drop, store
+
+
+
+
+operator: address
+pool_address: address
+recipient: address
+amount: u64
+const EINVALID_COMMISSION_PERCENTAGE: u64 = 2;
+
+
+
+
+
+
+Chaning beneficiaries for operators is not supported.
+
+
+const EOPERATOR_BENEFICIARY_CHANGE_NOT_SUPPORTED: u64 = 9;
+
+
+
+
+
+
+Staking contracts can't be merged.
+
+
+const ECANT_MERGE_STAKING_CONTRACTS: u64 = 5;
+
+
+
+
+
+
+Not enough active stake to withdraw. Some stake might still pending and will be active in the next epoch.
+
+
+const EINSUFFICIENT_ACTIVE_STAKE_TO_WITHDRAW: u64 = 7;
+
+
+
+
+
+
+Store amount must be at least the min stake required for a stake pool to join the validator set.
+
+
+const EINSUFFICIENT_STAKE_AMOUNT: u64 = 1;
+
+
+
+
+
+
+Caller must be either the staker, operator, or beneficiary.
+
+
+const ENOT_STAKER_OR_OPERATOR_OR_BENEFICIARY: u64 = 8;
+
+
+
+
+
+
+No staking contract between the staker and operator found.
+
+
+const ENO_STAKING_CONTRACT_FOUND_FOR_OPERATOR: u64 = 4;
+
+
+
+
+
+
+Staker has no staking contracts.
+
+
+const ENO_STAKING_CONTRACT_FOUND_FOR_STAKER: u64 = 3;
+
+
+
+
+
+
+The staking contract already exists and cannot be re-created.
+
+
+const ESTAKING_CONTRACT_ALREADY_EXISTS: u64 = 6;
+
+
+
+
+
+
+Maximum number of distributions a stake pool can support.
+
+
+const MAXIMUM_PENDING_DISTRIBUTIONS: u64 = 20;
+
+
+
+
+
+
+
+
+const SALT: vector<u8> = [97, 112, 116, 111, 115, 95, 102, 114, 97, 109, 101, 119, 111, 114, 107, 58, 58, 115, 116, 97, 107, 105, 110, 103, 95, 99, 111, 110, 116, 114, 97, 99, 116];
+
+
+
+
+
+
+## Function `stake_pool_address`
+
+Return the address of the underlying stake pool for the staking contract between the provided staker and
+operator.
+
+This errors out the staking contract with the provided staker and operator doesn't exist.
+
+
+#[view]
+public fun stake_pool_address(staker: address, operator: address): address
+
+
+
+
+public fun stake_pool_address(staker: address, operator: address): address acquires Store {
+ assert_staking_contract_exists(staker, operator);
+ let staking_contracts = &borrow_global<Store>(staker).staking_contracts;
+ simple_map::borrow(staking_contracts, &operator).pool_address
+}
+
+
+
+
+#[view]
+public fun last_recorded_principal(staker: address, operator: address): u64
+
+
+
+
+public fun last_recorded_principal(staker: address, operator: address): u64 acquires Store {
+ assert_staking_contract_exists(staker, operator);
+ let staking_contracts = &borrow_global<Store>(staker).staking_contracts;
+ simple_map::borrow(staking_contracts, &operator).principal
+}
+
+
+
+
+#[view]
+public fun commission_percentage(staker: address, operator: address): u64
+
+
+
+
+public fun commission_percentage(staker: address, operator: address): u64 acquires Store {
+ assert_staking_contract_exists(staker, operator);
+ let staking_contracts = &borrow_global<Store>(staker).staking_contracts;
+ simple_map::borrow(staking_contracts, &operator).commission_percentage
+}
+
+
+
+
+#[view]
+public fun staking_contract_amounts(staker: address, operator: address): (u64, u64, u64)
+
+
+
+
+public fun staking_contract_amounts(staker: address, operator: address): (u64, u64, u64) acquires Store {
+ assert_staking_contract_exists(staker, operator);
+ let staking_contracts = &borrow_global<Store>(staker).staking_contracts;
+ let staking_contract = simple_map::borrow(staking_contracts, &operator);
+ get_staking_contract_amounts_internal(staking_contract)
+}
+
+
+
+
+#[view]
+public fun pending_distribution_counts(staker: address, operator: address): u64
+
+
+
+
+public fun pending_distribution_counts(staker: address, operator: address): u64 acquires Store {
+ assert_staking_contract_exists(staker, operator);
+ let staking_contracts = &borrow_global<Store>(staker).staking_contracts;
+ pool_u64::shareholders_count(&simple_map::borrow(staking_contracts, &operator).distribution_pool)
+}
+
+
+
+
+#[view]
+public fun staking_contract_exists(staker: address, operator: address): bool
+
+
+
+
+public fun staking_contract_exists(staker: address, operator: address): bool acquires Store {
+ if (!exists<Store>(staker)) {
+ return false
+ };
+
+ let store = borrow_global<Store>(staker);
+ simple_map::contains_key(&store.staking_contracts, &operator)
+}
+
+
+
+
+#[view]
+public fun beneficiary_for_operator(operator: address): address
+
+
+
+
+public fun beneficiary_for_operator(operator: address): address acquires BeneficiaryForOperator {
+ if (exists<BeneficiaryForOperator>(operator)) {
+ return borrow_global<BeneficiaryForOperator>(operator).beneficiary_for_operator
+ } else {
+ operator
+ }
+}
+
+
+
+
+#[view]
+public fun get_expected_stake_pool_address(staker: address, operator: address, contract_creation_seed: vector<u8>): address
+
+
+
+
+public fun get_expected_stake_pool_address(
+ staker: address,
+ operator: address,
+ contract_creation_seed: vector<u8>,
+): address {
+ let seed = create_resource_account_seed(staker, operator, contract_creation_seed);
+ account::create_resource_address(&staker, seed)
+}
+
+
+
+
+public entry fun create_staking_contract(staker: &signer, operator: address, voter: address, amount: u64, commission_percentage: u64, contract_creation_seed: vector<u8>)
+
+
+
+
+public entry fun create_staking_contract(
+ staker: &signer,
+ operator: address,
+ voter: address,
+ amount: u64,
+ commission_percentage: u64,
+ // Optional seed used when creating the staking contract account.
+ contract_creation_seed: vector<u8>,
+) acquires Store {
+ let staked_coins = coin::withdraw<AptosCoin>(staker, amount);
+ create_staking_contract_with_coins(
+ staker, operator, voter, staked_coins, commission_percentage, contract_creation_seed);
+}
+
+
+
+
+public fun create_staking_contract_with_coins(staker: &signer, operator: address, voter: address, coins: coin::Coin<aptos_coin::AptosCoin>, commission_percentage: u64, contract_creation_seed: vector<u8>): address
+
+
+
+
+public fun create_staking_contract_with_coins(
+ staker: &signer,
+ operator: address,
+ voter: address,
+ coins: Coin<AptosCoin>,
+ commission_percentage: u64,
+ // Optional seed used when creating the staking contract account.
+ contract_creation_seed: vector<u8>,
+): address acquires Store {
+ assert!(
+ commission_percentage >= 0 && commission_percentage <= 100,
+ error::invalid_argument(EINVALID_COMMISSION_PERCENTAGE),
+ );
+ // The amount should be at least the min_stake_required, so the stake pool will be eligible to join the
+ // validator set.
+ let (min_stake_required, _) = staking_config::get_required_stake(&staking_config::get());
+ let principal = coin::value(&coins);
+ assert!(principal >= min_stake_required, error::invalid_argument(EINSUFFICIENT_STAKE_AMOUNT));
+
+ // Initialize Store resource if this is the first time the staker has delegated to anyone.
+ let staker_address = signer::address_of(staker);
+ if (!exists<Store>(staker_address)) {
+ move_to(staker, new_staking_contracts_holder(staker));
+ };
+
+ // Cannot create the staking contract if it already exists.
+ let store = borrow_global_mut<Store>(staker_address);
+ let staking_contracts = &mut store.staking_contracts;
+ assert!(
+ !simple_map::contains_key(staking_contracts, &operator),
+ error::already_exists(ESTAKING_CONTRACT_ALREADY_EXISTS)
+ );
+
+ // Initialize the stake pool in a new resource account. This allows the same staker to contract with multiple
+ // different operators.
+ let (stake_pool_signer, stake_pool_signer_cap, owner_cap) =
+ create_stake_pool(staker, operator, voter, contract_creation_seed);
+
+ // Add the stake to the stake pool.
+ stake::add_stake_with_cap(&owner_cap, coins);
+
+ // Create the contract record.
+ let pool_address = signer::address_of(&stake_pool_signer);
+ simple_map::add(staking_contracts, operator, StakingContract {
+ principal,
+ pool_address,
+ owner_cap,
+ commission_percentage,
+ // Make sure we don't have too many pending recipients in the distribution pool.
+ // Otherwise, a griefing attack is possible where the staker can keep switching operators and create too
+ // many pending distributions. This can lead to out-of-gas failure whenever distribute() is called.
+ distribution_pool: pool_u64::create(MAXIMUM_PENDING_DISTRIBUTIONS),
+ signer_cap: stake_pool_signer_cap,
+ });
+
+ if (std::features::module_event_migration_enabled()) {
+ emit(CreateStakingContract { operator, voter, pool_address, principal, commission_percentage });
+ };
+ emit_event(
+ &mut store.create_staking_contract_events,
+ CreateStakingContractEvent { operator, voter, pool_address, principal, commission_percentage },
+ );
+ pool_address
+}
+
+
+
+
+public entry fun add_stake(staker: &signer, operator: address, amount: u64)
+
+
+
+
+public entry fun add_stake(staker: &signer, operator: address, amount: u64) acquires Store {
+ let staker_address = signer::address_of(staker);
+ assert_staking_contract_exists(staker_address, operator);
+
+ let store = borrow_global_mut<Store>(staker_address);
+ let staking_contract = simple_map::borrow_mut(&mut store.staking_contracts, &operator);
+
+ // Add the stake to the stake pool.
+ let staked_coins = coin::withdraw<AptosCoin>(staker, amount);
+ stake::add_stake_with_cap(&staking_contract.owner_cap, staked_coins);
+
+ staking_contract.principal = staking_contract.principal + amount;
+ let pool_address = staking_contract.pool_address;
+ if (std::features::module_event_migration_enabled()) {
+ emit(AddStake { operator, pool_address, amount });
+ };
+ emit_event(
+ &mut store.add_stake_events,
+ AddStakeEvent { operator, pool_address, amount },
+ );
+}
+
+
+
+
+public entry fun update_voter(staker: &signer, operator: address, new_voter: address)
+
+
+
+
+public entry fun update_voter(staker: &signer, operator: address, new_voter: address) acquires Store {
+ let staker_address = signer::address_of(staker);
+ assert_staking_contract_exists(staker_address, operator);
+
+ let store = borrow_global_mut<Store>(staker_address);
+ let staking_contract = simple_map::borrow_mut(&mut store.staking_contracts, &operator);
+ let pool_address = staking_contract.pool_address;
+ let old_voter = stake::get_delegated_voter(pool_address);
+ stake::set_delegated_voter_with_cap(&staking_contract.owner_cap, new_voter);
+
+ if (std::features::module_event_migration_enabled()) {
+ emit(UpdateVoter { operator, pool_address, old_voter, new_voter });
+ };
+ emit_event(
+ &mut store.update_voter_events,
+ UpdateVoterEvent { operator, pool_address, old_voter, new_voter },
+ );
+
+}
+
+
+
+
+public entry fun reset_lockup(staker: &signer, operator: address)
+
+
+
+
+public entry fun reset_lockup(staker: &signer, operator: address) acquires Store {
+ let staker_address = signer::address_of(staker);
+ assert_staking_contract_exists(staker_address, operator);
+
+ let store = borrow_global_mut<Store>(staker_address);
+ let staking_contract = simple_map::borrow_mut(&mut store.staking_contracts, &operator);
+ let pool_address = staking_contract.pool_address;
+ stake::increase_lockup_with_cap(&staking_contract.owner_cap);
+
+ if (std::features::module_event_migration_enabled()) {
+ emit(ResetLockup { operator, pool_address });
+ };
+ emit_event(&mut store.reset_lockup_events, ResetLockupEvent { operator, pool_address });
+}
+
+
+
+
+public entry fun update_commision(staker: &signer, operator: address, new_commission_percentage: u64)
+
+
+
+
+public entry fun update_commision(
+ staker: &signer,
+ operator: address,
+ new_commission_percentage: u64
+) acquires Store, BeneficiaryForOperator, StakingGroupUpdateCommissionEvent {
+ assert!(
+ new_commission_percentage >= 0 && new_commission_percentage <= 100,
+ error::invalid_argument(EINVALID_COMMISSION_PERCENTAGE),
+ );
+
+ let staker_address = signer::address_of(staker);
+ assert!(exists<Store>(staker_address), error::not_found(ENO_STAKING_CONTRACT_FOUND_FOR_STAKER));
+
+ let store = borrow_global_mut<Store>(staker_address);
+ let staking_contract = simple_map::borrow_mut(&mut store.staking_contracts, &operator);
+ distribute_internal(staker_address, operator, staking_contract, &mut store.distribute_events);
+ request_commission_internal(
+ operator,
+ staking_contract,
+ &mut store.add_distribution_events,
+ &mut store.request_commission_events,
+ );
+ let old_commission_percentage = staking_contract.commission_percentage;
+ staking_contract.commission_percentage = new_commission_percentage;
+ if (!exists<StakingGroupUpdateCommissionEvent>(staker_address)) {
+ move_to(
+ staker,
+ StakingGroupUpdateCommissionEvent {
+ update_commission_events: account::new_event_handle<UpdateCommissionEvent>(
+ staker
+ )
+ }
+ )
+ };
+ if (std::features::module_event_migration_enabled()) {
+ emit(
+ UpdateCommission { staker: staker_address, operator, old_commission_percentage, new_commission_percentage }
+ );
+ };
+ emit_event(
+ &mut borrow_global_mut<StakingGroupUpdateCommissionEvent>(staker_address).update_commission_events,
+ UpdateCommissionEvent { staker: staker_address, operator, old_commission_percentage, new_commission_percentage }
+ );
+}
+
+
+
+
+public entry fun request_commission(account: &signer, staker: address, operator: address)
+
+
+
+
+public entry fun request_commission(
+ account: &signer,
+ staker: address,
+ operator: address
+) acquires Store, BeneficiaryForOperator {
+ let account_addr = signer::address_of(account);
+ assert!(
+ account_addr == staker || account_addr == operator || account_addr == beneficiary_for_operator(operator),
+ error::unauthenticated(ENOT_STAKER_OR_OPERATOR_OR_BENEFICIARY)
+ );
+ assert_staking_contract_exists(staker, operator);
+
+ let store = borrow_global_mut<Store>(staker);
+ let staking_contract = simple_map::borrow_mut(&mut store.staking_contracts, &operator);
+ // Short-circuit if zero commission.
+ if (staking_contract.commission_percentage == 0) {
+ return
+ };
+
+ // Force distribution of any already inactive stake.
+ distribute_internal(staker, operator, staking_contract, &mut store.distribute_events);
+
+ request_commission_internal(
+ operator,
+ staking_contract,
+ &mut store.add_distribution_events,
+ &mut store.request_commission_events,
+ );
+}
+
+
+
+
+fun request_commission_internal(operator: address, staking_contract: &mut staking_contract::StakingContract, add_distribution_events: &mut event::EventHandle<staking_contract::AddDistributionEvent>, request_commission_events: &mut event::EventHandle<staking_contract::RequestCommissionEvent>): u64
+
+
+
+
+fun request_commission_internal(
+ operator: address,
+ staking_contract: &mut StakingContract,
+ add_distribution_events: &mut EventHandle<AddDistributionEvent>,
+ request_commission_events: &mut EventHandle<RequestCommissionEvent>,
+): u64 {
+ // Unlock just the commission portion from the stake pool.
+ let (total_active_stake, accumulated_rewards, commission_amount) =
+ get_staking_contract_amounts_internal(staking_contract);
+ staking_contract.principal = total_active_stake - commission_amount;
+
+ // Short-circuit if there's no commission to pay.
+ if (commission_amount == 0) {
+ return 0
+ };
+
+ // Add a distribution for the operator.
+ add_distribution(operator, staking_contract, operator, commission_amount, add_distribution_events);
+
+ // Request to unlock the commission from the stake pool.
+ // This won't become fully unlocked until the stake pool's lockup expires.
+ stake::unlock_with_cap(commission_amount, &staking_contract.owner_cap);
+
+ let pool_address = staking_contract.pool_address;
+ if (std::features::module_event_migration_enabled()) {
+ emit(RequestCommission { operator, pool_address, accumulated_rewards, commission_amount });
+ };
+ emit_event(
+ request_commission_events,
+ RequestCommissionEvent { operator, pool_address, accumulated_rewards, commission_amount },
+ );
+
+ commission_amount
+}
+
+
+
+
+public entry fun unlock_stake(staker: &signer, operator: address, amount: u64)
+
+
+
+
+public entry fun unlock_stake(
+ staker: &signer,
+ operator: address,
+ amount: u64
+) acquires Store, BeneficiaryForOperator {
+ // Short-circuit if amount is 0.
+ if (amount == 0) return;
+
+ let staker_address = signer::address_of(staker);
+ assert_staking_contract_exists(staker_address, operator);
+
+ let store = borrow_global_mut<Store>(staker_address);
+ let staking_contract = simple_map::borrow_mut(&mut store.staking_contracts, &operator);
+
+ // Force distribution of any already inactive stake.
+ distribute_internal(staker_address, operator, staking_contract, &mut store.distribute_events);
+
+ // For simplicity, we request commission to be paid out first. This avoids having to ensure to staker doesn't
+ // withdraw into the commission portion.
+ let commission_paid = request_commission_internal(
+ operator,
+ staking_contract,
+ &mut store.add_distribution_events,
+ &mut store.request_commission_events,
+ );
+
+ // If there's less active stake remaining than the amount requested (potentially due to commission),
+ // only withdraw up to the active amount.
+ let (active, _, _, _) = stake::get_stake(staking_contract.pool_address);
+ if (active < amount) {
+ amount = active;
+ };
+ staking_contract.principal = staking_contract.principal - amount;
+
+ // Record a distribution for the staker.
+ add_distribution(operator, staking_contract, staker_address, amount, &mut store.add_distribution_events);
+
+ // Request to unlock the distribution amount from the stake pool.
+ // This won't become fully unlocked until the stake pool's lockup expires.
+ stake::unlock_with_cap(amount, &staking_contract.owner_cap);
+
+ let pool_address = staking_contract.pool_address;
+ if (std::features::module_event_migration_enabled()) {
+ emit(UnlockStake { pool_address, operator, amount, commission_paid });
+ };
+ emit_event(
+ &mut store.unlock_stake_events,
+ UnlockStakeEvent { pool_address, operator, amount, commission_paid },
+ );
+}
+
+
+
+
+public entry fun unlock_rewards(staker: &signer, operator: address)
+
+
+
+
+public entry fun unlock_rewards(staker: &signer, operator: address) acquires Store, BeneficiaryForOperator {
+ let staker_address = signer::address_of(staker);
+ assert_staking_contract_exists(staker_address, operator);
+
+ // Calculate how much rewards belongs to the staker after commission is paid.
+ let (_, accumulated_rewards, unpaid_commission) = staking_contract_amounts(staker_address, operator);
+ let staker_rewards = accumulated_rewards - unpaid_commission;
+ unlock_stake(staker, operator, staker_rewards);
+}
+
+
+
+
+public entry fun switch_operator_with_same_commission(staker: &signer, old_operator: address, new_operator: address)
+
+
+
+
+public entry fun switch_operator_with_same_commission(
+ staker: &signer,
+ old_operator: address,
+ new_operator: address,
+) acquires Store, BeneficiaryForOperator {
+ let staker_address = signer::address_of(staker);
+ assert_staking_contract_exists(staker_address, old_operator);
+
+ let commission_percentage = commission_percentage(staker_address, old_operator);
+ switch_operator(staker, old_operator, new_operator, commission_percentage);
+}
+
+
+
+
+public entry fun switch_operator(staker: &signer, old_operator: address, new_operator: address, new_commission_percentage: u64)
+
+
+
+
+public entry fun switch_operator(
+ staker: &signer,
+ old_operator: address,
+ new_operator: address,
+ new_commission_percentage: u64,
+) acquires Store, BeneficiaryForOperator {
+ let staker_address = signer::address_of(staker);
+ assert_staking_contract_exists(staker_address, old_operator);
+
+ // Merging two existing staking contracts is too complex as we'd need to merge two separate stake pools.
+ let store = borrow_global_mut<Store>(staker_address);
+ let staking_contracts = &mut store.staking_contracts;
+ assert!(
+ !simple_map::contains_key(staking_contracts, &new_operator),
+ error::invalid_state(ECANT_MERGE_STAKING_CONTRACTS),
+ );
+
+ let (_, staking_contract) = simple_map::remove(staking_contracts, &old_operator);
+ // Force distribution of any already inactive stake.
+ distribute_internal(staker_address, old_operator, &mut staking_contract, &mut store.distribute_events);
+
+ // For simplicity, we request commission to be paid out first. This avoids having to ensure to staker doesn't
+ // withdraw into the commission portion.
+ request_commission_internal(
+ old_operator,
+ &mut staking_contract,
+ &mut store.add_distribution_events,
+ &mut store.request_commission_events,
+ );
+
+ // Update the staking contract's commission rate and stake pool's operator.
+ stake::set_operator_with_cap(&staking_contract.owner_cap, new_operator);
+ staking_contract.commission_percentage = new_commission_percentage;
+
+ let pool_address = staking_contract.pool_address;
+ simple_map::add(staking_contracts, new_operator, staking_contract);
+ if (std::features::module_event_migration_enabled()) {
+ emit(SwitchOperator { pool_address, old_operator, new_operator });
+ };
+ emit_event(
+ &mut store.switch_operator_events,
+ SwitchOperatorEvent { pool_address, old_operator, new_operator }
+ );
+}
+
+
+
+
+distribute
before switching
+the beneficiary. An operator can set one beneficiary for staking contract pools, not a separate one for each pool.
+
+
+public entry fun set_beneficiary_for_operator(operator: &signer, new_beneficiary: address)
+
+
+
+
+public entry fun set_beneficiary_for_operator(
+ operator: &signer,
+ new_beneficiary: address
+) acquires BeneficiaryForOperator {
+ assert!(features::operator_beneficiary_change_enabled(), std::error::invalid_state(
+ EOPERATOR_BENEFICIARY_CHANGE_NOT_SUPPORTED
+ ));
+ // The beneficiay address of an operator is stored under the operator's address.
+ // So, the operator does not need to be validated with respect to a staking pool.
+ let operator_addr = signer::address_of(operator);
+ let old_beneficiary = beneficiary_for_operator(operator_addr);
+ if (exists<BeneficiaryForOperator>(operator_addr)) {
+ borrow_global_mut<BeneficiaryForOperator>(operator_addr).beneficiary_for_operator = new_beneficiary;
+ } else {
+ move_to(operator, BeneficiaryForOperator { beneficiary_for_operator: new_beneficiary });
+ };
+
+ emit(SetBeneficiaryForOperator {
+ operator: operator_addr,
+ old_beneficiary,
+ new_beneficiary,
+ });
+}
+
+
+
+
+public entry fun distribute(staker: address, operator: address)
+
+
+
+
+public entry fun distribute(staker: address, operator: address) acquires Store, BeneficiaryForOperator {
+ assert_staking_contract_exists(staker, operator);
+ let store = borrow_global_mut<Store>(staker);
+ let staking_contract = simple_map::borrow_mut(&mut store.staking_contracts, &operator);
+ distribute_internal(staker, operator, staking_contract, &mut store.distribute_events);
+}
+
+
+
+
+fun distribute_internal(staker: address, operator: address, staking_contract: &mut staking_contract::StakingContract, distribute_events: &mut event::EventHandle<staking_contract::DistributeEvent>)
+
+
+
+
+fun distribute_internal(
+ staker: address,
+ operator: address,
+ staking_contract: &mut StakingContract,
+ distribute_events: &mut EventHandle<DistributeEvent>,
+) acquires BeneficiaryForOperator {
+ let pool_address = staking_contract.pool_address;
+ let (_, inactive, _, pending_inactive) = stake::get_stake(pool_address);
+ let total_potential_withdrawable = inactive + pending_inactive;
+ let coins = stake::withdraw_with_cap(&staking_contract.owner_cap, total_potential_withdrawable);
+ let distribution_amount = coin::value(&coins);
+ if (distribution_amount == 0) {
+ coin::destroy_zero(coins);
+ return
+ };
+
+ let distribution_pool = &mut staking_contract.distribution_pool;
+ update_distribution_pool(
+ distribution_pool, distribution_amount, operator, staking_contract.commission_percentage);
+
+ // Buy all recipients out of the distribution pool.
+ while (pool_u64::shareholders_count(distribution_pool) > 0) {
+ let recipients = pool_u64::shareholders(distribution_pool);
+ let recipient = *vector::borrow(&mut recipients, 0);
+ let current_shares = pool_u64::shares(distribution_pool, recipient);
+ let amount_to_distribute = pool_u64::redeem_shares(distribution_pool, recipient, current_shares);
+ // If the recipient is the operator, send the commission to the beneficiary instead.
+ if (recipient == operator) {
+ recipient = beneficiary_for_operator(operator);
+ };
+ aptos_account::deposit_coins(recipient, coin::extract(&mut coins, amount_to_distribute));
+
+ if (std::features::module_event_migration_enabled()) {
+ emit(Distribute { operator, pool_address, recipient, amount: amount_to_distribute });
+ };
+ emit_event(
+ distribute_events,
+ DistributeEvent { operator, pool_address, recipient, amount: amount_to_distribute }
+ );
+ };
+
+ // In case there's any dust left, send them all to the staker.
+ if (coin::value(&coins) > 0) {
+ aptos_account::deposit_coins(staker, coins);
+ pool_u64::update_total_coins(distribution_pool, 0);
+ } else {
+ coin::destroy_zero(coins);
+ }
+}
+
+
+
+
+fun assert_staking_contract_exists(staker: address, operator: address)
+
+
+
+
+fun assert_staking_contract_exists(staker: address, operator: address) acquires Store {
+ assert!(exists<Store>(staker), error::not_found(ENO_STAKING_CONTRACT_FOUND_FOR_STAKER));
+ let staking_contracts = &mut borrow_global_mut<Store>(staker).staking_contracts;
+ assert!(
+ simple_map::contains_key(staking_contracts, &operator),
+ error::not_found(ENO_STAKING_CONTRACT_FOUND_FOR_OPERATOR),
+ );
+}
+
+
+
+
+recipient
and amount
to the staking contract's distributions list.
+
+
+fun add_distribution(operator: address, staking_contract: &mut staking_contract::StakingContract, recipient: address, coins_amount: u64, add_distribution_events: &mut event::EventHandle<staking_contract::AddDistributionEvent>)
+
+
+
+
+fun add_distribution(
+ operator: address,
+ staking_contract: &mut StakingContract,
+ recipient: address,
+ coins_amount: u64,
+ add_distribution_events: &mut EventHandle<AddDistributionEvent>
+) {
+ let distribution_pool = &mut staking_contract.distribution_pool;
+ let (_, _, _, total_distribution_amount) = stake::get_stake(staking_contract.pool_address);
+ update_distribution_pool(
+ distribution_pool, total_distribution_amount, operator, staking_contract.commission_percentage);
+
+ pool_u64::buy_in(distribution_pool, recipient, coins_amount);
+ let pool_address = staking_contract.pool_address;
+ if (std::features::module_event_migration_enabled()) {
+ emit(AddDistribution { operator, pool_address, amount: coins_amount });
+ };
+ emit_event(
+ add_distribution_events,
+ AddDistributionEvent { operator, pool_address, amount: coins_amount }
+ );
+}
+
+
+
+
+fun get_staking_contract_amounts_internal(staking_contract: &staking_contract::StakingContract): (u64, u64, u64)
+
+
+
+
+fun get_staking_contract_amounts_internal(staking_contract: &StakingContract): (u64, u64, u64) {
+ // Pending_inactive is not included in the calculation because pending_inactive can only come from:
+ // 1. Outgoing commissions. This means commission has already been extracted.
+ // 2. Stake withdrawals from stakers. This also means commission has already been extracted as
+ // request_commission_internal is called in unlock_stake
+ let (active, _, pending_active, _) = stake::get_stake(staking_contract.pool_address);
+ let total_active_stake = active + pending_active;
+ let accumulated_rewards = total_active_stake - staking_contract.principal;
+ let commission_amount = accumulated_rewards * staking_contract.commission_percentage / 100;
+
+ (total_active_stake, accumulated_rewards, commission_amount)
+}
+
+
+
+
+fun create_stake_pool(staker: &signer, operator: address, voter: address, contract_creation_seed: vector<u8>): (signer, account::SignerCapability, stake::OwnerCapability)
+
+
+
+
+fun create_stake_pool(
+ staker: &signer,
+ operator: address,
+ voter: address,
+ contract_creation_seed: vector<u8>,
+): (signer, SignerCapability, OwnerCapability) {
+ // Generate a seed that will be used to create the resource account that hosts the staking contract.
+ let seed = create_resource_account_seed(
+ signer::address_of(staker), operator, contract_creation_seed);
+
+ let (stake_pool_signer, stake_pool_signer_cap) = account::create_resource_account(staker, seed);
+ stake::initialize_stake_owner(&stake_pool_signer, 0, operator, voter);
+
+ // Extract owner_cap from the StakePool, so we have control over it in the staking_contracts flow.
+ // This is stored as part of the staking_contract. Thus, the staker would not have direct control over it without
+ // going through well-defined functions in this module.
+ let owner_cap = stake::extract_owner_cap(&stake_pool_signer);
+
+ (stake_pool_signer, stake_pool_signer_cap, owner_cap)
+}
+
+
+
+
+fun update_distribution_pool(distribution_pool: &mut pool_u64::Pool, updated_total_coins: u64, operator: address, commission_percentage: u64)
+
+
+
+
+fun update_distribution_pool(
+ distribution_pool: &mut Pool,
+ updated_total_coins: u64,
+ operator: address,
+ commission_percentage: u64,
+) {
+ // Short-circuit and do nothing if the pool's total value has not changed.
+ if (pool_u64::total_coins(distribution_pool) == updated_total_coins) {
+ return
+ };
+
+ // Charge all stakeholders (except for the operator themselves) commission on any rewards earnt relatively to the
+ // previous value of the distribution pool.
+ let shareholders = &pool_u64::shareholders(distribution_pool);
+ vector::for_each_ref(shareholders, |shareholder| {
+ let shareholder: address = *shareholder;
+ if (shareholder != operator) {
+ let shares = pool_u64::shares(distribution_pool, shareholder);
+ let previous_worth = pool_u64::balance(distribution_pool, shareholder);
+ let current_worth = pool_u64::shares_to_amount_with_total_coins(
+ distribution_pool, shares, updated_total_coins);
+ let unpaid_commission = (current_worth - previous_worth) * commission_percentage / 100;
+ // Transfer shares from current shareholder to the operator as payment.
+ // The value of the shares should use the updated pool's total value.
+ let shares_to_transfer = pool_u64::amount_to_shares_with_total_coins(
+ distribution_pool, unpaid_commission, updated_total_coins);
+ pool_u64::transfer_shares(distribution_pool, shareholder, operator, shares_to_transfer);
+ };
+ });
+
+ pool_u64::update_total_coins(distribution_pool, updated_total_coins);
+}
+
+
+
+
+fun create_resource_account_seed(staker: address, operator: address, contract_creation_seed: vector<u8>): vector<u8>
+
+
+
+
+fun create_resource_account_seed(
+ staker: address,
+ operator: address,
+ contract_creation_seed: vector<u8>,
+): vector<u8> {
+ let seed = bcs::to_bytes(&staker);
+ vector::append(&mut seed, bcs::to_bytes(&operator));
+ // Include a salt to avoid conflicts with any other modules out there that might also generate
+ // deterministic resource accounts for the same staker + operator addresses.
+ vector::append(&mut seed, SALT);
+ // Add an extra salt given by the staker in case an account with the same address has already been created.
+ vector::append(&mut seed, contract_creation_seed);
+ seed
+}
+
+
+
+
+fun new_staking_contracts_holder(staker: &signer): staking_contract::Store
+
+
+
+
+fun new_staking_contracts_holder(staker: &signer): Store {
+ Store {
+ staking_contracts: simple_map::create<address, StakingContract>(),
+ // Events.
+ create_staking_contract_events: account::new_event_handle<CreateStakingContractEvent>(staker),
+ update_voter_events: account::new_event_handle<UpdateVoterEvent>(staker),
+ reset_lockup_events: account::new_event_handle<ResetLockupEvent>(staker),
+ add_stake_events: account::new_event_handle<AddStakeEvent>(staker),
+ request_commission_events: account::new_event_handle<RequestCommissionEvent>(staker),
+ unlock_stake_events: account::new_event_handle<UnlockStakeEvent>(staker),
+ switch_operator_events: account::new_event_handle<SwitchOperatorEvent>(staker),
+ add_distribution_events: account::new_event_handle<AddDistributionEvent>(staker),
+ distribute_events: account::new_event_handle<DistributeEvent>(staker),
+ }
+}
+
+
+
+
+No. | Requirement | Criticality | Implementation | Enforcement | +
---|---|---|---|---|
1 | +The Store structure for the staker exists after the staking contract is created. | +Medium | +The create_staking_contract_with_coins function ensures that the staker account has a Store structure assigned. | +Formally verified via CreateStakingContractWithCoinsAbortsifAndEnsures. | +
2 | +A staking contract is created and stored in a mapping within the Store resource. | +High | +The create_staking_contract_with_coins function adds the newly created StakingContract to the staking_contracts map with the operator as a key of the Store resource, effectively storing the staking contract. | +Formally verified via CreateStakingContractWithCoinsAbortsifAndEnsures. | +
3 | +Adding stake to the stake pool increases the principal value of the pool, reflecting the additional stake amount. | +High | +The add_stake function transfers the specified amount of staked coins from the staker's account to the stake pool associated with the staking contract. It increases the principal value of the staking contract by the added stake amount. | +Formally verified via add_stake. | +
4 | +The staker may update the voter of a staking contract, enabling them to modify the assigned voter address and ensure it accurately reflects their desired choice. | +High | +The update_voter function ensures that the voter address in a staking contract may be updated by the staker, resulting in the modification of the delegated voter address in the associated stake pool to reflect the new address provided. | +Formally verified via update_voter. | +
5 | +Only the owner of the stake pool has the permission to reset the lockup period of the pool. | +Critical | +The reset_lockup function ensures that only the staker who owns the stake pool has the authority to reset the lockup period of the pool. | +Formally verified via reset_lockup. | +
6 | +Unlocked funds are correctly distributed to recipients based on their distribution shares, taking into account the associated commission percentage. | +High | +The distribution process, implemented in the distribute_internal function, accurately allocates unlocked funds to their intended recipients based on their distribution shares. It guarantees that each recipient receives the correct amount of funds, considering the commission percentage associated with the staking contract. | +Audited that the correct amount of unlocked funds is distributed according to distribution shares. | +
7 | +The stake pool ensures that the commission is correctly requested and paid out from the old operator's stake pool before allowing the switch to the new operator. | +High | +The switch_operator function initiates the commission payout from the stake pool associated with the old operator, ensuring a smooth transition. Paying out the commission before the switch guarantees that the staker receives the appropriate commission amount and maintains the integrity of the staking process. | +Audited that the commission is paid to the old operator. | +
8 | +Stakers can withdraw their funds from the staking contract, ensuring the unlocked amount becomes available for withdrawal after the lockup period. | +High | +The unlock_stake function ensures that the requested amount is properly unlocked from the stake pool, considering the lockup period and that the funds become available for withdrawal when the lockup expires. | +Audited that funds are unlocked properly. | +
pragma verify = true;
+pragma aborts_if_is_strict;
+
+
+
+
+
+
+### Function `stake_pool_address`
+
+
+#[view]
+public fun stake_pool_address(staker: address, operator: address): address
+
+
+
+
+
+include ContractExistsAbortsIf;
+let staking_contracts = global<Store>(staker).staking_contracts;
+ensures result == simple_map::spec_get(staking_contracts, operator).pool_address;
+
+
+
+
+
+
+### Function `last_recorded_principal`
+
+
+#[view]
+public fun last_recorded_principal(staker: address, operator: address): u64
+
+
+
+Staking_contract exists the stacker/operator pair.
+
+
+include ContractExistsAbortsIf;
+let staking_contracts = global<Store>(staker).staking_contracts;
+ensures result == simple_map::spec_get(staking_contracts, operator).principal;
+
+
+
+
+
+
+### Function `commission_percentage`
+
+
+#[view]
+public fun commission_percentage(staker: address, operator: address): u64
+
+
+
+Staking_contract exists the stacker/operator pair.
+
+
+include ContractExistsAbortsIf;
+let staking_contracts = global<Store>(staker).staking_contracts;
+ensures result == simple_map::spec_get(staking_contracts, operator).commission_percentage;
+
+
+
+
+
+
+### Function `staking_contract_amounts`
+
+
+#[view]
+public fun staking_contract_amounts(staker: address, operator: address): (u64, u64, u64)
+
+
+
+Staking_contract exists the stacker/operator pair.
+
+
+pragma verify_duration_estimate = 120;
+requires staking_contract.commission_percentage >= 0 && staking_contract.commission_percentage <= 100;
+let staking_contracts = global<Store>(staker).staking_contracts;
+let staking_contract = simple_map::spec_get(staking_contracts, operator);
+include ContractExistsAbortsIf;
+include GetStakingContractAmountsAbortsIf { staking_contract };
+let pool_address = staking_contract.pool_address;
+let stake_pool = global<stake::StakePool>(pool_address);
+let active = coin::value(stake_pool.active);
+let pending_active = coin::value(stake_pool.pending_active);
+let total_active_stake = active + pending_active;
+let accumulated_rewards = total_active_stake - staking_contract.principal;
+ensures result_1 == total_active_stake;
+ensures result_2 == accumulated_rewards;
+
+
+
+
+
+
+### Function `pending_distribution_counts`
+
+
+#[view]
+public fun pending_distribution_counts(staker: address, operator: address): u64
+
+
+
+Staking_contract exists the stacker/operator pair.
+
+
+include ContractExistsAbortsIf;
+let staking_contracts = global<Store>(staker).staking_contracts;
+let staking_contract = simple_map::spec_get(staking_contracts, operator);
+let shareholders_count = len(staking_contract.distribution_pool.shareholders);
+ensures result == shareholders_count;
+
+
+
+
+
+
+### Function `staking_contract_exists`
+
+
+#[view]
+public fun staking_contract_exists(staker: address, operator: address): bool
+
+
+
+
+
+aborts_if false;
+ensures result == spec_staking_contract_exists(staker, operator);
+
+
+
+
+
+
+
+
+fun spec_staking_contract_exists(staker: address, operator: address): bool {
+ if (!exists<Store>(staker)) {
+ false
+ } else {
+ let store = global<Store>(staker);
+ simple_map::spec_contains_key(store.staking_contracts, operator)
+ }
+}
+
+
+
+
+
+
+### Function `beneficiary_for_operator`
+
+
+#[view]
+public fun beneficiary_for_operator(operator: address): address
+
+
+
+
+
+pragma verify = false;
+
+
+
+
+
+
+### Function `create_staking_contract`
+
+
+public entry fun create_staking_contract(staker: &signer, operator: address, voter: address, amount: u64, commission_percentage: u64, contract_creation_seed: vector<u8>)
+
+
+
+Account is not frozen and sufficient to withdraw.
+
+
+pragma aborts_if_is_partial;
+pragma verify_duration_estimate = 120;
+include PreconditionsInCreateContract;
+include WithdrawAbortsIf<AptosCoin> { account: staker };
+include CreateStakingContractWithCoinsAbortsIfAndEnsures;
+
+
+
+
+
+
+### Function `create_staking_contract_with_coins`
+
+
+public fun create_staking_contract_with_coins(staker: &signer, operator: address, voter: address, coins: coin::Coin<aptos_coin::AptosCoin>, commission_percentage: u64, contract_creation_seed: vector<u8>): address
+
+
+
+The amount should be at least the min_stake_required, so the stake pool will be eligible to join the validator set.
+Initialize Store resource if this is the first time the staker has delegated to anyone.
+Cannot create the staking contract if it already exists.
+
+
+pragma verify_duration_estimate = 120;
+pragma aborts_if_is_partial;
+include PreconditionsInCreateContract;
+let amount = coins.value;
+include CreateStakingContractWithCoinsAbortsIfAndEnsures { amount };
+
+
+
+
+
+
+### Function `add_stake`
+
+
+public entry fun add_stake(staker: &signer, operator: address, amount: u64)
+
+
+
+Account is not frozen and sufficient to withdraw.
+Staking_contract exists the stacker/operator pair.
+
+
+pragma verify_duration_estimate = 600;
+include stake::ResourceRequirement;
+aborts_if reconfiguration_state::spec_is_in_progress();
+let staker_address = signer::address_of(staker);
+include ContractExistsAbortsIf { staker: staker_address };
+let store = global<Store>(staker_address);
+let staking_contract = simple_map::spec_get(store.staking_contracts, operator);
+include WithdrawAbortsIf<AptosCoin> { account: staker };
+let balance = global<coin::CoinStore<AptosCoin>>(staker_address).coin.value;
+let post post_coin = global<coin::CoinStore<AptosCoin>>(staker_address).coin.value;
+ensures post_coin == balance - amount;
+let owner_cap = staking_contract.owner_cap;
+include stake::AddStakeWithCapAbortsIfAndEnsures { owner_cap };
+let post post_store = global<Store>(staker_address);
+let post post_staking_contract = simple_map::spec_get(post_store.staking_contracts, operator);
+aborts_if staking_contract.principal + amount > MAX_U64;
+// This enforces high-level requirement 3:
+ensures post_staking_contract.principal == staking_contract.principal + amount;
+
+
+
+
+
+
+### Function `update_voter`
+
+
+public entry fun update_voter(staker: &signer, operator: address, new_voter: address)
+
+
+
+Staking_contract exists the stacker/operator pair.
+
+
+let staker_address = signer::address_of(staker);
+include UpdateVoterSchema { staker: staker_address };
+let post store = global<Store>(staker_address);
+let post staking_contract = simple_map::spec_get(store.staking_contracts, operator);
+let post pool_address = staking_contract.owner_cap.pool_address;
+let post new_delegated_voter = global<stake::StakePool>(pool_address).delegated_voter;
+ensures new_delegated_voter == new_voter;
+
+
+
+
+
+
+### Function `reset_lockup`
+
+
+public entry fun reset_lockup(staker: &signer, operator: address)
+
+
+
+Staking_contract exists the stacker/operator pair.
+Only active validator can update locked_until_secs.
+
+
+let staker_address = signer::address_of(staker);
+// This enforces high-level requirement 5:
+include ContractExistsAbortsIf { staker: staker_address };
+include IncreaseLockupWithCapAbortsIf { staker: staker_address };
+
+
+
+
+
+
+### Function `update_commision`
+
+
+public entry fun update_commision(staker: &signer, operator: address, new_commission_percentage: u64)
+
+
+
+
+
+pragma verify = false;
+let staker_address = signer::address_of(staker);
+aborts_if new_commission_percentage > 100;
+include ContractExistsAbortsIf { staker: staker_address };
+
+
+
+
+
+
+### Function `request_commission`
+
+
+public entry fun request_commission(account: &signer, staker: address, operator: address)
+
+
+
+Only staker or operator can call this.
+
+
+pragma verify = false;
+let account_addr = signer::address_of(account);
+include ContractExistsAbortsIf { staker };
+aborts_if account_addr != staker && account_addr != operator;
+
+
+
+
+
+
+### Function `request_commission_internal`
+
+
+fun request_commission_internal(operator: address, staking_contract: &mut staking_contract::StakingContract, add_distribution_events: &mut event::EventHandle<staking_contract::AddDistributionEvent>, request_commission_events: &mut event::EventHandle<staking_contract::RequestCommissionEvent>): u64
+
+
+
+
+
+pragma verify = false;
+include GetStakingContractAmountsAbortsIf;
+
+
+
+
+
+
+### Function `unlock_stake`
+
+
+public entry fun unlock_stake(staker: &signer, operator: address, amount: u64)
+
+
+
+
+
+pragma verify = false;
+requires amount > 0;
+let staker_address = signer::address_of(staker);
+include ContractExistsAbortsIf { staker: staker_address };
+
+
+
+
+
+
+### Function `unlock_rewards`
+
+
+public entry fun unlock_rewards(staker: &signer, operator: address)
+
+
+
+Staking_contract exists the stacker/operator pair.
+
+
+pragma verify = false;
+// This enforces high-level requirement 4:
+requires staking_contract.commission_percentage >= 0 && staking_contract.commission_percentage <= 100;
+let staker_address = signer::address_of(staker);
+let staking_contracts = global<Store>(staker_address).staking_contracts;
+let staking_contract = simple_map::spec_get(staking_contracts, operator);
+include ContractExistsAbortsIf { staker: staker_address };
+
+
+
+
+
+
+### Function `switch_operator_with_same_commission`
+
+
+public entry fun switch_operator_with_same_commission(staker: &signer, old_operator: address, new_operator: address)
+
+
+
+Staking_contract exists the stacker/operator pair.
+
+
+pragma verify_duration_estimate = 120;
+pragma aborts_if_is_partial;
+let staker_address = signer::address_of(staker);
+include ContractExistsAbortsIf { staker: staker_address, operator: old_operator };
+
+
+
+
+
+
+### Function `switch_operator`
+
+
+public entry fun switch_operator(staker: &signer, old_operator: address, new_operator: address, new_commission_percentage: u64)
+
+
+
+Staking_contract exists the stacker/operator pair.
+
+
+pragma verify = false;
+let staker_address = signer::address_of(staker);
+include ContractExistsAbortsIf { staker: staker_address, operator: old_operator };
+let store = global<Store>(staker_address);
+let staking_contracts = store.staking_contracts;
+aborts_if simple_map::spec_contains_key(staking_contracts, new_operator);
+
+
+
+
+
+
+### Function `set_beneficiary_for_operator`
+
+
+public entry fun set_beneficiary_for_operator(operator: &signer, new_beneficiary: address)
+
+
+
+
+
+pragma verify = false;
+
+
+
+
+
+
+### Function `distribute`
+
+
+public entry fun distribute(staker: address, operator: address)
+
+
+
+Staking_contract exists the stacker/operator pair.
+
+
+pragma verify_duration_estimate = 120;
+pragma aborts_if_is_partial;
+include ContractExistsAbortsIf;
+
+
+
+
+
+
+### Function `distribute_internal`
+
+
+fun distribute_internal(staker: address, operator: address, staking_contract: &mut staking_contract::StakingContract, distribute_events: &mut event::EventHandle<staking_contract::DistributeEvent>)
+
+
+
+The StakePool exists under the pool_address of StakingContract.
+The value of inactive and pending_inactive in the stake_pool is up to MAX_U64.
+
+
+pragma verify_duration_estimate = 120;
+pragma aborts_if_is_partial;
+let pool_address = staking_contract.pool_address;
+let stake_pool = borrow_global<stake::StakePool>(pool_address);
+aborts_if !exists<stake::StakePool>(pool_address);
+aborts_if stake_pool.inactive.value + stake_pool.pending_inactive.value > MAX_U64;
+aborts_if !exists<stake::StakePool>(staking_contract.owner_cap.pool_address);
+
+
+
+
+
+
+### Function `assert_staking_contract_exists`
+
+
+fun assert_staking_contract_exists(staker: address, operator: address)
+
+
+
+Staking_contract exists the stacker/operator pair.
+
+
+include ContractExistsAbortsIf;
+
+
+
+
+
+
+### Function `add_distribution`
+
+
+fun add_distribution(operator: address, staking_contract: &mut staking_contract::StakingContract, recipient: address, coins_amount: u64, add_distribution_events: &mut event::EventHandle<staking_contract::AddDistributionEvent>)
+
+
+
+
+
+pragma verify = false;
+
+
+
+
+
+
+### Function `get_staking_contract_amounts_internal`
+
+
+fun get_staking_contract_amounts_internal(staking_contract: &staking_contract::StakingContract): (u64, u64, u64)
+
+
+
+The StakePool exists under the pool_address of StakingContract.
+
+
+pragma verify_duration_estimate = 120;
+include GetStakingContractAmountsAbortsIf;
+let pool_address = staking_contract.pool_address;
+let stake_pool = global<stake::StakePool>(pool_address);
+let active = coin::value(stake_pool.active);
+let pending_active = coin::value(stake_pool.pending_active);
+let total_active_stake = active + pending_active;
+let accumulated_rewards = total_active_stake - staking_contract.principal;
+let commission_amount = accumulated_rewards * staking_contract.commission_percentage / 100;
+ensures result_1 == total_active_stake;
+ensures result_2 == accumulated_rewards;
+ensures result_3 == commission_amount;
+
+
+
+
+
+
+### Function `create_stake_pool`
+
+
+fun create_stake_pool(staker: &signer, operator: address, voter: address, contract_creation_seed: vector<u8>): (signer, account::SignerCapability, stake::OwnerCapability)
+
+
+
+
+
+pragma verify_duration_estimate = 120;
+include stake::ResourceRequirement;
+let staker_address = signer::address_of(staker);
+let seed_0 = bcs::to_bytes(staker_address);
+let seed_1 = concat(concat(concat(seed_0, bcs::to_bytes(operator)), SALT), contract_creation_seed);
+let resource_addr = account::spec_create_resource_address(staker_address, seed_1);
+include CreateStakePoolAbortsIf { resource_addr };
+ensures exists<account::Account>(resource_addr);
+let post post_account = global<account::Account>(resource_addr);
+ensures post_account.authentication_key == account::ZERO_AUTH_KEY;
+ensures post_account.signer_capability_offer.for == std::option::spec_some(resource_addr);
+ensures exists<stake::StakePool>(resource_addr);
+let post post_owner_cap = global<stake::OwnerCapability>(resource_addr);
+let post post_pool_address = post_owner_cap.pool_address;
+let post post_stake_pool = global<stake::StakePool>(post_pool_address);
+let post post_operator = post_stake_pool.operator_address;
+let post post_delegated_voter = post_stake_pool.delegated_voter;
+ensures resource_addr != operator ==> post_operator == operator;
+ensures resource_addr != voter ==> post_delegated_voter == voter;
+ensures signer::address_of(result_1) == resource_addr;
+ensures result_2 == SignerCapability { account: resource_addr };
+ensures result_3 == OwnerCapability { pool_address: resource_addr };
+
+
+
+
+
+
+### Function `update_distribution_pool`
+
+
+fun update_distribution_pool(distribution_pool: &mut pool_u64::Pool, updated_total_coins: u64, operator: address, commission_percentage: u64)
+
+
+
+
+
+pragma aborts_if_is_partial;
+
+
+
+
+
+
+### Function `new_staking_contracts_holder`
+
+
+fun new_staking_contracts_holder(staker: &signer): staking_contract::Store
+
+
+
+The Account exists under the staker.
+The guid_creation_num of the ccount resource is up to MAX_U64.
+
+
+include NewStakingContractsHolderAbortsIf;
+
+
+
+
+
+
+
+
+schema NewStakingContractsHolderAbortsIf {
+ staker: signer;
+ let addr = signer::address_of(staker);
+ let account = global<account::Account>(addr);
+ aborts_if !exists<account::Account>(addr);
+ aborts_if account.guid_creation_num + 9 >= account::MAX_GUID_CREATION_NUM;
+ aborts_if account.guid_creation_num + 9 > MAX_U64;
+}
+
+
+
+The Store exists under the staker.
+a staking_contract exists for the staker/operator pair.
+
+
+
+
+
+schema ContractExistsAbortsIf {
+ staker: address;
+ operator: address;
+ aborts_if !exists<Store>(staker);
+ let staking_contracts = global<Store>(staker).staking_contracts;
+ aborts_if !simple_map::spec_contains_key(staking_contracts, operator);
+}
+
+
+
+
+
+
+
+
+schema UpdateVoterSchema {
+ staker: address;
+ operator: address;
+ let store = global<Store>(staker);
+ let staking_contract = simple_map::spec_get(store.staking_contracts, operator);
+ let pool_address = staking_contract.pool_address;
+ aborts_if !exists<stake::StakePool>(pool_address);
+ aborts_if !exists<stake::StakePool>(staking_contract.owner_cap.pool_address);
+ include ContractExistsAbortsIf;
+}
+
+
+
+
+
+
+
+
+schema WithdrawAbortsIf<CoinType> {
+ account: signer;
+ amount: u64;
+ let account_addr = signer::address_of(account);
+ let coin_store = global<coin::CoinStore<CoinType>>(account_addr);
+ let balance = coin_store.coin.value;
+ aborts_if !exists<coin::CoinStore<CoinType>>(account_addr);
+ aborts_if coin_store.frozen;
+ aborts_if balance < amount;
+}
+
+
+
+
+
+
+
+
+schema GetStakingContractAmountsAbortsIf {
+ staking_contract: StakingContract;
+ let pool_address = staking_contract.pool_address;
+ let stake_pool = global<stake::StakePool>(pool_address);
+ let active = coin::value(stake_pool.active);
+ let pending_active = coin::value(stake_pool.pending_active);
+ let total_active_stake = active + pending_active;
+ let accumulated_rewards = total_active_stake - staking_contract.principal;
+ aborts_if !exists<stake::StakePool>(pool_address);
+ aborts_if active + pending_active > MAX_U64;
+ aborts_if total_active_stake < staking_contract.principal;
+ aborts_if accumulated_rewards * staking_contract.commission_percentage > MAX_U64;
+}
+
+
+
+
+
+
+
+
+schema IncreaseLockupWithCapAbortsIf {
+ staker: address;
+ operator: address;
+ let store = global<Store>(staker);
+ let staking_contract = simple_map::spec_get(store.staking_contracts, operator);
+ let pool_address = staking_contract.owner_cap.pool_address;
+ aborts_if !stake::stake_pool_exists(pool_address);
+ aborts_if !exists<staking_config::StakingConfig>(@aptos_framework);
+ let config = global<staking_config::StakingConfig>(@aptos_framework);
+ let stake_pool = global<stake::StakePool>(pool_address);
+ let old_locked_until_secs = stake_pool.locked_until_secs;
+ let seconds = global<timestamp::CurrentTimeMicroseconds>(
+ @aptos_framework
+ ).microseconds / timestamp::MICRO_CONVERSION_FACTOR;
+ let new_locked_until_secs = seconds + config.recurring_lockup_duration_secs;
+ aborts_if seconds + config.recurring_lockup_duration_secs > MAX_U64;
+ aborts_if old_locked_until_secs > new_locked_until_secs || old_locked_until_secs == new_locked_until_secs;
+ aborts_if !exists<timestamp::CurrentTimeMicroseconds>(@aptos_framework);
+ let post post_store = global<Store>(staker);
+ let post post_staking_contract = simple_map::spec_get(post_store.staking_contracts, operator);
+ let post post_stake_pool = global<stake::StakePool>(post_staking_contract.owner_cap.pool_address);
+ ensures post_stake_pool.locked_until_secs == new_locked_until_secs;
+}
+
+
+
+
+
+
+
+
+schema CreateStakingContractWithCoinsAbortsIfAndEnsures {
+ staker: signer;
+ operator: address;
+ voter: address;
+ amount: u64;
+ commission_percentage: u64;
+ contract_creation_seed: vector<u8>;
+ aborts_if commission_percentage > 100;
+ aborts_if !exists<staking_config::StakingConfig>(@aptos_framework);
+ let config = global<staking_config::StakingConfig>(@aptos_framework);
+ let min_stake_required = config.minimum_stake;
+ aborts_if amount < min_stake_required;
+ let staker_address = signer::address_of(staker);
+ let account = global<account::Account>(staker_address);
+ aborts_if !exists<Store>(staker_address) && !exists<account::Account>(staker_address);
+ aborts_if !exists<Store>(staker_address) && account.guid_creation_num + 9 >= account::MAX_GUID_CREATION_NUM;
+ // This enforces high-level requirement 1:
+ ensures exists<Store>(staker_address);
+ let store = global<Store>(staker_address);
+ let staking_contracts = store.staking_contracts;
+ let owner_cap = simple_map::spec_get(store.staking_contracts, operator).owner_cap;
+ let post post_store = global<Store>(staker_address);
+ let post post_staking_contracts = post_store.staking_contracts;
+}
+
+
+
+
+
+
+
+
+schema PreconditionsInCreateContract {
+ requires exists<stake::ValidatorPerformance>(@aptos_framework);
+ requires exists<stake::ValidatorSet>(@aptos_framework);
+ requires exists<staking_config::StakingRewardsConfig>(
+ @aptos_framework
+ ) || !std::features::spec_periodical_reward_rate_decrease_enabled();
+ requires exists<stake::ValidatorFees>(@aptos_framework);
+ requires exists<aptos_framework::timestamp::CurrentTimeMicroseconds>(@aptos_framework);
+ requires exists<stake::AptosCoinCapabilities>(@aptos_framework);
+}
+
+
+
+
+
+
+
+
+schema CreateStakePoolAbortsIf {
+ resource_addr: address;
+ operator: address;
+ voter: address;
+ contract_creation_seed: vector<u8>;
+ let acc = global<account::Account>(resource_addr);
+ aborts_if exists<account::Account>(resource_addr) && (len(
+ acc.signer_capability_offer.for.vec
+ ) != 0 || acc.sequence_number != 0);
+ aborts_if !exists<account::Account>(resource_addr) && len(bcs::to_bytes(resource_addr)) != 32;
+ aborts_if len(account::ZERO_AUTH_KEY) != 32;
+ aborts_if exists<stake::ValidatorConfig>(resource_addr);
+ let allowed = global<stake::AllowedValidators>(@aptos_framework);
+ aborts_if exists<stake::AllowedValidators>(@aptos_framework) && !contains(allowed.accounts, resource_addr);
+ aborts_if exists<stake::StakePool>(resource_addr);
+ aborts_if exists<stake::OwnerCapability>(resource_addr);
+ aborts_if exists<account::Account>(
+ resource_addr
+ ) && acc.guid_creation_num + 12 >= account::MAX_GUID_CREATION_NUM;
+}
+
+
+
+[move-book]: https://aptos.dev/move/book/SUMMARY
diff --git a/aptos-move/framework/aptos-framework/tests/compiler-v2-doc/staking_proxy.md b/aptos-move/framework/aptos-framework/tests/compiler-v2-doc/staking_proxy.md
new file mode 100644
index 0000000000000..17cd7ab0c01ff
--- /dev/null
+++ b/aptos-move/framework/aptos-framework/tests/compiler-v2-doc/staking_proxy.md
@@ -0,0 +1,566 @@
+
+
+
+# Module `0x1::staking_proxy`
+
+
+
+- [Function `set_operator`](#0x1_staking_proxy_set_operator)
+- [Function `set_voter`](#0x1_staking_proxy_set_voter)
+- [Function `set_vesting_contract_operator`](#0x1_staking_proxy_set_vesting_contract_operator)
+- [Function `set_staking_contract_operator`](#0x1_staking_proxy_set_staking_contract_operator)
+- [Function `set_stake_pool_operator`](#0x1_staking_proxy_set_stake_pool_operator)
+- [Function `set_vesting_contract_voter`](#0x1_staking_proxy_set_vesting_contract_voter)
+- [Function `set_staking_contract_voter`](#0x1_staking_proxy_set_staking_contract_voter)
+- [Function `set_stake_pool_voter`](#0x1_staking_proxy_set_stake_pool_voter)
+- [Specification](#@Specification_0)
+ - [High-level Requirements](#high-level-req)
+ - [Module-level Specification](#module-level-spec)
+ - [Function `set_operator`](#@Specification_0_set_operator)
+ - [Function `set_voter`](#@Specification_0_set_voter)
+ - [Function `set_vesting_contract_operator`](#@Specification_0_set_vesting_contract_operator)
+ - [Function `set_staking_contract_operator`](#@Specification_0_set_staking_contract_operator)
+ - [Function `set_stake_pool_operator`](#@Specification_0_set_stake_pool_operator)
+ - [Function `set_vesting_contract_voter`](#@Specification_0_set_vesting_contract_voter)
+ - [Function `set_staking_contract_voter`](#@Specification_0_set_staking_contract_voter)
+ - [Function `set_stake_pool_voter`](#@Specification_0_set_stake_pool_voter)
+
+
+use 0x1::signer;
+use 0x1::stake;
+use 0x1::staking_contract;
+use 0x1::vesting;
+
+
+
+
+
+
+## Function `set_operator`
+
+
+
+public entry fun set_operator(owner: &signer, old_operator: address, new_operator: address)
+
+
+
+
+public entry fun set_operator(owner: &signer, old_operator: address, new_operator: address) {
+ set_vesting_contract_operator(owner, old_operator, new_operator);
+ set_staking_contract_operator(owner, old_operator, new_operator);
+ set_stake_pool_operator(owner, new_operator);
+}
+
+
+
+
+public entry fun set_voter(owner: &signer, operator: address, new_voter: address)
+
+
+
+
+public entry fun set_voter(owner: &signer, operator: address, new_voter: address) {
+ set_vesting_contract_voter(owner, operator, new_voter);
+ set_staking_contract_voter(owner, operator, new_voter);
+ set_stake_pool_voter(owner, new_voter);
+}
+
+
+
+
+public entry fun set_vesting_contract_operator(owner: &signer, old_operator: address, new_operator: address)
+
+
+
+
+public entry fun set_vesting_contract_operator(owner: &signer, old_operator: address, new_operator: address) {
+ let owner_address = signer::address_of(owner);
+ let vesting_contracts = &vesting::vesting_contracts(owner_address);
+ vector::for_each_ref(vesting_contracts, |vesting_contract| {
+ let vesting_contract = *vesting_contract;
+ if (vesting::operator(vesting_contract) == old_operator) {
+ let current_commission_percentage = vesting::operator_commission_percentage(vesting_contract);
+ vesting::update_operator(owner, vesting_contract, new_operator, current_commission_percentage);
+ };
+ });
+}
+
+
+
+
+public entry fun set_staking_contract_operator(owner: &signer, old_operator: address, new_operator: address)
+
+
+
+
+public entry fun set_staking_contract_operator(owner: &signer, old_operator: address, new_operator: address) {
+ let owner_address = signer::address_of(owner);
+ if (staking_contract::staking_contract_exists(owner_address, old_operator)) {
+ let current_commission_percentage = staking_contract::commission_percentage(owner_address, old_operator);
+ staking_contract::switch_operator(owner, old_operator, new_operator, current_commission_percentage);
+ };
+}
+
+
+
+
+public entry fun set_stake_pool_operator(owner: &signer, new_operator: address)
+
+
+
+
+public entry fun set_stake_pool_operator(owner: &signer, new_operator: address) {
+ let owner_address = signer::address_of(owner);
+ if (stake::stake_pool_exists(owner_address)) {
+ stake::set_operator(owner, new_operator);
+ };
+}
+
+
+
+
+public entry fun set_vesting_contract_voter(owner: &signer, operator: address, new_voter: address)
+
+
+
+
+public entry fun set_vesting_contract_voter(owner: &signer, operator: address, new_voter: address) {
+ let owner_address = signer::address_of(owner);
+ let vesting_contracts = &vesting::vesting_contracts(owner_address);
+ vector::for_each_ref(vesting_contracts, |vesting_contract| {
+ let vesting_contract = *vesting_contract;
+ if (vesting::operator(vesting_contract) == operator) {
+ vesting::update_voter(owner, vesting_contract, new_voter);
+ };
+ });
+}
+
+
+
+
+public entry fun set_staking_contract_voter(owner: &signer, operator: address, new_voter: address)
+
+
+
+
+public entry fun set_staking_contract_voter(owner: &signer, operator: address, new_voter: address) {
+ let owner_address = signer::address_of(owner);
+ if (staking_contract::staking_contract_exists(owner_address, operator)) {
+ staking_contract::update_voter(owner, operator, new_voter);
+ };
+}
+
+
+
+
+public entry fun set_stake_pool_voter(owner: &signer, new_voter: address)
+
+
+
+
+public entry fun set_stake_pool_voter(owner: &signer, new_voter: address) {
+ if (stake::stake_pool_exists(signer::address_of(owner))) {
+ stake::set_delegated_voter(owner, new_voter);
+ };
+}
+
+
+
+
+No. | Requirement | Criticality | Implementation | Enforcement | +
---|---|---|---|---|
1 | +When updating the Vesting operator, it should be updated throughout all depending units. | +Medium | +The VestingContract contains a StakingInfo object that has an operator field, and this operator is mapped to a StakingContract object that in turn encompasses a StakePool object where the operator matches. | +Audited that it ensures the two operator fields hold the new value after the update. | +
2 | +When updating the Vesting voter, it should be updated throughout all depending units. | +Medium | +The VestingContract contains a StakingInfo object that has an operator field, and this operator is mapped to a StakingContract object that in turn encompasses a StakePool object where the operator matches. | +Audited that it ensures the two operator fields hold the new value after the update. | +
3 | +The operator and voter of a Vesting Contract should only be updated by the owner of the contract. | +High | +The owner-operator-voter model, as defined in the documentation, grants distinct abilities to each role. Therefore, it's crucial to ensure that only the owner has the authority to modify the operator or voter, to prevent the compromise of the StakePool. | +Audited that it ensures the signer owns the AdminStore resource and that the operator or voter intended for the update actually exists. | +
4 | +The operator and voter of a Staking Contract should only be updated by the owner of the contract. | +High | +The owner-operator-voter model, as defined in the documentation, grants distinct abilities to each role. Therefore, it's crucial to ensure that only the owner has the authority to modify the operator or voter, to prevent the compromise of the StakePool. | +Audited the patterns of updating operators and voters in the staking contract. | +
5 | +Staking Contract's operators should be unique inside a store. | +Medium | +Duplicates among operators could result in incorrectly updating the operator or voter associated with the incorrect StakingContract. | +Enforced via SimpleMap. | +
pragma verify = true;
+pragma aborts_if_is_strict;
+
+
+
+
+
+
+### Function `set_operator`
+
+
+public entry fun set_operator(owner: &signer, old_operator: address, new_operator: address)
+
+
+
+Aborts if conditions of SetStakePoolOperator are not met
+
+
+pragma verify = false;
+pragma aborts_if_is_partial;
+include SetStakePoolOperator;
+include SetStakingContractOperator;
+
+
+
+
+
+
+### Function `set_voter`
+
+
+public entry fun set_voter(owner: &signer, operator: address, new_voter: address)
+
+
+
+Aborts if conditions of SetStackingContractVoter and SetStackPoolVoterAbortsIf are not met
+
+
+pragma aborts_if_is_partial;
+include SetStakingContractVoter;
+include SetStakePoolVoterAbortsIf;
+
+
+
+
+
+
+### Function `set_vesting_contract_operator`
+
+
+public entry fun set_vesting_contract_operator(owner: &signer, old_operator: address, new_operator: address)
+
+
+
+
+
+pragma verify = false;
+
+
+
+
+
+
+### Function `set_staking_contract_operator`
+
+
+public entry fun set_staking_contract_operator(owner: &signer, old_operator: address, new_operator: address)
+
+
+
+
+
+pragma aborts_if_is_partial;
+pragma verify = false;
+include SetStakingContractOperator;
+
+
+
+
+
+
+
+
+schema SetStakingContractOperator {
+ owner: signer;
+ old_operator: address;
+ new_operator: address;
+ let owner_address = signer::address_of(owner);
+ let store = global<Store>(owner_address);
+ let staking_contract_exists = exists<Store>(owner_address) && simple_map::spec_contains_key(store.staking_contracts, old_operator);
+ aborts_if staking_contract_exists && simple_map::spec_contains_key(store.staking_contracts, new_operator);
+ let post post_store = global<Store>(owner_address);
+ ensures staking_contract_exists ==> !simple_map::spec_contains_key(post_store.staking_contracts, old_operator);
+ let staking_contract = simple_map::spec_get(store.staking_contracts, old_operator);
+ let stake_pool = global<stake::StakePool>(staking_contract.pool_address);
+ let active = coin::value(stake_pool.active);
+ let pending_active = coin::value(stake_pool.pending_active);
+ let total_active_stake = active + pending_active;
+ let accumulated_rewards = total_active_stake - staking_contract.principal;
+ let commission_amount = accumulated_rewards * staking_contract.commission_percentage / 100;
+ aborts_if staking_contract_exists && !exists<stake::StakePool>(staking_contract.pool_address);
+ ensures staking_contract_exists ==>
+ simple_map::spec_get(post_store.staking_contracts, new_operator).principal == total_active_stake - commission_amount;
+ let pool_address = staking_contract.owner_cap.pool_address;
+ let current_commission_percentage = staking_contract.commission_percentage;
+ aborts_if staking_contract_exists && commission_amount != 0 && !exists<stake::StakePool>(pool_address);
+ ensures staking_contract_exists && commission_amount != 0 ==>
+ global<stake::StakePool>(pool_address).operator_address == new_operator
+ && simple_map::spec_get(post_store.staking_contracts, new_operator).commission_percentage == current_commission_percentage;
+ ensures staking_contract_exists ==> simple_map::spec_contains_key(post_store.staking_contracts, new_operator);
+}
+
+
+
+
+
+
+### Function `set_stake_pool_operator`
+
+
+public entry fun set_stake_pool_operator(owner: &signer, new_operator: address)
+
+
+
+Aborts if stake_pool is exists and when OwnerCapability or stake_pool_exists
+One of them are not exists
+
+
+include SetStakePoolOperator;
+
+
+
+
+
+
+
+
+schema SetStakePoolOperator {
+ owner: &signer;
+ new_operator: address;
+ let owner_address = signer::address_of(owner);
+ let ownership_cap = borrow_global<stake::OwnerCapability>(owner_address);
+ let pool_address = ownership_cap.pool_address;
+ aborts_if stake::stake_pool_exists(owner_address) && !(exists<stake::OwnerCapability>(owner_address) && stake::stake_pool_exists(pool_address));
+ ensures stake::stake_pool_exists(owner_address) ==> global<stake::StakePool>(pool_address).operator_address == new_operator;
+}
+
+
+
+
+
+
+### Function `set_vesting_contract_voter`
+
+
+public entry fun set_vesting_contract_voter(owner: &signer, operator: address, new_voter: address)
+
+
+
+
+
+pragma verify = false;
+
+
+
+
+
+
+### Function `set_staking_contract_voter`
+
+
+public entry fun set_staking_contract_voter(owner: &signer, operator: address, new_voter: address)
+
+
+
+
+
+include SetStakingContractVoter;
+
+
+
+Make sure staking_contract_exists first
+Then abort if the resource is not exist
+
+
+
+
+
+schema SetStakingContractVoter {
+ owner: &signer;
+ operator: address;
+ new_voter: address;
+ let owner_address = signer::address_of(owner);
+ let staker = owner_address;
+ let store = global<Store>(staker);
+ let staking_contract_exists = exists<Store>(staker) && simple_map::spec_contains_key(store.staking_contracts, operator);
+ let staker_address = owner_address;
+ let staking_contract = simple_map::spec_get(store.staking_contracts, operator);
+ let pool_address = staking_contract.pool_address;
+ let pool_address1 = staking_contract.owner_cap.pool_address;
+ aborts_if staking_contract_exists && !exists<stake::StakePool>(pool_address);
+ aborts_if staking_contract_exists && !exists<stake::StakePool>(staking_contract.owner_cap.pool_address);
+ ensures staking_contract_exists ==> global<stake::StakePool>(pool_address1).delegated_voter == new_voter;
+}
+
+
+
+
+
+
+### Function `set_stake_pool_voter`
+
+
+public entry fun set_stake_pool_voter(owner: &signer, new_voter: address)
+
+
+
+
+
+include SetStakePoolVoterAbortsIf;
+
+
+
+
+
+
+
+
+schema SetStakePoolVoterAbortsIf {
+ owner: &signer;
+ new_voter: address;
+ let owner_address = signer::address_of(owner);
+ let ownership_cap = global<stake::OwnerCapability>(owner_address);
+ let pool_address = ownership_cap.pool_address;
+ aborts_if stake::stake_pool_exists(owner_address) && !(exists<stake::OwnerCapability>(owner_address) && stake::stake_pool_exists(pool_address));
+ ensures stake::stake_pool_exists(owner_address) ==> global<stake::StakePool>(pool_address).delegated_voter == new_voter;
+}
+
+
+
+[move-book]: https://aptos.dev/move/book/SUMMARY
diff --git a/aptos-move/framework/aptos-framework/tests/compiler-v2-doc/state_storage.md b/aptos-move/framework/aptos-framework/tests/compiler-v2-doc/state_storage.md
new file mode 100644
index 0000000000000..d73e08033129a
--- /dev/null
+++ b/aptos-move/framework/aptos-framework/tests/compiler-v2-doc/state_storage.md
@@ -0,0 +1,456 @@
+
+
+
+# Module `0x1::state_storage`
+
+
+
+- [Struct `Usage`](#0x1_state_storage_Usage)
+- [Resource `StateStorageUsage`](#0x1_state_storage_StateStorageUsage)
+- [Resource `GasParameter`](#0x1_state_storage_GasParameter)
+- [Constants](#@Constants_0)
+- [Function `initialize`](#0x1_state_storage_initialize)
+- [Function `on_new_block`](#0x1_state_storage_on_new_block)
+- [Function `current_items_and_bytes`](#0x1_state_storage_current_items_and_bytes)
+- [Function `get_state_storage_usage_only_at_epoch_beginning`](#0x1_state_storage_get_state_storage_usage_only_at_epoch_beginning)
+- [Function `on_reconfig`](#0x1_state_storage_on_reconfig)
+- [Specification](#@Specification_1)
+ - [High-level Requirements](#high-level-req)
+ - [Module-level Specification](#module-level-spec)
+ - [Function `initialize`](#@Specification_1_initialize)
+ - [Function `on_new_block`](#@Specification_1_on_new_block)
+ - [Function `current_items_and_bytes`](#@Specification_1_current_items_and_bytes)
+ - [Function `get_state_storage_usage_only_at_epoch_beginning`](#@Specification_1_get_state_storage_usage_only_at_epoch_beginning)
+ - [Function `on_reconfig`](#@Specification_1_on_reconfig)
+
+
+use 0x1::error;
+use 0x1::system_addresses;
+
+
+
+
+
+
+## Struct `Usage`
+
+
+
+struct Usage has copy, drop, store
+
+
+
+
+items: u64
+bytes: u64
+struct StateStorageUsage has store, key
+
+
+
+
+epoch: u64
+usage: state_storage::Usage
+struct GasParameter has store, key
+
+
+
+
+usage: state_storage::Usage
+const ESTATE_STORAGE_USAGE: u64 = 0;
+
+
+
+
+
+
+## Function `initialize`
+
+
+
+public(friend) fun initialize(aptos_framework: &signer)
+
+
+
+
+public(friend) fun initialize(aptos_framework: &signer) {
+ system_addresses::assert_aptos_framework(aptos_framework);
+ assert!(
+ !exists<StateStorageUsage>(@aptos_framework),
+ error::already_exists(ESTATE_STORAGE_USAGE)
+ );
+ move_to(aptos_framework, StateStorageUsage {
+ epoch: 0,
+ usage: Usage {
+ items: 0,
+ bytes: 0,
+ }
+ });
+}
+
+
+
+
+public(friend) fun on_new_block(epoch: u64)
+
+
+
+
+public(friend) fun on_new_block(epoch: u64) acquires StateStorageUsage {
+ assert!(
+ exists<StateStorageUsage>(@aptos_framework),
+ error::not_found(ESTATE_STORAGE_USAGE)
+ );
+ let usage = borrow_global_mut<StateStorageUsage>(@aptos_framework);
+ if (epoch != usage.epoch) {
+ usage.epoch = epoch;
+ usage.usage = get_state_storage_usage_only_at_epoch_beginning();
+ }
+}
+
+
+
+
+public(friend) fun current_items_and_bytes(): (u64, u64)
+
+
+
+
+public(friend) fun current_items_and_bytes(): (u64, u64) acquires StateStorageUsage {
+ assert!(
+ exists<StateStorageUsage>(@aptos_framework),
+ error::not_found(ESTATE_STORAGE_USAGE)
+ );
+ let usage = borrow_global<StateStorageUsage>(@aptos_framework);
+ (usage.usage.items, usage.usage.bytes)
+}
+
+
+
+
+fun get_state_storage_usage_only_at_epoch_beginning(): state_storage::Usage
+
+
+
+
+native fun get_state_storage_usage_only_at_epoch_beginning(): Usage;
+
+
+
+
+public(friend) fun on_reconfig()
+
+
+
+
+public(friend) fun on_reconfig() {
+ abort 0
+}
+
+
+
+
+No. | Requirement | Criticality | Implementation | Enforcement | +
---|---|---|---|---|
1 | +Given the blockchain is in an operating state, the resources for tracking state storage usage and gas parameters must exist for the Aptos framework address. | +Critical | +The initialize function ensures only the Aptos framework address can call it. | +Formally verified via module. | +
2 | +During the initialization of the module, it is guaranteed that the resource for tracking state storage usage will be moved under the Aptos framework account with default initial values. | +Medium | +The resource for tracking state storage usage may only be initialized with specific values and published under the aptos_framework account. | +Formally verified via initialize. | +
3 | +The initialization function is only called once, during genesis. | +Medium | +The initialize function ensures StateStorageUsage does not already exist. | +Formally verified via initialize. | +
4 | +During the initialization of the module, it is guaranteed that the resource for tracking state storage usage will be moved under the Aptos framework account with default initial values. | +Medium | +The resource for tracking state storage usage may only be initialized with specific values and published under the aptos_framework account. | +Formally verified via initialize. | +
5 | +The structure for tracking state storage usage should exist for it to be updated at the beginning of each new block and for retrieving the values of structure members. | +Medium | +The functions on_new_block and current_items_and_bytes verify that the StateStorageUsage structure exists before performing any further operations. | +Formally Verified via current_items_and_bytes, on_new_block, and the global invariant. | +
pragma verify = true;
+pragma aborts_if_is_strict;
+// This enforces high-level requirement 1 and high-level requirement 5:
+invariant [suspendable] chain_status::is_operating() ==> exists<StateStorageUsage>(@aptos_framework);
+invariant [suspendable] chain_status::is_operating() ==> exists<GasParameter>(@aptos_framework);
+
+
+
+
+
+
+### Function `initialize`
+
+
+public(friend) fun initialize(aptos_framework: &signer)
+
+
+
+ensure caller is admin.
+aborts if StateStorageUsage already exists.
+
+
+let addr = signer::address_of(aptos_framework);
+// This enforces high-level requirement 4:
+aborts_if !system_addresses::is_aptos_framework_address(addr);
+// This enforces high-level requirement 3:
+aborts_if exists<StateStorageUsage>(@aptos_framework);
+ensures exists<StateStorageUsage>(@aptos_framework);
+let post state_usage = global<StateStorageUsage>(@aptos_framework);
+// This enforces high-level requirement 2:
+ensures state_usage.epoch == 0 && state_usage.usage.bytes == 0 && state_usage.usage.items == 0;
+
+
+
+
+
+
+### Function `on_new_block`
+
+
+public(friend) fun on_new_block(epoch: u64)
+
+
+
+
+
+// This enforces high-level requirement 5:
+requires chain_status::is_operating();
+aborts_if false;
+ensures epoch == global<StateStorageUsage>(@aptos_framework).epoch;
+
+
+
+
+
+
+### Function `current_items_and_bytes`
+
+
+public(friend) fun current_items_and_bytes(): (u64, u64)
+
+
+
+
+
+// This enforces high-level requirement 5:
+aborts_if !exists<StateStorageUsage>(@aptos_framework);
+
+
+
+
+
+
+### Function `get_state_storage_usage_only_at_epoch_beginning`
+
+
+fun get_state_storage_usage_only_at_epoch_beginning(): state_storage::Usage
+
+
+
+
+
+pragma opaque;
+
+
+
+
+
+
+### Function `on_reconfig`
+
+
+public(friend) fun on_reconfig()
+
+
+
+
+
+aborts_if true;
+
+
+
+[move-book]: https://aptos.dev/move/book/SUMMARY
diff --git a/aptos-move/framework/aptos-framework/tests/compiler-v2-doc/storage_gas.md b/aptos-move/framework/aptos-framework/tests/compiler-v2-doc/storage_gas.md
new file mode 100644
index 0000000000000..1e17497c79d00
--- /dev/null
+++ b/aptos-move/framework/aptos-framework/tests/compiler-v2-doc/storage_gas.md
@@ -0,0 +1,1636 @@
+
+
+
+# Module `0x1::storage_gas`
+
+Gas parameters for global storage.
+
+
+
+
+## General overview sections
+
+
+[Definitions](#definitions)
+
+* [Utilization dimensions](#utilization-dimensions)
+* [Utilization ratios](#utilization-ratios)
+* [Gas curve lookup](#gas-curve-lookup)
+* [Item-wise operations](#item-wise-operations)
+* [Byte-wise operations](#byte-wise-operations)
+
+[Function dependencies](#function-dependencies)
+
+* [Initialization](#initialization)
+* [Reconfiguration](#reconfiguration)
+* [Setting configurations](#setting-configurations)
+
+
+
+
+## Definitions
+
+
+
+
+
+### Utilization dimensions
+
+
+Global storage gas fluctuates each epoch based on total utilization,
+which is defined across two dimensions:
+
+1. The number of "items" in global storage.
+2. The number of bytes in global storage.
+
+"Items" include:
+
+1. Resources having the key
attribute, which have been moved into
+global storage via a move_to()
operation.
+2. Table entries.
+
+
+
+
+### Utilization ratios
+
+
+initialize()
sets an arbitrary "target" utilization for both
+item-wise and byte-wise storage, then each epoch, gas parameters are
+reconfigured based on the "utilization ratio" for each of the two
+utilization dimensions. The utilization ratio for a given dimension,
+either item-wise or byte-wise, is taken as the quotient of actual
+utilization and target utilization. For example, given a 500 GB
+target and 250 GB actual utilization, the byte-wise utilization
+ratio is 50%.
+
+See base_8192_exponential_curve()
for mathematical definitions.
+
+
+
+
+### Gas curve lookup
+
+
+The utilization ratio in a given epoch is used as a lookup value in
+a Eulerian approximation to an exponential curve, known as a
+GasCurve
, which is defined in base_8192_exponential_curve()
,
+based on a minimum gas charge and a maximum gas charge.
+
+The minimum gas charge and maximum gas charge at the endpoints of
+the curve are set in initialize()
, and correspond to the following
+operations defined in StorageGas
:
+
+1. Per-item read
+2. Per-item create
+3. Per-item write
+4. Per-byte read
+5. Per-byte create
+6. Per-byte write
+
+For example, if the byte-wise utilization ratio is 50%, then
+per-byte reads will charge the minimum per-byte gas cost, plus
+1.09% of the difference between the maximum and the minimum cost.
+See base_8192_exponential_curve()
for a supporting calculation.
+
+
+
+
+### Item-wise operations
+
+
+1. Per-item read gas is assessed whenever an item is read from
+global storage via borrow_global<T>()
or via a table entry read
+operation.
+2. Per-item create gas is assessed whenever an item is created in
+global storage via move_to<T>()
or via a table entry creation
+operation.
+3. Per-item write gas is assessed whenever an item is overwritten in
+global storage via borrow_global_mut<T>
or via a table entry
+mutation operation.
+
+
+
+
+### Byte-wise operations
+
+
+Byte-wise operations are assessed in a manner similar to per-item
+operations, but account for the number of bytes affected by the
+given operation. Notably, this number denotes the total number of
+bytes in an *entire item*.
+
+For example, if an operation mutates a u8
field in a resource that
+has 5 other u128
fields, the per-byte gas write cost will account
+for $(5 * 128) / 8 + 1 = 81$ bytes. Vectors are similarly treated
+as fields.
+
+
+
+
+## Function dependencies
+
+
+The below dependency chart uses mermaid.js
syntax, which can be
+automatically rendered into a diagram (depending on the browser)
+when viewing the documentation file generated from source code. If
+a browser renders the diagrams with coloring that makes it difficult
+to read, try a different browser.
+
+
+
+
+### Initialization
+
+
+```mermaid
+
+flowchart LR
+
+initialize --> base_8192_exponential_curve
+base_8192_exponential_curve --> new_gas_curve
+base_8192_exponential_curve --> new_point
+new_gas_curve --> validate_points
+
+```
+
+
+
+
+### Reconfiguration
+
+
+```mermaid
+
+flowchart LR
+
+calculate_gas --> Interpolate %% capitalized
+calculate_read_gas --> calculate_gas
+calculate_create_gas --> calculate_gas
+calculate_write_gas --> calculate_gas
+on_reconfig --> calculate_read_gas
+on_reconfig --> calculate_create_gas
+on_reconfig --> calculate_write_gas
+reconfiguration::reconfigure --> on_reconfig
+
+```
+
+Here, the function interpolate()
is spelled Interpolate
because
+interpolate
is a reserved word in mermaid.js
.
+
+
+
+
+### Setting configurations
+
+
+```mermaid
+
+flowchart LR
+
+gas_schedule::set_storage_gas_config --> set_config
+
+```
+
+
+
+
+## Complete docgen index
+
+
+The below index is automatically generated from source code:
+
+
+- [General overview sections](#@General_overview_sections_0)
+- [Definitions](#@Definitions_1)
+ - [Utilization dimensions](#@Utilization_dimensions_2)
+ - [Utilization ratios](#@Utilization_ratios_3)
+ - [Gas curve lookup](#@Gas_curve_lookup_4)
+ - [Item-wise operations](#@Item-wise_operations_5)
+ - [Byte-wise operations](#@Byte-wise_operations_6)
+- [Function dependencies](#@Function_dependencies_7)
+ - [Initialization](#@Initialization_8)
+ - [Reconfiguration](#@Reconfiguration_9)
+ - [Setting configurations](#@Setting_configurations_10)
+- [Complete docgen index](#@Complete_docgen_index_11)
+- [Resource `StorageGas`](#0x1_storage_gas_StorageGas)
+- [Struct `Point`](#0x1_storage_gas_Point)
+- [Struct `UsageGasConfig`](#0x1_storage_gas_UsageGasConfig)
+- [Struct `GasCurve`](#0x1_storage_gas_GasCurve)
+- [Resource `StorageGasConfig`](#0x1_storage_gas_StorageGasConfig)
+- [Constants](#@Constants_12)
+- [Function `base_8192_exponential_curve`](#0x1_storage_gas_base_8192_exponential_curve)
+ - [Function definition](#@Function_definition_13)
+ - [Example](#@Example_14)
+ - [Utilization multipliers](#@Utilization_multipliers_15)
+- [Function `new_point`](#0x1_storage_gas_new_point)
+- [Function `new_gas_curve`](#0x1_storage_gas_new_gas_curve)
+- [Function `new_usage_gas_config`](#0x1_storage_gas_new_usage_gas_config)
+- [Function `new_storage_gas_config`](#0x1_storage_gas_new_storage_gas_config)
+- [Function `set_config`](#0x1_storage_gas_set_config)
+- [Function `initialize`](#0x1_storage_gas_initialize)
+- [Function `validate_points`](#0x1_storage_gas_validate_points)
+- [Function `calculate_gas`](#0x1_storage_gas_calculate_gas)
+- [Function `interpolate`](#0x1_storage_gas_interpolate)
+- [Function `calculate_read_gas`](#0x1_storage_gas_calculate_read_gas)
+- [Function `calculate_create_gas`](#0x1_storage_gas_calculate_create_gas)
+- [Function `calculate_write_gas`](#0x1_storage_gas_calculate_write_gas)
+- [Function `on_reconfig`](#0x1_storage_gas_on_reconfig)
+- [Specification](#@Specification_16)
+ - [Struct `Point`](#@Specification_16_Point)
+ - [Struct `UsageGasConfig`](#@Specification_16_UsageGasConfig)
+ - [High-level Requirements](#high-level-req)
+ - [Module-level Specification](#module-level-spec)
+ - [Struct `GasCurve`](#@Specification_16_GasCurve)
+ - [Function `base_8192_exponential_curve`](#@Specification_16_base_8192_exponential_curve)
+ - [Function `new_point`](#@Specification_16_new_point)
+ - [Function `new_gas_curve`](#@Specification_16_new_gas_curve)
+ - [Function `new_usage_gas_config`](#@Specification_16_new_usage_gas_config)
+ - [Function `new_storage_gas_config`](#@Specification_16_new_storage_gas_config)
+ - [Function `set_config`](#@Specification_16_set_config)
+ - [Function `initialize`](#@Specification_16_initialize)
+ - [Function `validate_points`](#@Specification_16_validate_points)
+ - [Function `calculate_gas`](#@Specification_16_calculate_gas)
+ - [Function `interpolate`](#@Specification_16_interpolate)
+ - [Function `on_reconfig`](#@Specification_16_on_reconfig)
+
+
+use 0x1::error;
+use 0x1::state_storage;
+use 0x1::system_addresses;
+
+
+
+
+
+
+## Resource `StorageGas`
+
+Storage parameters, reconfigured each epoch.
+
+Parameters are updated during reconfiguration via
+on_reconfig()
, based on storage utilization at the beginning
+of the epoch in which the reconfiguration transaction is
+executed. The gas schedule derived from these parameters will
+then be used to calculate gas for the entirety of the
+following epoch, such that the data is one epoch older than
+ideal. Notably, however, per this approach, the virtual machine
+does not need to reload gas parameters after the
+first transaction of an epoch.
+
+
+struct StorageGas has key
+
+
+
+
+per_item_read: u64
+per_item_create: u64
+per_item_write: u64
+per_byte_read: u64
+per_byte_create: u64
+per_byte_write: u64
+1
| 00.01 % |
+| 10
| 00.10 % |
+| 100
| 01.00 % |
+| 1000
| 10.00 % |
+
+
+struct Point has copy, drop, store
+
+
+
+
+x: u64
+base_8192_exponential_curve()
.
+y: u64
+base_8192_exponential_curve()
.
+StorageGasConfig
.
+
+
+struct UsageGasConfig has copy, drop, store
+
+
+
+
+target_usage: u64
+read_curve: storage_gas::GasCurve
+create_curve: storage_gas::GasCurve
+write_curve: storage_gas::GasCurve
+base_8192_exponential_curve()
.
+
+
+struct GasCurve has copy, drop, store
+
+
+
+
+min_gas: u64
+max_gas: u64
+points: vector<storage_gas::Point>
+struct StorageGasConfig has copy, drop, key
+
+
+
+
+item_config: storage_gas::UsageGasConfig
+byte_config: storage_gas::UsageGasConfig
+const MAX_U64: u64 = 18446744073709551615;
+
+
+
+
+
+
+
+
+const BASIS_POINT_DENOMINATION: u64 = 10000;
+
+
+
+
+
+
+
+
+const EINVALID_GAS_RANGE: u64 = 2;
+
+
+
+
+
+
+
+
+const EINVALID_MONOTONICALLY_NON_DECREASING_CURVE: u64 = 5;
+
+
+
+
+
+
+
+
+const EINVALID_POINT_RANGE: u64 = 6;
+
+
+
+
+
+
+
+
+const ESTORAGE_GAS: u64 = 1;
+
+
+
+
+
+
+
+
+const ESTORAGE_GAS_CONFIG: u64 = 0;
+
+
+
+
+
+
+
+
+const ETARGET_USAGE_TOO_BIG: u64 = 4;
+
+
+
+
+
+
+
+
+const EZERO_TARGET_USAGE: u64 = 3;
+
+
+
+
+
+
+## Function `base_8192_exponential_curve`
+
+Default exponential curve having base 8192.
+
+
+
+
+### Function definition
+
+
+Gas price as a function of utilization ratio is defined as:
+
+$$g(u_r) = g_{min} + \frac{(b^{u_r} - 1)}{b - 1} \Delta_g$$
+
+$$g(u_r) = g_{min} + u_m \Delta_g$$
+
+| Variable | Description |
+|-------------------------------------|------------------------|
+| $g_{min}$ | min_gas
|
+| $g_{max}$ | max_gas
|
+| $\Delta_{g} = g_{max} - g_{min}$ | Gas delta |
+| $u$ | Utilization |
+| $u_t$ | Target utilization |
+| $u_r = u / u_t$ | Utilization ratio |
+| $u_m = \frac{(b^{u_r} - 1)}{b - 1}$ | Utilization multiplier |
+| $b = 8192$ | Exponent base |
+
+
+
+
+### Example
+
+
+Hence for a utilization ratio of 50% ( $u_r = 0.5$ ):
+
+$$g(0.5) = g_{min} + \frac{8192^{0.5} - 1}{8192 - 1} \Delta_g$$
+
+$$g(0.5) \approx g_{min} + 0.0109 \Delta_g$$
+
+Which means that the price above min_gas
is approximately
+1.09% of the difference between max_gas
and min_gas
.
+
+
+
+
+### Utilization multipliers
+
+
+| $u_r$ | $u_m$ (approximate) |
+|-------|---------------------|
+| 10% | 0.02% |
+| 20% | 0.06% |
+| 30% | 0.17% |
+| 40% | 0.44% |
+| 50% | 1.09% |
+| 60% | 2.71% |
+| 70% | 6.69% |
+| 80% | 16.48% |
+| 90% | 40.61% |
+| 95% | 63.72% |
+| 99% | 91.38% |
+
+
+public fun base_8192_exponential_curve(min_gas: u64, max_gas: u64): storage_gas::GasCurve
+
+
+
+
+public fun base_8192_exponential_curve(min_gas: u64, max_gas: u64): GasCurve {
+ new_gas_curve(min_gas, max_gas,
+ vector[
+ new_point(1000, 2),
+ new_point(2000, 6),
+ new_point(3000, 17),
+ new_point(4000, 44),
+ new_point(5000, 109),
+ new_point(6000, 271),
+ new_point(7000, 669),
+ new_point(8000, 1648),
+ new_point(9000, 4061),
+ new_point(9500, 6372),
+ new_point(9900, 9138),
+ ]
+ )
+}
+
+
+
+
+public fun new_point(x: u64, y: u64): storage_gas::Point
+
+
+
+
+public fun new_point(x: u64, y: u64): Point {
+ assert!(
+ x <= BASIS_POINT_DENOMINATION && y <= BASIS_POINT_DENOMINATION,
+ error::invalid_argument(EINVALID_POINT_RANGE)
+ );
+ Point { x, y }
+}
+
+
+
+
+public fun new_gas_curve(min_gas: u64, max_gas: u64, points: vector<storage_gas::Point>): storage_gas::GasCurve
+
+
+
+
+public fun new_gas_curve(min_gas: u64, max_gas: u64, points: vector<Point>): GasCurve {
+ assert!(max_gas >= min_gas, error::invalid_argument(EINVALID_GAS_RANGE));
+ assert!(max_gas <= MAX_U64 / BASIS_POINT_DENOMINATION, error::invalid_argument(EINVALID_GAS_RANGE));
+ validate_points(&points);
+ GasCurve {
+ min_gas,
+ max_gas,
+ points
+ }
+}
+
+
+
+
+public fun new_usage_gas_config(target_usage: u64, read_curve: storage_gas::GasCurve, create_curve: storage_gas::GasCurve, write_curve: storage_gas::GasCurve): storage_gas::UsageGasConfig
+
+
+
+
+public fun new_usage_gas_config(target_usage: u64, read_curve: GasCurve, create_curve: GasCurve, write_curve: GasCurve): UsageGasConfig {
+ assert!(target_usage > 0, error::invalid_argument(EZERO_TARGET_USAGE));
+ assert!(target_usage <= MAX_U64 / BASIS_POINT_DENOMINATION, error::invalid_argument(ETARGET_USAGE_TOO_BIG));
+ UsageGasConfig {
+ target_usage,
+ read_curve,
+ create_curve,
+ write_curve,
+ }
+}
+
+
+
+
+public fun new_storage_gas_config(item_config: storage_gas::UsageGasConfig, byte_config: storage_gas::UsageGasConfig): storage_gas::StorageGasConfig
+
+
+
+
+public fun new_storage_gas_config(item_config: UsageGasConfig, byte_config: UsageGasConfig): StorageGasConfig {
+ StorageGasConfig {
+ item_config,
+ byte_config
+ }
+}
+
+
+
+
+public(friend) fun set_config(aptos_framework: &signer, config: storage_gas::StorageGasConfig)
+
+
+
+
+public(friend) fun set_config(aptos_framework: &signer, config: StorageGasConfig) acquires StorageGasConfig {
+ system_addresses::assert_aptos_framework(aptos_framework);
+ *borrow_global_mut<StorageGasConfig>(@aptos_framework) = config;
+}
+
+
+
+
+GasCurve
endpoints are initialized as follows:
+
+| Data style | Operation | Minimum gas | Maximum gas |
+|------------|-----------|-------------|-------------|
+| Per item | Read | 300K | 300K * 100 |
+| Per item | Create | 300k | 300k * 100 |
+| Per item | Write | 300K | 300K * 100 |
+| Per byte | Read | 300 | 300 * 100 |
+| Per byte | Create | 5K | 5K * 100 |
+| Per byte | Write | 5K | 5K * 100 |
+
+StorageGas
values are additionally initialized, but per
+on_reconfig()
, they will be reconfigured for each subsequent
+epoch after initialization.
+
+See base_8192_exponential_curve()
fore more information on
+target utilization.
+
+
+public fun initialize(aptos_framework: &signer)
+
+
+
+
+public fun initialize(aptos_framework: &signer) {
+ system_addresses::assert_aptos_framework(aptos_framework);
+ assert!(
+ !exists<StorageGasConfig>(@aptos_framework),
+ error::already_exists(ESTORAGE_GAS_CONFIG)
+ );
+
+ let k: u64 = 1000;
+ let m: u64 = 1000 * 1000;
+
+ let item_config = UsageGasConfig {
+ target_usage: 2 * k * m, // 2 billion
+ read_curve: base_8192_exponential_curve(300 * k, 300 * k * 100),
+ create_curve: base_8192_exponential_curve(300 * k, 300 * k * 100),
+ write_curve: base_8192_exponential_curve(300 * k, 300 * k * 100),
+ };
+ let byte_config = UsageGasConfig {
+ target_usage: 1 * m * m, // 1TB
+ read_curve: base_8192_exponential_curve(300, 300 * 100),
+ create_curve: base_8192_exponential_curve(5 * k, 5 * k * 100),
+ write_curve: base_8192_exponential_curve(5 * k, 5 * k * 100),
+ };
+ move_to(aptos_framework, StorageGasConfig {
+ item_config,
+ byte_config,
+ });
+
+ assert!(
+ !exists<StorageGas>(@aptos_framework),
+ error::already_exists(ESTORAGE_GAS)
+ );
+ move_to(aptos_framework, StorageGas {
+ per_item_read: 300 * k,
+ per_item_create: 5 * m,
+ per_item_write: 300 * k,
+ per_byte_read: 300,
+ per_byte_create: 5 * k,
+ per_byte_write: 5 * k,
+ });
+}
+
+
+
+
+fun validate_points(points: &vector<storage_gas::Point>)
+
+
+
+
+fun validate_points(points: &vector<Point>) {
+ let len = vector::length(points);
+ spec {
+ assume len < MAX_U64;
+ };
+ let i = 0;
+ while ({
+ spec {
+ invariant forall j in 0..i: {
+ let cur = if (j == 0) { Point { x: 0, y: 0 } } else { points[j - 1] };
+ let next = if (j == len) { Point { x: BASIS_POINT_DENOMINATION, y: BASIS_POINT_DENOMINATION } } else { points[j] };
+ cur.x < next.x && cur.y <= next.y
+ };
+ };
+ i <= len
+ }) {
+ let cur = if (i == 0) { &Point { x: 0, y: 0 } } else { vector::borrow(points, i - 1) };
+ let next = if (i == len) { &Point { x: BASIS_POINT_DENOMINATION, y: BASIS_POINT_DENOMINATION } } else { vector::borrow(points, i) };
+ assert!(cur.x < next.x && cur.y <= next.y, error::invalid_argument(EINVALID_MONOTONICALLY_NON_DECREASING_CURVE));
+ i = i + 1;
+ }
+}
+
+
+
+
+fun calculate_gas(max_usage: u64, current_usage: u64, curve: &storage_gas::GasCurve): u64
+
+
+
+
+fun calculate_gas(max_usage: u64, current_usage: u64, curve: &GasCurve): u64 {
+ let capped_current_usage = if (current_usage > max_usage) max_usage else current_usage;
+ let points = &curve.points;
+ let num_points = vector::length(points);
+ let current_usage_bps = capped_current_usage * BASIS_POINT_DENOMINATION / max_usage;
+
+ // Check the corner case that current_usage_bps drops before the first point.
+ let (left, right) = if (num_points == 0) {
+ (&Point { x: 0, y: 0 }, &Point { x: BASIS_POINT_DENOMINATION, y: BASIS_POINT_DENOMINATION })
+ } else if (current_usage_bps < vector::borrow(points, 0).x) {
+ (&Point { x: 0, y: 0 }, vector::borrow(points, 0))
+ } else if (vector::borrow(points, num_points - 1).x <= current_usage_bps) {
+ (vector::borrow(points, num_points - 1), &Point { x: BASIS_POINT_DENOMINATION, y: BASIS_POINT_DENOMINATION })
+ } else {
+ let (i, j) = (0, num_points - 2);
+ while ({
+ spec {
+ invariant i <= j;
+ invariant j < num_points - 1;
+ invariant points[i].x <= current_usage_bps;
+ invariant current_usage_bps < points[j + 1].x;
+ };
+ i < j
+ }) {
+ let mid = j - (j - i) / 2;
+ if (current_usage_bps < vector::borrow(points, mid).x) {
+ spec {
+ // j is strictly decreasing.
+ assert mid - 1 < j;
+ };
+ j = mid - 1;
+ } else {
+ spec {
+ // i is strictly increasing.
+ assert i < mid;
+ };
+ i = mid;
+ };
+ };
+ (vector::borrow(points, i), vector::borrow(points, i + 1))
+ };
+ let y_interpolated = interpolate(left.x, right.x, left.y, right.y, current_usage_bps);
+ interpolate(0, BASIS_POINT_DENOMINATION, curve.min_gas, curve.max_gas, y_interpolated)
+}
+
+
+
+
+fun interpolate(x0: u64, x1: u64, y0: u64, y1: u64, x: u64): u64
+
+
+
+
+fun interpolate(x0: u64, x1: u64, y0: u64, y1: u64, x: u64): u64 {
+ y0 + (x - x0) * (y1 - y0) / (x1 - x0)
+}
+
+
+
+
+fun calculate_read_gas(config: &storage_gas::UsageGasConfig, usage: u64): u64
+
+
+
+
+fun calculate_read_gas(config: &UsageGasConfig, usage: u64): u64 {
+ calculate_gas(config.target_usage, usage, &config.read_curve)
+}
+
+
+
+
+fun calculate_create_gas(config: &storage_gas::UsageGasConfig, usage: u64): u64
+
+
+
+
+fun calculate_create_gas(config: &UsageGasConfig, usage: u64): u64 {
+ calculate_gas(config.target_usage, usage, &config.create_curve)
+}
+
+
+
+
+fun calculate_write_gas(config: &storage_gas::UsageGasConfig, usage: u64): u64
+
+
+
+
+fun calculate_write_gas(config: &UsageGasConfig, usage: u64): u64 {
+ calculate_gas(config.target_usage, usage, &config.write_curve)
+}
+
+
+
+
+public(friend) fun on_reconfig()
+
+
+
+
+public(friend) fun on_reconfig() acquires StorageGas, StorageGasConfig {
+ assert!(
+ exists<StorageGasConfig>(@aptos_framework),
+ error::not_found(ESTORAGE_GAS_CONFIG)
+ );
+ assert!(
+ exists<StorageGas>(@aptos_framework),
+ error::not_found(ESTORAGE_GAS)
+ );
+ let (items, bytes) = state_storage::current_items_and_bytes();
+ let gas_config = borrow_global<StorageGasConfig>(@aptos_framework);
+ let gas = borrow_global_mut<StorageGas>(@aptos_framework);
+ gas.per_item_read = calculate_read_gas(&gas_config.item_config, items);
+ gas.per_item_create = calculate_create_gas(&gas_config.item_config, items);
+ gas.per_item_write = calculate_write_gas(&gas_config.item_config, items);
+ gas.per_byte_read = calculate_read_gas(&gas_config.byte_config, bytes);
+ gas.per_byte_create = calculate_create_gas(&gas_config.byte_config, bytes);
+ gas.per_byte_write = calculate_write_gas(&gas_config.byte_config, bytes);
+}
+
+
+
+
+fun spec_calculate_gas(max_usage: u64, current_usage: u64, curve: GasCurve): u64;
+
+
+
+
+
+
+
+
+schema NewGasCurveAbortsIf {
+ min_gas: u64;
+ max_gas: u64;
+ aborts_if max_gas < min_gas;
+ aborts_if max_gas > MAX_U64 / BASIS_POINT_DENOMINATION;
+}
+
+
+
+A non decreasing curve must ensure that next is greater than cur.
+
+
+
+
+
+schema ValidatePointsAbortsIf {
+ points: vector<Point>;
+ // This enforces high-level requirement 2:
+ aborts_if exists i in 0..len(points) - 1: (
+ points[i].x >= points[i + 1].x || points[i].y > points[i + 1].y
+ );
+ aborts_if len(points) > 0 && points[0].x == 0;
+ aborts_if len(points) > 0 && points[len(points) - 1].x == BASIS_POINT_DENOMINATION;
+}
+
+
+
+
+
+
+### Struct `Point`
+
+
+struct Point has copy, drop, store
+
+
+
+
+x: u64
+base_8192_exponential_curve()
.
+y: u64
+base_8192_exponential_curve()
.
+invariant x <= BASIS_POINT_DENOMINATION;
+invariant y <= BASIS_POINT_DENOMINATION;
+
+
+
+
+
+
+### Struct `UsageGasConfig`
+
+
+struct UsageGasConfig has copy, drop, store
+
+
+
+
+target_usage: u64
+read_curve: storage_gas::GasCurve
+create_curve: storage_gas::GasCurve
+write_curve: storage_gas::GasCurve
+invariant target_usage > 0;
+invariant target_usage <= MAX_U64 / BASIS_POINT_DENOMINATION;
+
+
+
+
+
+
+
+
+### High-level Requirements
+
+No. | Requirement | Criticality | Implementation | Enforcement | +
---|---|---|---|---|
1 | +The module's initialization guarantees the creation of the StorageGasConfig resource with a precise configuration, including accurate gas curves for per-item and per-byte operations. | +Medium | +The initialize function is responsible for setting up the initial state of the module, ensuring the fulfillment of the following conditions: (1) the creation of the StorageGasConfig resource, indicating its existence witqhin the module's context, and (2) the configuration of the StorageGasConfig resource includes the precise gas curves that define the behavior of per-item and per-byte operations. | +Formally verified via initialize. Moreover, the native gas logic has been manually audited. | +
2 | +The gas curve approximates an exponential curve based on a minimum and maximum gas charge. | +High | +The validate_points function ensures that the provided vector of points represents a monotonically non-decreasing curve. | +Formally verified via validate_points. Moreover, the configuration logic has been manually audited. | +
3 | +The initialized gas curve structure has values set according to the provided parameters. | +Low | +The new_gas_curve function initializes the GasCurve structure with values provided as parameters. | +Formally verified via new_gas_curve. | +
4 | +The initialized usage gas configuration structure has values set according to the provided parameters. | +Low | +The new_usage_gas_config function initializes the UsageGasConfig structure with values provided as parameters. | +Formally verified via new_usage_gas_config. | +
pragma verify = true;
+pragma aborts_if_is_strict;
+invariant [suspendable] chain_status::is_operating() ==> exists<StorageGasConfig>(@aptos_framework);
+invariant [suspendable] chain_status::is_operating() ==> exists<StorageGas>(@aptos_framework);
+
+
+
+
+
+
+### Struct `GasCurve`
+
+
+struct GasCurve has copy, drop, store
+
+
+
+
+min_gas: u64
+max_gas: u64
+points: vector<storage_gas::Point>
+invariant min_gas <= max_gas;
+
+
+
+Invariant 2: The maximum gas charge is capped by MAX_U64 scaled down by the basis point denomination.
+
+
+invariant max_gas <= MAX_U64 / BASIS_POINT_DENOMINATION;
+
+
+
+Invariant 3: The x-coordinate increases monotonically and the y-coordinate increasing strictly monotonically,
+that is, the gas-curve is a monotonically increasing function.
+
+
+invariant (len(points) > 0 ==> points[0].x > 0)
+ && (len(points) > 0 ==> points[len(points) - 1].x < BASIS_POINT_DENOMINATION)
+ && (forall i in 0..len(points) - 1: (points[i].x < points[i + 1].x && points[i].y <= points[i + 1].y));
+
+
+
+
+
+
+### Function `base_8192_exponential_curve`
+
+
+public fun base_8192_exponential_curve(min_gas: u64, max_gas: u64): storage_gas::GasCurve
+
+
+
+
+
+include NewGasCurveAbortsIf;
+
+
+
+
+
+
+### Function `new_point`
+
+
+public fun new_point(x: u64, y: u64): storage_gas::Point
+
+
+
+
+
+aborts_if x > BASIS_POINT_DENOMINATION || y > BASIS_POINT_DENOMINATION;
+ensures result.x == x;
+ensures result.y == y;
+
+
+
+
+
+
+### Function `new_gas_curve`
+
+
+public fun new_gas_curve(min_gas: u64, max_gas: u64, points: vector<storage_gas::Point>): storage_gas::GasCurve
+
+
+
+A non decreasing curve must ensure that next is greater than cur.
+
+
+pragma verify_duration_estimate = 120;
+include NewGasCurveAbortsIf;
+include ValidatePointsAbortsIf;
+// This enforces high-level requirement 3:
+ensures result == GasCurve {
+ min_gas,
+ max_gas,
+ points
+};
+
+
+
+
+
+
+### Function `new_usage_gas_config`
+
+
+public fun new_usage_gas_config(target_usage: u64, read_curve: storage_gas::GasCurve, create_curve: storage_gas::GasCurve, write_curve: storage_gas::GasCurve): storage_gas::UsageGasConfig
+
+
+
+
+
+aborts_if target_usage == 0;
+aborts_if target_usage > MAX_U64 / BASIS_POINT_DENOMINATION;
+// This enforces high-level requirement 4:
+ensures result == UsageGasConfig {
+ target_usage,
+ read_curve,
+ create_curve,
+ write_curve,
+};
+
+
+
+
+
+
+### Function `new_storage_gas_config`
+
+
+public fun new_storage_gas_config(item_config: storage_gas::UsageGasConfig, byte_config: storage_gas::UsageGasConfig): storage_gas::StorageGasConfig
+
+
+
+
+
+aborts_if false;
+ensures result.item_config == item_config;
+ensures result.byte_config == byte_config;
+
+
+
+
+
+
+### Function `set_config`
+
+
+public(friend) fun set_config(aptos_framework: &signer, config: storage_gas::StorageGasConfig)
+
+
+
+Signer address must be @aptos_framework and StorageGasConfig exists.
+
+
+include system_addresses::AbortsIfNotAptosFramework{ account: aptos_framework };
+aborts_if !exists<StorageGasConfig>(@aptos_framework);
+
+
+
+
+
+
+### Function `initialize`
+
+
+public fun initialize(aptos_framework: &signer)
+
+
+
+Signer address must be @aptos_framework.
+Address @aptos_framework does not exist StorageGasConfig and StorageGas before the function call is restricted
+and exists after the function is executed.
+
+
+include system_addresses::AbortsIfNotAptosFramework{ account: aptos_framework };
+pragma verify_duration_estimate = 120;
+aborts_if exists<StorageGasConfig>(@aptos_framework);
+aborts_if exists<StorageGas>(@aptos_framework);
+// This enforces high-level requirement 1:
+ensures exists<StorageGasConfig>(@aptos_framework);
+ensures exists<StorageGas>(@aptos_framework);
+
+
+
+
+
+
+### Function `validate_points`
+
+
+fun validate_points(points: &vector<storage_gas::Point>)
+
+
+
+A non decreasing curve must ensure that next is greater than cur.
+
+
+pragma aborts_if_is_strict = false;
+pragma verify = false;
+pragma opaque;
+include ValidatePointsAbortsIf;
+
+
+
+
+
+
+### Function `calculate_gas`
+
+
+fun calculate_gas(max_usage: u64, current_usage: u64, curve: &storage_gas::GasCurve): u64
+
+
+
+
+
+pragma opaque;
+pragma verify_duration_estimate = 120;
+requires max_usage > 0;
+requires max_usage <= MAX_U64 / BASIS_POINT_DENOMINATION;
+aborts_if false;
+ensures [abstract] result == spec_calculate_gas(max_usage, current_usage, curve);
+
+
+
+
+
+
+### Function `interpolate`
+
+
+fun interpolate(x0: u64, x1: u64, y0: u64, y1: u64, x: u64): u64
+
+
+
+
+
+pragma opaque;
+pragma intrinsic;
+aborts_if false;
+
+
+
+
+
+
+### Function `on_reconfig`
+
+
+public(friend) fun on_reconfig()
+
+
+
+Address @aptos_framework must exist StorageGasConfig and StorageGas and StateStorageUsage.
+
+
+requires chain_status::is_operating();
+aborts_if !exists<StorageGasConfig>(@aptos_framework);
+aborts_if !exists<StorageGas>(@aptos_framework);
+aborts_if !exists<state_storage::StateStorageUsage>(@aptos_framework);
+
+
+
+[move-book]: https://aptos.dev/move/book/SUMMARY
diff --git a/aptos-move/framework/aptos-framework/tests/compiler-v2-doc/system_addresses.md b/aptos-move/framework/aptos-framework/tests/compiler-v2-doc/system_addresses.md
new file mode 100644
index 0000000000000..d17af72b2f98b
--- /dev/null
+++ b/aptos-move/framework/aptos-framework/tests/compiler-v2-doc/system_addresses.md
@@ -0,0 +1,594 @@
+
+
+
+# Module `0x1::system_addresses`
+
+
+
+- [Constants](#@Constants_0)
+- [Function `assert_core_resource`](#0x1_system_addresses_assert_core_resource)
+- [Function `assert_core_resource_address`](#0x1_system_addresses_assert_core_resource_address)
+- [Function `is_core_resource_address`](#0x1_system_addresses_is_core_resource_address)
+- [Function `assert_aptos_framework`](#0x1_system_addresses_assert_aptos_framework)
+- [Function `assert_framework_reserved_address`](#0x1_system_addresses_assert_framework_reserved_address)
+- [Function `assert_framework_reserved`](#0x1_system_addresses_assert_framework_reserved)
+- [Function `is_framework_reserved_address`](#0x1_system_addresses_is_framework_reserved_address)
+- [Function `is_aptos_framework_address`](#0x1_system_addresses_is_aptos_framework_address)
+- [Function `assert_vm`](#0x1_system_addresses_assert_vm)
+- [Function `is_vm`](#0x1_system_addresses_is_vm)
+- [Function `is_vm_address`](#0x1_system_addresses_is_vm_address)
+- [Function `is_reserved_address`](#0x1_system_addresses_is_reserved_address)
+- [Specification](#@Specification_1)
+ - [High-level Requirements](#high-level-req)
+ - [Module-level Specification](#module-level-spec)
+ - [Function `assert_core_resource`](#@Specification_1_assert_core_resource)
+ - [Function `assert_core_resource_address`](#@Specification_1_assert_core_resource_address)
+ - [Function `is_core_resource_address`](#@Specification_1_is_core_resource_address)
+ - [Function `assert_aptos_framework`](#@Specification_1_assert_aptos_framework)
+ - [Function `assert_framework_reserved_address`](#@Specification_1_assert_framework_reserved_address)
+ - [Function `assert_framework_reserved`](#@Specification_1_assert_framework_reserved)
+ - [Function `assert_vm`](#@Specification_1_assert_vm)
+
+
+use 0x1::error;
+use 0x1::signer;
+
+
+
+
+
+
+## Constants
+
+
+
+
+The address/account did not correspond to the core framework address
+
+
+const ENOT_APTOS_FRAMEWORK_ADDRESS: u64 = 3;
+
+
+
+
+
+
+The address/account did not correspond to the core resource address
+
+
+const ENOT_CORE_RESOURCE_ADDRESS: u64 = 1;
+
+
+
+
+
+
+The address is not framework reserved address
+
+
+const ENOT_FRAMEWORK_RESERVED_ADDRESS: u64 = 4;
+
+
+
+
+
+
+The operation can only be performed by the VM
+
+
+const EVM: u64 = 2;
+
+
+
+
+
+
+## Function `assert_core_resource`
+
+
+
+public fun assert_core_resource(account: &signer)
+
+
+
+
+public fun assert_core_resource(account: &signer) {
+ assert_core_resource_address(signer::address_of(account))
+}
+
+
+
+
+public fun assert_core_resource_address(addr: address)
+
+
+
+
+public fun assert_core_resource_address(addr: address) {
+ assert!(is_core_resource_address(addr), error::permission_denied(ENOT_CORE_RESOURCE_ADDRESS))
+}
+
+
+
+
+public fun is_core_resource_address(addr: address): bool
+
+
+
+
+public fun is_core_resource_address(addr: address): bool {
+ addr == @core_resources
+}
+
+
+
+
+public fun assert_aptos_framework(account: &signer)
+
+
+
+
+public fun assert_aptos_framework(account: &signer) {
+ assert!(
+ is_aptos_framework_address(signer::address_of(account)),
+ error::permission_denied(ENOT_APTOS_FRAMEWORK_ADDRESS),
+ )
+}
+
+
+
+
+public fun assert_framework_reserved_address(account: &signer)
+
+
+
+
+public fun assert_framework_reserved_address(account: &signer) {
+ assert_framework_reserved(signer::address_of(account));
+}
+
+
+
+
+public fun assert_framework_reserved(addr: address)
+
+
+
+
+public fun assert_framework_reserved(addr: address) {
+ assert!(
+ is_framework_reserved_address(addr),
+ error::permission_denied(ENOT_FRAMEWORK_RESERVED_ADDRESS),
+ )
+}
+
+
+
+
+addr
is 0x0 or under the on chain governance's control.
+
+
+public fun is_framework_reserved_address(addr: address): bool
+
+
+
+
+public fun is_framework_reserved_address(addr: address): bool {
+ is_aptos_framework_address(addr) ||
+ addr == @0x2 ||
+ addr == @0x3 ||
+ addr == @0x4 ||
+ addr == @0x5 ||
+ addr == @0x6 ||
+ addr == @0x7 ||
+ addr == @0x8 ||
+ addr == @0x9 ||
+ addr == @0xa
+}
+
+
+
+
+addr
is 0x1.
+
+
+public fun is_aptos_framework_address(addr: address): bool
+
+
+
+
+public fun is_aptos_framework_address(addr: address): bool {
+ addr == @aptos_framework
+}
+
+
+
+
+public fun assert_vm(account: &signer)
+
+
+
+
+public fun assert_vm(account: &signer) {
+ assert!(is_vm(account), error::permission_denied(EVM))
+}
+
+
+
+
+addr
is a reserved address for the VM to call system modules.
+
+
+public fun is_vm(account: &signer): bool
+
+
+
+
+public fun is_vm(account: &signer): bool {
+ is_vm_address(signer::address_of(account))
+}
+
+
+
+
+addr
is a reserved address for the VM to call system modules.
+
+
+public fun is_vm_address(addr: address): bool
+
+
+
+
+public fun is_vm_address(addr: address): bool {
+ addr == @vm_reserved
+}
+
+
+
+
+addr
is either the VM address or an Aptos Framework address.
+
+
+public fun is_reserved_address(addr: address): bool
+
+
+
+
+public fun is_reserved_address(addr: address): bool {
+ is_aptos_framework_address(addr) || is_vm_address(addr)
+}
+
+
+
+
+No. | Requirement | Criticality | Implementation | Enforcement | +
---|---|---|---|---|
1 | +Asserting that a provided address corresponds to the Core Resources address should always yield a true result when matched. | +Low | +The assert_core_resource and assert_core_resource_address functions ensure that the provided signer or address belong to the @core_resources account. | +Formally verified via AbortsIfNotCoreResource. | +
2 | +Asserting that a provided address corresponds to the Aptos Framework Resources address should always yield a true result when matched. | +High | +The assert_aptos_framework function ensures that the provided signer belongs to the @aptos_framework account. | +Formally verified via AbortsIfNotAptosFramework. | +
3 | +Asserting that a provided address corresponds to the VM address should always yield a true result when matched. | +High | +The assert_vm function ensure that the provided signer belongs to the @vm_reserved account. | +Formally verified via AbortsIfNotVM. | +
pragma verify = true;
+pragma aborts_if_is_strict;
+
+
+
+
+
+
+### Function `assert_core_resource`
+
+
+public fun assert_core_resource(account: &signer)
+
+
+
+
+
+pragma opaque;
+include AbortsIfNotCoreResource { addr: signer::address_of(account) };
+
+
+
+
+
+
+### Function `assert_core_resource_address`
+
+
+public fun assert_core_resource_address(addr: address)
+
+
+
+
+
+pragma opaque;
+include AbortsIfNotCoreResource;
+
+
+
+
+
+
+### Function `is_core_resource_address`
+
+
+public fun is_core_resource_address(addr: address): bool
+
+
+
+
+
+pragma opaque;
+aborts_if false;
+ensures result == (addr == @core_resources);
+
+
+
+
+
+
+### Function `assert_aptos_framework`
+
+
+public fun assert_aptos_framework(account: &signer)
+
+
+
+
+
+pragma opaque;
+include AbortsIfNotAptosFramework;
+
+
+
+
+
+
+### Function `assert_framework_reserved_address`
+
+
+public fun assert_framework_reserved_address(account: &signer)
+
+
+
+
+
+aborts_if !is_framework_reserved_address(signer::address_of(account));
+
+
+
+
+
+
+### Function `assert_framework_reserved`
+
+
+public fun assert_framework_reserved(addr: address)
+
+
+
+
+
+aborts_if !is_framework_reserved_address(addr);
+
+
+
+Specifies that a function aborts if the account does not have the aptos framework address.
+
+
+
+
+
+schema AbortsIfNotAptosFramework {
+ account: signer;
+ // This enforces high-level requirement 2:
+ aborts_if signer::address_of(account) != @aptos_framework with error::PERMISSION_DENIED;
+}
+
+
+
+
+
+
+### Function `assert_vm`
+
+
+public fun assert_vm(account: &signer)
+
+
+
+
+
+pragma opaque;
+include AbortsIfNotVM;
+
+
+
+Specifies that a function aborts if the account does not have the VM reserved address.
+
+
+
+
+
+schema AbortsIfNotVM {
+ account: signer;
+ // This enforces high-level requirement 3:
+ aborts_if signer::address_of(account) != @vm_reserved with error::PERMISSION_DENIED;
+}
+
+
+
+[move-book]: https://aptos.dev/move/book/SUMMARY
diff --git a/aptos-move/framework/aptos-framework/tests/compiler-v2-doc/timestamp.md b/aptos-move/framework/aptos-framework/tests/compiler-v2-doc/timestamp.md
new file mode 100644
index 0000000000000..45db531cc6b00
--- /dev/null
+++ b/aptos-move/framework/aptos-framework/tests/compiler-v2-doc/timestamp.md
@@ -0,0 +1,334 @@
+
+
+
+# Module `0x1::timestamp`
+
+This module keeps a global wall clock that stores the current Unix time in microseconds.
+It interacts with the other modules in the following ways:
+* genesis: to initialize the timestamp
+* block: to reach consensus on the global wall clock time
+
+
+- [Resource `CurrentTimeMicroseconds`](#0x1_timestamp_CurrentTimeMicroseconds)
+- [Constants](#@Constants_0)
+- [Function `set_time_has_started`](#0x1_timestamp_set_time_has_started)
+- [Function `update_global_time`](#0x1_timestamp_update_global_time)
+- [Function `now_microseconds`](#0x1_timestamp_now_microseconds)
+- [Function `now_seconds`](#0x1_timestamp_now_seconds)
+- [Specification](#@Specification_1)
+ - [High-level Requirements](#high-level-req)
+ - [Module-level Specification](#module-level-spec)
+ - [Function `update_global_time`](#@Specification_1_update_global_time)
+
+
+use 0x1::error;
+use 0x1::system_addresses;
+
+
+
+
+
+
+## Resource `CurrentTimeMicroseconds`
+
+A singleton resource holding the current Unix time in microseconds
+
+
+struct CurrentTimeMicroseconds has key
+
+
+
+
+microseconds: u64
+const ENOT_OPERATING: u64 = 1;
+
+
+
+
+
+
+An invalid timestamp was provided
+
+
+const EINVALID_TIMESTAMP: u64 = 2;
+
+
+
+
+
+
+Conversion factor between seconds and microseconds
+
+
+const MICRO_CONVERSION_FACTOR: u64 = 1000000;
+
+
+
+
+
+
+## Function `set_time_has_started`
+
+Marks that time has started. This can only be called from genesis and with the aptos framework account.
+
+
+public(friend) fun set_time_has_started(aptos_framework: &signer)
+
+
+
+
+public(friend) fun set_time_has_started(aptos_framework: &signer) {
+ system_addresses::assert_aptos_framework(aptos_framework);
+ let timer = CurrentTimeMicroseconds { microseconds: 0 };
+ move_to(aptos_framework, timer);
+}
+
+
+
+
+public fun update_global_time(account: &signer, proposer: address, timestamp: u64)
+
+
+
+
+public fun update_global_time(
+ account: &signer,
+ proposer: address,
+ timestamp: u64
+) acquires CurrentTimeMicroseconds {
+ // Can only be invoked by AptosVM signer.
+ system_addresses::assert_vm(account);
+
+ let global_timer = borrow_global_mut<CurrentTimeMicroseconds>(@aptos_framework);
+ let now = global_timer.microseconds;
+ if (proposer == @vm_reserved) {
+ // NIL block with null address as proposer. Timestamp must be equal.
+ assert!(now == timestamp, error::invalid_argument(EINVALID_TIMESTAMP));
+ } else {
+ // Normal block. Time must advance
+ assert!(now < timestamp, error::invalid_argument(EINVALID_TIMESTAMP));
+ global_timer.microseconds = timestamp;
+ };
+}
+
+
+
+
+#[view]
+public fun now_microseconds(): u64
+
+
+
+
+public fun now_microseconds(): u64 acquires CurrentTimeMicroseconds {
+ borrow_global<CurrentTimeMicroseconds>(@aptos_framework).microseconds
+}
+
+
+
+
+#[view]
+public fun now_seconds(): u64
+
+
+
+
+public fun now_seconds(): u64 acquires CurrentTimeMicroseconds {
+ now_microseconds() / MICRO_CONVERSION_FACTOR
+}
+
+
+
+
+No. | Requirement | Criticality | Implementation | Enforcement | +
---|---|---|---|---|
1 | +There should only exist one global wall clock and it should be created during genesis. | +High | +The function set_time_has_started is only called by genesis::initialize and ensures that no other resources of this type exist by only assigning it to a predefined account. | +Formally verified via module. | +
2 | +The global wall clock resource should only be owned by the Aptos framework. | +High | +The function set_time_has_started ensures that only the aptos_framework account can possess the CurrentTimeMicroseconds resource using the assert_aptos_framework function. | +Formally verified via module. | +
3 | +The clock time should only be updated by the VM account. | +High | +The update_global_time function asserts that the transaction signer is the vm_reserved account. | +Formally verified via UpdateGlobalTimeAbortsIf. | +
4 | +The clock time should increase with every update as agreed through consensus and proposed by the current epoch's validator. | +High | +The update_global_time function asserts that the new timestamp is greater than the current timestamp. | +Formally verified via UpdateGlobalTimeAbortsIf. | +
// This enforces high-level requirement 1 and high-level requirement 2:
+invariant [suspendable] chain_status::is_operating() ==> exists<CurrentTimeMicroseconds>(@aptos_framework);
+
+
+
+
+
+
+### Function `update_global_time`
+
+
+public fun update_global_time(account: &signer, proposer: address, timestamp: u64)
+
+
+
+
+
+requires chain_status::is_operating();
+include UpdateGlobalTimeAbortsIf;
+ensures (proposer != @vm_reserved) ==> (spec_now_microseconds() == timestamp);
+
+
+
+
+
+
+
+
+schema UpdateGlobalTimeAbortsIf {
+ account: signer;
+ proposer: address;
+ timestamp: u64;
+ // This enforces high-level requirement 3:
+ aborts_if !system_addresses::is_vm(account);
+ // This enforces high-level requirement 4:
+ aborts_if (proposer == @vm_reserved) && (spec_now_microseconds() != timestamp);
+ aborts_if (proposer != @vm_reserved) && (spec_now_microseconds() >= timestamp);
+}
+
+
+
+
+
+
+
+
+fun spec_now_microseconds(): u64 {
+ global<CurrentTimeMicroseconds>(@aptos_framework).microseconds
+}
+
+
+
+
+
+
+
+
+fun spec_now_seconds(): u64 {
+ spec_now_microseconds() / MICRO_CONVERSION_FACTOR
+}
+
+
+
+[move-book]: https://aptos.dev/move/book/SUMMARY
diff --git a/aptos-move/framework/aptos-framework/tests/compiler-v2-doc/transaction_context.md b/aptos-move/framework/aptos-framework/tests/compiler-v2-doc/transaction_context.md
new file mode 100644
index 0000000000000..2bdbe14562187
--- /dev/null
+++ b/aptos-move/framework/aptos-framework/tests/compiler-v2-doc/transaction_context.md
@@ -0,0 +1,1288 @@
+
+
+
+# Module `0x1::transaction_context`
+
+
+
+- [Struct `AUID`](#0x1_transaction_context_AUID)
+- [Struct `EntryFunctionPayload`](#0x1_transaction_context_EntryFunctionPayload)
+- [Struct `MultisigPayload`](#0x1_transaction_context_MultisigPayload)
+- [Constants](#@Constants_0)
+- [Function `get_txn_hash`](#0x1_transaction_context_get_txn_hash)
+- [Function `get_transaction_hash`](#0x1_transaction_context_get_transaction_hash)
+- [Function `generate_unique_address`](#0x1_transaction_context_generate_unique_address)
+- [Function `generate_auid_address`](#0x1_transaction_context_generate_auid_address)
+- [Function `get_script_hash`](#0x1_transaction_context_get_script_hash)
+- [Function `generate_auid`](#0x1_transaction_context_generate_auid)
+- [Function `auid_address`](#0x1_transaction_context_auid_address)
+- [Function `sender`](#0x1_transaction_context_sender)
+- [Function `sender_internal`](#0x1_transaction_context_sender_internal)
+- [Function `secondary_signers`](#0x1_transaction_context_secondary_signers)
+- [Function `secondary_signers_internal`](#0x1_transaction_context_secondary_signers_internal)
+- [Function `gas_payer`](#0x1_transaction_context_gas_payer)
+- [Function `gas_payer_internal`](#0x1_transaction_context_gas_payer_internal)
+- [Function `max_gas_amount`](#0x1_transaction_context_max_gas_amount)
+- [Function `max_gas_amount_internal`](#0x1_transaction_context_max_gas_amount_internal)
+- [Function `gas_unit_price`](#0x1_transaction_context_gas_unit_price)
+- [Function `gas_unit_price_internal`](#0x1_transaction_context_gas_unit_price_internal)
+- [Function `chain_id`](#0x1_transaction_context_chain_id)
+- [Function `chain_id_internal`](#0x1_transaction_context_chain_id_internal)
+- [Function `entry_function_payload`](#0x1_transaction_context_entry_function_payload)
+- [Function `entry_function_payload_internal`](#0x1_transaction_context_entry_function_payload_internal)
+- [Function `account_address`](#0x1_transaction_context_account_address)
+- [Function `module_name`](#0x1_transaction_context_module_name)
+- [Function `function_name`](#0x1_transaction_context_function_name)
+- [Function `type_arg_names`](#0x1_transaction_context_type_arg_names)
+- [Function `args`](#0x1_transaction_context_args)
+- [Function `multisig_payload`](#0x1_transaction_context_multisig_payload)
+- [Function `multisig_payload_internal`](#0x1_transaction_context_multisig_payload_internal)
+- [Function `multisig_address`](#0x1_transaction_context_multisig_address)
+- [Function `inner_entry_function_payload`](#0x1_transaction_context_inner_entry_function_payload)
+- [Specification](#@Specification_1)
+ - [Function `get_txn_hash`](#@Specification_1_get_txn_hash)
+ - [Function `get_transaction_hash`](#@Specification_1_get_transaction_hash)
+ - [Function `generate_unique_address`](#@Specification_1_generate_unique_address)
+ - [Function `generate_auid_address`](#@Specification_1_generate_auid_address)
+ - [Function `get_script_hash`](#@Specification_1_get_script_hash)
+ - [High-level Requirements](#high-level-req)
+ - [Module-level Specification](#module-level-spec)
+ - [Function `auid_address`](#@Specification_1_auid_address)
+ - [Function `sender_internal`](#@Specification_1_sender_internal)
+ - [Function `secondary_signers_internal`](#@Specification_1_secondary_signers_internal)
+ - [Function `gas_payer_internal`](#@Specification_1_gas_payer_internal)
+ - [Function `max_gas_amount_internal`](#@Specification_1_max_gas_amount_internal)
+ - [Function `gas_unit_price_internal`](#@Specification_1_gas_unit_price_internal)
+ - [Function `chain_id_internal`](#@Specification_1_chain_id_internal)
+ - [Function `entry_function_payload_internal`](#@Specification_1_entry_function_payload_internal)
+ - [Function `multisig_payload_internal`](#@Specification_1_multisig_payload_internal)
+
+
+use 0x1::error;
+use 0x1::features;
+use 0x1::option;
+use 0x1::string;
+
+
+
+
+
+
+## Struct `AUID`
+
+A wrapper denoting aptos unique identifer (AUID)
+for storing an address
+
+
+struct AUID has drop, store
+
+
+
+
+unique_address: address
+struct EntryFunctionPayload has copy, drop
+
+
+
+
+account_address: address
+module_name: string::String
+function_name: string::String
+ty_args_names: vector<string::String>
+args: vector<vector<u8>>
+struct MultisigPayload has copy, drop
+
+
+
+
+multisig_address: address
+entry_function_payload: option::Option<transaction_context::EntryFunctionPayload>
+const ETRANSACTION_CONTEXT_EXTENSION_NOT_ENABLED: u64 = 2;
+
+
+
+
+
+
+Transaction context is only available in the user transaction prologue, execution, or epilogue phases.
+
+
+const ETRANSACTION_CONTEXT_NOT_AVAILABLE: u64 = 1;
+
+
+
+
+
+
+## Function `get_txn_hash`
+
+Returns the transaction hash of the current transaction.
+
+
+fun get_txn_hash(): vector<u8>
+
+
+
+
+native fun get_txn_hash(): vector<u8>;
+
+
+
+
+get_txn_hash
.
+This function is created for to feature gate the get_txn_hash
function.
+
+
+public fun get_transaction_hash(): vector<u8>
+
+
+
+
+public fun get_transaction_hash(): vector<u8> {
+ get_txn_hash()
+}
+
+
+
+
+fun generate_unique_address(): address
+
+
+
+
+native fun generate_unique_address(): address;
+
+
+
+
+generate_unique_address
. This function is
+created for to feature gate the generate_unique_address
function.
+
+
+public fun generate_auid_address(): address
+
+
+
+
+public fun generate_auid_address(): address {
+ generate_unique_address()
+}
+
+
+
+
+public fun get_script_hash(): vector<u8>
+
+
+
+
+public native fun get_script_hash(): vector<u8>;
+
+
+
+
+generate_unique_address
native function and returns
+the generated unique address wrapped in the AUID class.
+
+
+public fun generate_auid(): transaction_context::AUID
+
+
+
+
+public fun generate_auid(): AUID {
+ return AUID {
+ unique_address: generate_unique_address()
+ }
+}
+
+
+
+
+public fun auid_address(auid: &transaction_context::AUID): address
+
+
+
+
+public fun auid_address(auid: &AUID): address {
+ auid.unique_address
+}
+
+
+
+
+public fun sender(): address
+
+
+
+
+public fun sender(): address {
+ assert!(features::transaction_context_extension_enabled(), error::invalid_state(ETRANSACTION_CONTEXT_EXTENSION_NOT_ENABLED));
+ sender_internal()
+}
+
+
+
+
+fun sender_internal(): address
+
+
+
+
+native fun sender_internal(): address;
+
+
+
+
+public fun secondary_signers(): vector<address>
+
+
+
+
+public fun secondary_signers(): vector<address> {
+ assert!(features::transaction_context_extension_enabled(), error::invalid_state(ETRANSACTION_CONTEXT_EXTENSION_NOT_ENABLED));
+ secondary_signers_internal()
+}
+
+
+
+
+fun secondary_signers_internal(): vector<address>
+
+
+
+
+native fun secondary_signers_internal(): vector<address>;
+
+
+
+
+public fun gas_payer(): address
+
+
+
+
+public fun gas_payer(): address {
+ assert!(features::transaction_context_extension_enabled(), error::invalid_state(ETRANSACTION_CONTEXT_EXTENSION_NOT_ENABLED));
+ gas_payer_internal()
+}
+
+
+
+
+fun gas_payer_internal(): address
+
+
+
+
+native fun gas_payer_internal(): address;
+
+
+
+
+public fun max_gas_amount(): u64
+
+
+
+
+public fun max_gas_amount(): u64 {
+ assert!(features::transaction_context_extension_enabled(), error::invalid_state(ETRANSACTION_CONTEXT_EXTENSION_NOT_ENABLED));
+ max_gas_amount_internal()
+}
+
+
+
+
+fun max_gas_amount_internal(): u64
+
+
+
+
+native fun max_gas_amount_internal(): u64;
+
+
+
+
+public fun gas_unit_price(): u64
+
+
+
+
+public fun gas_unit_price(): u64 {
+ assert!(features::transaction_context_extension_enabled(), error::invalid_state(ETRANSACTION_CONTEXT_EXTENSION_NOT_ENABLED));
+ gas_unit_price_internal()
+}
+
+
+
+
+fun gas_unit_price_internal(): u64
+
+
+
+
+native fun gas_unit_price_internal(): u64;
+
+
+
+
+public fun chain_id(): u8
+
+
+
+
+public fun chain_id(): u8 {
+ assert!(features::transaction_context_extension_enabled(), error::invalid_state(ETRANSACTION_CONTEXT_EXTENSION_NOT_ENABLED));
+ chain_id_internal()
+}
+
+
+
+
+fun chain_id_internal(): u8
+
+
+
+
+native fun chain_id_internal(): u8;
+
+
+
+
+None
.
+This function aborts if called outside of the transaction prologue, execution, or epilogue phases.
+
+
+public fun entry_function_payload(): option::Option<transaction_context::EntryFunctionPayload>
+
+
+
+
+public fun entry_function_payload(): Option<EntryFunctionPayload> {
+ assert!(features::transaction_context_extension_enabled(), error::invalid_state(ETRANSACTION_CONTEXT_EXTENSION_NOT_ENABLED));
+ entry_function_payload_internal()
+}
+
+
+
+
+fun entry_function_payload_internal(): option::Option<transaction_context::EntryFunctionPayload>
+
+
+
+
+native fun entry_function_payload_internal(): Option<EntryFunctionPayload>;
+
+
+
+
+public fun account_address(payload: &transaction_context::EntryFunctionPayload): address
+
+
+
+
+public fun account_address(payload: &EntryFunctionPayload): address {
+ assert!(features::transaction_context_extension_enabled(), error::invalid_state(ETRANSACTION_CONTEXT_EXTENSION_NOT_ENABLED));
+ payload.account_address
+}
+
+
+
+
+public fun module_name(payload: &transaction_context::EntryFunctionPayload): string::String
+
+
+
+
+public fun module_name(payload: &EntryFunctionPayload): String {
+ assert!(features::transaction_context_extension_enabled(), error::invalid_state(ETRANSACTION_CONTEXT_EXTENSION_NOT_ENABLED));
+ payload.module_name
+}
+
+
+
+
+public fun function_name(payload: &transaction_context::EntryFunctionPayload): string::String
+
+
+
+
+public fun function_name(payload: &EntryFunctionPayload): String {
+ assert!(features::transaction_context_extension_enabled(), error::invalid_state(ETRANSACTION_CONTEXT_EXTENSION_NOT_ENABLED));
+ payload.function_name
+}
+
+
+
+
+public fun type_arg_names(payload: &transaction_context::EntryFunctionPayload): vector<string::String>
+
+
+
+
+public fun type_arg_names(payload: &EntryFunctionPayload): vector<String> {
+ assert!(features::transaction_context_extension_enabled(), error::invalid_state(ETRANSACTION_CONTEXT_EXTENSION_NOT_ENABLED));
+ payload.ty_args_names
+}
+
+
+
+
+public fun args(payload: &transaction_context::EntryFunctionPayload): vector<vector<u8>>
+
+
+
+
+public fun args(payload: &EntryFunctionPayload): vector<vector<u8>> {
+ assert!(features::transaction_context_extension_enabled(), error::invalid_state(ETRANSACTION_CONTEXT_EXTENSION_NOT_ENABLED));
+ payload.args
+}
+
+
+
+
+None
.
+This function aborts if called outside of the transaction prologue, execution, or epilogue phases.
+
+
+public fun multisig_payload(): option::Option<transaction_context::MultisigPayload>
+
+
+
+
+public fun multisig_payload(): Option<MultisigPayload> {
+ assert!(features::transaction_context_extension_enabled(), error::invalid_state(ETRANSACTION_CONTEXT_EXTENSION_NOT_ENABLED));
+ multisig_payload_internal()
+}
+
+
+
+
+fun multisig_payload_internal(): option::Option<transaction_context::MultisigPayload>
+
+
+
+
+native fun multisig_payload_internal(): Option<MultisigPayload>;
+
+
+
+
+public fun multisig_address(payload: &transaction_context::MultisigPayload): address
+
+
+
+
+public fun multisig_address(payload: &MultisigPayload): address {
+ assert!(features::transaction_context_extension_enabled(), error::invalid_state(ETRANSACTION_CONTEXT_EXTENSION_NOT_ENABLED));
+ payload.multisig_address
+}
+
+
+
+
+public fun inner_entry_function_payload(payload: &transaction_context::MultisigPayload): option::Option<transaction_context::EntryFunctionPayload>
+
+
+
+
+public fun inner_entry_function_payload(payload: &MultisigPayload): Option<EntryFunctionPayload> {
+ assert!(features::transaction_context_extension_enabled(), error::invalid_state(ETRANSACTION_CONTEXT_EXTENSION_NOT_ENABLED));
+ payload.entry_function_payload
+}
+
+
+
+
+fun get_txn_hash(): vector<u8>
+
+
+
+
+
+pragma opaque;
+aborts_if [abstract] false;
+ensures result == spec_get_txn_hash();
+
+
+
+
+
+
+
+
+fun spec_get_txn_hash(): vector<u8>;
+
+
+
+
+
+
+### Function `get_transaction_hash`
+
+
+public fun get_transaction_hash(): vector<u8>
+
+
+
+
+
+pragma opaque;
+aborts_if [abstract] false;
+ensures result == spec_get_txn_hash();
+// This enforces high-level requirement 1:
+ensures [abstract] len(result) == 32;
+
+
+
+
+
+
+### Function `generate_unique_address`
+
+
+fun generate_unique_address(): address
+
+
+
+
+
+pragma opaque;
+ensures [abstract] result == spec_generate_unique_address();
+
+
+
+
+
+
+
+
+fun spec_generate_unique_address(): address;
+
+
+
+
+
+
+### Function `generate_auid_address`
+
+
+public fun generate_auid_address(): address
+
+
+
+
+
+pragma opaque;
+// This enforces high-level requirement 3:
+ensures [abstract] result == spec_generate_unique_address();
+
+
+
+
+
+
+### Function `get_script_hash`
+
+
+public fun get_script_hash(): vector<u8>
+
+
+
+
+
+
+
+
+### High-level Requirements
+
+No. | Requirement | Criticality | Implementation | Enforcement | +
---|---|---|---|---|
1 | +Fetching the transaction hash should return a vector with 32 bytes. | +Medium | +The get_transaction_hash function calls the native function get_txn_hash, which fetches the NativeTransactionContext struct and returns the txn_hash field. | +Audited that the native function returns the txn hash, whose size is 32 bytes. This has been modeled as the abstract postcondition that the returned vector is of length 32. Formally verified via get_txn_hash. | +
2 | +Fetching the unique address should never abort. | +Low | +The function auid_address returns the unique address from a supplied AUID resource. | +Formally verified via auid_address. | +
3 | +Generating the unique address should return a vector with 32 bytes. | +Medium | +The generate_auid_address function checks calls the native function generate_unique_address which fetches the NativeTransactionContext struct, increments the auid_counter by one, and then creates a new authentication key from a preimage, which is then returned. | +Audited that the native function returns an address, and the length of an address is 32 bytes. This has been modeled as the abstract postcondition that the returned vector is of length 32. Formally verified via generate_auid_address. | +
4 | +Fetching the script hash of the current entry function should never fail and should return a vector with 32 bytes if the transaction payload is a script, otherwise an empty vector. | +Low | +The native function get_script_hash returns the NativeTransactionContext.script_hash field. | +Audited that the native function holds the required property. This has been modeled as the abstract spec. Formally verified via get_script_hash. | +
pragma opaque;
+// This enforces high-level requirement 4:
+aborts_if [abstract] false;
+ensures [abstract] result == spec_get_script_hash();
+ensures [abstract] len(result) == 32;
+
+
+
+
+
+
+
+
+fun spec_get_script_hash(): vector<u8>;
+
+
+
+
+
+
+### Function `auid_address`
+
+
+public fun auid_address(auid: &transaction_context::AUID): address
+
+
+
+
+
+// This enforces high-level requirement 2:
+aborts_if false;
+
+
+
+
+
+
+### Function `sender_internal`
+
+
+fun sender_internal(): address
+
+
+
+
+
+pragma opaque;
+
+
+
+
+
+
+### Function `secondary_signers_internal`
+
+
+fun secondary_signers_internal(): vector<address>
+
+
+
+
+
+pragma opaque;
+
+
+
+
+
+
+### Function `gas_payer_internal`
+
+
+fun gas_payer_internal(): address
+
+
+
+
+
+pragma opaque;
+
+
+
+
+
+
+### Function `max_gas_amount_internal`
+
+
+fun max_gas_amount_internal(): u64
+
+
+
+
+
+pragma opaque;
+
+
+
+
+
+
+### Function `gas_unit_price_internal`
+
+
+fun gas_unit_price_internal(): u64
+
+
+
+
+
+pragma opaque;
+
+
+
+
+
+
+### Function `chain_id_internal`
+
+
+fun chain_id_internal(): u8
+
+
+
+
+
+pragma opaque;
+
+
+
+
+
+
+### Function `entry_function_payload_internal`
+
+
+fun entry_function_payload_internal(): option::Option<transaction_context::EntryFunctionPayload>
+
+
+
+
+
+pragma opaque;
+
+
+
+
+
+
+### Function `multisig_payload_internal`
+
+
+fun multisig_payload_internal(): option::Option<transaction_context::MultisigPayload>
+
+
+
+
+
+pragma opaque;
+
+
+
+[move-book]: https://aptos.dev/move/book/SUMMARY
diff --git a/aptos-move/framework/aptos-framework/tests/compiler-v2-doc/transaction_fee.md b/aptos-move/framework/aptos-framework/tests/compiler-v2-doc/transaction_fee.md
new file mode 100644
index 0000000000000..8051db864cac9
--- /dev/null
+++ b/aptos-move/framework/aptos-framework/tests/compiler-v2-doc/transaction_fee.md
@@ -0,0 +1,1245 @@
+
+
+
+# Module `0x1::transaction_fee`
+
+This module provides an interface to burn or collect and redistribute transaction fees.
+
+
+- [Resource `AptosCoinCapabilities`](#0x1_transaction_fee_AptosCoinCapabilities)
+- [Resource `AptosFABurnCapabilities`](#0x1_transaction_fee_AptosFABurnCapabilities)
+- [Resource `AptosCoinMintCapability`](#0x1_transaction_fee_AptosCoinMintCapability)
+- [Resource `CollectedFeesPerBlock`](#0x1_transaction_fee_CollectedFeesPerBlock)
+- [Struct `FeeStatement`](#0x1_transaction_fee_FeeStatement)
+- [Constants](#@Constants_0)
+- [Function `initialize_fee_collection_and_distribution`](#0x1_transaction_fee_initialize_fee_collection_and_distribution)
+- [Function `is_fees_collection_enabled`](#0x1_transaction_fee_is_fees_collection_enabled)
+- [Function `upgrade_burn_percentage`](#0x1_transaction_fee_upgrade_burn_percentage)
+- [Function `register_proposer_for_fee_collection`](#0x1_transaction_fee_register_proposer_for_fee_collection)
+- [Function `burn_coin_fraction`](#0x1_transaction_fee_burn_coin_fraction)
+- [Function `process_collected_fees`](#0x1_transaction_fee_process_collected_fees)
+- [Function `burn_fee`](#0x1_transaction_fee_burn_fee)
+- [Function `mint_and_refund`](#0x1_transaction_fee_mint_and_refund)
+- [Function `collect_fee`](#0x1_transaction_fee_collect_fee)
+- [Function `store_aptos_coin_burn_cap`](#0x1_transaction_fee_store_aptos_coin_burn_cap)
+- [Function `convert_to_aptos_fa_burn_ref`](#0x1_transaction_fee_convert_to_aptos_fa_burn_ref)
+- [Function `store_aptos_coin_mint_cap`](#0x1_transaction_fee_store_aptos_coin_mint_cap)
+- [Function `initialize_storage_refund`](#0x1_transaction_fee_initialize_storage_refund)
+- [Function `emit_fee_statement`](#0x1_transaction_fee_emit_fee_statement)
+- [Specification](#@Specification_1)
+ - [High-level Requirements](#high-level-req)
+ - [Module-level Specification](#module-level-spec)
+ - [Resource `CollectedFeesPerBlock`](#@Specification_1_CollectedFeesPerBlock)
+ - [Function `initialize_fee_collection_and_distribution`](#@Specification_1_initialize_fee_collection_and_distribution)
+ - [Function `upgrade_burn_percentage`](#@Specification_1_upgrade_burn_percentage)
+ - [Function `register_proposer_for_fee_collection`](#@Specification_1_register_proposer_for_fee_collection)
+ - [Function `burn_coin_fraction`](#@Specification_1_burn_coin_fraction)
+ - [Function `process_collected_fees`](#@Specification_1_process_collected_fees)
+ - [Function `burn_fee`](#@Specification_1_burn_fee)
+ - [Function `mint_and_refund`](#@Specification_1_mint_and_refund)
+ - [Function `collect_fee`](#@Specification_1_collect_fee)
+ - [Function `store_aptos_coin_burn_cap`](#@Specification_1_store_aptos_coin_burn_cap)
+ - [Function `store_aptos_coin_mint_cap`](#@Specification_1_store_aptos_coin_mint_cap)
+ - [Function `initialize_storage_refund`](#@Specification_1_initialize_storage_refund)
+ - [Function `emit_fee_statement`](#@Specification_1_emit_fee_statement)
+
+
+use 0x1::aptos_account;
+use 0x1::aptos_coin;
+use 0x1::coin;
+use 0x1::error;
+use 0x1::event;
+use 0x1::features;
+use 0x1::fungible_asset;
+use 0x1::option;
+use 0x1::signer;
+use 0x1::stake;
+use 0x1::system_addresses;
+
+
+
+
+
+
+## Resource `AptosCoinCapabilities`
+
+Stores burn capability to burn the gas fees.
+
+
+struct AptosCoinCapabilities has key
+
+
+
+
+burn_cap: coin::BurnCapability<aptos_coin::AptosCoin>
+struct AptosFABurnCapabilities has key
+
+
+
+
+burn_ref: fungible_asset::BurnRef
+struct AptosCoinMintCapability has key
+
+
+
+
+mint_cap: coin::MintCapability<aptos_coin::AptosCoin>
+struct CollectedFeesPerBlock has key
+
+
+
+
+amount: coin::AggregatableCoin<aptos_coin::AptosCoin>
+proposer: option::Option<address>
+burn_percentage: u8
+gas_used
in the on-chain TransactionInfo
.
+This is the sum of the sub-items below. Notice that there's potential precision loss when
+the conversion between internal and external gas units and between native token and gas
+units, so it's possible that the numbers don't add up exactly. -- This number is the final
+charge, while the break down is merely informational.
+- gas charge for execution (CPU time): execution_gas_units
+- gas charge for IO (storage random access): io_gas_units
+- storage fee charge (storage space): storage_fee_octas
, to be included in
+total_charge_gas_unit
, this number is converted to gas units according to the user
+specified gas_unit_price
on the transaction.
+- storage deletion refund: storage_fee_refund_octas
, this is not included in gas_used
or
+total_charge_gas_units
, the net charge / refund is calculated by
+total_charge_gas_units
* gas_unit_price
- storage_fee_refund_octas
.
+
+This is meant to emitted as a module event.
+
+
+#[event]
+struct FeeStatement has drop, store
+
+
+
+
+total_charge_gas_units: u64
+execution_gas_units: u64
+io_gas_units: u64
+storage_fee_octas: u64
+storage_fee_refund_octas: u64
+const EALREADY_COLLECTING_FEES: u64 = 1;
+
+
+
+
+
+
+
+
+const EFA_GAS_CHARGING_NOT_ENABLED: u64 = 5;
+
+
+
+
+
+
+The burn percentage is out of range [0, 100].
+
+
+const EINVALID_BURN_PERCENTAGE: u64 = 3;
+
+
+
+
+
+
+No longer supported.
+
+
+const ENO_LONGER_SUPPORTED: u64 = 4;
+
+
+
+
+
+
+## Function `initialize_fee_collection_and_distribution`
+
+Initializes the resource storing information about gas fees collection and
+distribution. Should be called by on-chain governance.
+
+
+public fun initialize_fee_collection_and_distribution(aptos_framework: &signer, burn_percentage: u8)
+
+
+
+
+public fun initialize_fee_collection_and_distribution(aptos_framework: &signer, burn_percentage: u8) {
+ system_addresses::assert_aptos_framework(aptos_framework);
+ assert!(
+ !exists<CollectedFeesPerBlock>(@aptos_framework),
+ error::already_exists(EALREADY_COLLECTING_FEES)
+ );
+ assert!(burn_percentage <= 100, error::out_of_range(EINVALID_BURN_PERCENTAGE));
+
+ // Make sure stakng module is aware of transaction fees collection.
+ stake::initialize_validator_fees(aptos_framework);
+
+ // Initially, no fees are collected and the block proposer is not set.
+ let collected_fees = CollectedFeesPerBlock {
+ amount: coin::initialize_aggregatable_coin(aptos_framework),
+ proposer: option::none(),
+ burn_percentage,
+ };
+ move_to(aptos_framework, collected_fees);
+}
+
+
+
+
+fun is_fees_collection_enabled(): bool
+
+
+
+
+fun is_fees_collection_enabled(): bool {
+ exists<CollectedFeesPerBlock>(@aptos_framework)
+}
+
+
+
+
+public fun upgrade_burn_percentage(aptos_framework: &signer, new_burn_percentage: u8)
+
+
+
+
+public fun upgrade_burn_percentage(
+ aptos_framework: &signer,
+ new_burn_percentage: u8
+) acquires AptosCoinCapabilities, CollectedFeesPerBlock {
+ system_addresses::assert_aptos_framework(aptos_framework);
+ assert!(new_burn_percentage <= 100, error::out_of_range(EINVALID_BURN_PERCENTAGE));
+
+ // Prior to upgrading the burn percentage, make sure to process collected
+ // fees. Otherwise we would use the new (incorrect) burn_percentage when
+ // processing fees later!
+ process_collected_fees();
+
+ if (is_fees_collection_enabled()) {
+ // Upgrade has no effect unless fees are being collected.
+ let burn_percentage = &mut borrow_global_mut<CollectedFeesPerBlock>(@aptos_framework).burn_percentage;
+ *burn_percentage = new_burn_percentage
+ }
+}
+
+
+
+
+public(friend) fun register_proposer_for_fee_collection(proposer_addr: address)
+
+
+
+
+public(friend) fun register_proposer_for_fee_collection(proposer_addr: address) acquires CollectedFeesPerBlock {
+ if (is_fees_collection_enabled()) {
+ let collected_fees = borrow_global_mut<CollectedFeesPerBlock>(@aptos_framework);
+ let _ = option::swap_or_fill(&mut collected_fees.proposer, proposer_addr);
+ }
+}
+
+
+
+
+fun burn_coin_fraction(coin: &mut coin::Coin<aptos_coin::AptosCoin>, burn_percentage: u8)
+
+
+
+
+fun burn_coin_fraction(coin: &mut Coin<AptosCoin>, burn_percentage: u8) acquires AptosCoinCapabilities {
+ assert!(burn_percentage <= 100, error::out_of_range(EINVALID_BURN_PERCENTAGE));
+
+ let collected_amount = coin::value(coin);
+ spec {
+ // We assume that `burn_percentage * collected_amount` does not overflow.
+ assume burn_percentage * collected_amount <= MAX_U64;
+ };
+ let amount_to_burn = (burn_percentage as u64) * collected_amount / 100;
+ if (amount_to_burn > 0) {
+ let coin_to_burn = coin::extract(coin, amount_to_burn);
+ coin::burn(
+ coin_to_burn,
+ &borrow_global<AptosCoinCapabilities>(@aptos_framework).burn_cap,
+ );
+ }
+}
+
+
+
+
+public(friend) fun process_collected_fees()
+
+
+
+
+public(friend) fun process_collected_fees() acquires AptosCoinCapabilities, CollectedFeesPerBlock {
+ if (!is_fees_collection_enabled()) {
+ return
+ };
+ let collected_fees = borrow_global_mut<CollectedFeesPerBlock>(@aptos_framework);
+
+ // If there are no collected fees, only unset the proposer. See the rationale for
+ // setting proposer to option::none() below.
+ if (coin::is_aggregatable_coin_zero(&collected_fees.amount)) {
+ if (option::is_some(&collected_fees.proposer)) {
+ let _ = option::extract(&mut collected_fees.proposer);
+ };
+ return
+ };
+
+ // Otherwise get the collected fee, and check if it can distributed later.
+ let coin = coin::drain_aggregatable_coin(&mut collected_fees.amount);
+ if (option::is_some(&collected_fees.proposer)) {
+ // Extract the address of proposer here and reset it to option::none(). This
+ // is particularly useful to avoid any undesired side-effects where coins are
+ // collected but never distributed or distributed to the wrong account.
+ // With this design, processing collected fees enforces that all fees will be burnt
+ // unless the proposer is specified in the block prologue. When we have a governance
+ // proposal that triggers reconfiguration, we distribute pending fees and burn the
+ // fee for the proposal. Otherwise, that fee would be leaked to the next block.
+ let proposer = option::extract(&mut collected_fees.proposer);
+
+ // Since the block can be produced by the VM itself, we have to make sure we catch
+ // this case.
+ if (proposer == @vm_reserved) {
+ burn_coin_fraction(&mut coin, 100);
+ coin::destroy_zero(coin);
+ return
+ };
+
+ burn_coin_fraction(&mut coin, collected_fees.burn_percentage);
+ stake::add_transaction_fee(proposer, coin);
+ return
+ };
+
+ // If checks did not pass, simply burn all collected coins and return none.
+ burn_coin_fraction(&mut coin, 100);
+ coin::destroy_zero(coin)
+}
+
+
+
+
+public(friend) fun burn_fee(account: address, fee: u64)
+
+
+
+
+public(friend) fun burn_fee(account: address, fee: u64) acquires AptosFABurnCapabilities, AptosCoinCapabilities {
+ if (exists<AptosFABurnCapabilities>(@aptos_framework)) {
+ let burn_ref = &borrow_global<AptosFABurnCapabilities>(@aptos_framework).burn_ref;
+ aptos_account::burn_from_fungible_store(burn_ref, account, fee);
+ } else {
+ let burn_cap = &borrow_global<AptosCoinCapabilities>(@aptos_framework).burn_cap;
+ if (features::operations_default_to_fa_apt_store_enabled()) {
+ let (burn_ref, burn_receipt) = coin::get_paired_burn_ref(burn_cap);
+ aptos_account::burn_from_fungible_store(&burn_ref, account, fee);
+ coin::return_paired_burn_ref(burn_ref, burn_receipt);
+ } else {
+ coin::burn_from<AptosCoin>(
+ account,
+ fee,
+ burn_cap,
+ );
+ };
+ };
+}
+
+
+
+
+public(friend) fun mint_and_refund(account: address, refund: u64)
+
+
+
+
+public(friend) fun mint_and_refund(account: address, refund: u64) acquires AptosCoinMintCapability {
+ let mint_cap = &borrow_global<AptosCoinMintCapability>(@aptos_framework).mint_cap;
+ let refund_coin = coin::mint(refund, mint_cap);
+ coin::force_deposit(account, refund_coin);
+}
+
+
+
+
+public(friend) fun collect_fee(account: address, fee: u64)
+
+
+
+
+public(friend) fun collect_fee(account: address, fee: u64) acquires CollectedFeesPerBlock {
+ let collected_fees = borrow_global_mut<CollectedFeesPerBlock>(@aptos_framework);
+
+ // Here, we are always optimistic and always collect fees. If the proposer is not set,
+ // or we cannot redistribute fees later for some reason (e.g. account cannot receive AptoCoin)
+ // we burn them all at once. This way we avoid having a check for every transaction epilogue.
+ let collected_amount = &mut collected_fees.amount;
+ coin::collect_into_aggregatable_coin<AptosCoin>(account, fee, collected_amount);
+}
+
+
+
+
+public(friend) fun store_aptos_coin_burn_cap(aptos_framework: &signer, burn_cap: coin::BurnCapability<aptos_coin::AptosCoin>)
+
+
+
+
+public(friend) fun store_aptos_coin_burn_cap(aptos_framework: &signer, burn_cap: BurnCapability<AptosCoin>) {
+ system_addresses::assert_aptos_framework(aptos_framework);
+
+ if (features::operations_default_to_fa_apt_store_enabled()) {
+ let burn_ref = coin::convert_and_take_paired_burn_ref(burn_cap);
+ move_to(aptos_framework, AptosFABurnCapabilities { burn_ref });
+ } else {
+ move_to(aptos_framework, AptosCoinCapabilities { burn_cap })
+ }
+}
+
+
+
+
+public entry fun convert_to_aptos_fa_burn_ref(aptos_framework: &signer)
+
+
+
+
+public entry fun convert_to_aptos_fa_burn_ref(aptos_framework: &signer) acquires AptosCoinCapabilities {
+ assert!(features::operations_default_to_fa_apt_store_enabled(), EFA_GAS_CHARGING_NOT_ENABLED);
+ system_addresses::assert_aptos_framework(aptos_framework);
+ let AptosCoinCapabilities {
+ burn_cap,
+ } = move_from<AptosCoinCapabilities>(signer::address_of(aptos_framework));
+ let burn_ref = coin::convert_and_take_paired_burn_ref(burn_cap);
+ move_to(aptos_framework, AptosFABurnCapabilities { burn_ref });
+}
+
+
+
+
+public(friend) fun store_aptos_coin_mint_cap(aptos_framework: &signer, mint_cap: coin::MintCapability<aptos_coin::AptosCoin>)
+
+
+
+
+public(friend) fun store_aptos_coin_mint_cap(aptos_framework: &signer, mint_cap: MintCapability<AptosCoin>) {
+ system_addresses::assert_aptos_framework(aptos_framework);
+ move_to(aptos_framework, AptosCoinMintCapability { mint_cap })
+}
+
+
+
+
+#[deprecated]
+public fun initialize_storage_refund(_: &signer)
+
+
+
+
+public fun initialize_storage_refund(_: &signer) {
+ abort error::not_implemented(ENO_LONGER_SUPPORTED)
+}
+
+
+
+
+fun emit_fee_statement(fee_statement: transaction_fee::FeeStatement)
+
+
+
+
+fun emit_fee_statement(fee_statement: FeeStatement) {
+ event::emit(fee_statement)
+}
+
+
+
+
+No. | Requirement | Criticality | Implementation | Enforcement | +
---|---|---|---|---|
1 | +Given the blockchain is in an operating state, it guarantees that the Aptos framework signer may burn Aptos coins. | +Critical | +The AptosCoinCapabilities structure is defined in this module and it stores burn capability to burn the gas fees. | +Formally Verified via module. | +
2 | +The initialization function may only be called once. | +Medium | +The initialize_fee_collection_and_distribution function ensures CollectedFeesPerBlock does not already exist. | +Formally verified via initialize_fee_collection_and_distribution. | +
3 | +Only the admin address is authorized to call the initialization function. | +Critical | +The initialize_fee_collection_and_distribution function ensures only the Aptos framework address calls it. | +Formally verified via initialize_fee_collection_and_distribution. | +
4 | +The percentage of the burnt collected fee is always a value from 0 to 100. | +Medium | +During the initialization of CollectedFeesPerBlock in Initialize_fee_collection_and_distribution, and while upgrading burn percentage, it asserts that burn_percentage is within the specified limits. | +Formally verified via CollectedFeesPerBlock. | +
5 | +Prior to upgrading the burn percentage, it must process all the fees collected up to that point. | +Critical | +The upgrade_burn_percentage function ensures process_collected_fees function is called before updating the burn percentage. | +Formally verified in ProcessCollectedFeesRequiresAndEnsures. | +
6 | +The presence of the resource, indicating collected fees per block under the Aptos framework account, is a prerequisite for the successful execution of the following functionalities: Upgrading burn percentage. Registering a block proposer. Processing collected fees. | +Low | +The functions: upgrade_burn_percentage, register_proposer_for_fee_collection, and process_collected_fees all ensure that the CollectedFeesPerBlock resource exists under aptos_framework by calling the is_fees_collection_enabled method, which returns a boolean value confirming if the resource exists or not. | +Formally verified via register_proposer_for_fee_collection, process_collected_fees, and upgrade_burn_percentage. | +
pragma verify = false;
+pragma aborts_if_is_strict;
+// This enforces high-level requirement 1:
+invariant [suspendable] chain_status::is_operating() ==> exists<AptosCoinCapabilities>(@aptos_framework) || exists<AptosFABurnCapabilities>(@aptos_framework);
+
+
+
+
+
+
+### Resource `CollectedFeesPerBlock`
+
+
+struct CollectedFeesPerBlock has key
+
+
+
+
+amount: coin::AggregatableCoin<aptos_coin::AptosCoin>
+proposer: option::Option<address>
+burn_percentage: u8
+// This enforces high-level requirement 4:
+invariant burn_percentage <= 100;
+
+
+
+
+
+
+### Function `initialize_fee_collection_and_distribution`
+
+
+public fun initialize_fee_collection_and_distribution(aptos_framework: &signer, burn_percentage: u8)
+
+
+
+
+
+// This enforces high-level requirement 2:
+aborts_if exists<CollectedFeesPerBlock>(@aptos_framework);
+aborts_if burn_percentage > 100;
+let aptos_addr = signer::address_of(aptos_framework);
+// This enforces high-level requirement 3:
+aborts_if !system_addresses::is_aptos_framework_address(aptos_addr);
+aborts_if exists<ValidatorFees>(aptos_addr);
+include system_addresses::AbortsIfNotAptosFramework { account: aptos_framework };
+include aggregator_factory::CreateAggregatorInternalAbortsIf;
+aborts_if exists<CollectedFeesPerBlock>(aptos_addr);
+ensures exists<ValidatorFees>(aptos_addr);
+ensures exists<CollectedFeesPerBlock>(aptos_addr);
+
+
+
+
+
+
+### Function `upgrade_burn_percentage`
+
+
+public fun upgrade_burn_percentage(aptos_framework: &signer, new_burn_percentage: u8)
+
+
+
+
+
+aborts_if new_burn_percentage > 100;
+let aptos_addr = signer::address_of(aptos_framework);
+aborts_if !system_addresses::is_aptos_framework_address(aptos_addr);
+// This enforces high-level requirement 5 and high-level requirement 6:
+include ProcessCollectedFeesRequiresAndEnsures;
+ensures exists<CollectedFeesPerBlock>(@aptos_framework) ==>
+ global<CollectedFeesPerBlock>(@aptos_framework).burn_percentage == new_burn_percentage;
+
+
+
+
+
+
+### Function `register_proposer_for_fee_collection`
+
+
+public(friend) fun register_proposer_for_fee_collection(proposer_addr: address)
+
+
+
+
+
+aborts_if false;
+// This enforces high-level requirement 6:
+ensures is_fees_collection_enabled() ==>
+ option::spec_borrow(global<CollectedFeesPerBlock>(@aptos_framework).proposer) == proposer_addr;
+
+
+
+
+
+
+### Function `burn_coin_fraction`
+
+
+fun burn_coin_fraction(coin: &mut coin::Coin<aptos_coin::AptosCoin>, burn_percentage: u8)
+
+
+
+
+
+requires burn_percentage <= 100;
+requires exists<AptosCoinCapabilities>(@aptos_framework);
+requires exists<CoinInfo<AptosCoin>>(@aptos_framework);
+let amount_to_burn = (burn_percentage * coin::value(coin)) / 100;
+include amount_to_burn > 0 ==> coin::CoinSubAbortsIf<AptosCoin> { amount: amount_to_burn };
+ensures coin.value == old(coin).value - amount_to_burn;
+
+
+
+
+
+
+
+
+fun collectedFeesAggregator(): AggregatableCoin<AptosCoin> {
+ global<CollectedFeesPerBlock>(@aptos_framework).amount
+}
+
+
+
+
+
+
+
+
+schema RequiresCollectedFeesPerValueLeqBlockAptosSupply {
+ let maybe_supply = coin::get_coin_supply_opt<AptosCoin>();
+ requires
+ (is_fees_collection_enabled() && option::is_some(maybe_supply)) ==>
+ (aggregator::spec_aggregator_get_val(global<CollectedFeesPerBlock>(@aptos_framework).amount.value) <=
+ optional_aggregator::optional_aggregator_value(
+ option::spec_borrow(coin::get_coin_supply_opt<AptosCoin>())
+ ));
+}
+
+
+
+
+
+
+
+
+schema ProcessCollectedFeesRequiresAndEnsures {
+ requires exists<AptosCoinCapabilities>(@aptos_framework);
+ requires exists<stake::ValidatorFees>(@aptos_framework);
+ requires exists<CoinInfo<AptosCoin>>(@aptos_framework);
+ include RequiresCollectedFeesPerValueLeqBlockAptosSupply;
+ aborts_if false;
+ let collected_fees = global<CollectedFeesPerBlock>(@aptos_framework);
+ let post post_collected_fees = global<CollectedFeesPerBlock>(@aptos_framework);
+ let pre_amount = aggregator::spec_aggregator_get_val(collected_fees.amount.value);
+ let post post_amount = aggregator::spec_aggregator_get_val(post_collected_fees.amount.value);
+ let fees_table = global<stake::ValidatorFees>(@aptos_framework).fees_table;
+ let post post_fees_table = global<stake::ValidatorFees>(@aptos_framework).fees_table;
+ let proposer = option::spec_borrow(collected_fees.proposer);
+ let fee_to_add = pre_amount - pre_amount * collected_fees.burn_percentage / 100;
+ ensures is_fees_collection_enabled() ==> option::spec_is_none(post_collected_fees.proposer) && post_amount == 0;
+ ensures is_fees_collection_enabled() && aggregator::spec_read(collected_fees.amount.value) > 0 &&
+ option::spec_is_some(collected_fees.proposer) ==>
+ if (proposer != @vm_reserved) {
+ if (table::spec_contains(fees_table, proposer)) {
+ table::spec_get(post_fees_table, proposer).value == table::spec_get(
+ fees_table,
+ proposer
+ ).value + fee_to_add
+ } else {
+ table::spec_get(post_fees_table, proposer).value == fee_to_add
+ }
+ } else {
+ option::spec_is_none(post_collected_fees.proposer) && post_amount == 0
+ };
+}
+
+
+
+
+
+
+### Function `process_collected_fees`
+
+
+public(friend) fun process_collected_fees()
+
+
+
+
+
+// This enforces high-level requirement 6:
+include ProcessCollectedFeesRequiresAndEnsures;
+
+
+
+
+
+
+### Function `burn_fee`
+
+
+public(friend) fun burn_fee(account: address, fee: u64)
+
+
+
+AptosCoinCapabilities
should be exists.
+
+
+pragma verify = false;
+aborts_if !exists<AptosCoinCapabilities>(@aptos_framework);
+let account_addr = account;
+let amount = fee;
+let aptos_addr = type_info::type_of<AptosCoin>().account_address;
+let coin_store = global<CoinStore<AptosCoin>>(account_addr);
+let post post_coin_store = global<CoinStore<AptosCoin>>(account_addr);
+aborts_if amount != 0 && !(exists<CoinInfo<AptosCoin>>(aptos_addr)
+ && exists<CoinStore<AptosCoin>>(account_addr));
+aborts_if coin_store.coin.value < amount;
+let maybe_supply = global<CoinInfo<AptosCoin>>(aptos_addr).supply;
+let supply_aggr = option::spec_borrow(maybe_supply);
+let value = optional_aggregator::optional_aggregator_value(supply_aggr);
+let post post_maybe_supply = global<CoinInfo<AptosCoin>>(aptos_addr).supply;
+let post post_supply = option::spec_borrow(post_maybe_supply);
+let post post_value = optional_aggregator::optional_aggregator_value(post_supply);
+aborts_if option::spec_is_some(maybe_supply) && value < amount;
+ensures post_coin_store.coin.value == coin_store.coin.value - amount;
+ensures if (option::spec_is_some(maybe_supply)) {
+ post_value == value - amount
+} else {
+ option::spec_is_none(post_maybe_supply)
+};
+ensures coin::supply<AptosCoin> == old(coin::supply<AptosCoin>) - amount;
+
+
+
+
+
+
+### Function `mint_and_refund`
+
+
+public(friend) fun mint_and_refund(account: address, refund: u64)
+
+
+
+
+
+pragma verify = false;
+let aptos_addr = type_info::type_of<AptosCoin>().account_address;
+aborts_if (refund != 0) && !exists<CoinInfo<AptosCoin>>(aptos_addr);
+include coin::CoinAddAbortsIf<AptosCoin> { amount: refund };
+aborts_if !exists<CoinStore<AptosCoin>>(account);
+aborts_if !exists<AptosCoinMintCapability>(@aptos_framework);
+let supply = coin::supply<AptosCoin>;
+let post post_supply = coin::supply<AptosCoin>;
+aborts_if [abstract] supply + refund > MAX_U128;
+ensures post_supply == supply + refund;
+
+
+
+
+
+
+### Function `collect_fee`
+
+
+public(friend) fun collect_fee(account: address, fee: u64)
+
+
+
+
+
+pragma verify = false;
+let collected_fees = global<CollectedFeesPerBlock>(@aptos_framework).amount;
+let aggr = collected_fees.value;
+let coin_store = global<coin::CoinStore<AptosCoin>>(account);
+aborts_if !exists<CollectedFeesPerBlock>(@aptos_framework);
+aborts_if fee > 0 && !exists<coin::CoinStore<AptosCoin>>(account);
+aborts_if fee > 0 && coin_store.coin.value < fee;
+aborts_if fee > 0 && aggregator::spec_aggregator_get_val(aggr)
+ + fee > aggregator::spec_get_limit(aggr);
+aborts_if fee > 0 && aggregator::spec_aggregator_get_val(aggr)
+ + fee > MAX_U128;
+let post post_coin_store = global<coin::CoinStore<AptosCoin>>(account);
+let post post_collected_fees = global<CollectedFeesPerBlock>(@aptos_framework).amount;
+ensures post_coin_store.coin.value == coin_store.coin.value - fee;
+ensures aggregator::spec_aggregator_get_val(post_collected_fees.value) == aggregator::spec_aggregator_get_val(
+ aggr
+) + fee;
+
+
+
+
+
+
+### Function `store_aptos_coin_burn_cap`
+
+
+public(friend) fun store_aptos_coin_burn_cap(aptos_framework: &signer, burn_cap: coin::BurnCapability<aptos_coin::AptosCoin>)
+
+
+
+Ensure caller is admin.
+Aborts if AptosCoinCapabilities
already exists.
+
+
+pragma verify = false;
+let addr = signer::address_of(aptos_framework);
+aborts_if !system_addresses::is_aptos_framework_address(addr);
+aborts_if exists<AptosFABurnCapabilities>(addr);
+aborts_if exists<AptosCoinCapabilities>(addr);
+ensures exists<AptosFABurnCapabilities>(addr) || exists<AptosCoinCapabilities>(addr);
+
+
+
+
+
+
+### Function `store_aptos_coin_mint_cap`
+
+
+public(friend) fun store_aptos_coin_mint_cap(aptos_framework: &signer, mint_cap: coin::MintCapability<aptos_coin::AptosCoin>)
+
+
+
+Ensure caller is admin.
+Aborts if AptosCoinMintCapability
already exists.
+
+
+let addr = signer::address_of(aptos_framework);
+aborts_if !system_addresses::is_aptos_framework_address(addr);
+aborts_if exists<AptosCoinMintCapability>(addr);
+ensures exists<AptosCoinMintCapability>(addr);
+
+
+
+
+
+
+### Function `initialize_storage_refund`
+
+
+#[deprecated]
+public fun initialize_storage_refund(_: &signer)
+
+
+
+Historical. Aborts.
+
+
+aborts_if true;
+
+
+
+
+
+
+### Function `emit_fee_statement`
+
+
+fun emit_fee_statement(fee_statement: transaction_fee::FeeStatement)
+
+
+
+Aborts if module event feature is not enabled.
+
+
+[move-book]: https://aptos.dev/move/book/SUMMARY
diff --git a/aptos-move/framework/aptos-framework/tests/compiler-v2-doc/transaction_validation.md b/aptos-move/framework/aptos-framework/tests/compiler-v2-doc/transaction_validation.md
new file mode 100644
index 0000000000000..adebab98ec1c2
--- /dev/null
+++ b/aptos-move/framework/aptos-framework/tests/compiler-v2-doc/transaction_validation.md
@@ -0,0 +1,1033 @@
+
+
+
+# Module `0x1::transaction_validation`
+
+
+
+- [Resource `TransactionValidation`](#0x1_transaction_validation_TransactionValidation)
+- [Constants](#@Constants_0)
+- [Function `initialize`](#0x1_transaction_validation_initialize)
+- [Function `prologue_common`](#0x1_transaction_validation_prologue_common)
+- [Function `script_prologue`](#0x1_transaction_validation_script_prologue)
+- [Function `multi_agent_script_prologue`](#0x1_transaction_validation_multi_agent_script_prologue)
+- [Function `multi_agent_common_prologue`](#0x1_transaction_validation_multi_agent_common_prologue)
+- [Function `fee_payer_script_prologue`](#0x1_transaction_validation_fee_payer_script_prologue)
+- [Function `epilogue`](#0x1_transaction_validation_epilogue)
+- [Function `epilogue_gas_payer`](#0x1_transaction_validation_epilogue_gas_payer)
+- [Specification](#@Specification_1)
+ - [High-level Requirements](#high-level-req)
+ - [Module-level Specification](#module-level-spec)
+ - [Function `initialize`](#@Specification_1_initialize)
+ - [Function `prologue_common`](#@Specification_1_prologue_common)
+ - [Function `script_prologue`](#@Specification_1_script_prologue)
+ - [Function `multi_agent_script_prologue`](#@Specification_1_multi_agent_script_prologue)
+ - [Function `multi_agent_common_prologue`](#@Specification_1_multi_agent_common_prologue)
+ - [Function `fee_payer_script_prologue`](#@Specification_1_fee_payer_script_prologue)
+ - [Function `epilogue`](#@Specification_1_epilogue)
+ - [Function `epilogue_gas_payer`](#@Specification_1_epilogue_gas_payer)
+
+
+use 0x1::account;
+use 0x1::aptos_account;
+use 0x1::aptos_coin;
+use 0x1::bcs;
+use 0x1::chain_id;
+use 0x1::coin;
+use 0x1::error;
+use 0x1::features;
+use 0x1::signer;
+use 0x1::system_addresses;
+use 0x1::timestamp;
+use 0x1::transaction_fee;
+
+
+
+
+
+
+## Resource `TransactionValidation`
+
+This holds information that will be picked up by the VM to call the
+correct chain-specific prologue and epilogue functions
+
+
+struct TransactionValidation has key
+
+
+
+
+module_addr: address
+module_name: vector<u8>
+script_prologue_name: vector<u8>
+module_prologue_name: vector<u8>
+multi_agent_prologue_name: vector<u8>
+user_epilogue_name: vector<u8>
+const MAX_U64: u128 = 18446744073709551615;
+
+
+
+
+
+
+Transaction exceeded its allocated max gas
+
+
+const EOUT_OF_GAS: u64 = 6;
+
+
+
+
+
+
+
+
+const PROLOGUE_EACCOUNT_DOES_NOT_EXIST: u64 = 1004;
+
+
+
+
+
+
+
+
+const PROLOGUE_EBAD_CHAIN_ID: u64 = 1007;
+
+
+
+
+
+
+
+
+const PROLOGUE_ECANT_PAY_GAS_DEPOSIT: u64 = 1005;
+
+
+
+
+
+
+
+
+const PROLOGUE_EFEE_PAYER_NOT_ENABLED: u64 = 1010;
+
+
+
+
+
+
+Prologue errors. These are separated out from the other errors in this
+module since they are mapped separately to major VM statuses, and are
+important to the semantics of the system.
+
+
+const PROLOGUE_EINVALID_ACCOUNT_AUTH_KEY: u64 = 1001;
+
+
+
+
+
+
+
+
+const PROLOGUE_ESECONDARY_KEYS_ADDRESSES_COUNT_MISMATCH: u64 = 1009;
+
+
+
+
+
+
+
+
+const PROLOGUE_ESEQUENCE_NUMBER_TOO_BIG: u64 = 1008;
+
+
+
+
+
+
+
+
+const PROLOGUE_ESEQUENCE_NUMBER_TOO_NEW: u64 = 1003;
+
+
+
+
+
+
+
+
+const PROLOGUE_ESEQUENCE_NUMBER_TOO_OLD: u64 = 1002;
+
+
+
+
+
+
+
+
+const PROLOGUE_ETRANSACTION_EXPIRED: u64 = 1006;
+
+
+
+
+
+
+## Function `initialize`
+
+Only called during genesis to initialize system resources for this module.
+
+
+public(friend) fun initialize(aptos_framework: &signer, script_prologue_name: vector<u8>, module_prologue_name: vector<u8>, multi_agent_prologue_name: vector<u8>, user_epilogue_name: vector<u8>)
+
+
+
+
+public(friend) fun initialize(
+ aptos_framework: &signer,
+ script_prologue_name: vector<u8>,
+ // module_prologue_name is deprecated and not used.
+ module_prologue_name: vector<u8>,
+ multi_agent_prologue_name: vector<u8>,
+ user_epilogue_name: vector<u8>,
+) {
+ system_addresses::assert_aptos_framework(aptos_framework);
+
+ move_to(aptos_framework, TransactionValidation {
+ module_addr: @aptos_framework,
+ module_name: b"transaction_validation",
+ script_prologue_name,
+ // module_prologue_name is deprecated and not used.
+ module_prologue_name,
+ multi_agent_prologue_name,
+ user_epilogue_name,
+ });
+}
+
+
+
+
+fun prologue_common(sender: signer, gas_payer: address, txn_sequence_number: u64, txn_authentication_key: vector<u8>, txn_gas_price: u64, txn_max_gas_units: u64, txn_expiration_time: u64, chain_id: u8)
+
+
+
+
+fun prologue_common(
+ sender: signer,
+ gas_payer: address,
+ txn_sequence_number: u64,
+ txn_authentication_key: vector<u8>,
+ txn_gas_price: u64,
+ txn_max_gas_units: u64,
+ txn_expiration_time: u64,
+ chain_id: u8,
+) {
+ assert!(
+ timestamp::now_seconds() < txn_expiration_time,
+ error::invalid_argument(PROLOGUE_ETRANSACTION_EXPIRED),
+ );
+ assert!(chain_id::get() == chain_id, error::invalid_argument(PROLOGUE_EBAD_CHAIN_ID));
+
+ let transaction_sender = signer::address_of(&sender);
+
+ if (
+ transaction_sender == gas_payer
+ || account::exists_at(transaction_sender)
+ || !features::sponsored_automatic_account_creation_enabled()
+ || txn_sequence_number > 0
+ ) {
+ assert!(account::exists_at(transaction_sender), error::invalid_argument(PROLOGUE_EACCOUNT_DOES_NOT_EXIST));
+ assert!(
+ txn_authentication_key == account::get_authentication_key(transaction_sender),
+ error::invalid_argument(PROLOGUE_EINVALID_ACCOUNT_AUTH_KEY),
+ );
+
+ let account_sequence_number = account::get_sequence_number(transaction_sender);
+ assert!(
+ txn_sequence_number < (1u64 << 63),
+ error::out_of_range(PROLOGUE_ESEQUENCE_NUMBER_TOO_BIG)
+ );
+
+ assert!(
+ txn_sequence_number >= account_sequence_number,
+ error::invalid_argument(PROLOGUE_ESEQUENCE_NUMBER_TOO_OLD)
+ );
+
+ assert!(
+ txn_sequence_number == account_sequence_number,
+ error::invalid_argument(PROLOGUE_ESEQUENCE_NUMBER_TOO_NEW)
+ );
+ } else {
+ // In this case, the transaction is sponsored and the account does not exist, so ensure
+ // the default values match.
+ assert!(
+ txn_sequence_number == 0,
+ error::invalid_argument(PROLOGUE_ESEQUENCE_NUMBER_TOO_NEW)
+ );
+
+ assert!(
+ txn_authentication_key == bcs::to_bytes(&transaction_sender),
+ error::invalid_argument(PROLOGUE_EINVALID_ACCOUNT_AUTH_KEY),
+ );
+ };
+
+ let max_transaction_fee = txn_gas_price * txn_max_gas_units;
+
+ if (features::operations_default_to_fa_apt_store_enabled()) {
+ assert!(
+ aptos_account::is_fungible_balance_at_least(gas_payer, max_transaction_fee),
+ error::invalid_argument(PROLOGUE_ECANT_PAY_GAS_DEPOSIT)
+ );
+ } else {
+ assert!(
+ coin::is_balance_at_least<AptosCoin>(gas_payer, max_transaction_fee),
+ error::invalid_argument(PROLOGUE_ECANT_PAY_GAS_DEPOSIT)
+ );
+ }
+}
+
+
+
+
+fun script_prologue(sender: signer, txn_sequence_number: u64, txn_public_key: vector<u8>, txn_gas_price: u64, txn_max_gas_units: u64, txn_expiration_time: u64, chain_id: u8, _script_hash: vector<u8>)
+
+
+
+
+fun script_prologue(
+ sender: signer,
+ txn_sequence_number: u64,
+ txn_public_key: vector<u8>,
+ txn_gas_price: u64,
+ txn_max_gas_units: u64,
+ txn_expiration_time: u64,
+ chain_id: u8,
+ _script_hash: vector<u8>,
+) {
+ let gas_payer = signer::address_of(&sender);
+ prologue_common(
+ sender,
+ gas_payer,
+ txn_sequence_number,
+ txn_public_key,
+ txn_gas_price,
+ txn_max_gas_units,
+ txn_expiration_time,
+ chain_id
+ )
+}
+
+
+
+
+fun multi_agent_script_prologue(sender: signer, txn_sequence_number: u64, txn_sender_public_key: vector<u8>, secondary_signer_addresses: vector<address>, secondary_signer_public_key_hashes: vector<vector<u8>>, txn_gas_price: u64, txn_max_gas_units: u64, txn_expiration_time: u64, chain_id: u8)
+
+
+
+
+fun multi_agent_script_prologue(
+ sender: signer,
+ txn_sequence_number: u64,
+ txn_sender_public_key: vector<u8>,
+ secondary_signer_addresses: vector<address>,
+ secondary_signer_public_key_hashes: vector<vector<u8>>,
+ txn_gas_price: u64,
+ txn_max_gas_units: u64,
+ txn_expiration_time: u64,
+ chain_id: u8,
+) {
+ let sender_addr = signer::address_of(&sender);
+ prologue_common(
+ sender,
+ sender_addr,
+ txn_sequence_number,
+ txn_sender_public_key,
+ txn_gas_price,
+ txn_max_gas_units,
+ txn_expiration_time,
+ chain_id,
+ );
+ multi_agent_common_prologue(secondary_signer_addresses, secondary_signer_public_key_hashes);
+}
+
+
+
+
+fun multi_agent_common_prologue(secondary_signer_addresses: vector<address>, secondary_signer_public_key_hashes: vector<vector<u8>>)
+
+
+
+
+fun multi_agent_common_prologue(
+ secondary_signer_addresses: vector<address>,
+ secondary_signer_public_key_hashes: vector<vector<u8>>,
+) {
+ let num_secondary_signers = vector::length(&secondary_signer_addresses);
+ assert!(
+ vector::length(&secondary_signer_public_key_hashes) == num_secondary_signers,
+ error::invalid_argument(PROLOGUE_ESECONDARY_KEYS_ADDRESSES_COUNT_MISMATCH),
+ );
+
+ let i = 0;
+ while ({
+ spec {
+ invariant i <= num_secondary_signers;
+ invariant forall j in 0..i:
+ account::exists_at(secondary_signer_addresses[j])
+ && secondary_signer_public_key_hashes[j]
+ == account::get_authentication_key(secondary_signer_addresses[j]);
+ };
+ (i < num_secondary_signers)
+ }) {
+ let secondary_address = *vector::borrow(&secondary_signer_addresses, i);
+ assert!(account::exists_at(secondary_address), error::invalid_argument(PROLOGUE_EACCOUNT_DOES_NOT_EXIST));
+
+ let signer_public_key_hash = *vector::borrow(&secondary_signer_public_key_hashes, i);
+ assert!(
+ signer_public_key_hash == account::get_authentication_key(secondary_address),
+ error::invalid_argument(PROLOGUE_EINVALID_ACCOUNT_AUTH_KEY),
+ );
+ i = i + 1;
+ }
+}
+
+
+
+
+fun fee_payer_script_prologue(sender: signer, txn_sequence_number: u64, txn_sender_public_key: vector<u8>, secondary_signer_addresses: vector<address>, secondary_signer_public_key_hashes: vector<vector<u8>>, fee_payer_address: address, fee_payer_public_key_hash: vector<u8>, txn_gas_price: u64, txn_max_gas_units: u64, txn_expiration_time: u64, chain_id: u8)
+
+
+
+
+fun fee_payer_script_prologue(
+ sender: signer,
+ txn_sequence_number: u64,
+ txn_sender_public_key: vector<u8>,
+ secondary_signer_addresses: vector<address>,
+ secondary_signer_public_key_hashes: vector<vector<u8>>,
+ fee_payer_address: address,
+ fee_payer_public_key_hash: vector<u8>,
+ txn_gas_price: u64,
+ txn_max_gas_units: u64,
+ txn_expiration_time: u64,
+ chain_id: u8,
+) {
+ assert!(features::fee_payer_enabled(), error::invalid_state(PROLOGUE_EFEE_PAYER_NOT_ENABLED));
+ prologue_common(
+ sender,
+ fee_payer_address,
+ txn_sequence_number,
+ txn_sender_public_key,
+ txn_gas_price,
+ txn_max_gas_units,
+ txn_expiration_time,
+ chain_id,
+ );
+ multi_agent_common_prologue(secondary_signer_addresses, secondary_signer_public_key_hashes);
+ assert!(
+ fee_payer_public_key_hash == account::get_authentication_key(fee_payer_address),
+ error::invalid_argument(PROLOGUE_EINVALID_ACCOUNT_AUTH_KEY),
+ );
+}
+
+
+
+
+fun epilogue(account: signer, storage_fee_refunded: u64, txn_gas_price: u64, txn_max_gas_units: u64, gas_units_remaining: u64)
+
+
+
+
+fun epilogue(
+ account: signer,
+ storage_fee_refunded: u64,
+ txn_gas_price: u64,
+ txn_max_gas_units: u64,
+ gas_units_remaining: u64
+) {
+ let addr = signer::address_of(&account);
+ epilogue_gas_payer(account, addr, storage_fee_refunded, txn_gas_price, txn_max_gas_units, gas_units_remaining);
+}
+
+
+
+
+fun epilogue_gas_payer(account: signer, gas_payer: address, storage_fee_refunded: u64, txn_gas_price: u64, txn_max_gas_units: u64, gas_units_remaining: u64)
+
+
+
+
+fun epilogue_gas_payer(
+ account: signer,
+ gas_payer: address,
+ storage_fee_refunded: u64,
+ txn_gas_price: u64,
+ txn_max_gas_units: u64,
+ gas_units_remaining: u64
+) {
+ assert!(txn_max_gas_units >= gas_units_remaining, error::invalid_argument(EOUT_OF_GAS));
+ let gas_used = txn_max_gas_units - gas_units_remaining;
+
+ assert!(
+ (txn_gas_price as u128) * (gas_used as u128) <= MAX_U64,
+ error::out_of_range(EOUT_OF_GAS)
+ );
+ let transaction_fee_amount = txn_gas_price * gas_used;
+
+ // it's important to maintain the error code consistent with vm
+ // to do failed transaction cleanup.
+ if (features::operations_default_to_fa_apt_store_enabled()) {
+ assert!(
+ aptos_account::is_fungible_balance_at_least(gas_payer, transaction_fee_amount),
+ error::out_of_range(PROLOGUE_ECANT_PAY_GAS_DEPOSIT),
+ );
+ } else {
+ assert!(
+ coin::is_balance_at_least<AptosCoin>(gas_payer, transaction_fee_amount),
+ error::out_of_range(PROLOGUE_ECANT_PAY_GAS_DEPOSIT),
+ );
+ };
+
+ let amount_to_burn = if (features::collect_and_distribute_gas_fees()) {
+ // TODO(gas): We might want to distinguish the refundable part of the charge and burn it or track
+ // it separately, so that we don't increase the total supply by refunding.
+
+ // If transaction fees are redistributed to validators, collect them here for
+ // later redistribution.
+ transaction_fee::collect_fee(gas_payer, transaction_fee_amount);
+ 0
+ } else {
+ // Otherwise, just burn the fee.
+ // TODO: this branch should be removed completely when transaction fee collection
+ // is tested and is fully proven to work well.
+ transaction_fee_amount
+ };
+
+ if (amount_to_burn > storage_fee_refunded) {
+ let burn_amount = amount_to_burn - storage_fee_refunded;
+ transaction_fee::burn_fee(gas_payer, burn_amount);
+ } else if (amount_to_burn < storage_fee_refunded) {
+ let mint_amount = storage_fee_refunded - amount_to_burn;
+ transaction_fee::mint_and_refund(gas_payer, mint_amount)
+ };
+
+ // Increment sequence number
+ let addr = signer::address_of(&account);
+ account::increment_sequence_number(addr);
+}
+
+
+
+
+No. | Requirement | Criticality | Implementation | Enforcement | +
---|---|---|---|---|
1 | +The sender of a transaction should have sufficient coin balance to pay the transaction fee. | +High | +The prologue_common function asserts that the transaction sender has enough coin balance to be paid as the max_transaction_fee. | +Formally verified via PrologueCommonAbortsIf. Moreover, the native transaction validation patterns have been manually audited. | +
2 | +All secondary signer addresses are verified to be authentic through a validation process. | +Critical | +The function multi_agent_script_prologue ensures that each secondary signer address undergoes authentication validation, including verification of account existence and authentication key matching, confirming their authenticity. | +Formally verified via multi_agent_script_prologue. Moreover, the native transaction validation patterns have been manually audited. | +
3 | +After successful execution, base the transaction fee on the configuration set by the features library. | +High | +The epilogue function collects the transaction fee for either redistribution or burning based on the feature::collect_and_distribute_gas_fees result. | +Formally Verified via epilogue. Moreover, the native transaction validation patterns have been manually audited. | +
pragma verify = true;
+pragma aborts_if_is_strict;
+
+
+
+
+
+
+### Function `initialize`
+
+
+public(friend) fun initialize(aptos_framework: &signer, script_prologue_name: vector<u8>, module_prologue_name: vector<u8>, multi_agent_prologue_name: vector<u8>, user_epilogue_name: vector<u8>)
+
+
+
+Ensure caller is aptos_framework
.
+Aborts if TransactionValidation already exists.
+
+
+let addr = signer::address_of(aptos_framework);
+aborts_if !system_addresses::is_aptos_framework_address(addr);
+aborts_if exists<TransactionValidation>(addr);
+ensures exists<TransactionValidation>(addr);
+
+
+
+Create a schema to reuse some code.
+Give some constraints that may abort according to the conditions.
+
+
+
+
+
+schema PrologueCommonAbortsIf {
+ sender: signer;
+ gas_payer: address;
+ txn_sequence_number: u64;
+ txn_authentication_key: vector<u8>;
+ txn_gas_price: u64;
+ txn_max_gas_units: u64;
+ txn_expiration_time: u64;
+ chain_id: u8;
+ aborts_if !exists<CurrentTimeMicroseconds>(@aptos_framework);
+ aborts_if !(timestamp::now_seconds() < txn_expiration_time);
+ aborts_if !exists<ChainId>(@aptos_framework);
+ aborts_if !(chain_id::get() == chain_id);
+ let transaction_sender = signer::address_of(sender);
+ aborts_if (
+ !features::spec_is_enabled(features::SPONSORED_AUTOMATIC_ACCOUNT_CREATION)
+ || account::exists_at(transaction_sender)
+ || transaction_sender == gas_payer
+ || txn_sequence_number > 0
+ ) && (
+ !(txn_sequence_number >= global<Account>(transaction_sender).sequence_number)
+ || !(txn_authentication_key == global<Account>(transaction_sender).authentication_key)
+ || !account::exists_at(transaction_sender)
+ || !(txn_sequence_number == global<Account>(transaction_sender).sequence_number)
+ );
+ aborts_if features::spec_is_enabled(features::SPONSORED_AUTOMATIC_ACCOUNT_CREATION)
+ && transaction_sender != gas_payer
+ && txn_sequence_number == 0
+ && !account::exists_at(transaction_sender)
+ && txn_authentication_key != bcs::to_bytes(transaction_sender);
+ aborts_if !(txn_sequence_number < (1u64 << 63));
+ let max_transaction_fee = txn_gas_price * txn_max_gas_units;
+ aborts_if max_transaction_fee > MAX_U64;
+ aborts_if !exists<CoinStore<AptosCoin>>(gas_payer);
+ // This enforces high-level requirement 1:
+ aborts_if !(global<CoinStore<AptosCoin>>(gas_payer).coin.value >= max_transaction_fee);
+}
+
+
+
+
+
+
+### Function `prologue_common`
+
+
+fun prologue_common(sender: signer, gas_payer: address, txn_sequence_number: u64, txn_authentication_key: vector<u8>, txn_gas_price: u64, txn_max_gas_units: u64, txn_expiration_time: u64, chain_id: u8)
+
+
+
+
+
+pragma verify = false;
+include PrologueCommonAbortsIf;
+
+
+
+
+
+
+### Function `script_prologue`
+
+
+fun script_prologue(sender: signer, txn_sequence_number: u64, txn_public_key: vector<u8>, txn_gas_price: u64, txn_max_gas_units: u64, txn_expiration_time: u64, chain_id: u8, _script_hash: vector<u8>)
+
+
+
+
+
+pragma verify = false;
+include PrologueCommonAbortsIf {
+ gas_payer: signer::address_of(sender),
+ txn_authentication_key: txn_public_key
+};
+
+
+
+
+
+
+
+
+schema MultiAgentPrologueCommonAbortsIf {
+ secondary_signer_addresses: vector<address>;
+ secondary_signer_public_key_hashes: vector<vector<u8>>;
+ let num_secondary_signers = len(secondary_signer_addresses);
+ aborts_if len(secondary_signer_public_key_hashes) != num_secondary_signers;
+ // This enforces high-level requirement 2:
+ aborts_if exists i in 0..num_secondary_signers:
+ !account::exists_at(secondary_signer_addresses[i])
+ || secondary_signer_public_key_hashes[i] !=
+ account::get_authentication_key(secondary_signer_addresses[i]);
+ ensures forall i in 0..num_secondary_signers:
+ account::exists_at(secondary_signer_addresses[i])
+ && secondary_signer_public_key_hashes[i] ==
+ account::get_authentication_key(secondary_signer_addresses[i]);
+}
+
+
+
+
+
+
+### Function `multi_agent_script_prologue`
+
+
+fun multi_agent_script_prologue(sender: signer, txn_sequence_number: u64, txn_sender_public_key: vector<u8>, secondary_signer_addresses: vector<address>, secondary_signer_public_key_hashes: vector<vector<u8>>, txn_gas_price: u64, txn_max_gas_units: u64, txn_expiration_time: u64, chain_id: u8)
+
+
+
+Aborts if length of public key hashed vector
+not equal the number of singers.
+
+
+pragma verify_duration_estimate = 120;
+let gas_payer = signer::address_of(sender);
+pragma verify = false;
+include PrologueCommonAbortsIf {
+ gas_payer,
+ txn_sequence_number,
+ txn_authentication_key: txn_sender_public_key,
+};
+include MultiAgentPrologueCommonAbortsIf {
+ secondary_signer_addresses,
+ secondary_signer_public_key_hashes,
+};
+
+
+
+
+
+
+### Function `multi_agent_common_prologue`
+
+
+fun multi_agent_common_prologue(secondary_signer_addresses: vector<address>, secondary_signer_public_key_hashes: vector<vector<u8>>)
+
+
+
+
+
+include MultiAgentPrologueCommonAbortsIf {
+ secondary_signer_addresses,
+ secondary_signer_public_key_hashes,
+};
+
+
+
+
+
+
+### Function `fee_payer_script_prologue`
+
+
+fun fee_payer_script_prologue(sender: signer, txn_sequence_number: u64, txn_sender_public_key: vector<u8>, secondary_signer_addresses: vector<address>, secondary_signer_public_key_hashes: vector<vector<u8>>, fee_payer_address: address, fee_payer_public_key_hash: vector<u8>, txn_gas_price: u64, txn_max_gas_units: u64, txn_expiration_time: u64, chain_id: u8)
+
+
+
+
+
+pragma verify_duration_estimate = 120;
+aborts_if !features::spec_is_enabled(features::FEE_PAYER_ENABLED);
+let gas_payer = fee_payer_address;
+include PrologueCommonAbortsIf {
+ gas_payer,
+ txn_sequence_number,
+ txn_authentication_key: txn_sender_public_key,
+};
+include MultiAgentPrologueCommonAbortsIf {
+ secondary_signer_addresses,
+ secondary_signer_public_key_hashes,
+};
+aborts_if !account::exists_at(gas_payer);
+aborts_if !(fee_payer_public_key_hash == account::get_authentication_key(gas_payer));
+aborts_if !features::spec_fee_payer_enabled();
+
+
+
+
+
+
+### Function `epilogue`
+
+
+fun epilogue(account: signer, storage_fee_refunded: u64, txn_gas_price: u64, txn_max_gas_units: u64, gas_units_remaining: u64)
+
+
+
+Abort according to the conditions.
+AptosCoinCapabilities
and CoinInfo
should exists.
+Skip transaction_fee::burn_fee verification.
+
+
+pragma verify = false;
+include EpilogueGasPayerAbortsIf { gas_payer: signer::address_of(account) };
+
+
+
+
+
+
+### Function `epilogue_gas_payer`
+
+
+fun epilogue_gas_payer(account: signer, gas_payer: address, storage_fee_refunded: u64, txn_gas_price: u64, txn_max_gas_units: u64, gas_units_remaining: u64)
+
+
+
+Abort according to the conditions.
+AptosCoinCapabilities
and CoinInfo
should exist.
+Skip transaction_fee::burn_fee verification.
+
+
+pragma verify = false;
+include EpilogueGasPayerAbortsIf;
+
+
+
+
+
+
+
+
+schema EpilogueGasPayerAbortsIf {
+ account: signer;
+ gas_payer: address;
+ storage_fee_refunded: u64;
+ txn_gas_price: u64;
+ txn_max_gas_units: u64;
+ gas_units_remaining: u64;
+ aborts_if !(txn_max_gas_units >= gas_units_remaining);
+ let gas_used = txn_max_gas_units - gas_units_remaining;
+ aborts_if !(txn_gas_price * gas_used <= MAX_U64);
+ let transaction_fee_amount = txn_gas_price * gas_used;
+ let addr = signer::address_of(account);
+ let pre_account = global<account::Account>(addr);
+ let post account = global<account::Account>(addr);
+ aborts_if !exists<CoinStore<AptosCoin>>(gas_payer);
+ aborts_if !exists<Account>(addr);
+ aborts_if !(global<Account>(addr).sequence_number < MAX_U64);
+ ensures account.sequence_number == pre_account.sequence_number + 1;
+ let collect_fee_enabled = features::spec_is_enabled(features::COLLECT_AND_DISTRIBUTE_GAS_FEES);
+ let collected_fees = global<CollectedFeesPerBlock>(@aptos_framework).amount;
+ let aggr = collected_fees.value;
+ let aggr_val = aggregator::spec_aggregator_get_val(aggr);
+ let aggr_lim = aggregator::spec_get_limit(aggr);
+ // This enforces high-level requirement 3:
+ aborts_if collect_fee_enabled && !exists<CollectedFeesPerBlock>(@aptos_framework);
+ aborts_if collect_fee_enabled && transaction_fee_amount > 0 && aggr_val + transaction_fee_amount > aggr_lim;
+ let amount_to_burn = if (collect_fee_enabled) {
+ 0
+ } else {
+ transaction_fee_amount - storage_fee_refunded
+ };
+ let apt_addr = type_info::type_of<AptosCoin>().account_address;
+ let maybe_apt_supply = global<CoinInfo<AptosCoin>>(apt_addr).supply;
+ let total_supply_enabled = option::spec_is_some(maybe_apt_supply);
+ let apt_supply = option::spec_borrow(maybe_apt_supply);
+ let apt_supply_value = optional_aggregator::optional_aggregator_value(apt_supply);
+ let post post_maybe_apt_supply = global<CoinInfo<AptosCoin>>(apt_addr).supply;
+ let post post_apt_supply = option::spec_borrow(post_maybe_apt_supply);
+ let post post_apt_supply_value = optional_aggregator::optional_aggregator_value(post_apt_supply);
+ aborts_if amount_to_burn > 0 && !exists<AptosCoinCapabilities>(@aptos_framework);
+ aborts_if amount_to_burn > 0 && !exists<CoinInfo<AptosCoin>>(apt_addr);
+ aborts_if amount_to_burn > 0 && total_supply_enabled && apt_supply_value < amount_to_burn;
+ ensures total_supply_enabled ==> apt_supply_value - amount_to_burn == post_apt_supply_value;
+ let amount_to_mint = if (collect_fee_enabled) {
+ storage_fee_refunded
+ } else {
+ storage_fee_refunded - transaction_fee_amount
+ };
+ let total_supply = coin::supply<AptosCoin>;
+ let post post_total_supply = coin::supply<AptosCoin>;
+ aborts_if amount_to_mint > 0 && !exists<CoinStore<AptosCoin>>(addr);
+ aborts_if amount_to_mint > 0 && !exists<AptosCoinMintCapability>(@aptos_framework);
+ aborts_if amount_to_mint > 0 && total_supply + amount_to_mint > MAX_U128;
+ ensures amount_to_mint > 0 ==> post_total_supply == total_supply + amount_to_mint;
+ let aptos_addr = type_info::type_of<AptosCoin>().account_address;
+ aborts_if (amount_to_mint != 0) && !exists<coin::CoinInfo<AptosCoin>>(aptos_addr);
+ include coin::CoinAddAbortsIf<AptosCoin> { amount: amount_to_mint };
+}
+
+
+
+[move-book]: https://aptos.dev/move/book/SUMMARY
diff --git a/aptos-move/framework/aptos-framework/tests/compiler-v2-doc/util.md b/aptos-move/framework/aptos-framework/tests/compiler-v2-doc/util.md
new file mode 100644
index 0000000000000..8a3933b170760
--- /dev/null
+++ b/aptos-move/framework/aptos-framework/tests/compiler-v2-doc/util.md
@@ -0,0 +1,149 @@
+
+
+
+# Module `0x1::util`
+
+Utility functions used by the framework modules.
+
+
+- [Function `from_bytes`](#0x1_util_from_bytes)
+- [Function `address_from_bytes`](#0x1_util_address_from_bytes)
+- [Specification](#@Specification_0)
+ - [Function `from_bytes`](#@Specification_0_from_bytes)
+ - [High-level Requirements](#high-level-req)
+ - [Module-level Specification](#module-level-spec)
+ - [Function `address_from_bytes`](#@Specification_0_address_from_bytes)
+
+
+
+
+
+
+
+
+## Function `from_bytes`
+
+Native function to deserialize a type T.
+
+Note that this function does not put any constraint on T
. If code uses this function to
+deserialized a linear value, its their responsibility that the data they deserialize is
+owned.
+
+
+public(friend) fun from_bytes<T>(bytes: vector<u8>): T
+
+
+
+
+public(friend) native fun from_bytes<T>(bytes: vector<u8>): T;
+
+
+
+
+public fun address_from_bytes(bytes: vector<u8>): address
+
+
+
+
+public fun address_from_bytes(bytes: vector<u8>): address {
+ from_bytes(bytes)
+}
+
+
+
+
+public(friend) fun from_bytes<T>(bytes: vector<u8>): T
+
+
+
+
+
+
+
+
+### High-level Requirements
+
+No. | Requirement | Criticality | Implementation | Enforcement | +
---|---|---|---|---|
1 | +The address input bytes should be exactly 32 bytes long. | +Low | +The address_from_bytes function should assert if the length of the input bytes is 32. | +Verified via address_from_bytes. | +
pragma opaque;
+aborts_if [abstract] false;
+ensures [abstract] result == spec_from_bytes<T>(bytes);
+
+
+
+
+
+
+
+
+fun spec_from_bytes<T>(bytes: vector<u8>): T;
+
+
+
+
+
+
+### Function `address_from_bytes`
+
+
+public fun address_from_bytes(bytes: vector<u8>): address
+
+
+
+
+
+// This enforces high-level requirement 1:
+aborts_if [abstract] len(bytes) != 32;
+
+
+
+[move-book]: https://aptos.dev/move/book/SUMMARY
diff --git a/aptos-move/framework/aptos-framework/tests/compiler-v2-doc/validator_consensus_info.md b/aptos-move/framework/aptos-framework/tests/compiler-v2-doc/validator_consensus_info.md
new file mode 100644
index 0000000000000..0100837fdd975
--- /dev/null
+++ b/aptos-move/framework/aptos-framework/tests/compiler-v2-doc/validator_consensus_info.md
@@ -0,0 +1,205 @@
+
+
+
+# Module `0x1::validator_consensus_info`
+
+Common type: ValidatorConsensusInfo
.
+
+
+- [Struct `ValidatorConsensusInfo`](#0x1_validator_consensus_info_ValidatorConsensusInfo)
+- [Function `default`](#0x1_validator_consensus_info_default)
+- [Function `new`](#0x1_validator_consensus_info_new)
+- [Function `get_addr`](#0x1_validator_consensus_info_get_addr)
+- [Function `get_pk_bytes`](#0x1_validator_consensus_info_get_pk_bytes)
+- [Function `get_voting_power`](#0x1_validator_consensus_info_get_voting_power)
+- [Specification](#@Specification_0)
+
+
+
+
+
+
+
+
+## Struct `ValidatorConsensusInfo`
+
+Information about a validator that participates consensus.
+
+
+struct ValidatorConsensusInfo has copy, drop, store
+
+
+
+
+addr: address
+pk_bytes: vector<u8>
+voting_power: u64
+ValidatorConsensusInfo
object. Value may be invalid. Only for place holding prupose.
+
+
+public fun default(): validator_consensus_info::ValidatorConsensusInfo
+
+
+
+
+public fun default(): ValidatorConsensusInfo {
+ ValidatorConsensusInfo {
+ addr: @vm,
+ pk_bytes: vector[],
+ voting_power: 0,
+ }
+}
+
+
+
+
+ValidatorConsensusInfo
object.
+
+
+public fun new(addr: address, pk_bytes: vector<u8>, voting_power: u64): validator_consensus_info::ValidatorConsensusInfo
+
+
+
+
+public fun new(addr: address, pk_bytes: vector<u8>, voting_power: u64): ValidatorConsensusInfo {
+ ValidatorConsensusInfo {
+ addr,
+ pk_bytes,
+ voting_power,
+ }
+}
+
+
+
+
+ValidatorConsensusInfo.addr
.
+
+
+public fun get_addr(vci: &validator_consensus_info::ValidatorConsensusInfo): address
+
+
+
+
+public fun get_addr(vci: &ValidatorConsensusInfo): address {
+ vci.addr
+}
+
+
+
+
+ValidatorConsensusInfo.pk_bytes
.
+
+
+public fun get_pk_bytes(vci: &validator_consensus_info::ValidatorConsensusInfo): vector<u8>
+
+
+
+
+public fun get_pk_bytes(vci: &ValidatorConsensusInfo): vector<u8> {
+ vci.pk_bytes
+}
+
+
+
+
+ValidatorConsensusInfo.voting_power
.
+
+
+public fun get_voting_power(vci: &validator_consensus_info::ValidatorConsensusInfo): u64
+
+
+
+
+public fun get_voting_power(vci: &ValidatorConsensusInfo): u64 {
+ vci.voting_power
+}
+
+
+
+
+pragma verify = true;
+
+
+
+[move-book]: https://aptos.dev/move/book/SUMMARY
diff --git a/aptos-move/framework/aptos-framework/tests/compiler-v2-doc/version.md b/aptos-move/framework/aptos-framework/tests/compiler-v2-doc/version.md
new file mode 100644
index 0000000000000..28e34690a97de
--- /dev/null
+++ b/aptos-move/framework/aptos-framework/tests/compiler-v2-doc/version.md
@@ -0,0 +1,430 @@
+
+
+
+# Module `0x1::version`
+
+Maintains the version number for the blockchain.
+
+
+- [Resource `Version`](#0x1_version_Version)
+- [Resource `SetVersionCapability`](#0x1_version_SetVersionCapability)
+- [Constants](#@Constants_0)
+- [Function `initialize`](#0x1_version_initialize)
+- [Function `set_version`](#0x1_version_set_version)
+- [Function `set_for_next_epoch`](#0x1_version_set_for_next_epoch)
+- [Function `on_new_epoch`](#0x1_version_on_new_epoch)
+- [Function `initialize_for_test`](#0x1_version_initialize_for_test)
+- [Specification](#@Specification_1)
+ - [High-level Requirements](#high-level-req)
+ - [Module-level Specification](#module-level-spec)
+ - [Function `initialize`](#@Specification_1_initialize)
+ - [Function `set_version`](#@Specification_1_set_version)
+ - [Function `set_for_next_epoch`](#@Specification_1_set_for_next_epoch)
+ - [Function `on_new_epoch`](#@Specification_1_on_new_epoch)
+ - [Function `initialize_for_test`](#@Specification_1_initialize_for_test)
+
+
+use 0x1::chain_status;
+use 0x1::config_buffer;
+use 0x1::error;
+use 0x1::reconfiguration;
+use 0x1::signer;
+use 0x1::system_addresses;
+
+
+
+
+
+
+## Resource `Version`
+
+
+
+struct Version has drop, store, key
+
+
+
+
+major: u64
+struct SetVersionCapability has key
+
+
+
+
+dummy_field: bool
+const EINVALID_MAJOR_VERSION_NUMBER: u64 = 1;
+
+
+
+
+
+
+Account is not authorized to make this change.
+
+
+const ENOT_AUTHORIZED: u64 = 2;
+
+
+
+
+
+
+## Function `initialize`
+
+Only called during genesis.
+Publishes the Version config.
+
+
+public(friend) fun initialize(aptos_framework: &signer, initial_version: u64)
+
+
+
+
+public(friend) fun initialize(aptos_framework: &signer, initial_version: u64) {
+ system_addresses::assert_aptos_framework(aptos_framework);
+
+ move_to(aptos_framework, Version { major: initial_version });
+ // Give aptos framework account capability to call set version. This allows on chain governance to do it through
+ // control of the aptos framework account.
+ move_to(aptos_framework, SetVersionCapability {});
+}
+
+
+
+
+set_for_next_epoch()
.
+
+WARNING: calling this while randomness is enabled will trigger a new epoch without randomness!
+
+TODO: update all the tests that reference this function, then disable this function.
+
+
+public entry fun set_version(account: &signer, major: u64)
+
+
+
+
+public entry fun set_version(account: &signer, major: u64) acquires Version {
+ assert!(exists<SetVersionCapability>(signer::address_of(account)), error::permission_denied(ENOT_AUTHORIZED));
+ chain_status::assert_genesis();
+
+ let old_major = borrow_global<Version>(@aptos_framework).major;
+ assert!(old_major < major, error::invalid_argument(EINVALID_MAJOR_VERSION_NUMBER));
+
+ let config = borrow_global_mut<Version>(@aptos_framework);
+ config.major = major;
+
+ // Need to trigger reconfiguration so validator nodes can sync on the updated version.
+ reconfiguration::reconfigure();
+}
+
+
+
+
+aptos_framework::version::set_for_next_epoch(&framework_signer, new_version);
+- aptos_framework::aptos_governance::reconfigure(&framework_signer);
+
+
+public entry fun set_for_next_epoch(account: &signer, major: u64)
+
+
+
+
+public entry fun set_for_next_epoch(account: &signer, major: u64) acquires Version {
+ assert!(exists<SetVersionCapability>(signer::address_of(account)), error::permission_denied(ENOT_AUTHORIZED));
+ let old_major = borrow_global<Version>(@aptos_framework).major;
+ assert!(old_major < major, error::invalid_argument(EINVALID_MAJOR_VERSION_NUMBER));
+ config_buffer::upsert(Version {major});
+}
+
+
+
+
+Version
, if there is any.
+
+
+public(friend) fun on_new_epoch(framework: &signer)
+
+
+
+
+public(friend) fun on_new_epoch(framework: &signer) acquires Version {
+ system_addresses::assert_aptos_framework(framework);
+ if (config_buffer::does_exist<Version>()) {
+ let new_value = config_buffer::extract<Version>();
+ if (exists<Version>(@aptos_framework)) {
+ *borrow_global_mut<Version>(@aptos_framework) = new_value;
+ } else {
+ move_to(framework, new_value);
+ }
+ }
+}
+
+
+
+
+fun initialize_for_test(core_resources: &signer)
+
+
+
+
+fun initialize_for_test(core_resources: &signer) {
+ system_addresses::assert_core_resource(core_resources);
+ move_to(core_resources, SetVersionCapability {});
+}
+
+
+
+
+No. | Requirement | Criticality | Implementation | Enforcement | +
---|---|---|---|---|
1 | +During genesis, the Version resource should be initialized with the initial version and stored along with its capability under the aptos framework account. | +Medium | +The initialize function ensures that the signer is the aptos framework account and stores the Version and SetVersionCapability resources in it. | +Formally verified via initialize. | +
2 | +The version should be updateable after initialization, but only by the Aptos framework account and with an increasing version number. | +Medium | +The version number for the blockchain should be updatable whenever necessary. This functionality is provided by the set_version function which ensures that the new version is greater than the previous one. | +Formally verified via set_version. | +
pragma verify = true;
+pragma aborts_if_is_strict;
+
+
+
+
+
+
+### Function `initialize`
+
+
+public(friend) fun initialize(aptos_framework: &signer, initial_version: u64)
+
+
+
+Abort if resource already exists in @aptos_framwork
when initializing.
+
+
+// This enforces high-level requirement 1:
+aborts_if signer::address_of(aptos_framework) != @aptos_framework;
+aborts_if exists<Version>(@aptos_framework);
+aborts_if exists<SetVersionCapability>(@aptos_framework);
+ensures exists<Version>(@aptos_framework);
+ensures exists<SetVersionCapability>(@aptos_framework);
+ensures global<Version>(@aptos_framework) == Version { major: initial_version };
+ensures global<SetVersionCapability>(@aptos_framework) == SetVersionCapability {};
+
+
+
+
+
+
+### Function `set_version`
+
+
+public entry fun set_version(account: &signer, major: u64)
+
+
+
+
+
+pragma verify_duration_estimate = 120;
+include transaction_fee::RequiresCollectedFeesPerValueLeqBlockAptosSupply;
+include staking_config::StakingRewardsConfigRequirement;
+requires chain_status::is_genesis();
+requires timestamp::spec_now_microseconds() >= reconfiguration::last_reconfiguration_time();
+requires exists<stake::ValidatorFees>(@aptos_framework);
+requires exists<CoinInfo<AptosCoin>>(@aptos_framework);
+aborts_if !exists<SetVersionCapability>(signer::address_of(account));
+aborts_if !exists<Version>(@aptos_framework);
+let old_major = global<Version>(@aptos_framework).major;
+// This enforces high-level requirement 2:
+aborts_if !(old_major < major);
+ensures global<Version>(@aptos_framework).major == major;
+
+
+
+
+
+
+### Function `set_for_next_epoch`
+
+
+public entry fun set_for_next_epoch(account: &signer, major: u64)
+
+
+
+
+
+aborts_if !exists<SetVersionCapability>(signer::address_of(account));
+aborts_if !exists<Version>(@aptos_framework);
+aborts_if global<Version>(@aptos_framework).major >= major;
+aborts_if !exists<config_buffer::PendingConfigs>(@aptos_framework);
+
+
+
+
+
+
+### Function `on_new_epoch`
+
+
+public(friend) fun on_new_epoch(framework: &signer)
+
+
+
+
+
+requires @aptos_framework == std::signer::address_of(framework);
+include config_buffer::OnNewEpochRequirement<Version>;
+aborts_if false;
+
+
+
+
+
+
+### Function `initialize_for_test`
+
+
+fun initialize_for_test(core_resources: &signer)
+
+
+
+This module turns on aborts_if_is_strict
, so need to add spec for test function initialize_for_test
.
+
+
+pragma verify = false;
+
+
+
+[move-book]: https://aptos.dev/move/book/SUMMARY
diff --git a/aptos-move/framework/aptos-framework/tests/compiler-v2-doc/vesting.md b/aptos-move/framework/aptos-framework/tests/compiler-v2-doc/vesting.md
new file mode 100644
index 0000000000000..f10b63becec13
--- /dev/null
+++ b/aptos-move/framework/aptos-framework/tests/compiler-v2-doc/vesting.md
@@ -0,0 +1,4528 @@
+
+
+
+# Module `0x1::vesting`
+
+
+Simple vesting contract that allows specifying how much APT coins should be vesting in each fixed-size period. The
+vesting contract also comes with staking and allows shareholders to withdraw rewards anytime.
+
+Vesting schedule is represented as a vector of distributions. For example, a vesting schedule of
+[3/48, 3/48, 1/48] means that after the vesting starts:
+1. The first and second periods will vest 3/48 of the total original grant.
+2. The third period will vest 1/48.
+3. All subsequent periods will also vest 1/48 (last distribution in the schedule) until the original grant runs out.
+
+Shareholder flow:
+1. Admin calls create_vesting_contract with a schedule of [3/48, 3/48, 1/48] with a vesting cliff of 1 year and
+vesting period of 1 month.
+2. After a month, a shareholder calls unlock_rewards to request rewards. They can also call vest() which would also
+unlocks rewards but since the 1 year cliff has not passed (vesting has not started), vest() would not release any of
+the original grant.
+3. After the unlocked rewards become fully withdrawable (as it's subject to staking lockup), shareholders can call
+distribute() to send all withdrawable funds to all shareholders based on the original grant's shares structure.
+4. After 1 year and 1 month, the vesting schedule now starts. Shareholders call vest() to unlock vested coins. vest()
+checks the schedule and unlocks 3/48 of the original grant in addition to any accumulated rewards since last
+unlock_rewards(). Once the unlocked coins become withdrawable, shareholders can call distribute().
+5. Assuming the shareholders forgot to call vest() for 2 months, when they call vest() again, they will unlock vested
+tokens for the next period since last vest. This would be for the first month they missed. They can call vest() a
+second time to unlock for the second month they missed.
+
+Admin flow:
+1. After creating the vesting contract, admin cannot change the vesting schedule.
+2. Admin can call update_voter, update_operator, or reset_lockup at any time to update the underlying staking
+contract.
+3. Admin can also call update_beneficiary for any shareholder. This would send all distributions (rewards, vested
+coins) of that shareholder to the beneficiary account. By defalt, if a beneficiary is not set, the distributions are
+send directly to the shareholder account.
+4. Admin can call terminate_vesting_contract to terminate the vesting. This would first finish any distribution but
+will prevent any further rewards or vesting distributions from being created. Once the locked up stake becomes
+withdrawable, admin can call admin_withdraw to withdraw all funds to the vesting contract's withdrawal address.
+
+
+- [Struct `VestingSchedule`](#0x1_vesting_VestingSchedule)
+- [Struct `StakingInfo`](#0x1_vesting_StakingInfo)
+- [Resource `VestingContract`](#0x1_vesting_VestingContract)
+- [Resource `VestingAccountManagement`](#0x1_vesting_VestingAccountManagement)
+- [Resource `AdminStore`](#0x1_vesting_AdminStore)
+- [Struct `CreateVestingContract`](#0x1_vesting_CreateVestingContract)
+- [Struct `UpdateOperator`](#0x1_vesting_UpdateOperator)
+- [Struct `UpdateVoter`](#0x1_vesting_UpdateVoter)
+- [Struct `ResetLockup`](#0x1_vesting_ResetLockup)
+- [Struct `SetBeneficiary`](#0x1_vesting_SetBeneficiary)
+- [Struct `UnlockRewards`](#0x1_vesting_UnlockRewards)
+- [Struct `Vest`](#0x1_vesting_Vest)
+- [Struct `Distribute`](#0x1_vesting_Distribute)
+- [Struct `Terminate`](#0x1_vesting_Terminate)
+- [Struct `AdminWithdraw`](#0x1_vesting_AdminWithdraw)
+- [Struct `CreateVestingContractEvent`](#0x1_vesting_CreateVestingContractEvent)
+- [Struct `UpdateOperatorEvent`](#0x1_vesting_UpdateOperatorEvent)
+- [Struct `UpdateVoterEvent`](#0x1_vesting_UpdateVoterEvent)
+- [Struct `ResetLockupEvent`](#0x1_vesting_ResetLockupEvent)
+- [Struct `SetBeneficiaryEvent`](#0x1_vesting_SetBeneficiaryEvent)
+- [Struct `UnlockRewardsEvent`](#0x1_vesting_UnlockRewardsEvent)
+- [Struct `VestEvent`](#0x1_vesting_VestEvent)
+- [Struct `DistributeEvent`](#0x1_vesting_DistributeEvent)
+- [Struct `TerminateEvent`](#0x1_vesting_TerminateEvent)
+- [Struct `AdminWithdrawEvent`](#0x1_vesting_AdminWithdrawEvent)
+- [Constants](#@Constants_0)
+- [Function `stake_pool_address`](#0x1_vesting_stake_pool_address)
+- [Function `vesting_start_secs`](#0x1_vesting_vesting_start_secs)
+- [Function `period_duration_secs`](#0x1_vesting_period_duration_secs)
+- [Function `remaining_grant`](#0x1_vesting_remaining_grant)
+- [Function `beneficiary`](#0x1_vesting_beneficiary)
+- [Function `operator_commission_percentage`](#0x1_vesting_operator_commission_percentage)
+- [Function `vesting_contracts`](#0x1_vesting_vesting_contracts)
+- [Function `operator`](#0x1_vesting_operator)
+- [Function `voter`](#0x1_vesting_voter)
+- [Function `vesting_schedule`](#0x1_vesting_vesting_schedule)
+- [Function `total_accumulated_rewards`](#0x1_vesting_total_accumulated_rewards)
+- [Function `accumulated_rewards`](#0x1_vesting_accumulated_rewards)
+- [Function `shareholders`](#0x1_vesting_shareholders)
+- [Function `shareholder`](#0x1_vesting_shareholder)
+- [Function `create_vesting_schedule`](#0x1_vesting_create_vesting_schedule)
+- [Function `create_vesting_contract`](#0x1_vesting_create_vesting_contract)
+- [Function `unlock_rewards`](#0x1_vesting_unlock_rewards)
+- [Function `unlock_rewards_many`](#0x1_vesting_unlock_rewards_many)
+- [Function `vest`](#0x1_vesting_vest)
+- [Function `vest_many`](#0x1_vesting_vest_many)
+- [Function `distribute`](#0x1_vesting_distribute)
+- [Function `distribute_many`](#0x1_vesting_distribute_many)
+- [Function `terminate_vesting_contract`](#0x1_vesting_terminate_vesting_contract)
+- [Function `admin_withdraw`](#0x1_vesting_admin_withdraw)
+- [Function `update_operator`](#0x1_vesting_update_operator)
+- [Function `update_operator_with_same_commission`](#0x1_vesting_update_operator_with_same_commission)
+- [Function `update_commission_percentage`](#0x1_vesting_update_commission_percentage)
+- [Function `update_voter`](#0x1_vesting_update_voter)
+- [Function `reset_lockup`](#0x1_vesting_reset_lockup)
+- [Function `set_beneficiary`](#0x1_vesting_set_beneficiary)
+- [Function `reset_beneficiary`](#0x1_vesting_reset_beneficiary)
+- [Function `set_management_role`](#0x1_vesting_set_management_role)
+- [Function `set_beneficiary_resetter`](#0x1_vesting_set_beneficiary_resetter)
+- [Function `set_beneficiary_for_operator`](#0x1_vesting_set_beneficiary_for_operator)
+- [Function `get_role_holder`](#0x1_vesting_get_role_holder)
+- [Function `get_vesting_account_signer`](#0x1_vesting_get_vesting_account_signer)
+- [Function `get_vesting_account_signer_internal`](#0x1_vesting_get_vesting_account_signer_internal)
+- [Function `create_vesting_contract_account`](#0x1_vesting_create_vesting_contract_account)
+- [Function `verify_admin`](#0x1_vesting_verify_admin)
+- [Function `assert_vesting_contract_exists`](#0x1_vesting_assert_vesting_contract_exists)
+- [Function `assert_active_vesting_contract`](#0x1_vesting_assert_active_vesting_contract)
+- [Function `unlock_stake`](#0x1_vesting_unlock_stake)
+- [Function `withdraw_stake`](#0x1_vesting_withdraw_stake)
+- [Function `get_beneficiary`](#0x1_vesting_get_beneficiary)
+- [Specification](#@Specification_1)
+ - [High-level Requirements](#high-level-req)
+ - [Module-level Specification](#module-level-spec)
+ - [Function `stake_pool_address`](#@Specification_1_stake_pool_address)
+ - [Function `vesting_start_secs`](#@Specification_1_vesting_start_secs)
+ - [Function `period_duration_secs`](#@Specification_1_period_duration_secs)
+ - [Function `remaining_grant`](#@Specification_1_remaining_grant)
+ - [Function `beneficiary`](#@Specification_1_beneficiary)
+ - [Function `operator_commission_percentage`](#@Specification_1_operator_commission_percentage)
+ - [Function `vesting_contracts`](#@Specification_1_vesting_contracts)
+ - [Function `operator`](#@Specification_1_operator)
+ - [Function `voter`](#@Specification_1_voter)
+ - [Function `vesting_schedule`](#@Specification_1_vesting_schedule)
+ - [Function `total_accumulated_rewards`](#@Specification_1_total_accumulated_rewards)
+ - [Function `accumulated_rewards`](#@Specification_1_accumulated_rewards)
+ - [Function `shareholders`](#@Specification_1_shareholders)
+ - [Function `shareholder`](#@Specification_1_shareholder)
+ - [Function `create_vesting_schedule`](#@Specification_1_create_vesting_schedule)
+ - [Function `create_vesting_contract`](#@Specification_1_create_vesting_contract)
+ - [Function `unlock_rewards`](#@Specification_1_unlock_rewards)
+ - [Function `unlock_rewards_many`](#@Specification_1_unlock_rewards_many)
+ - [Function `vest`](#@Specification_1_vest)
+ - [Function `vest_many`](#@Specification_1_vest_many)
+ - [Function `distribute`](#@Specification_1_distribute)
+ - [Function `distribute_many`](#@Specification_1_distribute_many)
+ - [Function `terminate_vesting_contract`](#@Specification_1_terminate_vesting_contract)
+ - [Function `admin_withdraw`](#@Specification_1_admin_withdraw)
+ - [Function `update_operator`](#@Specification_1_update_operator)
+ - [Function `update_operator_with_same_commission`](#@Specification_1_update_operator_with_same_commission)
+ - [Function `update_commission_percentage`](#@Specification_1_update_commission_percentage)
+ - [Function `update_voter`](#@Specification_1_update_voter)
+ - [Function `reset_lockup`](#@Specification_1_reset_lockup)
+ - [Function `set_beneficiary`](#@Specification_1_set_beneficiary)
+ - [Function `reset_beneficiary`](#@Specification_1_reset_beneficiary)
+ - [Function `set_management_role`](#@Specification_1_set_management_role)
+ - [Function `set_beneficiary_resetter`](#@Specification_1_set_beneficiary_resetter)
+ - [Function `set_beneficiary_for_operator`](#@Specification_1_set_beneficiary_for_operator)
+ - [Function `get_role_holder`](#@Specification_1_get_role_holder)
+ - [Function `get_vesting_account_signer`](#@Specification_1_get_vesting_account_signer)
+ - [Function `get_vesting_account_signer_internal`](#@Specification_1_get_vesting_account_signer_internal)
+ - [Function `create_vesting_contract_account`](#@Specification_1_create_vesting_contract_account)
+ - [Function `verify_admin`](#@Specification_1_verify_admin)
+ - [Function `assert_vesting_contract_exists`](#@Specification_1_assert_vesting_contract_exists)
+ - [Function `assert_active_vesting_contract`](#@Specification_1_assert_active_vesting_contract)
+ - [Function `unlock_stake`](#@Specification_1_unlock_stake)
+ - [Function `withdraw_stake`](#@Specification_1_withdraw_stake)
+ - [Function `get_beneficiary`](#@Specification_1_get_beneficiary)
+
+
+use 0x1::account;
+use 0x1::aptos_account;
+use 0x1::aptos_coin;
+use 0x1::bcs;
+use 0x1::coin;
+use 0x1::error;
+use 0x1::event;
+use 0x1::features;
+use 0x1::fixed_point32;
+use 0x1::math64;
+use 0x1::pool_u64;
+use 0x1::signer;
+use 0x1::simple_map;
+use 0x1::stake;
+use 0x1::staking_contract;
+use 0x1::string;
+use 0x1::system_addresses;
+use 0x1::timestamp;
+use 0x1::vector;
+
+
+
+
+
+
+## Struct `VestingSchedule`
+
+
+
+struct VestingSchedule has copy, drop, store
+
+
+
+
+schedule: vector<fixed_point32::FixedPoint32>
+start_timestamp_secs: u64
+period_duration: u64
+last_vested_period: u64
+struct StakingInfo has store
+
+
+
+
+pool_address: address
+operator: address
+voter: address
+commission_percentage: u64
+struct VestingContract has key
+
+
+
+
+state: u64
+admin: address
+grant_pool: pool_u64::Pool
+beneficiaries: simple_map::SimpleMap<address, address>
+vesting_schedule: vesting::VestingSchedule
+withdrawal_address: address
+staking: vesting::StakingInfo
+remaining_grant: u64
+signer_cap: account::SignerCapability
+update_operator_events: event::EventHandle<vesting::UpdateOperatorEvent>
+update_voter_events: event::EventHandle<vesting::UpdateVoterEvent>
+reset_lockup_events: event::EventHandle<vesting::ResetLockupEvent>
+set_beneficiary_events: event::EventHandle<vesting::SetBeneficiaryEvent>
+unlock_rewards_events: event::EventHandle<vesting::UnlockRewardsEvent>
+vest_events: event::EventHandle<vesting::VestEvent>
+distribute_events: event::EventHandle<vesting::DistributeEvent>
+terminate_events: event::EventHandle<vesting::TerminateEvent>
+admin_withdraw_events: event::EventHandle<vesting::AdminWithdrawEvent>
+struct VestingAccountManagement has key
+
+
+
+
+roles: simple_map::SimpleMap<string::String, address>
+struct AdminStore has key
+
+
+
+
+vesting_contracts: vector<address>
+nonce: u64
+create_events: event::EventHandle<vesting::CreateVestingContractEvent>
+#[event]
+struct CreateVestingContract has drop, store
+
+
+
+
+operator: address
+voter: address
+grant_amount: u64
+withdrawal_address: address
+vesting_contract_address: address
+staking_pool_address: address
+commission_percentage: u64
+#[event]
+struct UpdateOperator has drop, store
+
+
+
+
+admin: address
+vesting_contract_address: address
+staking_pool_address: address
+old_operator: address
+new_operator: address
+commission_percentage: u64
+#[event]
+struct UpdateVoter has drop, store
+
+
+
+
+admin: address
+vesting_contract_address: address
+staking_pool_address: address
+old_voter: address
+new_voter: address
+#[event]
+struct ResetLockup has drop, store
+
+
+
+
+admin: address
+vesting_contract_address: address
+staking_pool_address: address
+new_lockup_expiration_secs: u64
+#[event]
+struct SetBeneficiary has drop, store
+
+
+
+
+admin: address
+vesting_contract_address: address
+shareholder: address
+old_beneficiary: address
+new_beneficiary: address
+#[event]
+struct UnlockRewards has drop, store
+
+
+
+
+admin: address
+vesting_contract_address: address
+staking_pool_address: address
+amount: u64
+#[event]
+struct Vest has drop, store
+
+
+
+
+admin: address
+vesting_contract_address: address
+staking_pool_address: address
+period_vested: u64
+amount: u64
+#[event]
+struct Distribute has drop, store
+
+
+
+
+admin: address
+vesting_contract_address: address
+amount: u64
+#[event]
+struct Terminate has drop, store
+
+
+
+
+admin: address
+vesting_contract_address: address
+#[event]
+struct AdminWithdraw has drop, store
+
+
+
+
+admin: address
+vesting_contract_address: address
+amount: u64
+struct CreateVestingContractEvent has drop, store
+
+
+
+
+operator: address
+voter: address
+grant_amount: u64
+withdrawal_address: address
+vesting_contract_address: address
+staking_pool_address: address
+commission_percentage: u64
+struct UpdateOperatorEvent has drop, store
+
+
+
+
+admin: address
+vesting_contract_address: address
+staking_pool_address: address
+old_operator: address
+new_operator: address
+commission_percentage: u64
+struct UpdateVoterEvent has drop, store
+
+
+
+
+admin: address
+vesting_contract_address: address
+staking_pool_address: address
+old_voter: address
+new_voter: address
+struct ResetLockupEvent has drop, store
+
+
+
+
+admin: address
+vesting_contract_address: address
+staking_pool_address: address
+new_lockup_expiration_secs: u64
+struct SetBeneficiaryEvent has drop, store
+
+
+
+
+admin: address
+vesting_contract_address: address
+shareholder: address
+old_beneficiary: address
+new_beneficiary: address
+struct UnlockRewardsEvent has drop, store
+
+
+
+
+admin: address
+vesting_contract_address: address
+staking_pool_address: address
+amount: u64
+struct VestEvent has drop, store
+
+
+
+
+admin: address
+vesting_contract_address: address
+staking_pool_address: address
+period_vested: u64
+amount: u64
+struct DistributeEvent has drop, store
+
+
+
+
+admin: address
+vesting_contract_address: address
+amount: u64
+struct TerminateEvent has drop, store
+
+
+
+
+admin: address
+vesting_contract_address: address
+struct AdminWithdrawEvent has drop, store
+
+
+
+
+admin: address
+vesting_contract_address: address
+amount: u64
+const EEMPTY_VESTING_SCHEDULE: u64 = 2;
+
+
+
+
+
+
+Withdrawal address is invalid.
+
+
+const EINVALID_WITHDRAWAL_ADDRESS: u64 = 1;
+
+
+
+
+
+
+The signer is not the admin of the vesting contract.
+
+
+const ENOT_ADMIN: u64 = 7;
+
+
+
+
+
+
+Shareholders list cannot be empty.
+
+
+const ENO_SHAREHOLDERS: u64 = 4;
+
+
+
+
+
+
+Cannot terminate the vesting contract with pending active stake. Need to wait until next epoch.
+
+
+const EPENDING_STAKE_FOUND: u64 = 11;
+
+
+
+
+
+
+Account is not admin or does not have the required role to take this action.
+
+
+const EPERMISSION_DENIED: u64 = 15;
+
+
+
+
+
+
+The vesting account has no such management role.
+
+
+const EROLE_NOT_FOUND: u64 = 14;
+
+
+
+
+
+
+The length of shareholders and shares lists don't match.
+
+
+const ESHARES_LENGTH_MISMATCH: u64 = 5;
+
+
+
+
+
+
+Zero items were provided to a *_many function.
+
+
+const EVEC_EMPTY_FOR_MANY_FUNCTION: u64 = 16;
+
+
+
+
+
+
+Vesting account has no other management roles beside admin.
+
+
+const EVESTING_ACCOUNT_HAS_NO_ROLES: u64 = 13;
+
+
+
+
+
+
+Vesting contract needs to be in active state.
+
+
+const EVESTING_CONTRACT_NOT_ACTIVE: u64 = 8;
+
+
+
+
+
+
+No vesting contract found at provided address.
+
+
+const EVESTING_CONTRACT_NOT_FOUND: u64 = 10;
+
+
+
+
+
+
+Admin can only withdraw from an inactive (paused or terminated) vesting contract.
+
+
+const EVESTING_CONTRACT_STILL_ACTIVE: u64 = 9;
+
+
+
+
+
+
+Vesting cannot start before or at the current block timestamp. Has to be in the future.
+
+
+const EVESTING_START_TOO_SOON: u64 = 6;
+
+
+
+
+
+
+Grant amount cannot be 0.
+
+
+const EZERO_GRANT: u64 = 12;
+
+
+
+
+
+
+Vesting period cannot be 0.
+
+
+const EZERO_VESTING_SCHEDULE_PERIOD: u64 = 3;
+
+
+
+
+
+
+Maximum number of shareholders a vesting pool can support.
+
+
+const MAXIMUM_SHAREHOLDERS: u64 = 30;
+
+
+
+
+
+
+Roles that can manage certain aspects of the vesting account beyond the main admin.
+
+
+const ROLE_BENEFICIARY_RESETTER: vector<u8> = [82, 79, 76, 69, 95, 66, 69, 78, 69, 70, 73, 67, 73, 65, 82, 89, 95, 82, 69, 83, 69, 84, 84, 69, 82];
+
+
+
+
+
+
+Vesting contract states.
+Vesting contract is active and distributions can be made.
+
+
+const VESTING_POOL_ACTIVE: u64 = 1;
+
+
+
+
+
+
+
+
+const VESTING_POOL_SALT: vector<u8> = [97, 112, 116, 111, 115, 95, 102, 114, 97, 109, 101, 119, 111, 114, 107, 58, 58, 118, 101, 115, 116, 105, 110, 103];
+
+
+
+
+
+
+Vesting contract has been terminated and all funds have been released back to the withdrawal address.
+
+
+const VESTING_POOL_TERMINATED: u64 = 2;
+
+
+
+
+
+
+## Function `stake_pool_address`
+
+Return the address of the underlying stake pool (separate resource account) of the vesting contract.
+
+This errors out if the vesting contract with the provided address doesn't exist.
+
+
+#[view]
+public fun stake_pool_address(vesting_contract_address: address): address
+
+
+
+
+public fun stake_pool_address(vesting_contract_address: address): address acquires VestingContract {
+ assert_vesting_contract_exists(vesting_contract_address);
+ borrow_global<VestingContract>(vesting_contract_address).staking.pool_address
+}
+
+
+
+
+#[view]
+public fun vesting_start_secs(vesting_contract_address: address): u64
+
+
+
+
+public fun vesting_start_secs(vesting_contract_address: address): u64 acquires VestingContract {
+ assert_vesting_contract_exists(vesting_contract_address);
+ borrow_global<VestingContract>(vesting_contract_address).vesting_schedule.start_timestamp_secs
+}
+
+
+
+
+#[view]
+public fun period_duration_secs(vesting_contract_address: address): u64
+
+
+
+
+public fun period_duration_secs(vesting_contract_address: address): u64 acquires VestingContract {
+ assert_vesting_contract_exists(vesting_contract_address);
+ borrow_global<VestingContract>(vesting_contract_address).vesting_schedule.period_duration
+}
+
+
+
+
+#[view]
+public fun remaining_grant(vesting_contract_address: address): u64
+
+
+
+
+public fun remaining_grant(vesting_contract_address: address): u64 acquires VestingContract {
+ assert_vesting_contract_exists(vesting_contract_address);
+ borrow_global<VestingContract>(vesting_contract_address).remaining_grant
+}
+
+
+
+
+#[view]
+public fun beneficiary(vesting_contract_address: address, shareholder: address): address
+
+
+
+
+public fun beneficiary(vesting_contract_address: address, shareholder: address): address acquires VestingContract {
+ assert_vesting_contract_exists(vesting_contract_address);
+ get_beneficiary(borrow_global<VestingContract>(vesting_contract_address), shareholder)
+}
+
+
+
+
+#[view]
+public fun operator_commission_percentage(vesting_contract_address: address): u64
+
+
+
+
+public fun operator_commission_percentage(vesting_contract_address: address): u64 acquires VestingContract {
+ assert_vesting_contract_exists(vesting_contract_address);
+ borrow_global<VestingContract>(vesting_contract_address).staking.commission_percentage
+}
+
+
+
+
+#[view]
+public fun vesting_contracts(admin: address): vector<address>
+
+
+
+
+public fun vesting_contracts(admin: address): vector<address> acquires AdminStore {
+ if (!exists<AdminStore>(admin)) {
+ vector::empty<address>()
+ } else {
+ borrow_global<AdminStore>(admin).vesting_contracts
+ }
+}
+
+
+
+
+#[view]
+public fun operator(vesting_contract_address: address): address
+
+
+
+
+public fun operator(vesting_contract_address: address): address acquires VestingContract {
+ assert_vesting_contract_exists(vesting_contract_address);
+ borrow_global<VestingContract>(vesting_contract_address).staking.operator
+}
+
+
+
+
+#[view]
+public fun voter(vesting_contract_address: address): address
+
+
+
+
+public fun voter(vesting_contract_address: address): address acquires VestingContract {
+ assert_vesting_contract_exists(vesting_contract_address);
+ borrow_global<VestingContract>(vesting_contract_address).staking.voter
+}
+
+
+
+
+#[view]
+public fun vesting_schedule(vesting_contract_address: address): vesting::VestingSchedule
+
+
+
+
+public fun vesting_schedule(vesting_contract_address: address): VestingSchedule acquires VestingContract {
+ assert_vesting_contract_exists(vesting_contract_address);
+ borrow_global<VestingContract>(vesting_contract_address).vesting_schedule
+}
+
+
+
+
+#[view]
+public fun total_accumulated_rewards(vesting_contract_address: address): u64
+
+
+
+
+public fun total_accumulated_rewards(vesting_contract_address: address): u64 acquires VestingContract {
+ assert_active_vesting_contract(vesting_contract_address);
+
+ let vesting_contract = borrow_global<VestingContract>(vesting_contract_address);
+ let (total_active_stake, _, commission_amount) =
+ staking_contract::staking_contract_amounts(vesting_contract_address, vesting_contract.staking.operator);
+ total_active_stake - vesting_contract.remaining_grant - commission_amount
+}
+
+
+
+
+#[view]
+public fun accumulated_rewards(vesting_contract_address: address, shareholder_or_beneficiary: address): u64
+
+
+
+
+public fun accumulated_rewards(
+ vesting_contract_address: address, shareholder_or_beneficiary: address): u64 acquires VestingContract {
+ assert_active_vesting_contract(vesting_contract_address);
+
+ let total_accumulated_rewards = total_accumulated_rewards(vesting_contract_address);
+ let shareholder = shareholder(vesting_contract_address, shareholder_or_beneficiary);
+ let vesting_contract = borrow_global<VestingContract>(vesting_contract_address);
+ let shares = pool_u64::shares(&vesting_contract.grant_pool, shareholder);
+ pool_u64::shares_to_amount_with_total_coins(&vesting_contract.grant_pool, shares, total_accumulated_rewards)
+}
+
+
+
+
+#[view]
+public fun shareholders(vesting_contract_address: address): vector<address>
+
+
+
+
+public fun shareholders(vesting_contract_address: address): vector<address> acquires VestingContract {
+ assert_active_vesting_contract(vesting_contract_address);
+
+ let vesting_contract = borrow_global<VestingContract>(vesting_contract_address);
+ pool_u64::shareholders(&vesting_contract.grant_pool)
+}
+
+
+
+
+#[view]
+public fun shareholder(vesting_contract_address: address, shareholder_or_beneficiary: address): address
+
+
+
+
+public fun shareholder(
+ vesting_contract_address: address,
+ shareholder_or_beneficiary: address
+): address acquires VestingContract {
+ assert_active_vesting_contract(vesting_contract_address);
+
+ let shareholders = &shareholders(vesting_contract_address);
+ if (vector::contains(shareholders, &shareholder_or_beneficiary)) {
+ return shareholder_or_beneficiary
+ };
+ let vesting_contract = borrow_global<VestingContract>(vesting_contract_address);
+ let result = @0x0;
+ vector::any(shareholders, |shareholder| {
+ if (shareholder_or_beneficiary == get_beneficiary(vesting_contract, *shareholder)) {
+ result = *shareholder;
+ true
+ } else {
+ false
+ }
+ });
+
+ result
+}
+
+
+
+
+public fun create_vesting_schedule(schedule: vector<fixed_point32::FixedPoint32>, start_timestamp_secs: u64, period_duration: u64): vesting::VestingSchedule
+
+
+
+
+public fun create_vesting_schedule(
+ schedule: vector<FixedPoint32>,
+ start_timestamp_secs: u64,
+ period_duration: u64,
+): VestingSchedule {
+ assert!(vector::length(&schedule) > 0, error::invalid_argument(EEMPTY_VESTING_SCHEDULE));
+ assert!(period_duration > 0, error::invalid_argument(EZERO_VESTING_SCHEDULE_PERIOD));
+ assert!(
+ start_timestamp_secs >= timestamp::now_seconds(),
+ error::invalid_argument(EVESTING_START_TOO_SOON),
+ );
+
+ VestingSchedule {
+ schedule,
+ start_timestamp_secs,
+ period_duration,
+ last_vested_period: 0,
+ }
+}
+
+
+
+
+public fun create_vesting_contract(admin: &signer, shareholders: &vector<address>, buy_ins: simple_map::SimpleMap<address, coin::Coin<aptos_coin::AptosCoin>>, vesting_schedule: vesting::VestingSchedule, withdrawal_address: address, operator: address, voter: address, commission_percentage: u64, contract_creation_seed: vector<u8>): address
+
+
+
+
+public fun create_vesting_contract(
+ admin: &signer,
+ shareholders: &vector<address>,
+ buy_ins: SimpleMap<address, Coin<AptosCoin>>,
+ vesting_schedule: VestingSchedule,
+ withdrawal_address: address,
+ operator: address,
+ voter: address,
+ commission_percentage: u64,
+ // Optional seed used when creating the staking contract account.
+ contract_creation_seed: vector<u8>,
+): address acquires AdminStore {
+ assert!(
+ !system_addresses::is_reserved_address(withdrawal_address),
+ error::invalid_argument(EINVALID_WITHDRAWAL_ADDRESS),
+ );
+ assert_account_is_registered_for_apt(withdrawal_address);
+ assert!(vector::length(shareholders) > 0, error::invalid_argument(ENO_SHAREHOLDERS));
+ assert!(
+ simple_map::length(&buy_ins) == vector::length(shareholders),
+ error::invalid_argument(ESHARES_LENGTH_MISMATCH),
+ );
+
+ // Create a coins pool to track shareholders and shares of the grant.
+ let grant = coin::zero<AptosCoin>();
+ let grant_amount = 0;
+ let grant_pool = pool_u64::create(MAXIMUM_SHAREHOLDERS);
+ vector::for_each_ref(shareholders, |shareholder| {
+ let shareholder: address = *shareholder;
+ let (_, buy_in) = simple_map::remove(&mut buy_ins, &shareholder);
+ let buy_in_amount = coin::value(&buy_in);
+ coin::merge(&mut grant, buy_in);
+ pool_u64::buy_in(
+ &mut grant_pool,
+ shareholder,
+ buy_in_amount,
+ );
+ grant_amount = grant_amount + buy_in_amount;
+ });
+ assert!(grant_amount > 0, error::invalid_argument(EZERO_GRANT));
+
+ // If this is the first time this admin account has created a vesting contract, initialize the admin store.
+ let admin_address = signer::address_of(admin);
+ if (!exists<AdminStore>(admin_address)) {
+ move_to(admin, AdminStore {
+ vesting_contracts: vector::empty<address>(),
+ nonce: 0,
+ create_events: new_event_handle<CreateVestingContractEvent>(admin),
+ });
+ };
+
+ // Initialize the vesting contract in a new resource account. This allows the same admin to create multiple
+ // pools.
+ let (contract_signer, contract_signer_cap) = create_vesting_contract_account(admin, contract_creation_seed);
+ let pool_address = staking_contract::create_staking_contract_with_coins(
+ &contract_signer, operator, voter, grant, commission_percentage, contract_creation_seed);
+
+ // Add the newly created vesting contract's address to the admin store.
+ let contract_address = signer::address_of(&contract_signer);
+ let admin_store = borrow_global_mut<AdminStore>(admin_address);
+ vector::push_back(&mut admin_store.vesting_contracts, contract_address);
+ if (std::features::module_event_migration_enabled()) {
+ emit(
+ CreateVestingContract {
+ operator,
+ voter,
+ withdrawal_address,
+ grant_amount,
+ vesting_contract_address: contract_address,
+ staking_pool_address: pool_address,
+ commission_percentage,
+ },
+ );
+ };
+ emit_event(
+ &mut admin_store.create_events,
+ CreateVestingContractEvent {
+ operator,
+ voter,
+ withdrawal_address,
+ grant_amount,
+ vesting_contract_address: contract_address,
+ staking_pool_address: pool_address,
+ commission_percentage,
+ },
+ );
+
+ move_to(&contract_signer, VestingContract {
+ state: VESTING_POOL_ACTIVE,
+ admin: admin_address,
+ grant_pool,
+ beneficiaries: simple_map::create<address, address>(),
+ vesting_schedule,
+ withdrawal_address,
+ staking: StakingInfo { pool_address, operator, voter, commission_percentage },
+ remaining_grant: grant_amount,
+ signer_cap: contract_signer_cap,
+ update_operator_events: new_event_handle<UpdateOperatorEvent>(&contract_signer),
+ update_voter_events: new_event_handle<UpdateVoterEvent>(&contract_signer),
+ reset_lockup_events: new_event_handle<ResetLockupEvent>(&contract_signer),
+ set_beneficiary_events: new_event_handle<SetBeneficiaryEvent>(&contract_signer),
+ unlock_rewards_events: new_event_handle<UnlockRewardsEvent>(&contract_signer),
+ vest_events: new_event_handle<VestEvent>(&contract_signer),
+ distribute_events: new_event_handle<DistributeEvent>(&contract_signer),
+ terminate_events: new_event_handle<TerminateEvent>(&contract_signer),
+ admin_withdraw_events: new_event_handle<AdminWithdrawEvent>(&contract_signer),
+ });
+
+ simple_map::destroy_empty(buy_ins);
+ contract_address
+}
+
+
+
+
+public entry fun unlock_rewards(contract_address: address)
+
+
+
+
+public entry fun unlock_rewards(contract_address: address) acquires VestingContract {
+ let accumulated_rewards = total_accumulated_rewards(contract_address);
+ let vesting_contract = borrow_global<VestingContract>(contract_address);
+ unlock_stake(vesting_contract, accumulated_rewards);
+}
+
+
+
+
+unlock_rewards
for many vesting contracts.
+
+
+public entry fun unlock_rewards_many(contract_addresses: vector<address>)
+
+
+
+
+public entry fun unlock_rewards_many(contract_addresses: vector<address>) acquires VestingContract {
+ let len = vector::length(&contract_addresses);
+
+ assert!(len != 0, error::invalid_argument(EVEC_EMPTY_FOR_MANY_FUNCTION));
+
+ vector::for_each_ref(&contract_addresses, |contract_address| {
+ let contract_address: address = *contract_address;
+ unlock_rewards(contract_address);
+ });
+}
+
+
+
+
+public entry fun vest(contract_address: address)
+
+
+
+
+public entry fun vest(contract_address: address) acquires VestingContract {
+ // Unlock all rewards first, if any.
+ unlock_rewards(contract_address);
+
+ // Unlock the vested amount. This amount will become withdrawable when the underlying stake pool's lockup
+ // expires.
+ let vesting_contract = borrow_global_mut<VestingContract>(contract_address);
+ // Short-circuit if vesting hasn't started yet.
+ if (vesting_contract.vesting_schedule.start_timestamp_secs > timestamp::now_seconds()) {
+ return
+ };
+
+ // Check if the next vested period has already passed. If not, short-circuit since there's nothing to vest.
+ let vesting_schedule = &mut vesting_contract.vesting_schedule;
+ let last_vested_period = vesting_schedule.last_vested_period;
+ let next_period_to_vest = last_vested_period + 1;
+ let last_completed_period =
+ (timestamp::now_seconds() - vesting_schedule.start_timestamp_secs) / vesting_schedule.period_duration;
+ if (last_completed_period < next_period_to_vest) {
+ return
+ };
+
+ // Calculate how much has vested, excluding rewards.
+ // Index is 0-based while period is 1-based so we need to subtract 1.
+ let schedule = &vesting_schedule.schedule;
+ let schedule_index = next_period_to_vest - 1;
+ let vesting_fraction = if (schedule_index < vector::length(schedule)) {
+ *vector::borrow(schedule, schedule_index)
+ } else {
+ // Last vesting schedule fraction will repeat until the grant runs out.
+ *vector::borrow(schedule, vector::length(schedule) - 1)
+ };
+ let total_grant = pool_u64::total_coins(&vesting_contract.grant_pool);
+ let vested_amount = fixed_point32::multiply_u64(total_grant, vesting_fraction);
+ // Cap vested amount by the remaining grant amount so we don't try to distribute more than what's remaining.
+ vested_amount = min(vested_amount, vesting_contract.remaining_grant);
+ vesting_contract.remaining_grant = vesting_contract.remaining_grant - vested_amount;
+ vesting_schedule.last_vested_period = next_period_to_vest;
+ unlock_stake(vesting_contract, vested_amount);
+
+ if (std::features::module_event_migration_enabled()) {
+ emit(
+ Vest {
+ admin: vesting_contract.admin,
+ vesting_contract_address: contract_address,
+ staking_pool_address: vesting_contract.staking.pool_address,
+ period_vested: next_period_to_vest,
+ amount: vested_amount,
+ },
+ );
+ };
+ emit_event(
+ &mut vesting_contract.vest_events,
+ VestEvent {
+ admin: vesting_contract.admin,
+ vesting_contract_address: contract_address,
+ staking_pool_address: vesting_contract.staking.pool_address,
+ period_vested: next_period_to_vest,
+ amount: vested_amount,
+ },
+ );
+}
+
+
+
+
+vest
for many vesting contracts.
+
+
+public entry fun vest_many(contract_addresses: vector<address>)
+
+
+
+
+public entry fun vest_many(contract_addresses: vector<address>) acquires VestingContract {
+ let len = vector::length(&contract_addresses);
+
+ assert!(len != 0, error::invalid_argument(EVEC_EMPTY_FOR_MANY_FUNCTION));
+
+ vector::for_each_ref(&contract_addresses, |contract_address| {
+ let contract_address = *contract_address;
+ vest(contract_address);
+ });
+}
+
+
+
+
+public entry fun distribute(contract_address: address)
+
+
+
+
+public entry fun distribute(contract_address: address) acquires VestingContract {
+ assert_active_vesting_contract(contract_address);
+
+ let vesting_contract = borrow_global_mut<VestingContract>(contract_address);
+ let coins = withdraw_stake(vesting_contract, contract_address);
+ let total_distribution_amount = coin::value(&coins);
+ if (total_distribution_amount == 0) {
+ coin::destroy_zero(coins);
+ return
+ };
+
+ // Distribute coins to all shareholders in the vesting contract.
+ let grant_pool = &vesting_contract.grant_pool;
+ let shareholders = &pool_u64::shareholders(grant_pool);
+ vector::for_each_ref(shareholders, |shareholder| {
+ let shareholder = *shareholder;
+ let shares = pool_u64::shares(grant_pool, shareholder);
+ let amount = pool_u64::shares_to_amount_with_total_coins(grant_pool, shares, total_distribution_amount);
+ let share_of_coins = coin::extract(&mut coins, amount);
+ let recipient_address = get_beneficiary(vesting_contract, shareholder);
+ aptos_account::deposit_coins(recipient_address, share_of_coins);
+ });
+
+ // Send any remaining "dust" (leftover due to rounding error) to the withdrawal address.
+ if (coin::value(&coins) > 0) {
+ aptos_account::deposit_coins(vesting_contract.withdrawal_address, coins);
+ } else {
+ coin::destroy_zero(coins);
+ };
+
+ if (std::features::module_event_migration_enabled()) {
+ emit(
+ Distribute {
+ admin: vesting_contract.admin,
+ vesting_contract_address: contract_address,
+ amount: total_distribution_amount,
+ },
+ );
+ };
+ emit_event(
+ &mut vesting_contract.distribute_events,
+ DistributeEvent {
+ admin: vesting_contract.admin,
+ vesting_contract_address: contract_address,
+ amount: total_distribution_amount,
+ },
+ );
+}
+
+
+
+
+distribute
for many vesting contracts.
+
+
+public entry fun distribute_many(contract_addresses: vector<address>)
+
+
+
+
+public entry fun distribute_many(contract_addresses: vector<address>) acquires VestingContract {
+ let len = vector::length(&contract_addresses);
+
+ assert!(len != 0, error::invalid_argument(EVEC_EMPTY_FOR_MANY_FUNCTION));
+
+ vector::for_each_ref(&contract_addresses, |contract_address| {
+ let contract_address = *contract_address;
+ distribute(contract_address);
+ });
+}
+
+
+
+
+public entry fun terminate_vesting_contract(admin: &signer, contract_address: address)
+
+
+
+
+public entry fun terminate_vesting_contract(admin: &signer, contract_address: address) acquires VestingContract {
+ assert_active_vesting_contract(contract_address);
+
+ // Distribute all withdrawable coins, which should have been from previous rewards withdrawal or vest.
+ distribute(contract_address);
+
+ let vesting_contract = borrow_global_mut<VestingContract>(contract_address);
+ verify_admin(admin, vesting_contract);
+ let (active_stake, _, pending_active_stake, _) = stake::get_stake(vesting_contract.staking.pool_address);
+ assert!(pending_active_stake == 0, error::invalid_state(EPENDING_STAKE_FOUND));
+
+ // Unlock all remaining active stake.
+ vesting_contract.state = VESTING_POOL_TERMINATED;
+ vesting_contract.remaining_grant = 0;
+ unlock_stake(vesting_contract, active_stake);
+
+ if (std::features::module_event_migration_enabled()) {
+ emit(
+ Terminate {
+ admin: vesting_contract.admin,
+ vesting_contract_address: contract_address,
+ },
+ );
+ };
+ emit_event(
+ &mut vesting_contract.terminate_events,
+ TerminateEvent {
+ admin: vesting_contract.admin,
+ vesting_contract_address: contract_address,
+ },
+ );
+}
+
+
+
+
+public entry fun admin_withdraw(admin: &signer, contract_address: address)
+
+
+
+
+public entry fun admin_withdraw(admin: &signer, contract_address: address) acquires VestingContract {
+ let vesting_contract = borrow_global<VestingContract>(contract_address);
+ assert!(
+ vesting_contract.state == VESTING_POOL_TERMINATED,
+ error::invalid_state(EVESTING_CONTRACT_STILL_ACTIVE)
+ );
+
+ let vesting_contract = borrow_global_mut<VestingContract>(contract_address);
+ verify_admin(admin, vesting_contract);
+ let coins = withdraw_stake(vesting_contract, contract_address);
+ let amount = coin::value(&coins);
+ if (amount == 0) {
+ coin::destroy_zero(coins);
+ return
+ };
+ aptos_account::deposit_coins(vesting_contract.withdrawal_address, coins);
+
+ if (std::features::module_event_migration_enabled()) {
+ emit(
+ AdminWithdraw {
+ admin: vesting_contract.admin,
+ vesting_contract_address: contract_address,
+ amount,
+ },
+ );
+ };
+ emit_event(
+ &mut vesting_contract.admin_withdraw_events,
+ AdminWithdrawEvent {
+ admin: vesting_contract.admin,
+ vesting_contract_address: contract_address,
+ amount,
+ },
+ );
+}
+
+
+
+
+public entry fun update_operator(admin: &signer, contract_address: address, new_operator: address, commission_percentage: u64)
+
+
+
+
+public entry fun update_operator(
+ admin: &signer,
+ contract_address: address,
+ new_operator: address,
+ commission_percentage: u64,
+) acquires VestingContract {
+ let vesting_contract = borrow_global_mut<VestingContract>(contract_address);
+ verify_admin(admin, vesting_contract);
+ let contract_signer = &get_vesting_account_signer_internal(vesting_contract);
+ let old_operator = vesting_contract.staking.operator;
+ staking_contract::switch_operator(contract_signer, old_operator, new_operator, commission_percentage);
+ vesting_contract.staking.operator = new_operator;
+ vesting_contract.staking.commission_percentage = commission_percentage;
+
+ if (std::features::module_event_migration_enabled()) {
+ emit(
+ UpdateOperator {
+ admin: vesting_contract.admin,
+ vesting_contract_address: contract_address,
+ staking_pool_address: vesting_contract.staking.pool_address,
+ old_operator,
+ new_operator,
+ commission_percentage,
+ },
+ );
+ };
+ emit_event(
+ &mut vesting_contract.update_operator_events,
+ UpdateOperatorEvent {
+ admin: vesting_contract.admin,
+ vesting_contract_address: contract_address,
+ staking_pool_address: vesting_contract.staking.pool_address,
+ old_operator,
+ new_operator,
+ commission_percentage,
+ },
+ );
+}
+
+
+
+
+public entry fun update_operator_with_same_commission(admin: &signer, contract_address: address, new_operator: address)
+
+
+
+
+public entry fun update_operator_with_same_commission(
+ admin: &signer,
+ contract_address: address,
+ new_operator: address,
+) acquires VestingContract {
+ let commission_percentage = operator_commission_percentage(contract_address);
+ update_operator(admin, contract_address, new_operator, commission_percentage);
+}
+
+
+
+
+public entry fun update_commission_percentage(admin: &signer, contract_address: address, new_commission_percentage: u64)
+
+
+
+
+public entry fun update_commission_percentage(
+ admin: &signer,
+ contract_address: address,
+ new_commission_percentage: u64,
+) acquires VestingContract {
+ let operator = operator(contract_address);
+ let vesting_contract = borrow_global_mut<VestingContract>(contract_address);
+ verify_admin(admin, vesting_contract);
+ let contract_signer = &get_vesting_account_signer_internal(vesting_contract);
+ staking_contract::update_commision(contract_signer, operator, new_commission_percentage);
+ vesting_contract.staking.commission_percentage = new_commission_percentage;
+ // This function does not emit an event. Instead, `staking_contract::update_commission_percentage`
+ // emits the event for this commission percentage update.
+}
+
+
+
+
+public entry fun update_voter(admin: &signer, contract_address: address, new_voter: address)
+
+
+
+
+public entry fun update_voter(
+ admin: &signer,
+ contract_address: address,
+ new_voter: address,
+) acquires VestingContract {
+ let vesting_contract = borrow_global_mut<VestingContract>(contract_address);
+ verify_admin(admin, vesting_contract);
+ let contract_signer = &get_vesting_account_signer_internal(vesting_contract);
+ let old_voter = vesting_contract.staking.voter;
+ staking_contract::update_voter(contract_signer, vesting_contract.staking.operator, new_voter);
+ vesting_contract.staking.voter = new_voter;
+
+ if (std::features::module_event_migration_enabled()) {
+ emit(
+ UpdateVoter {
+ admin: vesting_contract.admin,
+ vesting_contract_address: contract_address,
+ staking_pool_address: vesting_contract.staking.pool_address,
+ old_voter,
+ new_voter,
+ },
+ );
+ };
+ emit_event(
+ &mut vesting_contract.update_voter_events,
+ UpdateVoterEvent {
+ admin: vesting_contract.admin,
+ vesting_contract_address: contract_address,
+ staking_pool_address: vesting_contract.staking.pool_address,
+ old_voter,
+ new_voter,
+ },
+ );
+}
+
+
+
+
+public entry fun reset_lockup(admin: &signer, contract_address: address)
+
+
+
+
+public entry fun reset_lockup(
+ admin: &signer,
+ contract_address: address,
+) acquires VestingContract {
+ let vesting_contract = borrow_global_mut<VestingContract>(contract_address);
+ verify_admin(admin, vesting_contract);
+ let contract_signer = &get_vesting_account_signer_internal(vesting_contract);
+ staking_contract::reset_lockup(contract_signer, vesting_contract.staking.operator);
+
+ if (std::features::module_event_migration_enabled()) {
+ emit(
+ ResetLockup {
+ admin: vesting_contract.admin,
+ vesting_contract_address: contract_address,
+ staking_pool_address: vesting_contract.staking.pool_address,
+ new_lockup_expiration_secs: stake::get_lockup_secs(vesting_contract.staking.pool_address),
+ },
+ );
+ };
+ emit_event(
+ &mut vesting_contract.reset_lockup_events,
+ ResetLockupEvent {
+ admin: vesting_contract.admin,
+ vesting_contract_address: contract_address,
+ staking_pool_address: vesting_contract.staking.pool_address,
+ new_lockup_expiration_secs: stake::get_lockup_secs(vesting_contract.staking.pool_address),
+ },
+ );
+}
+
+
+
+
+public entry fun set_beneficiary(admin: &signer, contract_address: address, shareholder: address, new_beneficiary: address)
+
+
+
+
+public entry fun set_beneficiary(
+ admin: &signer,
+ contract_address: address,
+ shareholder: address,
+ new_beneficiary: address,
+) acquires VestingContract {
+ // Verify that the beneficiary account is set up to receive APT. This is a requirement so distribute() wouldn't
+ // fail and block all other accounts from receiving APT if one beneficiary is not registered.
+ assert_account_is_registered_for_apt(new_beneficiary);
+
+ let vesting_contract = borrow_global_mut<VestingContract>(contract_address);
+ verify_admin(admin, vesting_contract);
+
+ let old_beneficiary = get_beneficiary(vesting_contract, shareholder);
+ let beneficiaries = &mut vesting_contract.beneficiaries;
+ if (simple_map::contains_key(beneficiaries, &shareholder)) {
+ let beneficiary = simple_map::borrow_mut(beneficiaries, &shareholder);
+ *beneficiary = new_beneficiary;
+ } else {
+ simple_map::add(beneficiaries, shareholder, new_beneficiary);
+ };
+
+ if (std::features::module_event_migration_enabled()) {
+ emit(
+ SetBeneficiary {
+ admin: vesting_contract.admin,
+ vesting_contract_address: contract_address,
+ shareholder,
+ old_beneficiary,
+ new_beneficiary,
+ },
+ );
+ };
+ emit_event(
+ &mut vesting_contract.set_beneficiary_events,
+ SetBeneficiaryEvent {
+ admin: vesting_contract.admin,
+ vesting_contract_address: contract_address,
+ shareholder,
+ old_beneficiary,
+ new_beneficiary,
+ },
+ );
+}
+
+
+
+
+public entry fun reset_beneficiary(account: &signer, contract_address: address, shareholder: address)
+
+
+
+
+public entry fun reset_beneficiary(
+ account: &signer,
+ contract_address: address,
+ shareholder: address,
+) acquires VestingAccountManagement, VestingContract {
+ let vesting_contract = borrow_global_mut<VestingContract>(contract_address);
+ let addr = signer::address_of(account);
+ assert!(
+ addr == vesting_contract.admin ||
+ addr == get_role_holder(contract_address, utf8(ROLE_BENEFICIARY_RESETTER)),
+ error::permission_denied(EPERMISSION_DENIED),
+ );
+
+ let beneficiaries = &mut vesting_contract.beneficiaries;
+ if (simple_map::contains_key(beneficiaries, &shareholder)) {
+ simple_map::remove(beneficiaries, &shareholder);
+ };
+}
+
+
+
+
+public entry fun set_management_role(admin: &signer, contract_address: address, role: string::String, role_holder: address)
+
+
+
+
+public entry fun set_management_role(
+ admin: &signer,
+ contract_address: address,
+ role: String,
+ role_holder: address,
+) acquires VestingAccountManagement, VestingContract {
+ let vesting_contract = borrow_global_mut<VestingContract>(contract_address);
+ verify_admin(admin, vesting_contract);
+
+ if (!exists<VestingAccountManagement>(contract_address)) {
+ let contract_signer = &get_vesting_account_signer_internal(vesting_contract);
+ move_to(contract_signer, VestingAccountManagement {
+ roles: simple_map::create<String, address>(),
+ })
+ };
+ let roles = &mut borrow_global_mut<VestingAccountManagement>(contract_address).roles;
+ if (simple_map::contains_key(roles, &role)) {
+ *simple_map::borrow_mut(roles, &role) = role_holder;
+ } else {
+ simple_map::add(roles, role, role_holder);
+ };
+}
+
+
+
+
+public entry fun set_beneficiary_resetter(admin: &signer, contract_address: address, beneficiary_resetter: address)
+
+
+
+
+public entry fun set_beneficiary_resetter(
+ admin: &signer,
+ contract_address: address,
+ beneficiary_resetter: address,
+) acquires VestingAccountManagement, VestingContract {
+ set_management_role(admin, contract_address, utf8(ROLE_BENEFICIARY_RESETTER), beneficiary_resetter);
+}
+
+
+
+
+public entry fun set_beneficiary_for_operator(operator: &signer, new_beneficiary: address)
+
+
+
+
+public entry fun set_beneficiary_for_operator(
+ operator: &signer,
+ new_beneficiary: address,
+) {
+ staking_contract::set_beneficiary_for_operator(operator, new_beneficiary);
+}
+
+
+
+
+public fun get_role_holder(contract_address: address, role: string::String): address
+
+
+
+
+public fun get_role_holder(contract_address: address, role: String): address acquires VestingAccountManagement {
+ assert!(exists<VestingAccountManagement>(contract_address), error::not_found(EVESTING_ACCOUNT_HAS_NO_ROLES));
+ let roles = &borrow_global<VestingAccountManagement>(contract_address).roles;
+ assert!(simple_map::contains_key(roles, &role), error::not_found(EROLE_NOT_FOUND));
+ *simple_map::borrow(roles, &role)
+}
+
+
+
+
+public fun get_vesting_account_signer(admin: &signer, contract_address: address): signer
+
+
+
+
+public fun get_vesting_account_signer(admin: &signer, contract_address: address): signer acquires VestingContract {
+ let vesting_contract = borrow_global_mut<VestingContract>(contract_address);
+ verify_admin(admin, vesting_contract);
+ get_vesting_account_signer_internal(vesting_contract)
+}
+
+
+
+
+fun get_vesting_account_signer_internal(vesting_contract: &vesting::VestingContract): signer
+
+
+
+
+fun get_vesting_account_signer_internal(vesting_contract: &VestingContract): signer {
+ account::create_signer_with_capability(&vesting_contract.signer_cap)
+}
+
+
+
+
+fun create_vesting_contract_account(admin: &signer, contract_creation_seed: vector<u8>): (signer, account::SignerCapability)
+
+
+
+
+fun create_vesting_contract_account(
+ admin: &signer,
+ contract_creation_seed: vector<u8>,
+): (signer, SignerCapability) acquires AdminStore {
+ let admin_store = borrow_global_mut<AdminStore>(signer::address_of(admin));
+ let seed = bcs::to_bytes(&signer::address_of(admin));
+ vector::append(&mut seed, bcs::to_bytes(&admin_store.nonce));
+ admin_store.nonce = admin_store.nonce + 1;
+
+ // Include a salt to avoid conflicts with any other modules out there that might also generate
+ // deterministic resource accounts for the same admin address + nonce.
+ vector::append(&mut seed, VESTING_POOL_SALT);
+ vector::append(&mut seed, contract_creation_seed);
+
+ let (account_signer, signer_cap) = account::create_resource_account(admin, seed);
+ // Register the vesting contract account to receive APT as it'll be sent to it when claiming unlocked stake from
+ // the underlying staking contract.
+ coin::register<AptosCoin>(&account_signer);
+
+ (account_signer, signer_cap)
+}
+
+
+
+
+fun verify_admin(admin: &signer, vesting_contract: &vesting::VestingContract)
+
+
+
+
+fun verify_admin(admin: &signer, vesting_contract: &VestingContract) {
+ assert!(signer::address_of(admin) == vesting_contract.admin, error::unauthenticated(ENOT_ADMIN));
+}
+
+
+
+
+fun assert_vesting_contract_exists(contract_address: address)
+
+
+
+
+fun assert_vesting_contract_exists(contract_address: address) {
+ assert!(exists<VestingContract>(contract_address), error::not_found(EVESTING_CONTRACT_NOT_FOUND));
+}
+
+
+
+
+fun assert_active_vesting_contract(contract_address: address)
+
+
+
+
+fun assert_active_vesting_contract(contract_address: address) acquires VestingContract {
+ assert_vesting_contract_exists(contract_address);
+ let vesting_contract = borrow_global<VestingContract>(contract_address);
+ assert!(vesting_contract.state == VESTING_POOL_ACTIVE, error::invalid_state(EVESTING_CONTRACT_NOT_ACTIVE));
+}
+
+
+
+
+fun unlock_stake(vesting_contract: &vesting::VestingContract, amount: u64)
+
+
+
+
+fun unlock_stake(vesting_contract: &VestingContract, amount: u64) {
+ let contract_signer = &get_vesting_account_signer_internal(vesting_contract);
+ staking_contract::unlock_stake(contract_signer, vesting_contract.staking.operator, amount);
+}
+
+
+
+
+fun withdraw_stake(vesting_contract: &vesting::VestingContract, contract_address: address): coin::Coin<aptos_coin::AptosCoin>
+
+
+
+
+fun withdraw_stake(vesting_contract: &VestingContract, contract_address: address): Coin<AptosCoin> {
+ // Claim any withdrawable distribution from the staking contract. The withdrawn coins will be sent directly to
+ // the vesting contract's account.
+ staking_contract::distribute(contract_address, vesting_contract.staking.operator);
+ let withdrawn_coins = coin::balance<AptosCoin>(contract_address);
+ let contract_signer = &get_vesting_account_signer_internal(vesting_contract);
+ coin::withdraw<AptosCoin>(contract_signer, withdrawn_coins)
+}
+
+
+
+
+fun get_beneficiary(contract: &vesting::VestingContract, shareholder: address): address
+
+
+
+
+fun get_beneficiary(contract: &VestingContract, shareholder: address): address {
+ if (simple_map::contains_key(&contract.beneficiaries, &shareholder)) {
+ *simple_map::borrow(&contract.beneficiaries, &shareholder)
+ } else {
+ shareholder
+ }
+}
+
+
+
+
+No. | Requirement | Criticality | Implementation | Enforcement | +
---|---|---|---|---|
1 | +In order to retrieve the address of the underlying stake pool, the vesting start timestamp of the vesting contract, the duration of the vesting period, the remaining grant of a vesting contract, the beneficiary account of a shareholder in a vesting contract, the percentage of accumulated rewards that is paid to the operator as commission, the operator who runs the validator, the voter who will be voting on-chain, and the vesting schedule of a vesting contract, the supplied vesting contract should exist. | +Low | +The vesting_start_secs, period_duration_secs, remaining_grant, beneficiary, operator_commission_percentage, operator, voter, and vesting_schedule functions ensure that the supplied vesting contract address exists by calling the assert_vesting_contract_exists function. | +Formally verified via assert_vesting_contract_exists. | +
2 | +The vesting pool should not exceed a maximum of 30 shareholders. | +Medium | +The maximum number of shareholders a vesting pool can support is stored as a constant in MAXIMUM_SHAREHOLDERS which is passed to the pool_u64::create function. | +Formally verified via a global invariant. | +
3 | +Retrieving all the vesting contracts of a given address and retrieving the list of beneficiaries from a vesting contract should never fail. | +Medium | +The function vesting_contracts checks if the supplied admin address contains an AdminStore resource and returns all the vesting contracts as a vector. Otherwise it returns an empty vector. The function get_beneficiary checks for a given vesting contract, a specific shareholder exists, and if so, the beneficiary will be returned, otherwise it will simply return the address of the shareholder. | +Formally verified via vesting_contracts and get_beneficiary. | +
4 | +The shareholders should be able to start vesting only after the vesting cliff and the first vesting period have transpired. | +High | +The end of the vesting cliff is stored under VestingContract.vesting_schedule.start_timestamp_secs. The vest function always checks that timestamp::now_seconds is greater or equal to the end of the vesting cliff period. | +Audited the check for the end of vesting cliff: vest module. | +
5 | +In order to retrieve the total accumulated rewards that have not been distributed, the accumulated rewards of a given beneficiary, the list of al shareholders in a vesting contract,the shareholder address given the beneficiary address in a given vesting contract, to terminate a vesting contract and to distribute any withdrawable stake from the stake pool, the supplied vesting contract should exist and be active. | +Low | +The distribute, terminate_vesting_contract, shareholder, shareholders, accumulated_rewards, and total_accumulated_rewards functions ensure that the supplied vesting contract address exists and is active by calling the assert_active_vesting_contract function. | +Formally verified via ActiveVestingContractAbortsIf. | +
6 | +A new vesting schedule should not be allowed to start vesting in the past or to supply an empty schedule or for the period duration to be zero. | +High | +The create_vesting_schedule function ensures that the length of the schedule vector is greater than 0, that the period duration is greater than 0 and that the start_timestamp_secs is greater or equal to timestamp::now_seconds. | +Formally verified via create_vesting_schedule. | +
7 | +The shareholders should be able to vest the tokens from previous periods. | +High | +When vesting, the last_completed_period is checked against the next period to vest. This allows to unlock vested tokens for the next period since last vested, in case they didn't call vest for some periods. | +Audited that vesting doesn't skip periods, but gradually increments to allow shareholders to retrieve all the vested tokens. | +
8 | +Actions such as obtaining a list of shareholders, calculating accrued rewards, distributing withdrawable stake, and terminating the vesting contract should be accessible exclusively while the vesting contract remains active. | +Low | +Restricting access to inactive vesting contracts is achieved through the assert_active_vesting_contract function. | +Formally verified via ActiveVestingContractAbortsIf. | +
9 | +The ability to terminate a vesting contract should only be available to the owner. | +High | +Limiting the access of accounts to specific function, is achieved by asserting that the signer matches the admin of the VestingContract. | +Formally verified via verify_admin. | +
10 | +A new vesting contract should not be allowed to have an empty list of shareholders, have a different amount of shareholders than buy-ins, and provide a withdrawal address which is either reserved or not registered for apt. | +High | +The create_vesting_contract function ensures that the withdrawal_address is not a reserved address, that it is registered for apt, that the list of shareholders is non-empty, and that the amount of shareholders matches the amount of buy_ins. | +Formally verified via create_vesting_contract. | +
11 | +Creating a vesting contract account should require the signer (admin) to own an admin store and should enforce that the seed of the resource account is composed of the admin store's nonce, the vesting pool salt, and the custom contract creation seed. | +Medium | +The create_vesting_contract_account concatenates to the seed first the admin_store.nonce then the VESTING_POOL_SALT then the contract_creation_seed and then it is passed to the create_resource_account function. | +Enforced via create_vesting_contract_account. | +
pragma verify = true;
+pragma aborts_if_is_strict;
+// This enforces high-level requirement 2:
+invariant forall a: address where exists<VestingContract>(a):
+ global<VestingContract>(a).grant_pool.shareholders_limit <= MAXIMUM_SHAREHOLDERS;
+
+
+
+
+
+
+### Function `stake_pool_address`
+
+
+#[view]
+public fun stake_pool_address(vesting_contract_address: address): address
+
+
+
+
+
+aborts_if !exists<VestingContract>(vesting_contract_address);
+
+
+
+
+
+
+### Function `vesting_start_secs`
+
+
+#[view]
+public fun vesting_start_secs(vesting_contract_address: address): u64
+
+
+
+
+
+aborts_if !exists<VestingContract>(vesting_contract_address);
+
+
+
+
+
+
+### Function `period_duration_secs`
+
+
+#[view]
+public fun period_duration_secs(vesting_contract_address: address): u64
+
+
+
+
+
+aborts_if !exists<VestingContract>(vesting_contract_address);
+
+
+
+
+
+
+### Function `remaining_grant`
+
+
+#[view]
+public fun remaining_grant(vesting_contract_address: address): u64
+
+
+
+
+
+aborts_if !exists<VestingContract>(vesting_contract_address);
+
+
+
+
+
+
+### Function `beneficiary`
+
+
+#[view]
+public fun beneficiary(vesting_contract_address: address, shareholder: address): address
+
+
+
+
+
+aborts_if !exists<VestingContract>(vesting_contract_address);
+
+
+
+
+
+
+### Function `operator_commission_percentage`
+
+
+#[view]
+public fun operator_commission_percentage(vesting_contract_address: address): u64
+
+
+
+
+
+aborts_if !exists<VestingContract>(vesting_contract_address);
+
+
+
+
+
+
+### Function `vesting_contracts`
+
+
+#[view]
+public fun vesting_contracts(admin: address): vector<address>
+
+
+
+
+
+// This enforces high-level requirement 3:
+aborts_if false;
+
+
+
+
+
+
+### Function `operator`
+
+
+#[view]
+public fun operator(vesting_contract_address: address): address
+
+
+
+
+
+aborts_if !exists<VestingContract>(vesting_contract_address);
+
+
+
+
+
+
+### Function `voter`
+
+
+#[view]
+public fun voter(vesting_contract_address: address): address
+
+
+
+
+
+aborts_if !exists<VestingContract>(vesting_contract_address);
+
+
+
+
+
+
+### Function `vesting_schedule`
+
+
+#[view]
+public fun vesting_schedule(vesting_contract_address: address): vesting::VestingSchedule
+
+
+
+
+
+aborts_if !exists<VestingContract>(vesting_contract_address);
+
+
+
+
+
+
+### Function `total_accumulated_rewards`
+
+
+#[view]
+public fun total_accumulated_rewards(vesting_contract_address: address): u64
+
+
+
+
+
+pragma verify = false;
+include TotalAccumulatedRewardsAbortsIf;
+
+
+
+
+
+
+
+
+schema TotalAccumulatedRewardsAbortsIf {
+ vesting_contract_address: address;
+ requires staking_contract.commission_percentage >= 0 && staking_contract.commission_percentage <= 100;
+ include ActiveVestingContractAbortsIf<VestingContract>{contract_address: vesting_contract_address};
+ let vesting_contract = global<VestingContract>(vesting_contract_address);
+ let staker = vesting_contract_address;
+ let operator = vesting_contract.staking.operator;
+ let staking_contracts = global<staking_contract::Store>(staker).staking_contracts;
+ let staking_contract = simple_map::spec_get(staking_contracts, operator);
+ aborts_if !exists<staking_contract::Store>(staker);
+ aborts_if !simple_map::spec_contains_key(staking_contracts, operator);
+ let pool_address = staking_contract.pool_address;
+ let stake_pool = global<stake::StakePool>(pool_address);
+ let active = coin::value(stake_pool.active);
+ let pending_active = coin::value(stake_pool.pending_active);
+ let total_active_stake = active + pending_active;
+ let accumulated_rewards = total_active_stake - staking_contract.principal;
+ let commission_amount = accumulated_rewards * staking_contract.commission_percentage / 100;
+ aborts_if !exists<stake::StakePool>(pool_address);
+ aborts_if active + pending_active > MAX_U64;
+ aborts_if total_active_stake < staking_contract.principal;
+ aborts_if accumulated_rewards * staking_contract.commission_percentage > MAX_U64;
+ aborts_if (vesting_contract.remaining_grant + commission_amount) > total_active_stake;
+ aborts_if total_active_stake < vesting_contract.remaining_grant;
+}
+
+
+
+
+
+
+### Function `accumulated_rewards`
+
+
+#[view]
+public fun accumulated_rewards(vesting_contract_address: address, shareholder_or_beneficiary: address): u64
+
+
+
+
+
+pragma verify = false;
+include TotalAccumulatedRewardsAbortsIf;
+let vesting_contract = global<VestingContract>(vesting_contract_address);
+let operator = vesting_contract.staking.operator;
+let staking_contracts = global<staking_contract::Store>(vesting_contract_address).staking_contracts;
+let staking_contract = simple_map::spec_get(staking_contracts, operator);
+let pool_address = staking_contract.pool_address;
+let stake_pool = global<stake::StakePool>(pool_address);
+let active = coin::value(stake_pool.active);
+let pending_active = coin::value(stake_pool.pending_active);
+let total_active_stake = active + pending_active;
+let accumulated_rewards = total_active_stake - staking_contract.principal;
+let commission_amount = accumulated_rewards * staking_contract.commission_percentage / 100;
+let total_accumulated_rewards = total_active_stake - vesting_contract.remaining_grant - commission_amount;
+let shareholder = spec_shareholder(vesting_contract_address, shareholder_or_beneficiary);
+let pool = vesting_contract.grant_pool;
+let shares = pool_u64::spec_shares(pool, shareholder);
+aborts_if pool.total_coins > 0 && pool.total_shares > 0
+ && (shares * total_accumulated_rewards) / pool.total_shares > MAX_U64;
+ensures result == pool_u64::spec_shares_to_amount_with_total_coins(pool, shares, total_accumulated_rewards);
+
+
+
+
+
+
+### Function `shareholders`
+
+
+#[view]
+public fun shareholders(vesting_contract_address: address): vector<address>
+
+
+
+
+
+include ActiveVestingContractAbortsIf<VestingContract>{contract_address: vesting_contract_address};
+
+
+
+
+
+
+
+
+fun spec_shareholder(vesting_contract_address: address, shareholder_or_beneficiary: address): address;
+
+
+
+
+
+
+### Function `shareholder`
+
+
+#[view]
+public fun shareholder(vesting_contract_address: address, shareholder_or_beneficiary: address): address
+
+
+
+
+
+pragma opaque;
+include ActiveVestingContractAbortsIf<VestingContract>{contract_address: vesting_contract_address};
+ensures [abstract] result == spec_shareholder(vesting_contract_address, shareholder_or_beneficiary);
+
+
+
+
+
+
+### Function `create_vesting_schedule`
+
+
+public fun create_vesting_schedule(schedule: vector<fixed_point32::FixedPoint32>, start_timestamp_secs: u64, period_duration: u64): vesting::VestingSchedule
+
+
+
+
+
+// This enforces high-level requirement 6:
+aborts_if !(len(schedule) > 0);
+aborts_if !(period_duration > 0);
+aborts_if !exists<timestamp::CurrentTimeMicroseconds>(@aptos_framework);
+aborts_if !(start_timestamp_secs >= timestamp::now_seconds());
+
+
+
+
+
+
+### Function `create_vesting_contract`
+
+
+public fun create_vesting_contract(admin: &signer, shareholders: &vector<address>, buy_ins: simple_map::SimpleMap<address, coin::Coin<aptos_coin::AptosCoin>>, vesting_schedule: vesting::VestingSchedule, withdrawal_address: address, operator: address, voter: address, commission_percentage: u64, contract_creation_seed: vector<u8>): address
+
+
+
+
+
+pragma verify = false;
+// This enforces high-level requirement 10:
+aborts_if withdrawal_address == @aptos_framework || withdrawal_address == @vm_reserved;
+aborts_if !exists<account::Account>(withdrawal_address);
+aborts_if !exists<coin::CoinStore<AptosCoin>>(withdrawal_address);
+aborts_if len(shareholders) == 0;
+aborts_if simple_map::spec_len(buy_ins) != len(shareholders);
+ensures global<VestingContract>(result).grant_pool.shareholders_limit == 30;
+
+
+
+
+
+
+### Function `unlock_rewards`
+
+
+public entry fun unlock_rewards(contract_address: address)
+
+
+
+
+
+pragma verify = false;
+include UnlockRewardsAbortsIf;
+
+
+
+
+
+
+
+
+schema UnlockRewardsAbortsIf {
+ contract_address: address;
+ include TotalAccumulatedRewardsAbortsIf { vesting_contract_address: contract_address };
+ let vesting_contract = global<VestingContract>(contract_address);
+ let operator = vesting_contract.staking.operator;
+ let staking_contracts = global<staking_contract::Store>(contract_address).staking_contracts;
+ let staking_contract = simple_map::spec_get(staking_contracts, operator);
+ let pool_address = staking_contract.pool_address;
+ let stake_pool = global<stake::StakePool>(pool_address);
+ let active = coin::value(stake_pool.active);
+ let pending_active = coin::value(stake_pool.pending_active);
+ let total_active_stake = active + pending_active;
+ let accumulated_rewards = total_active_stake - staking_contract.principal;
+ let commission_amount = accumulated_rewards * staking_contract.commission_percentage / 100;
+ let amount = total_active_stake - vesting_contract.remaining_grant - commission_amount;
+ include UnlockStakeAbortsIf { vesting_contract, amount };
+}
+
+
+
+
+
+
+### Function `unlock_rewards_many`
+
+
+public entry fun unlock_rewards_many(contract_addresses: vector<address>)
+
+
+
+
+
+pragma verify = false;
+aborts_if len(contract_addresses) == 0;
+include PreconditionAbortsIf;
+
+
+
+
+
+
+### Function `vest`
+
+
+public entry fun vest(contract_address: address)
+
+
+
+
+
+pragma verify = false;
+include UnlockRewardsAbortsIf;
+
+
+
+
+
+
+### Function `vest_many`
+
+
+public entry fun vest_many(contract_addresses: vector<address>)
+
+
+
+
+
+pragma verify = false;
+aborts_if len(contract_addresses) == 0;
+include PreconditionAbortsIf;
+
+
+
+
+
+
+### Function `distribute`
+
+
+public entry fun distribute(contract_address: address)
+
+
+
+
+
+pragma verify = false;
+include ActiveVestingContractAbortsIf<VestingContract>;
+let vesting_contract = global<VestingContract>(contract_address);
+include WithdrawStakeAbortsIf { vesting_contract };
+
+
+
+
+
+
+### Function `distribute_many`
+
+
+public entry fun distribute_many(contract_addresses: vector<address>)
+
+
+
+
+
+pragma verify = false;
+aborts_if len(contract_addresses) == 0;
+
+
+
+
+
+
+### Function `terminate_vesting_contract`
+
+
+public entry fun terminate_vesting_contract(admin: &signer, contract_address: address)
+
+
+
+
+
+pragma verify = false;
+include ActiveVestingContractAbortsIf<VestingContract>;
+let vesting_contract = global<VestingContract>(contract_address);
+include WithdrawStakeAbortsIf { vesting_contract };
+
+
+
+
+
+
+### Function `admin_withdraw`
+
+
+public entry fun admin_withdraw(admin: &signer, contract_address: address)
+
+
+
+
+
+pragma verify = false;
+let vesting_contract = global<VestingContract>(contract_address);
+aborts_if vesting_contract.state != VESTING_POOL_TERMINATED;
+include VerifyAdminAbortsIf;
+include WithdrawStakeAbortsIf { vesting_contract };
+
+
+
+
+
+
+### Function `update_operator`
+
+
+public entry fun update_operator(admin: &signer, contract_address: address, new_operator: address, commission_percentage: u64)
+
+
+
+
+
+pragma verify = false;
+include VerifyAdminAbortsIf;
+let vesting_contract = global<VestingContract>(contract_address);
+let acc = vesting_contract.signer_cap.account;
+let old_operator = vesting_contract.staking.operator;
+include staking_contract::ContractExistsAbortsIf { staker: acc, operator: old_operator };
+let store = global<staking_contract::Store>(acc);
+let staking_contracts = store.staking_contracts;
+aborts_if simple_map::spec_contains_key(staking_contracts, new_operator);
+let staking_contract = simple_map::spec_get(staking_contracts, old_operator);
+include DistributeInternalAbortsIf { staker: acc, operator: old_operator, staking_contract, distribute_events: store.distribute_events };
+
+
+
+
+
+
+### Function `update_operator_with_same_commission`
+
+
+public entry fun update_operator_with_same_commission(admin: &signer, contract_address: address, new_operator: address)
+
+
+
+
+
+pragma verify = false;
+
+
+
+
+
+
+### Function `update_commission_percentage`
+
+
+public entry fun update_commission_percentage(admin: &signer, contract_address: address, new_commission_percentage: u64)
+
+
+
+
+
+pragma verify = false;
+
+
+
+
+
+
+### Function `update_voter`
+
+
+public entry fun update_voter(admin: &signer, contract_address: address, new_voter: address)
+
+
+
+
+
+pragma verify_duration_estimate = 300;
+include VerifyAdminAbortsIf;
+let vesting_contract = global<VestingContract>(contract_address);
+let operator = vesting_contract.staking.operator;
+let staker = vesting_contract.signer_cap.account;
+include staking_contract::UpdateVoterSchema;
+
+
+
+
+
+
+### Function `reset_lockup`
+
+
+public entry fun reset_lockup(admin: &signer, contract_address: address)
+
+
+
+
+
+pragma verify_duration_estimate = 300;
+aborts_if !exists<VestingContract>(contract_address);
+let vesting_contract = global<VestingContract>(contract_address);
+aborts_if signer::address_of(admin) != vesting_contract.admin;
+let operator = vesting_contract.staking.operator;
+let staker = vesting_contract.signer_cap.account;
+include staking_contract::ContractExistsAbortsIf {staker, operator};
+include staking_contract::IncreaseLockupWithCapAbortsIf {staker, operator};
+let store = global<staking_contract::Store>(staker);
+let staking_contract = simple_map::spec_get(store.staking_contracts, operator);
+let pool_address = staking_contract.owner_cap.pool_address;
+aborts_if !exists<stake::StakePool>(vesting_contract.staking.pool_address);
+
+
+
+
+
+
+### Function `set_beneficiary`
+
+
+public entry fun set_beneficiary(admin: &signer, contract_address: address, shareholder: address, new_beneficiary: address)
+
+
+
+
+
+pragma verify_duration_estimate = 300;
+pragma aborts_if_is_partial;
+aborts_if !account::exists_at(new_beneficiary);
+aborts_if !coin::spec_is_account_registered<AptosCoin>(new_beneficiary);
+include VerifyAdminAbortsIf;
+let post vesting_contract = global<VestingContract>(contract_address);
+ensures simple_map::spec_contains_key(vesting_contract.beneficiaries,shareholder);
+
+
+
+
+
+
+### Function `reset_beneficiary`
+
+
+public entry fun reset_beneficiary(account: &signer, contract_address: address, shareholder: address)
+
+
+
+
+
+aborts_if !exists<VestingContract>(contract_address);
+let addr = signer::address_of(account);
+let vesting_contract = global<VestingContract>(contract_address);
+aborts_if addr != vesting_contract.admin && !std::string::spec_internal_check_utf8(ROLE_BENEFICIARY_RESETTER);
+aborts_if addr != vesting_contract.admin && !exists<VestingAccountManagement>(contract_address);
+let roles = global<VestingAccountManagement>(contract_address).roles;
+let role = std::string::spec_utf8(ROLE_BENEFICIARY_RESETTER);
+aborts_if addr != vesting_contract.admin && !simple_map::spec_contains_key(roles, role);
+aborts_if addr != vesting_contract.admin && addr != simple_map::spec_get(roles, role);
+let post post_vesting_contract = global<VestingContract>(contract_address);
+ensures !simple_map::spec_contains_key(post_vesting_contract.beneficiaries,shareholder);
+
+
+
+
+
+
+### Function `set_management_role`
+
+
+public entry fun set_management_role(admin: &signer, contract_address: address, role: string::String, role_holder: address)
+
+
+
+
+
+pragma aborts_if_is_partial;
+include SetManagementRoleAbortsIf;
+
+
+
+
+
+
+### Function `set_beneficiary_resetter`
+
+
+public entry fun set_beneficiary_resetter(admin: &signer, contract_address: address, beneficiary_resetter: address)
+
+
+
+
+
+pragma aborts_if_is_partial;
+aborts_if !std::string::spec_internal_check_utf8(ROLE_BENEFICIARY_RESETTER);
+include SetManagementRoleAbortsIf;
+
+
+
+
+
+
+### Function `set_beneficiary_for_operator`
+
+
+public entry fun set_beneficiary_for_operator(operator: &signer, new_beneficiary: address)
+
+
+
+
+
+pragma verify = false;
+
+
+
+
+
+
+### Function `get_role_holder`
+
+
+public fun get_role_holder(contract_address: address, role: string::String): address
+
+
+
+
+
+aborts_if !exists<VestingAccountManagement>(contract_address);
+let roles = global<VestingAccountManagement>(contract_address).roles;
+aborts_if !simple_map::spec_contains_key(roles,role);
+
+
+
+
+
+
+### Function `get_vesting_account_signer`
+
+
+public fun get_vesting_account_signer(admin: &signer, contract_address: address): signer
+
+
+
+
+
+include VerifyAdminAbortsIf;
+
+
+
+
+
+
+### Function `get_vesting_account_signer_internal`
+
+
+fun get_vesting_account_signer_internal(vesting_contract: &vesting::VestingContract): signer
+
+
+
+
+
+aborts_if false;
+
+
+
+
+
+
+
+
+fun spec_get_vesting_account_signer(vesting_contract: VestingContract): signer;
+
+
+
+
+
+
+### Function `create_vesting_contract_account`
+
+
+fun create_vesting_contract_account(admin: &signer, contract_creation_seed: vector<u8>): (signer, account::SignerCapability)
+
+
+
+
+
+pragma verify_duration_estimate = 300;
+let admin_addr = signer::address_of(admin);
+let admin_store = global<AdminStore>(admin_addr);
+let seed = bcs::to_bytes(admin_addr);
+let nonce = bcs::to_bytes(admin_store.nonce);
+let first = concat(seed, nonce);
+let second = concat(first, VESTING_POOL_SALT);
+let end = concat(second, contract_creation_seed);
+// This enforces high-level requirement 11:
+let resource_addr = account::spec_create_resource_address(admin_addr, end);
+aborts_if !exists<AdminStore>(admin_addr);
+aborts_if len(account::ZERO_AUTH_KEY) != 32;
+aborts_if admin_store.nonce + 1 > MAX_U64;
+let ea = account::exists_at(resource_addr);
+include if (ea) account::CreateResourceAccountAbortsIf else account::CreateAccountAbortsIf {addr: resource_addr};
+let acc = global<account::Account>(resource_addr);
+let post post_acc = global<account::Account>(resource_addr);
+aborts_if !exists<coin::CoinStore<AptosCoin>>(resource_addr) && !aptos_std::type_info::spec_is_struct<AptosCoin>();
+aborts_if !exists<coin::CoinStore<AptosCoin>>(resource_addr) && ea && acc.guid_creation_num + 2 > MAX_U64;
+aborts_if !exists<coin::CoinStore<AptosCoin>>(resource_addr) && ea && acc.guid_creation_num + 2 >= account::MAX_GUID_CREATION_NUM;
+ensures exists<account::Account>(resource_addr) && post_acc.authentication_key == account::ZERO_AUTH_KEY &&
+ exists<coin::CoinStore<AptosCoin>>(resource_addr);
+ensures signer::address_of(result_1) == resource_addr;
+ensures result_2.account == resource_addr;
+
+
+
+
+
+
+### Function `verify_admin`
+
+
+fun verify_admin(admin: &signer, vesting_contract: &vesting::VestingContract)
+
+
+
+
+
+// This enforces high-level requirement 9:
+aborts_if signer::address_of(admin) != vesting_contract.admin;
+
+
+
+
+
+
+### Function `assert_vesting_contract_exists`
+
+
+fun assert_vesting_contract_exists(contract_address: address)
+
+
+
+
+
+// This enforces high-level requirement 1:
+aborts_if !exists<VestingContract>(contract_address);
+
+
+
+
+
+
+### Function `assert_active_vesting_contract`
+
+
+fun assert_active_vesting_contract(contract_address: address)
+
+
+
+
+
+include ActiveVestingContractAbortsIf<VestingContract>;
+
+
+
+
+
+
+### Function `unlock_stake`
+
+
+fun unlock_stake(vesting_contract: &vesting::VestingContract, amount: u64)
+
+
+
+
+
+pragma verify = false;
+include UnlockStakeAbortsIf;
+
+
+
+
+
+
+
+
+schema UnlockStakeAbortsIf {
+ vesting_contract: &VestingContract;
+ amount: u64;
+ let acc = vesting_contract.signer_cap.account;
+ let operator = vesting_contract.staking.operator;
+ include amount != 0 ==> staking_contract::ContractExistsAbortsIf { staker: acc, operator };
+ let store = global<staking_contract::Store>(acc);
+ let staking_contract = simple_map::spec_get(store.staking_contracts, operator);
+ include amount != 0 ==> DistributeInternalAbortsIf { staker: acc, operator, staking_contract, distribute_events: store.distribute_events };
+}
+
+
+
+
+
+
+### Function `withdraw_stake`
+
+
+fun withdraw_stake(vesting_contract: &vesting::VestingContract, contract_address: address): coin::Coin<aptos_coin::AptosCoin>
+
+
+
+
+
+pragma verify = false;
+include WithdrawStakeAbortsIf;
+
+
+
+
+
+
+
+
+schema WithdrawStakeAbortsIf {
+ vesting_contract: &VestingContract;
+ contract_address: address;
+ let operator = vesting_contract.staking.operator;
+ include staking_contract::ContractExistsAbortsIf { staker: contract_address, operator };
+ let store = global<staking_contract::Store>(contract_address);
+ let staking_contract = simple_map::spec_get(store.staking_contracts, operator);
+ include DistributeInternalAbortsIf { staker: contract_address, operator, staking_contract, distribute_events: store.distribute_events };
+}
+
+
+
+
+
+
+
+
+schema DistributeInternalAbortsIf {
+ staker: address;
+ operator: address;
+ staking_contract: staking_contract::StakingContract;
+ distribute_events: EventHandle<staking_contract::DistributeEvent>;
+ let pool_address = staking_contract.pool_address;
+ aborts_if !exists<stake::StakePool>(pool_address);
+ let stake_pool = global<stake::StakePool>(pool_address);
+ let inactive = stake_pool.inactive.value;
+ let pending_inactive = stake_pool.pending_inactive.value;
+ aborts_if inactive + pending_inactive > MAX_U64;
+ let total_potential_withdrawable = inactive + pending_inactive;
+ let pool_address_1 = staking_contract.owner_cap.pool_address;
+ aborts_if !exists<stake::StakePool>(pool_address_1);
+ let stake_pool_1 = global<stake::StakePool>(pool_address_1);
+ aborts_if !exists<stake::ValidatorSet>(@aptos_framework);
+ let validator_set = global<stake::ValidatorSet>(@aptos_framework);
+ let inactive_state = !stake::spec_contains(validator_set.pending_active, pool_address_1)
+ && !stake::spec_contains(validator_set.active_validators, pool_address_1)
+ && !stake::spec_contains(validator_set.pending_inactive, pool_address_1);
+ let inactive_1 = stake_pool_1.inactive.value;
+ let pending_inactive_1 = stake_pool_1.pending_inactive.value;
+ let new_inactive_1 = inactive_1 + pending_inactive_1;
+ aborts_if inactive_state && timestamp::spec_now_seconds() >= stake_pool_1.locked_until_secs
+ && inactive_1 + pending_inactive_1 > MAX_U64;
+}
+
+
+
+
+
+
+### Function `get_beneficiary`
+
+
+fun get_beneficiary(contract: &vesting::VestingContract, shareholder: address): address
+
+
+
+
+
+// This enforces high-level requirement 3:
+aborts_if false;
+
+
+
+
+
+
+
+
+schema SetManagementRoleAbortsIf {
+ contract_address: address;
+ admin: signer;
+ aborts_if !exists<VestingContract>(contract_address);
+ let vesting_contract = global<VestingContract>(contract_address);
+ aborts_if signer::address_of(admin) != vesting_contract.admin;
+}
+
+
+
+
+
+
+
+
+schema VerifyAdminAbortsIf {
+ contract_address: address;
+ admin: signer;
+ aborts_if !exists<VestingContract>(contract_address);
+ let vesting_contract = global<VestingContract>(contract_address);
+ aborts_if signer::address_of(admin) != vesting_contract.admin;
+}
+
+
+
+
+
+
+
+
+schema ActiveVestingContractAbortsIf<VestingContract> {
+ contract_address: address;
+ // This enforces high-level requirement 5:
+ aborts_if !exists<VestingContract>(contract_address);
+ let vesting_contract = global<VestingContract>(contract_address);
+ // This enforces high-level requirement 8:
+ aborts_if vesting_contract.state != VESTING_POOL_ACTIVE;
+}
+
+
+
+[move-book]: https://aptos.dev/move/book/SUMMARY
diff --git a/aptos-move/framework/aptos-framework/tests/compiler-v2-doc/voting.md b/aptos-move/framework/aptos-framework/tests/compiler-v2-doc/voting.md
new file mode 100644
index 0000000000000..431eb53db9cb5
--- /dev/null
+++ b/aptos-move/framework/aptos-framework/tests/compiler-v2-doc/voting.md
@@ -0,0 +1,2596 @@
+
+
+
+# Module `0x1::voting`
+
+
+This is the general Voting module that can be used as part of a DAO Governance. Voting is designed to be used by
+standalone governance modules, who has full control over the voting flow and is responsible for voting power
+calculation and including proper capabilities when creating the proposal so resolution can go through.
+On-chain governance of the Aptos network also uses Voting.
+
+The voting flow:
+1. The Voting module can be deployed at a known address (e.g. 0x1 for Aptos on-chain governance)
+2. The governance module, e.g. AptosGovernance, can be deployed later and define a GovernanceProposal resource type
+that can also contain other information such as Capability resource for authorization.
+3. The governance module's owner can then register the ProposalType with Voting. This also hosts the proposal list
+(forum) on the calling account.
+4. A proposer, through the governance module, can call Voting::create_proposal to create a proposal. create_proposal
+cannot be called directly not through the governance module. A script hash of the resolution script that can later
+be called to execute the proposal is required.
+5. A voter, through the governance module, can call Voting::vote on a proposal. vote requires passing a &ProposalType
+and thus only the governance module that registers ProposalType can call vote.
+6. Once the proposal's expiration time has passed and more than the defined threshold has voted yes on the proposal,
+anyone can call resolve which returns the content of the proposal (of type ProposalType) that can be used to execute.
+7. Only the resolution script with the same script hash specified in the proposal can call Voting::resolve as part of
+the resolution process.
+
+
+- [Struct `Proposal`](#0x1_voting_Proposal)
+- [Resource `VotingForum`](#0x1_voting_VotingForum)
+- [Struct `VotingEvents`](#0x1_voting_VotingEvents)
+- [Struct `CreateProposal`](#0x1_voting_CreateProposal)
+- [Struct `RegisterForum`](#0x1_voting_RegisterForum)
+- [Struct `Vote`](#0x1_voting_Vote)
+- [Struct `ResolveProposal`](#0x1_voting_ResolveProposal)
+- [Struct `CreateProposalEvent`](#0x1_voting_CreateProposalEvent)
+- [Struct `RegisterForumEvent`](#0x1_voting_RegisterForumEvent)
+- [Struct `VoteEvent`](#0x1_voting_VoteEvent)
+- [Constants](#@Constants_0)
+- [Function `register`](#0x1_voting_register)
+- [Function `create_proposal`](#0x1_voting_create_proposal)
+- [Function `create_proposal_v2`](#0x1_voting_create_proposal_v2)
+- [Function `vote`](#0x1_voting_vote)
+- [Function `is_proposal_resolvable`](#0x1_voting_is_proposal_resolvable)
+- [Function `resolve`](#0x1_voting_resolve)
+- [Function `resolve_proposal_v2`](#0x1_voting_resolve_proposal_v2)
+- [Function `next_proposal_id`](#0x1_voting_next_proposal_id)
+- [Function `get_proposer`](#0x1_voting_get_proposer)
+- [Function `is_voting_closed`](#0x1_voting_is_voting_closed)
+- [Function `can_be_resolved_early`](#0x1_voting_can_be_resolved_early)
+- [Function `get_proposal_metadata`](#0x1_voting_get_proposal_metadata)
+- [Function `get_proposal_metadata_value`](#0x1_voting_get_proposal_metadata_value)
+- [Function `get_proposal_state`](#0x1_voting_get_proposal_state)
+- [Function `get_proposal_creation_secs`](#0x1_voting_get_proposal_creation_secs)
+- [Function `get_proposal_expiration_secs`](#0x1_voting_get_proposal_expiration_secs)
+- [Function `get_execution_hash`](#0x1_voting_get_execution_hash)
+- [Function `get_min_vote_threshold`](#0x1_voting_get_min_vote_threshold)
+- [Function `get_early_resolution_vote_threshold`](#0x1_voting_get_early_resolution_vote_threshold)
+- [Function `get_votes`](#0x1_voting_get_votes)
+- [Function `is_resolved`](#0x1_voting_is_resolved)
+- [Function `get_resolution_time_secs`](#0x1_voting_get_resolution_time_secs)
+- [Function `is_multi_step_proposal_in_execution`](#0x1_voting_is_multi_step_proposal_in_execution)
+- [Function `is_voting_period_over`](#0x1_voting_is_voting_period_over)
+- [Function `get_proposal`](#0x1_voting_get_proposal)
+- [Specification](#@Specification_1)
+ - [High-level Requirements](#high-level-req)
+ - [Module-level Specification](#module-level-spec)
+ - [Function `register`](#@Specification_1_register)
+ - [Function `create_proposal`](#@Specification_1_create_proposal)
+ - [Function `create_proposal_v2`](#@Specification_1_create_proposal_v2)
+ - [Function `vote`](#@Specification_1_vote)
+ - [Function `is_proposal_resolvable`](#@Specification_1_is_proposal_resolvable)
+ - [Function `resolve`](#@Specification_1_resolve)
+ - [Function `resolve_proposal_v2`](#@Specification_1_resolve_proposal_v2)
+ - [Function `next_proposal_id`](#@Specification_1_next_proposal_id)
+ - [Function `get_proposer`](#@Specification_1_get_proposer)
+ - [Function `is_voting_closed`](#@Specification_1_is_voting_closed)
+ - [Function `can_be_resolved_early`](#@Specification_1_can_be_resolved_early)
+ - [Function `get_proposal_metadata`](#@Specification_1_get_proposal_metadata)
+ - [Function `get_proposal_metadata_value`](#@Specification_1_get_proposal_metadata_value)
+ - [Function `get_proposal_state`](#@Specification_1_get_proposal_state)
+ - [Function `get_proposal_creation_secs`](#@Specification_1_get_proposal_creation_secs)
+ - [Function `get_proposal_expiration_secs`](#@Specification_1_get_proposal_expiration_secs)
+ - [Function `get_execution_hash`](#@Specification_1_get_execution_hash)
+ - [Function `get_min_vote_threshold`](#@Specification_1_get_min_vote_threshold)
+ - [Function `get_early_resolution_vote_threshold`](#@Specification_1_get_early_resolution_vote_threshold)
+ - [Function `get_votes`](#@Specification_1_get_votes)
+ - [Function `is_resolved`](#@Specification_1_is_resolved)
+ - [Function `get_resolution_time_secs`](#@Specification_1_get_resolution_time_secs)
+ - [Function `is_multi_step_proposal_in_execution`](#@Specification_1_is_multi_step_proposal_in_execution)
+ - [Function `is_voting_period_over`](#@Specification_1_is_voting_period_over)
+
+
+use 0x1::account;
+use 0x1::bcs;
+use 0x1::error;
+use 0x1::event;
+use 0x1::features;
+use 0x1::from_bcs;
+use 0x1::option;
+use 0x1::signer;
+use 0x1::simple_map;
+use 0x1::string;
+use 0x1::table;
+use 0x1::timestamp;
+use 0x1::transaction_context;
+use 0x1::type_info;
+
+
+
+
+
+
+## Struct `Proposal`
+
+Extra metadata (e.g. description, code url) can be part of the ProposalType struct.
+
+
+struct Proposal<ProposalType: store> has store
+
+
+
+
+proposer: address
+execution_content: option::Option<ProposalType>
+metadata: simple_map::SimpleMap<string::String, vector<u8>>
+creation_time_secs: u64
+execution_hash: vector<u8>
+min_vote_threshold: u128
+expiration_secs: u64
+early_resolution_vote_threshold: option::Option<u128>
+yes_votes: u128
+no_votes: u128
+is_resolved: bool
+resolution_time_secs: u64
+struct VotingForum<ProposalType: store> has key
+
+
+
+
+proposals: table::Table<u64, voting::Proposal<ProposalType>>
+events: voting::VotingEvents
+next_proposal_id: u64
+struct VotingEvents has store
+
+
+
+
+create_proposal_events: event::EventHandle<voting::CreateProposalEvent>
+register_forum_events: event::EventHandle<voting::RegisterForumEvent>
+resolve_proposal_events: event::EventHandle<voting::ResolveProposal>
+vote_events: event::EventHandle<voting::VoteEvent>
+#[event]
+struct CreateProposal has drop, store
+
+
+
+
+proposal_id: u64
+early_resolution_vote_threshold: option::Option<u128>
+execution_hash: vector<u8>
+expiration_secs: u64
+metadata: simple_map::SimpleMap<string::String, vector<u8>>
+min_vote_threshold: u128
+#[event]
+struct RegisterForum has drop, store
+
+
+
+
+hosting_account: address
+proposal_type_info: type_info::TypeInfo
+#[event]
+struct Vote has drop, store
+
+
+
+
+proposal_id: u64
+num_votes: u64
+#[event]
+struct ResolveProposal has drop, store
+
+
+
+
+proposal_id: u64
+yes_votes: u128
+no_votes: u128
+resolved_early: bool
+struct CreateProposalEvent has drop, store
+
+
+
+
+proposal_id: u64
+early_resolution_vote_threshold: option::Option<u128>
+execution_hash: vector<u8>
+expiration_secs: u64
+metadata: simple_map::SimpleMap<string::String, vector<u8>>
+min_vote_threshold: u128
+struct RegisterForumEvent has drop, store
+
+
+
+
+hosting_account: address
+proposal_type_info: type_info::TypeInfo
+struct VoteEvent has drop, store
+
+
+
+
+proposal_id: u64
+num_votes: u64
+const EINVALID_MIN_VOTE_THRESHOLD: u64 = 7;
+
+
+
+
+
+
+If a proposal is multi-step, we need to use resolve_proposal_v2()
to resolve it.
+If we use resolve()
to resolve a multi-step proposal, it will fail with EMULTI_STEP_PROPOSAL_CANNOT_USE_SINGLE_STEP_RESOLVE_FUNCTION.
+
+
+const EMULTI_STEP_PROPOSAL_CANNOT_USE_SINGLE_STEP_RESOLVE_FUNCTION: u64 = 10;
+
+
+
+
+
+
+Cannot vote if the specified multi-step proposal is in execution.
+
+
+const EMULTI_STEP_PROPOSAL_IN_EXECUTION: u64 = 9;
+
+
+
+
+
+
+Proposal cannot be resolved more than once
+
+
+const EPROPOSAL_ALREADY_RESOLVED: u64 = 3;
+
+
+
+
+
+
+Proposal cannot be resolved. Either voting duration has not passed, not enough votes, or fewer yes than no votes
+
+
+const EPROPOSAL_CANNOT_BE_RESOLVED: u64 = 2;
+
+
+
+
+
+
+Proposal cannot contain an empty execution script hash
+
+
+const EPROPOSAL_EMPTY_EXECUTION_HASH: u64 = 4;
+
+
+
+
+
+
+Current script's execution hash does not match the specified proposal's
+
+
+const EPROPOSAL_EXECUTION_HASH_NOT_MATCHING: u64 = 1;
+
+
+
+
+
+
+Cannot call is_multi_step_proposal_in_execution()
on single-step proposals.
+
+
+const EPROPOSAL_IS_SINGLE_STEP: u64 = 12;
+
+
+
+
+
+
+Proposal's voting period has already ended.
+
+
+const EPROPOSAL_VOTING_ALREADY_ENDED: u64 = 5;
+
+
+
+
+
+
+Resolution of a proposal cannot happen atomically in the same transaction as the last vote.
+
+
+const ERESOLUTION_CANNOT_BE_ATOMIC: u64 = 8;
+
+
+
+
+
+
+If we call resolve_proposal_v2()
to resolve a single-step proposal, the next_execution_hash
parameter should be an empty vector.
+
+
+const ESINGLE_STEP_PROPOSAL_CANNOT_HAVE_NEXT_EXECUTION_HASH: u64 = 11;
+
+
+
+
+
+
+Voting forum has already been registered.
+
+
+const EVOTING_FORUM_ALREADY_REGISTERED: u64 = 6;
+
+
+
+
+
+
+Key used to track if the multi-step proposal is in execution / resolving in progress.
+
+
+const IS_MULTI_STEP_PROPOSAL_IN_EXECUTION_KEY: vector<u8> = [73, 83, 95, 77, 85, 76, 84, 73, 95, 83, 84, 69, 80, 95, 80, 82, 79, 80, 79, 83, 65, 76, 95, 73, 78, 95, 69, 88, 69, 67, 85, 84, 73, 79, 78];
+
+
+
+
+
+
+Key used to track if the proposal is multi-step
+
+
+const IS_MULTI_STEP_PROPOSAL_KEY: vector<u8> = [73, 83, 95, 77, 85, 76, 84, 73, 95, 83, 84, 69, 80, 95, 80, 82, 79, 80, 79, 83, 65, 76, 95, 75, 69, 89];
+
+
+
+
+
+
+Proposal has failed because either the min vote threshold is not met or majority voted no.
+
+
+const PROPOSAL_STATE_FAILED: u64 = 3;
+
+
+
+
+
+
+ProposalStateEnum representing proposal state.
+
+
+const PROPOSAL_STATE_PENDING: u64 = 0;
+
+
+
+
+
+
+
+
+const PROPOSAL_STATE_SUCCEEDED: u64 = 1;
+
+
+
+
+
+
+Key used to track the resolvable time in the proposal's metadata.
+
+
+const RESOLVABLE_TIME_METADATA_KEY: vector<u8> = [82, 69, 83, 79, 76, 86, 65, 66, 76, 69, 95, 84, 73, 77, 69, 95, 77, 69, 84, 65, 68, 65, 84, 65, 95, 75, 69, 89];
+
+
+
+
+
+
+## Function `register`
+
+
+
+public fun register<ProposalType: store>(account: &signer)
+
+
+
+
+public fun register<ProposalType: store>(account: &signer) {
+ let addr = signer::address_of(account);
+ assert!(!exists<VotingForum<ProposalType>>(addr), error::already_exists(EVOTING_FORUM_ALREADY_REGISTERED));
+
+ let voting_forum = VotingForum<ProposalType> {
+ next_proposal_id: 0,
+ proposals: table::new<u64, Proposal<ProposalType>>(),
+ events: VotingEvents {
+ create_proposal_events: account::new_event_handle<CreateProposalEvent>(account),
+ register_forum_events: account::new_event_handle<RegisterForumEvent>(account),
+ resolve_proposal_events: account::new_event_handle<ResolveProposal>(account),
+ vote_events: account::new_event_handle<VoteEvent>(account),
+ }
+ };
+
+ if (std::features::module_event_migration_enabled()) {
+ event::emit(
+ RegisterForum {
+ hosting_account: addr,
+ proposal_type_info: type_info::type_of<ProposalType>(),
+ },
+ );
+ };
+ event::emit_event<RegisterForumEvent>(
+ &mut voting_forum.events.register_forum_events,
+ RegisterForumEvent {
+ hosting_account: addr,
+ proposal_type_info: type_info::type_of<ProposalType>(),
+ },
+ );
+
+ move_to(account, voting_forum);
+}
+
+
+
+
+public fun create_proposal<ProposalType: store>(proposer: address, voting_forum_address: address, execution_content: ProposalType, execution_hash: vector<u8>, min_vote_threshold: u128, expiration_secs: u64, early_resolution_vote_threshold: option::Option<u128>, metadata: simple_map::SimpleMap<string::String, vector<u8>>): u64
+
+
+
+
+public fun create_proposal<ProposalType: store>(
+ proposer: address,
+ voting_forum_address: address,
+ execution_content: ProposalType,
+ execution_hash: vector<u8>,
+ min_vote_threshold: u128,
+ expiration_secs: u64,
+ early_resolution_vote_threshold: Option<u128>,
+ metadata: SimpleMap<String, vector<u8>>,
+): u64 acquires VotingForum {
+ create_proposal_v2(
+ proposer,
+ voting_forum_address,
+ execution_content,
+ execution_hash,
+ min_vote_threshold,
+ expiration_secs,
+ early_resolution_vote_threshold,
+ metadata,
+ false
+ )
+}
+
+
+
+
+public fun create_proposal_v2<ProposalType: store>(proposer: address, voting_forum_address: address, execution_content: ProposalType, execution_hash: vector<u8>, min_vote_threshold: u128, expiration_secs: u64, early_resolution_vote_threshold: option::Option<u128>, metadata: simple_map::SimpleMap<string::String, vector<u8>>, is_multi_step_proposal: bool): u64
+
+
+
+
+public fun create_proposal_v2<ProposalType: store>(
+ proposer: address,
+ voting_forum_address: address,
+ execution_content: ProposalType,
+ execution_hash: vector<u8>,
+ min_vote_threshold: u128,
+ expiration_secs: u64,
+ early_resolution_vote_threshold: Option<u128>,
+ metadata: SimpleMap<String, vector<u8>>,
+ is_multi_step_proposal: bool,
+): u64 acquires VotingForum {
+ if (option::is_some(&early_resolution_vote_threshold)) {
+ assert!(
+ min_vote_threshold <= *option::borrow(&early_resolution_vote_threshold),
+ error::invalid_argument(EINVALID_MIN_VOTE_THRESHOLD),
+ );
+ };
+ // Make sure the execution script's hash is not empty.
+ assert!(vector::length(&execution_hash) > 0, error::invalid_argument(EPROPOSAL_EMPTY_EXECUTION_HASH));
+
+ let voting_forum = borrow_global_mut<VotingForum<ProposalType>>(voting_forum_address);
+ let proposal_id = voting_forum.next_proposal_id;
+ voting_forum.next_proposal_id = voting_forum.next_proposal_id + 1;
+
+ // Add a flag to indicate if this proposal is single-step or multi-step.
+ simple_map::add(&mut metadata, utf8(IS_MULTI_STEP_PROPOSAL_KEY), to_bytes(&is_multi_step_proposal));
+
+ let is_multi_step_in_execution_key = utf8(IS_MULTI_STEP_PROPOSAL_IN_EXECUTION_KEY);
+ if (is_multi_step_proposal) {
+ // If the given proposal is a multi-step proposal, we will add a flag to indicate if this multi-step proposal is in execution.
+ // This value is by default false. We turn this value to true when we start executing the multi-step proposal. This value
+ // will be used to disable further voting after we started executing the multi-step proposal.
+ simple_map::add(&mut metadata, is_multi_step_in_execution_key, to_bytes(&false));
+ // If the proposal is a single-step proposal, we check if the metadata passed by the client has the IS_MULTI_STEP_PROPOSAL_IN_EXECUTION_KEY key.
+ // If they have the key, we will remove it, because a single-step proposal that doesn't need this key.
+ } else if (simple_map::contains_key(&mut metadata, &is_multi_step_in_execution_key)) {
+ simple_map::remove(&mut metadata, &is_multi_step_in_execution_key);
+ };
+
+ table::add(&mut voting_forum.proposals, proposal_id, Proposal {
+ proposer,
+ creation_time_secs: timestamp::now_seconds(),
+ execution_content: option::some<ProposalType>(execution_content),
+ execution_hash,
+ metadata,
+ min_vote_threshold,
+ expiration_secs,
+ early_resolution_vote_threshold,
+ yes_votes: 0,
+ no_votes: 0,
+ is_resolved: false,
+ resolution_time_secs: 0,
+ });
+
+ if (std::features::module_event_migration_enabled()) {
+ event::emit(
+ CreateProposal {
+ proposal_id,
+ early_resolution_vote_threshold,
+ execution_hash,
+ expiration_secs,
+ metadata,
+ min_vote_threshold,
+ },
+ );
+ };
+ event::emit_event<CreateProposalEvent>(
+ &mut voting_forum.events.create_proposal_events,
+ CreateProposalEvent {
+ proposal_id,
+ early_resolution_vote_threshold,
+ execution_hash,
+ expiration_secs,
+ metadata,
+ min_vote_threshold,
+ },
+ );
+
+ proposal_id
+}
+
+
+
+
+public fun vote<ProposalType: store>(_proof: &ProposalType, voting_forum_address: address, proposal_id: u64, num_votes: u64, should_pass: bool)
+
+
+
+
+public fun vote<ProposalType: store>(
+ _proof: &ProposalType,
+ voting_forum_address: address,
+ proposal_id: u64,
+ num_votes: u64,
+ should_pass: bool,
+) acquires VotingForum {
+ let voting_forum = borrow_global_mut<VotingForum<ProposalType>>(voting_forum_address);
+ let proposal = table::borrow_mut(&mut voting_forum.proposals, proposal_id);
+ // Voting might still be possible after the proposal has enough yes votes to be resolved early. This would only
+ // lead to possible proposal resolution failure if the resolve early threshold is not definitive (e.g. < 50% + 1
+ // of the total voting token's supply). In this case, more voting might actually still be desirable.
+ // Governance mechanisms built on this voting module can apply additional rules on when voting is closed as
+ // appropriate.
+ assert!(!is_voting_period_over(proposal), error::invalid_state(EPROPOSAL_VOTING_ALREADY_ENDED));
+ assert!(!proposal.is_resolved, error::invalid_state(EPROPOSAL_ALREADY_RESOLVED));
+ // Assert this proposal is single-step, or if the proposal is multi-step, it is not in execution yet.
+ assert!(!simple_map::contains_key(&proposal.metadata, &utf8(IS_MULTI_STEP_PROPOSAL_IN_EXECUTION_KEY))
+ || *simple_map::borrow(&proposal.metadata, &utf8(IS_MULTI_STEP_PROPOSAL_IN_EXECUTION_KEY)) == to_bytes(
+ &false
+ ),
+ error::invalid_state(EMULTI_STEP_PROPOSAL_IN_EXECUTION));
+
+ if (should_pass) {
+ proposal.yes_votes = proposal.yes_votes + (num_votes as u128);
+ } else {
+ proposal.no_votes = proposal.no_votes + (num_votes as u128);
+ };
+
+ // Record the resolvable time to ensure that resolution has to be done non-atomically.
+ let timestamp_secs_bytes = to_bytes(×tamp::now_seconds());
+ let key = utf8(RESOLVABLE_TIME_METADATA_KEY);
+ if (simple_map::contains_key(&proposal.metadata, &key)) {
+ *simple_map::borrow_mut(&mut proposal.metadata, &key) = timestamp_secs_bytes;
+ } else {
+ simple_map::add(&mut proposal.metadata, key, timestamp_secs_bytes);
+ };
+
+ if (std::features::module_event_migration_enabled()) {
+ event::emit(Vote { proposal_id, num_votes });
+ };
+ event::emit_event<VoteEvent>(
+ &mut voting_forum.events.vote_events,
+ VoteEvent { proposal_id, num_votes },
+ );
+}
+
+
+
+
+fun is_proposal_resolvable<ProposalType: store>(voting_forum_address: address, proposal_id: u64)
+
+
+
+
+fun is_proposal_resolvable<ProposalType: store>(
+ voting_forum_address: address,
+ proposal_id: u64,
+) acquires VotingForum {
+ let proposal_state = get_proposal_state<ProposalType>(voting_forum_address, proposal_id);
+ assert!(proposal_state == PROPOSAL_STATE_SUCCEEDED, error::invalid_state(EPROPOSAL_CANNOT_BE_RESOLVED));
+
+ let voting_forum = borrow_global_mut<VotingForum<ProposalType>>(voting_forum_address);
+ let proposal = table::borrow_mut(&mut voting_forum.proposals, proposal_id);
+ assert!(!proposal.is_resolved, error::invalid_state(EPROPOSAL_ALREADY_RESOLVED));
+
+ // We need to make sure that the resolution is happening in
+ // a separate transaction from the last vote to guard against any potential flashloan attacks.
+ let resolvable_time = to_u64(*simple_map::borrow(&proposal.metadata, &utf8(RESOLVABLE_TIME_METADATA_KEY)));
+ assert!(timestamp::now_seconds() > resolvable_time, error::invalid_state(ERESOLUTION_CANNOT_BE_ATOMIC));
+
+ assert!(
+ transaction_context::get_script_hash() == proposal.execution_hash,
+ error::invalid_argument(EPROPOSAL_EXECUTION_HASH_NOT_MATCHING),
+ );
+}
+
+
+
+
+public fun resolve<ProposalType: store>(voting_forum_address: address, proposal_id: u64): ProposalType
+
+
+
+
+public fun resolve<ProposalType: store>(
+ voting_forum_address: address,
+ proposal_id: u64,
+): ProposalType acquires VotingForum {
+ is_proposal_resolvable<ProposalType>(voting_forum_address, proposal_id);
+
+ let voting_forum = borrow_global_mut<VotingForum<ProposalType>>(voting_forum_address);
+ let proposal = table::borrow_mut(&mut voting_forum.proposals, proposal_id);
+
+ // Assert that the specified proposal is not a multi-step proposal.
+ let multi_step_key = utf8(IS_MULTI_STEP_PROPOSAL_KEY);
+ let has_multi_step_key = simple_map::contains_key(&proposal.metadata, &multi_step_key);
+ if (has_multi_step_key) {
+ let is_multi_step_proposal = from_bcs::to_bool(*simple_map::borrow(&proposal.metadata, &multi_step_key));
+ assert!(
+ !is_multi_step_proposal,
+ error::permission_denied(EMULTI_STEP_PROPOSAL_CANNOT_USE_SINGLE_STEP_RESOLVE_FUNCTION)
+ );
+ };
+
+ let resolved_early = can_be_resolved_early(proposal);
+ proposal.is_resolved = true;
+ proposal.resolution_time_secs = timestamp::now_seconds();
+
+ if (std::features::module_event_migration_enabled()) {
+ event::emit(
+ ResolveProposal {
+ proposal_id,
+ yes_votes: proposal.yes_votes,
+ no_votes: proposal.no_votes,
+ resolved_early,
+ },
+ );
+ };
+ event::emit_event<ResolveProposal>(
+ &mut voting_forum.events.resolve_proposal_events,
+ ResolveProposal {
+ proposal_id,
+ yes_votes: proposal.yes_votes,
+ no_votes: proposal.no_votes,
+ resolved_early,
+ },
+ );
+
+ option::extract(&mut proposal.execution_content)
+}
+
+
+
+
+public fun resolve_proposal_v2<ProposalType: store>(voting_forum_address: address, proposal_id: u64, next_execution_hash: vector<u8>)
+
+
+
+
+public fun resolve_proposal_v2<ProposalType: store>(
+ voting_forum_address: address,
+ proposal_id: u64,
+ next_execution_hash: vector<u8>,
+) acquires VotingForum {
+ is_proposal_resolvable<ProposalType>(voting_forum_address, proposal_id);
+
+ let voting_forum = borrow_global_mut<VotingForum<ProposalType>>(voting_forum_address);
+ let proposal = table::borrow_mut(&mut voting_forum.proposals, proposal_id);
+
+ // Update the IS_MULTI_STEP_PROPOSAL_IN_EXECUTION_KEY key to indicate that the multi-step proposal is in execution.
+ let multi_step_in_execution_key = utf8(IS_MULTI_STEP_PROPOSAL_IN_EXECUTION_KEY);
+ if (simple_map::contains_key(&proposal.metadata, &multi_step_in_execution_key)) {
+ let is_multi_step_proposal_in_execution_value = simple_map::borrow_mut(
+ &mut proposal.metadata,
+ &multi_step_in_execution_key
+ );
+ *is_multi_step_proposal_in_execution_value = to_bytes(&true);
+ };
+
+ let multi_step_key = utf8(IS_MULTI_STEP_PROPOSAL_KEY);
+ let is_multi_step = simple_map::contains_key(&proposal.metadata, &multi_step_key) && from_bcs::to_bool(
+ *simple_map::borrow(&proposal.metadata, &multi_step_key)
+ );
+ let next_execution_hash_is_empty = vector::length(&next_execution_hash) == 0;
+
+ // Assert that if this proposal is single-step, the `next_execution_hash` parameter is empty.
+ assert!(
+ is_multi_step || next_execution_hash_is_empty,
+ error::invalid_argument(ESINGLE_STEP_PROPOSAL_CANNOT_HAVE_NEXT_EXECUTION_HASH)
+ );
+
+ // If the `next_execution_hash` parameter is empty, it means that either
+ // - this proposal is a single-step proposal, or
+ // - this proposal is multi-step and we're currently resolving the last step in the multi-step proposal.
+ // We can mark that this proposal is resolved.
+ if (next_execution_hash_is_empty) {
+ proposal.is_resolved = true;
+ proposal.resolution_time_secs = timestamp::now_seconds();
+
+ // Set the `IS_MULTI_STEP_PROPOSAL_IN_EXECUTION_KEY` value to false upon successful resolution of the last step of a multi-step proposal.
+ if (is_multi_step) {
+ let is_multi_step_proposal_in_execution_value = simple_map::borrow_mut(
+ &mut proposal.metadata,
+ &multi_step_in_execution_key
+ );
+ *is_multi_step_proposal_in_execution_value = to_bytes(&false);
+ };
+ } else {
+ // If the current step is not the last step,
+ // update the proposal's execution hash on-chain to the execution hash of the next step.
+ proposal.execution_hash = next_execution_hash;
+ };
+
+ // For single-step proposals, we emit one `ResolveProposal` event per proposal.
+ // For multi-step proposals, we emit one `ResolveProposal` event per step in the multi-step proposal. This means
+ // that we emit multiple `ResolveProposal` events for the same multi-step proposal.
+ let resolved_early = can_be_resolved_early(proposal);
+ if (std::features::module_event_migration_enabled()) {
+ event::emit(
+ ResolveProposal {
+ proposal_id,
+ yes_votes: proposal.yes_votes,
+ no_votes: proposal.no_votes,
+ resolved_early,
+ },
+ );
+ };
+ event::emit_event(
+ &mut voting_forum.events.resolve_proposal_events,
+ ResolveProposal {
+ proposal_id,
+ yes_votes: proposal.yes_votes,
+ no_votes: proposal.no_votes,
+ resolved_early,
+ },
+ );
+
+}
+
+
+
+
+#[view]
+public fun next_proposal_id<ProposalType: store>(voting_forum_address: address): u64
+
+
+
+
+public fun next_proposal_id<ProposalType: store>(voting_forum_address: address, ): u64 acquires VotingForum {
+ let voting_forum = borrow_global<VotingForum<ProposalType>>(voting_forum_address);
+ voting_forum.next_proposal_id
+}
+
+
+
+
+#[view]
+public fun get_proposer<ProposalType: store>(voting_forum_address: address, proposal_id: u64): address
+
+
+
+
+public fun get_proposer<ProposalType: store>(
+ voting_forum_address: address,
+ proposal_id: u64
+): address acquires VotingForum {
+ let proposal = get_proposal<ProposalType>(voting_forum_address, proposal_id);
+ proposal.proposer
+}
+
+
+
+
+#[view]
+public fun is_voting_closed<ProposalType: store>(voting_forum_address: address, proposal_id: u64): bool
+
+
+
+
+public fun is_voting_closed<ProposalType: store>(
+ voting_forum_address: address,
+ proposal_id: u64
+): bool acquires VotingForum {
+ let proposal = get_proposal<ProposalType>(voting_forum_address, proposal_id);
+ can_be_resolved_early(proposal) || is_voting_period_over(proposal)
+}
+
+
+
+
+public fun can_be_resolved_early<ProposalType: store>(proposal: &voting::Proposal<ProposalType>): bool
+
+
+
+
+public fun can_be_resolved_early<ProposalType: store>(proposal: &Proposal<ProposalType>): bool {
+ if (option::is_some(&proposal.early_resolution_vote_threshold)) {
+ let early_resolution_threshold = *option::borrow(&proposal.early_resolution_vote_threshold);
+ if (proposal.yes_votes >= early_resolution_threshold || proposal.no_votes >= early_resolution_threshold) {
+ return true
+ };
+ };
+ false
+}
+
+
+
+
+#[view]
+public fun get_proposal_metadata<ProposalType: store>(voting_forum_address: address, proposal_id: u64): simple_map::SimpleMap<string::String, vector<u8>>
+
+
+
+
+public fun get_proposal_metadata<ProposalType: store>(
+ voting_forum_address: address,
+ proposal_id: u64,
+): SimpleMap<String, vector<u8>> acquires VotingForum {
+ let proposal = get_proposal<ProposalType>(voting_forum_address, proposal_id);
+ proposal.metadata
+}
+
+
+
+
+#[view]
+public fun get_proposal_metadata_value<ProposalType: store>(voting_forum_address: address, proposal_id: u64, metadata_key: string::String): vector<u8>
+
+
+
+
+public fun get_proposal_metadata_value<ProposalType: store>(
+ voting_forum_address: address,
+ proposal_id: u64,
+ metadata_key: String,
+): vector<u8> acquires VotingForum {
+ let proposal = get_proposal<ProposalType>(voting_forum_address, proposal_id);
+ *simple_map::borrow(&proposal.metadata, &metadata_key)
+}
+
+
+
+
+#[view]
+public fun get_proposal_state<ProposalType: store>(voting_forum_address: address, proposal_id: u64): u64
+
+
+
+
+public fun get_proposal_state<ProposalType: store>(
+ voting_forum_address: address,
+ proposal_id: u64,
+): u64 acquires VotingForum {
+ if (is_voting_closed<ProposalType>(voting_forum_address, proposal_id)) {
+ let proposal = get_proposal<ProposalType>(voting_forum_address, proposal_id);
+ let yes_votes = proposal.yes_votes;
+ let no_votes = proposal.no_votes;
+
+ if (yes_votes > no_votes && yes_votes + no_votes >= proposal.min_vote_threshold) {
+ PROPOSAL_STATE_SUCCEEDED
+ } else {
+ PROPOSAL_STATE_FAILED
+ }
+ } else {
+ PROPOSAL_STATE_PENDING
+ }
+}
+
+
+
+
+#[view]
+public fun get_proposal_creation_secs<ProposalType: store>(voting_forum_address: address, proposal_id: u64): u64
+
+
+
+
+public fun get_proposal_creation_secs<ProposalType: store>(
+ voting_forum_address: address,
+ proposal_id: u64,
+): u64 acquires VotingForum {
+ let proposal = get_proposal<ProposalType>(voting_forum_address, proposal_id);
+ proposal.creation_time_secs
+}
+
+
+
+
+#[view]
+public fun get_proposal_expiration_secs<ProposalType: store>(voting_forum_address: address, proposal_id: u64): u64
+
+
+
+
+public fun get_proposal_expiration_secs<ProposalType: store>(
+ voting_forum_address: address,
+ proposal_id: u64,
+): u64 acquires VotingForum {
+ let proposal = get_proposal<ProposalType>(voting_forum_address, proposal_id);
+ proposal.expiration_secs
+}
+
+
+
+
+#[view]
+public fun get_execution_hash<ProposalType: store>(voting_forum_address: address, proposal_id: u64): vector<u8>
+
+
+
+
+public fun get_execution_hash<ProposalType: store>(
+ voting_forum_address: address,
+ proposal_id: u64,
+): vector<u8> acquires VotingForum {
+ let proposal = get_proposal<ProposalType>(voting_forum_address, proposal_id);
+ proposal.execution_hash
+}
+
+
+
+
+#[view]
+public fun get_min_vote_threshold<ProposalType: store>(voting_forum_address: address, proposal_id: u64): u128
+
+
+
+
+public fun get_min_vote_threshold<ProposalType: store>(
+ voting_forum_address: address,
+ proposal_id: u64,
+): u128 acquires VotingForum {
+ let proposal = get_proposal<ProposalType>(voting_forum_address, proposal_id);
+ proposal.min_vote_threshold
+}
+
+
+
+
+#[view]
+public fun get_early_resolution_vote_threshold<ProposalType: store>(voting_forum_address: address, proposal_id: u64): option::Option<u128>
+
+
+
+
+public fun get_early_resolution_vote_threshold<ProposalType: store>(
+ voting_forum_address: address,
+ proposal_id: u64,
+): Option<u128> acquires VotingForum {
+ let proposal = get_proposal<ProposalType>(voting_forum_address, proposal_id);
+ proposal.early_resolution_vote_threshold
+}
+
+
+
+
+#[view]
+public fun get_votes<ProposalType: store>(voting_forum_address: address, proposal_id: u64): (u128, u128)
+
+
+
+
+public fun get_votes<ProposalType: store>(
+ voting_forum_address: address,
+ proposal_id: u64,
+): (u128, u128) acquires VotingForum {
+ let proposal = get_proposal<ProposalType>(voting_forum_address, proposal_id);
+ (proposal.yes_votes, proposal.no_votes)
+}
+
+
+
+
+#[view]
+public fun is_resolved<ProposalType: store>(voting_forum_address: address, proposal_id: u64): bool
+
+
+
+
+public fun is_resolved<ProposalType: store>(
+ voting_forum_address: address,
+ proposal_id: u64,
+): bool acquires VotingForum {
+ let proposal = get_proposal<ProposalType>(voting_forum_address, proposal_id);
+ proposal.is_resolved
+}
+
+
+
+
+#[view]
+public fun get_resolution_time_secs<ProposalType: store>(voting_forum_address: address, proposal_id: u64): u64
+
+
+
+
+public fun get_resolution_time_secs<ProposalType: store>(
+ voting_forum_address: address,
+ proposal_id: u64,
+): u64 acquires VotingForum {
+ let proposal = get_proposal<ProposalType>(voting_forum_address, proposal_id);
+ proposal.resolution_time_secs
+}
+
+
+
+
+#[view]
+public fun is_multi_step_proposal_in_execution<ProposalType: store>(voting_forum_address: address, proposal_id: u64): bool
+
+
+
+
+public fun is_multi_step_proposal_in_execution<ProposalType: store>(
+ voting_forum_address: address,
+ proposal_id: u64,
+): bool acquires VotingForum {
+ let voting_forum = borrow_global<VotingForum<ProposalType>>(voting_forum_address);
+ let proposal = table::borrow(&voting_forum.proposals, proposal_id);
+ let is_multi_step_in_execution_key = utf8(IS_MULTI_STEP_PROPOSAL_IN_EXECUTION_KEY);
+ assert!(
+ simple_map::contains_key(&proposal.metadata, &is_multi_step_in_execution_key),
+ error::invalid_argument(EPROPOSAL_IS_SINGLE_STEP)
+ );
+ from_bcs::to_bool(*simple_map::borrow(&proposal.metadata, &is_multi_step_in_execution_key))
+}
+
+
+
+
+fun is_voting_period_over<ProposalType: store>(proposal: &voting::Proposal<ProposalType>): bool
+
+
+
+
+fun is_voting_period_over<ProposalType: store>(proposal: &Proposal<ProposalType>): bool {
+ timestamp::now_seconds() > proposal.expiration_secs
+}
+
+
+
+
+fun get_proposal<ProposalType: store>(voting_forum_address: address, proposal_id: u64): &voting::Proposal<ProposalType>
+
+
+
+
+inline fun get_proposal<ProposalType: store>(
+ voting_forum_address: address,
+ proposal_id: u64,
+): &Proposal<ProposalType> acquires VotingForum {
+ let voting_forum = borrow_global<VotingForum<ProposalType>>(voting_forum_address);
+ table::borrow(&voting_forum.proposals, proposal_id)
+}
+
+
+
+
+No. | Requirement | Criticality | Implementation | Enforcement | +
---|---|---|---|---|
1 | +The proposal ID in a voting forum is unique and always increases monotonically with each new proposal created for that voting forum. | +High | +The create_proposal and create_proposal_v2 create a new proposal with a unique ID derived from the voting_forum's next_proposal_id incrementally. | +Formally verified via create_proposal. | +
2 | +While voting, it ensures that only the governance module that defines ProposalType may initiate voting and that the proposal under vote exists in the specified voting forum. | +Critical | +The vote function verifies the eligibility and validity of a proposal before allowing voting. It ensures that only the correct governance module initiates voting. The function checks if the proposal is currently eligible for voting by confirming it has not resolved and the voting period has not ended. | +Formally verified via vote. | +
3 | +After resolving a single-step proposal, the corresponding proposal is guaranteed to be marked as successfully resolved. | +High | +Upon invoking the resolve function on a proposal, it undergoes a series of checks to ensure its validity. These include verifying if the proposal exists, is a single-step proposal, and meets the criteria for resolution. If the checks pass, the proposal's is_resolved flag becomes true, indicating a successful resolution. | +Formally verified via resolve. | +
4 | +In the context of v2 proposal resolving, both single-step and multi-step proposals are accurately handled. It ensures that for single-step proposals, the next execution hash is empty and resolves the proposal, while for multi-step proposals, it guarantees that the next execution hash corresponds to the hash of the next step, maintaining the integrity of the proposal execution sequence. | +Medium | +The function resolve_proposal_v2 correctly handles both single-step and multi-step proposals. For single-step proposals, it ensures that the next_execution_hash parameter is empty and resolves the proposal. For multi-step proposals, it ensures that the next_execution_hash parameter contains the hash of the next step. | +Formally verified via resolve_proposal_v2. | +
pragma verify = true;
+pragma aborts_if_is_strict;
+
+
+
+
+
+
+### Function `register`
+
+
+public fun register<ProposalType: store>(account: &signer)
+
+
+
+
+
+let addr = signer::address_of(account);
+aborts_if exists<VotingForum<ProposalType>>(addr);
+aborts_if !exists<account::Account>(addr);
+let register_account = global<account::Account>(addr);
+aborts_if register_account.guid_creation_num + 4 >= account::MAX_GUID_CREATION_NUM;
+aborts_if register_account.guid_creation_num + 4 > MAX_U64;
+aborts_if !type_info::spec_is_struct<ProposalType>();
+ensures exists<VotingForum<ProposalType>>(addr);
+
+
+
+
+
+
+### Function `create_proposal`
+
+
+public fun create_proposal<ProposalType: store>(proposer: address, voting_forum_address: address, execution_content: ProposalType, execution_hash: vector<u8>, min_vote_threshold: u128, expiration_secs: u64, early_resolution_vote_threshold: option::Option<u128>, metadata: simple_map::SimpleMap<string::String, vector<u8>>): u64
+
+
+
+
+
+requires chain_status::is_operating();
+include CreateProposalAbortsIfAndEnsures<ProposalType>{is_multi_step_proposal: false};
+// This enforces high-level requirement 1:
+ensures result == old(global<VotingForum<ProposalType>>(voting_forum_address)).next_proposal_id;
+
+
+
+
+
+
+### Function `create_proposal_v2`
+
+
+public fun create_proposal_v2<ProposalType: store>(proposer: address, voting_forum_address: address, execution_content: ProposalType, execution_hash: vector<u8>, min_vote_threshold: u128, expiration_secs: u64, early_resolution_vote_threshold: option::Option<u128>, metadata: simple_map::SimpleMap<string::String, vector<u8>>, is_multi_step_proposal: bool): u64
+
+
+
+
+
+requires chain_status::is_operating();
+include CreateProposalAbortsIfAndEnsures<ProposalType>;
+ensures result == old(global<VotingForum<ProposalType>>(voting_forum_address)).next_proposal_id;
+
+
+
+
+
+
+
+
+schema CreateProposalAbortsIfAndEnsures<ProposalType> {
+ voting_forum_address: address;
+ execution_hash: vector<u8>;
+ min_vote_threshold: u128;
+ early_resolution_vote_threshold: Option<u128>;
+ metadata: SimpleMap<String, vector<u8>>;
+ is_multi_step_proposal: bool;
+ let voting_forum = global<VotingForum<ProposalType>>(voting_forum_address);
+ let proposal_id = voting_forum.next_proposal_id;
+ aborts_if !exists<VotingForum<ProposalType>>(voting_forum_address);
+ aborts_if table::spec_contains(voting_forum.proposals,proposal_id);
+ aborts_if len(early_resolution_vote_threshold.vec) != 0 && min_vote_threshold > early_resolution_vote_threshold.vec[0];
+ aborts_if !std::string::spec_internal_check_utf8(IS_MULTI_STEP_PROPOSAL_KEY);
+ aborts_if !std::string::spec_internal_check_utf8(IS_MULTI_STEP_PROPOSAL_IN_EXECUTION_KEY);
+ aborts_if len(execution_hash) == 0;
+ let execution_key = std::string::spec_utf8(IS_MULTI_STEP_PROPOSAL_KEY);
+ aborts_if simple_map::spec_contains_key(metadata, execution_key);
+ aborts_if voting_forum.next_proposal_id + 1 > MAX_U64;
+ let is_multi_step_in_execution_key = std::string::spec_utf8(IS_MULTI_STEP_PROPOSAL_IN_EXECUTION_KEY);
+ aborts_if is_multi_step_proposal && simple_map::spec_contains_key(metadata, is_multi_step_in_execution_key);
+ let post post_voting_forum = global<VotingForum<ProposalType>>(voting_forum_address);
+ let post post_metadata = table::spec_get(post_voting_forum.proposals, proposal_id).metadata;
+ ensures post_voting_forum.next_proposal_id == voting_forum.next_proposal_id + 1;
+ ensures table::spec_contains(post_voting_forum.proposals, proposal_id);
+ ensures if (is_multi_step_proposal) {
+ simple_map::spec_get(post_metadata, is_multi_step_in_execution_key) == std::bcs::serialize(false)
+ } else {
+ !simple_map::spec_contains_key(post_metadata, is_multi_step_in_execution_key)
+ };
+}
+
+
+
+
+
+
+### Function `vote`
+
+
+public fun vote<ProposalType: store>(_proof: &ProposalType, voting_forum_address: address, proposal_id: u64, num_votes: u64, should_pass: bool)
+
+
+
+
+
+requires chain_status::is_operating();
+// This enforces high-level requirement 2:
+aborts_if !exists<VotingForum<ProposalType>>(voting_forum_address);
+let voting_forum = global<VotingForum<ProposalType>>(voting_forum_address);
+let proposal = table::spec_get(voting_forum.proposals, proposal_id);
+aborts_if !table::spec_contains(voting_forum.proposals, proposal_id);
+aborts_if is_voting_period_over(proposal);
+aborts_if proposal.is_resolved;
+aborts_if !exists<timestamp::CurrentTimeMicroseconds>(@aptos_framework);
+aborts_if !std::string::spec_internal_check_utf8(IS_MULTI_STEP_PROPOSAL_IN_EXECUTION_KEY);
+let execution_key = std::string::spec_utf8(IS_MULTI_STEP_PROPOSAL_IN_EXECUTION_KEY);
+aborts_if simple_map::spec_contains_key(proposal.metadata, execution_key) &&
+ simple_map::spec_get(proposal.metadata, execution_key) != std::bcs::serialize(false);
+aborts_if if (should_pass) { proposal.yes_votes + num_votes > MAX_U128 } else { proposal.no_votes + num_votes > MAX_U128 };
+aborts_if !std::string::spec_internal_check_utf8(RESOLVABLE_TIME_METADATA_KEY);
+let post post_voting_forum = global<VotingForum<ProposalType>>(voting_forum_address);
+let post post_proposal = table::spec_get(post_voting_forum.proposals, proposal_id);
+ensures if (should_pass) {
+ post_proposal.yes_votes == proposal.yes_votes + num_votes
+} else {
+ post_proposal.no_votes == proposal.no_votes + num_votes
+};
+let timestamp_secs_bytes = std::bcs::serialize(timestamp::spec_now_seconds());
+let key = std::string::spec_utf8(RESOLVABLE_TIME_METADATA_KEY);
+ensures simple_map::spec_get(post_proposal.metadata, key) == timestamp_secs_bytes;
+
+
+
+
+
+
+### Function `is_proposal_resolvable`
+
+
+fun is_proposal_resolvable<ProposalType: store>(voting_forum_address: address, proposal_id: u64)
+
+
+
+
+
+requires chain_status::is_operating();
+include IsProposalResolvableAbortsIf<ProposalType>;
+
+
+
+
+
+
+
+
+schema IsProposalResolvableAbortsIf<ProposalType> {
+ voting_forum_address: address;
+ proposal_id: u64;
+ include AbortsIfNotContainProposalID<ProposalType>;
+ let voting_forum = global<VotingForum<ProposalType>>(voting_forum_address);
+ let proposal = table::spec_get(voting_forum.proposals, proposal_id);
+ let voting_closed = spec_is_voting_closed<ProposalType>(voting_forum_address, proposal_id);
+ aborts_if voting_closed && (proposal.yes_votes <= proposal.no_votes || proposal.yes_votes + proposal.no_votes < proposal.min_vote_threshold);
+ aborts_if !voting_closed;
+ aborts_if proposal.is_resolved;
+ aborts_if !std::string::spec_internal_check_utf8(RESOLVABLE_TIME_METADATA_KEY);
+ aborts_if !simple_map::spec_contains_key(proposal.metadata, std::string::spec_utf8(RESOLVABLE_TIME_METADATA_KEY));
+ aborts_if !from_bcs::deserializable<u64>(simple_map::spec_get(proposal.metadata, std::string::spec_utf8(RESOLVABLE_TIME_METADATA_KEY)));
+ aborts_if timestamp::spec_now_seconds() <= from_bcs::deserialize<u64>(simple_map::spec_get(proposal.metadata, std::string::spec_utf8(RESOLVABLE_TIME_METADATA_KEY)));
+ aborts_if transaction_context::spec_get_script_hash() != proposal.execution_hash;
+}
+
+
+
+
+
+
+### Function `resolve`
+
+
+public fun resolve<ProposalType: store>(voting_forum_address: address, proposal_id: u64): ProposalType
+
+
+
+
+
+requires chain_status::is_operating();
+include IsProposalResolvableAbortsIf<ProposalType>;
+aborts_if !std::string::spec_internal_check_utf8(IS_MULTI_STEP_PROPOSAL_KEY);
+let voting_forum = global<VotingForum<ProposalType>>(voting_forum_address);
+let proposal = table::spec_get(voting_forum.proposals, proposal_id);
+let multi_step_key = std::string::spec_utf8(IS_MULTI_STEP_PROPOSAL_KEY);
+let has_multi_step_key = simple_map::spec_contains_key(proposal.metadata, multi_step_key);
+aborts_if has_multi_step_key && !from_bcs::deserializable<bool>(simple_map::spec_get(proposal.metadata, multi_step_key));
+aborts_if has_multi_step_key && from_bcs::deserialize<bool>(simple_map::spec_get(proposal.metadata, multi_step_key));
+let post post_voting_forum = global<VotingForum<ProposalType>>(voting_forum_address);
+let post post_proposal = table::spec_get(post_voting_forum.proposals, proposal_id);
+aborts_if !exists<timestamp::CurrentTimeMicroseconds>(@aptos_framework);
+// This enforces high-level requirement 3:
+ensures post_proposal.is_resolved == true;
+ensures post_proposal.resolution_time_secs == timestamp::spec_now_seconds();
+aborts_if option::spec_is_none(proposal.execution_content);
+ensures result == option::spec_borrow(proposal.execution_content);
+ensures option::spec_is_none(post_proposal.execution_content);
+
+
+
+
+
+
+### Function `resolve_proposal_v2`
+
+
+public fun resolve_proposal_v2<ProposalType: store>(voting_forum_address: address, proposal_id: u64, next_execution_hash: vector<u8>)
+
+
+
+
+
+pragma verify_duration_estimate = 300;
+requires chain_status::is_operating();
+include IsProposalResolvableAbortsIf<ProposalType>;
+let voting_forum = global<VotingForum<ProposalType>>(voting_forum_address);
+let proposal = table::spec_get(voting_forum.proposals, proposal_id);
+let post post_voting_forum = global<VotingForum<ProposalType>>(voting_forum_address);
+let post post_proposal = table::spec_get(post_voting_forum.proposals, proposal_id);
+let multi_step_in_execution_key = std::string::spec_utf8(IS_MULTI_STEP_PROPOSAL_IN_EXECUTION_KEY);
+aborts_if !std::string::spec_internal_check_utf8(IS_MULTI_STEP_PROPOSAL_IN_EXECUTION_KEY);
+aborts_if !std::string::spec_internal_check_utf8(IS_MULTI_STEP_PROPOSAL_KEY);
+ensures (simple_map::spec_contains_key(proposal.metadata, multi_step_in_execution_key) && len(next_execution_hash) != 0) ==>
+ simple_map::spec_get(post_proposal.metadata, multi_step_in_execution_key) == std::bcs::serialize(true);
+ensures (simple_map::spec_contains_key(proposal.metadata, multi_step_in_execution_key) &&
+ (len(next_execution_hash) == 0 && !is_multi_step)) ==>
+ simple_map::spec_get(post_proposal.metadata, multi_step_in_execution_key) == std::bcs::serialize(true);
+let multi_step_key = std::string::spec_utf8(IS_MULTI_STEP_PROPOSAL_KEY);
+aborts_if simple_map::spec_contains_key(proposal.metadata, multi_step_key) &&
+ !from_bcs::deserializable<bool>(simple_map::spec_get(proposal.metadata, multi_step_key));
+let is_multi_step = simple_map::spec_contains_key(proposal.metadata, multi_step_key) &&
+ from_bcs::deserialize(simple_map::spec_get(proposal.metadata, multi_step_key));
+aborts_if !is_multi_step && len(next_execution_hash) != 0;
+aborts_if len(next_execution_hash) == 0 && !exists<timestamp::CurrentTimeMicroseconds>(@aptos_framework);
+aborts_if len(next_execution_hash) == 0 && is_multi_step && !simple_map::spec_contains_key(proposal.metadata, multi_step_in_execution_key);
+// This enforces high-level requirement 4:
+ensures len(next_execution_hash) == 0 ==> post_proposal.resolution_time_secs == timestamp::spec_now_seconds();
+ensures len(next_execution_hash) == 0 ==> post_proposal.is_resolved == true;
+ensures (len(next_execution_hash) == 0 && is_multi_step) ==> simple_map::spec_get(post_proposal.metadata, multi_step_in_execution_key) == std::bcs::serialize(false);
+ensures len(next_execution_hash) != 0 ==> post_proposal.execution_hash == next_execution_hash;
+
+
+
+
+
+
+### Function `next_proposal_id`
+
+
+#[view]
+public fun next_proposal_id<ProposalType: store>(voting_forum_address: address): u64
+
+
+
+
+
+aborts_if !exists<VotingForum<ProposalType>>(voting_forum_address);
+ensures result == global<VotingForum<ProposalType>>(voting_forum_address).next_proposal_id;
+
+
+
+
+
+
+### Function `get_proposer`
+
+
+#[view]
+public fun get_proposer<ProposalType: store>(voting_forum_address: address, proposal_id: u64): address
+
+
+
+
+
+include AbortsIfNotContainProposalID<ProposalType>;
+let voting_forum = global<VotingForum<ProposalType>>(voting_forum_address);
+let proposal = table::spec_get(voting_forum.proposals, proposal_id);
+ensures result == proposal.proposer;
+
+
+
+
+
+
+### Function `is_voting_closed`
+
+
+#[view]
+public fun is_voting_closed<ProposalType: store>(voting_forum_address: address, proposal_id: u64): bool
+
+
+
+
+
+requires chain_status::is_operating();
+include AbortsIfNotContainProposalID<ProposalType>;
+aborts_if !exists<timestamp::CurrentTimeMicroseconds>(@aptos_framework);
+ensures result == spec_is_voting_closed<ProposalType>(voting_forum_address, proposal_id);
+
+
+
+
+
+
+
+
+fun spec_is_voting_closed<ProposalType: store>(voting_forum_address: address, proposal_id: u64): bool {
+ let voting_forum = global<VotingForum<ProposalType>>(voting_forum_address);
+ let proposal = table::spec_get(voting_forum.proposals, proposal_id);
+ spec_can_be_resolved_early<ProposalType>(proposal) || is_voting_period_over(proposal)
+}
+
+
+
+
+
+
+### Function `can_be_resolved_early`
+
+
+public fun can_be_resolved_early<ProposalType: store>(proposal: &voting::Proposal<ProposalType>): bool
+
+
+
+
+
+aborts_if false;
+ensures result == spec_can_be_resolved_early<ProposalType>(proposal);
+
+
+
+
+
+
+
+
+fun spec_can_be_resolved_early<ProposalType: store>(proposal: Proposal<ProposalType>): bool {
+ if (option::spec_is_some(proposal.early_resolution_vote_threshold)) {
+ let early_resolution_threshold = option::spec_borrow(proposal.early_resolution_vote_threshold);
+ if (proposal.yes_votes >= early_resolution_threshold || proposal.no_votes >= early_resolution_threshold) {
+ true
+ } else{
+ false
+ }
+ } else {
+ false
+ }
+}
+
+
+
+
+
+
+
+
+fun spec_get_proposal_state<ProposalType>(
+ voting_forum_address: address,
+ proposal_id: u64,
+ voting_forum: VotingForum<ProposalType>
+): u64 {
+ let proposal = table::spec_get(voting_forum.proposals, proposal_id);
+ let voting_closed = spec_is_voting_closed<ProposalType>(voting_forum_address, proposal_id);
+ let proposal_vote_cond = (proposal.yes_votes > proposal.no_votes && proposal.yes_votes + proposal.no_votes >= proposal.min_vote_threshold);
+ if (voting_closed && proposal_vote_cond) {
+ PROPOSAL_STATE_SUCCEEDED
+ } else if (voting_closed && !proposal_vote_cond) {
+ PROPOSAL_STATE_FAILED
+ } else {
+ PROPOSAL_STATE_PENDING
+ }
+}
+
+
+
+
+
+
+
+
+fun spec_get_proposal_expiration_secs<ProposalType: store>(
+ voting_forum_address: address,
+ proposal_id: u64,
+): u64 {
+ let voting_forum = global<VotingForum<ProposalType>>(voting_forum_address);
+ let proposal = table::spec_get(voting_forum.proposals, proposal_id);
+ proposal.expiration_secs
+}
+
+
+
+
+
+
+### Function `get_proposal_metadata`
+
+
+#[view]
+public fun get_proposal_metadata<ProposalType: store>(voting_forum_address: address, proposal_id: u64): simple_map::SimpleMap<string::String, vector<u8>>
+
+
+
+
+
+include AbortsIfNotContainProposalID<ProposalType>;
+let voting_forum = global<VotingForum<ProposalType>>(voting_forum_address);
+let proposal = table::spec_get(voting_forum.proposals, proposal_id);
+ensures result == proposal.metadata;
+
+
+
+
+
+
+### Function `get_proposal_metadata_value`
+
+
+#[view]
+public fun get_proposal_metadata_value<ProposalType: store>(voting_forum_address: address, proposal_id: u64, metadata_key: string::String): vector<u8>
+
+
+
+
+
+include AbortsIfNotContainProposalID<ProposalType>;
+let voting_forum = global<VotingForum<ProposalType>>(voting_forum_address);
+let proposal = table::spec_get(voting_forum.proposals, proposal_id);
+aborts_if !simple_map::spec_contains_key(proposal.metadata, metadata_key);
+ensures result == simple_map::spec_get(proposal.metadata, metadata_key);
+
+
+
+
+
+
+### Function `get_proposal_state`
+
+
+#[view]
+public fun get_proposal_state<ProposalType: store>(voting_forum_address: address, proposal_id: u64): u64
+
+
+
+
+
+pragma addition_overflow_unchecked;
+requires chain_status::is_operating();
+include AbortsIfNotContainProposalID<ProposalType>;
+let voting_forum = global<VotingForum<ProposalType>>(voting_forum_address);
+ensures result == spec_get_proposal_state(voting_forum_address, proposal_id, voting_forum);
+
+
+
+
+
+
+### Function `get_proposal_creation_secs`
+
+
+#[view]
+public fun get_proposal_creation_secs<ProposalType: store>(voting_forum_address: address, proposal_id: u64): u64
+
+
+
+
+
+include AbortsIfNotContainProposalID<ProposalType>;
+let voting_forum = global<VotingForum<ProposalType>>(voting_forum_address);
+let proposal = table::spec_get(voting_forum.proposals, proposal_id);
+ensures result == proposal.creation_time_secs;
+
+
+
+
+
+
+### Function `get_proposal_expiration_secs`
+
+
+#[view]
+public fun get_proposal_expiration_secs<ProposalType: store>(voting_forum_address: address, proposal_id: u64): u64
+
+
+
+
+
+include AbortsIfNotContainProposalID<ProposalType>;
+ensures result == spec_get_proposal_expiration_secs<ProposalType>(voting_forum_address, proposal_id);
+
+
+
+
+
+
+### Function `get_execution_hash`
+
+
+#[view]
+public fun get_execution_hash<ProposalType: store>(voting_forum_address: address, proposal_id: u64): vector<u8>
+
+
+
+
+
+include AbortsIfNotContainProposalID<ProposalType>;
+let voting_forum = global<VotingForum<ProposalType>>(voting_forum_address);
+let proposal = table::spec_get(voting_forum.proposals, proposal_id);
+ensures result == proposal.execution_hash;
+
+
+
+
+
+
+### Function `get_min_vote_threshold`
+
+
+#[view]
+public fun get_min_vote_threshold<ProposalType: store>(voting_forum_address: address, proposal_id: u64): u128
+
+
+
+
+
+include AbortsIfNotContainProposalID<ProposalType>;
+let voting_forum = global<VotingForum<ProposalType>>(voting_forum_address);
+let proposal = table::spec_get(voting_forum.proposals, proposal_id);
+ensures result == proposal.min_vote_threshold;
+
+
+
+
+
+
+### Function `get_early_resolution_vote_threshold`
+
+
+#[view]
+public fun get_early_resolution_vote_threshold<ProposalType: store>(voting_forum_address: address, proposal_id: u64): option::Option<u128>
+
+
+
+
+
+include AbortsIfNotContainProposalID<ProposalType>;
+let voting_forum = global<VotingForum<ProposalType>>(voting_forum_address);
+let proposal = table::spec_get(voting_forum.proposals, proposal_id);
+ensures result == proposal.early_resolution_vote_threshold;
+
+
+
+
+
+
+### Function `get_votes`
+
+
+#[view]
+public fun get_votes<ProposalType: store>(voting_forum_address: address, proposal_id: u64): (u128, u128)
+
+
+
+
+
+include AbortsIfNotContainProposalID<ProposalType>;
+let voting_forum = global<VotingForum<ProposalType>>(voting_forum_address);
+let proposal = table::spec_get(voting_forum.proposals, proposal_id);
+ensures result_1 == proposal.yes_votes;
+ensures result_2 == proposal.no_votes;
+
+
+
+
+
+
+### Function `is_resolved`
+
+
+#[view]
+public fun is_resolved<ProposalType: store>(voting_forum_address: address, proposal_id: u64): bool
+
+
+
+
+
+include AbortsIfNotContainProposalID<ProposalType>;
+let voting_forum = global<VotingForum<ProposalType>>(voting_forum_address);
+let proposal = table::spec_get(voting_forum.proposals, proposal_id);
+ensures result == proposal.is_resolved;
+
+
+
+
+
+
+
+
+schema AbortsIfNotContainProposalID<ProposalType> {
+ proposal_id: u64;
+ voting_forum_address: address;
+ let voting_forum = global<VotingForum<ProposalType>>(voting_forum_address);
+ aborts_if !table::spec_contains(voting_forum.proposals, proposal_id);
+ aborts_if !exists<VotingForum<ProposalType>>(voting_forum_address);
+}
+
+
+
+
+
+
+### Function `get_resolution_time_secs`
+
+
+#[view]
+public fun get_resolution_time_secs<ProposalType: store>(voting_forum_address: address, proposal_id: u64): u64
+
+
+
+
+
+include AbortsIfNotContainProposalID<ProposalType>;
+let voting_forum = global<VotingForum<ProposalType>>(voting_forum_address);
+let proposal = table::spec_get(voting_forum.proposals, proposal_id);
+ensures result == proposal.resolution_time_secs;
+
+
+
+
+
+
+### Function `is_multi_step_proposal_in_execution`
+
+
+#[view]
+public fun is_multi_step_proposal_in_execution<ProposalType: store>(voting_forum_address: address, proposal_id: u64): bool
+
+
+
+
+
+include AbortsIfNotContainProposalID<ProposalType>;
+let voting_forum = global<VotingForum<ProposalType>>(voting_forum_address);
+let proposal = table::spec_get(voting_forum.proposals,proposal_id);
+aborts_if !std::string::spec_internal_check_utf8(IS_MULTI_STEP_PROPOSAL_IN_EXECUTION_KEY);
+let execution_key = std::string::spec_utf8(IS_MULTI_STEP_PROPOSAL_IN_EXECUTION_KEY);
+aborts_if !simple_map::spec_contains_key(proposal.metadata,execution_key);
+let is_multi_step_in_execution_key = simple_map::spec_get(proposal.metadata,execution_key);
+aborts_if !from_bcs::deserializable<bool>(is_multi_step_in_execution_key);
+ensures result == from_bcs::deserialize<bool>(is_multi_step_in_execution_key);
+
+
+
+
+
+
+### Function `is_voting_period_over`
+
+
+fun is_voting_period_over<ProposalType: store>(proposal: &voting::Proposal<ProposalType>): bool
+
+
+
+
+
+requires chain_status::is_operating();
+aborts_if false;
+ensures result == (timestamp::spec_now_seconds() > proposal.expiration_secs);
+
+
+
+[move-book]: https://aptos.dev/move/book/SUMMARY
diff --git a/aptos-move/framework/aptos-stdlib/tests/compiler-v2-doc/any.md b/aptos-move/framework/aptos-stdlib/tests/compiler-v2-doc/any.md
new file mode 100644
index 0000000000000..cb36046ce688c
--- /dev/null
+++ b/aptos-move/framework/aptos-stdlib/tests/compiler-v2-doc/any.md
@@ -0,0 +1,252 @@
+
+
+
+# Module `0x1::any`
+
+
+
+- [Struct `Any`](#0x1_any_Any)
+- [Constants](#@Constants_0)
+- [Function `pack`](#0x1_any_pack)
+- [Function `unpack`](#0x1_any_unpack)
+- [Function `type_name`](#0x1_any_type_name)
+- [Specification](#@Specification_1)
+ - [Function `pack`](#@Specification_1_pack)
+ - [Function `unpack`](#@Specification_1_unpack)
+ - [Function `type_name`](#@Specification_1_type_name)
+
+
+use 0x1::bcs;
+use 0x1::error;
+use 0x1::from_bcs;
+use 0x1::string;
+use 0x1::type_info;
+
+
+
+
+
+
+## Struct `Any`
+
+A type which can represent a value of any type. This allows for representation of 'unknown' future
+values. For example, to define a resource such that it can be later be extended without breaking
+changes one can do
+
+```move
+struct Resource {
+field: Type,
+...
+extension: Optionstruct Any has drop, store
+
+
+
+
+type_name: string::String
+data: vector<u8>
+unpack
is not the same as was given for pack
.
+
+
+const ETYPE_MISMATCH: u64 = 1;
+
+
+
+
+
+
+## Function `pack`
+
+Pack a value into the Any
representation. Because Any can be stored and dropped, this is
+also required from T
.
+
+
+public fun pack<T: drop, store>(x: T): any::Any
+
+
+
+
+public fun pack<T: drop + store>(x: T): Any {
+ Any {
+ type_name: type_info::type_name<T>(),
+ data: to_bytes(&x)
+ }
+}
+
+
+
+
+Any
representation. This aborts if the value has not the expected type T
.
+
+
+public fun unpack<T>(x: any::Any): T
+
+
+
+
+public fun unpack<T>(x: Any): T {
+ assert!(type_info::type_name<T>() == x.type_name, error::invalid_argument(ETYPE_MISMATCH));
+ from_bytes<T>(x.data)
+}
+
+
+
+
+public fun type_name(x: &any::Any): &string::String
+
+
+
+
+public fun type_name(x: &Any): &String {
+ &x.type_name
+}
+
+
+
+
+public fun pack<T: drop, store>(x: T): any::Any
+
+
+
+
+
+aborts_if false;
+ensures result == Any {
+ type_name: type_info::type_name<T>(),
+ data: bcs::serialize<T>(x)
+};
+ensures [abstract] from_bcs::deserializable<T>(result.data);
+
+
+
+
+
+
+### Function `unpack`
+
+
+public fun unpack<T>(x: any::Any): T
+
+
+
+
+
+include UnpackAbortsIf<T>;
+ensures result == from_bcs::deserialize<T>(x.data);
+
+
+
+
+
+
+
+
+schema UnpackAbortsIf<T> {
+ x: Any;
+ aborts_if type_info::type_name<T>() != x.type_name;
+ aborts_if !from_bcs::deserializable<T>(x.data);
+}
+
+
+
+
+
+
+
+
+schema UnpackRequirement<T> {
+ x: Any;
+ requires type_info::type_name<T>() == x.type_name;
+ requires from_bcs::deserializable<T>(x.data);
+}
+
+
+
+
+
+
+### Function `type_name`
+
+
+public fun type_name(x: &any::Any): &string::String
+
+
+
+
+
+aborts_if false;
+ensures result == x.type_name;
+
+
+
+[move-book]: https://aptos.dev/move/book/SUMMARY
diff --git a/aptos-move/framework/aptos-stdlib/tests/compiler-v2-doc/big_vector.md b/aptos-move/framework/aptos-stdlib/tests/compiler-v2-doc/big_vector.md
new file mode 100644
index 0000000000000..a983f2f515a13
--- /dev/null
+++ b/aptos-move/framework/aptos-stdlib/tests/compiler-v2-doc/big_vector.md
@@ -0,0 +1,1120 @@
+
+
+
+# Module `0x1::big_vector`
+
+
+
+- [Struct `BigVector`](#0x1_big_vector_BigVector)
+- [Constants](#@Constants_0)
+- [Function `empty`](#0x1_big_vector_empty)
+- [Function `singleton`](#0x1_big_vector_singleton)
+- [Function `destroy_empty`](#0x1_big_vector_destroy_empty)
+- [Function `destroy`](#0x1_big_vector_destroy)
+- [Function `borrow`](#0x1_big_vector_borrow)
+- [Function `borrow_mut`](#0x1_big_vector_borrow_mut)
+- [Function `append`](#0x1_big_vector_append)
+- [Function `push_back`](#0x1_big_vector_push_back)
+- [Function `pop_back`](#0x1_big_vector_pop_back)
+- [Function `remove`](#0x1_big_vector_remove)
+- [Function `swap_remove`](#0x1_big_vector_swap_remove)
+- [Function `swap`](#0x1_big_vector_swap)
+- [Function `reverse`](#0x1_big_vector_reverse)
+- [Function `index_of`](#0x1_big_vector_index_of)
+- [Function `contains`](#0x1_big_vector_contains)
+- [Function `to_vector`](#0x1_big_vector_to_vector)
+- [Function `length`](#0x1_big_vector_length)
+- [Function `is_empty`](#0x1_big_vector_is_empty)
+- [Specification](#@Specification_1)
+ - [Struct `BigVector`](#@Specification_1_BigVector)
+ - [Function `empty`](#@Specification_1_empty)
+ - [Function `singleton`](#@Specification_1_singleton)
+ - [Function `destroy_empty`](#@Specification_1_destroy_empty)
+ - [Function `borrow`](#@Specification_1_borrow)
+ - [Function `borrow_mut`](#@Specification_1_borrow_mut)
+ - [Function `append`](#@Specification_1_append)
+ - [Function `push_back`](#@Specification_1_push_back)
+ - [Function `pop_back`](#@Specification_1_pop_back)
+ - [Function `remove`](#@Specification_1_remove)
+ - [Function `swap_remove`](#@Specification_1_swap_remove)
+ - [Function `swap`](#@Specification_1_swap)
+ - [Function `reverse`](#@Specification_1_reverse)
+ - [Function `index_of`](#@Specification_1_index_of)
+
+
+use 0x1::error;
+use 0x1::table_with_length;
+use 0x1::vector;
+
+
+
+
+
+
+## Struct `BigVector`
+
+A scalable vector implementation based on tables where elements are grouped into buckets.
+Each bucket has a capacity of bucket_size
elements.
+
+
+struct BigVector<T> has store
+
+
+
+
+buckets: table_with_length::TableWithLength<u64, vector<T>>
+end_index: u64
+bucket_size: u64
+const EINDEX_OUT_OF_BOUNDS: u64 = 1;
+
+
+
+
+
+
+Cannot pop back from an empty vector
+
+
+const EVECTOR_EMPTY: u64 = 3;
+
+
+
+
+
+
+Cannot destroy a non-empty vector
+
+
+const EVECTOR_NOT_EMPTY: u64 = 2;
+
+
+
+
+
+
+bucket_size cannot be 0
+
+
+const EZERO_BUCKET_SIZE: u64 = 4;
+
+
+
+
+
+
+## Function `empty`
+
+Regular Vector API
+Create an empty vector.
+
+
+public(friend) fun empty<T: store>(bucket_size: u64): big_vector::BigVector<T>
+
+
+
+
+public(friend) fun empty<T: store>(bucket_size: u64): BigVector<T> {
+ assert!(bucket_size > 0, error::invalid_argument(EZERO_BUCKET_SIZE));
+ BigVector {
+ buckets: table_with_length::new(),
+ end_index: 0,
+ bucket_size,
+ }
+}
+
+
+
+
+public(friend) fun singleton<T: store>(element: T, bucket_size: u64): big_vector::BigVector<T>
+
+
+
+
+public(friend) fun singleton<T: store>(element: T, bucket_size: u64): BigVector<T> {
+ let v = empty(bucket_size);
+ push_back(&mut v, element);
+ v
+}
+
+
+
+
+v
.
+Aborts if v
is not empty.
+
+
+public fun destroy_empty<T>(v: big_vector::BigVector<T>)
+
+
+
+
+public fun destroy_empty<T>(v: BigVector<T>) {
+ assert!(is_empty(&v), error::invalid_argument(EVECTOR_NOT_EMPTY));
+ let BigVector { buckets, end_index: _, bucket_size: _ } = v;
+ table_with_length::destroy_empty(buckets);
+}
+
+
+
+
+v
if T has drop
+
+
+public fun destroy<T: drop>(v: big_vector::BigVector<T>)
+
+
+
+
+public fun destroy<T: drop>(v: BigVector<T>) {
+ let BigVector { buckets, end_index, bucket_size: _ } = v;
+ let i = 0;
+ while (end_index > 0) {
+ let num_elements = vector::length(&table_with_length::remove(&mut buckets, i));
+ end_index = end_index - num_elements;
+ i = i + 1;
+ };
+ table_with_length::destroy_empty(buckets);
+}
+
+
+
+
+i
th element of the vector v
.
+Aborts if i
is out of bounds.
+
+
+public fun borrow<T>(v: &big_vector::BigVector<T>, i: u64): &T
+
+
+
+
+public fun borrow<T>(v: &BigVector<T>, i: u64): &T {
+ assert!(i < length(v), error::invalid_argument(EINDEX_OUT_OF_BOUNDS));
+ vector::borrow(table_with_length::borrow(&v.buckets, i / v.bucket_size), i % v.bucket_size)
+}
+
+
+
+
+i
th element in the vector v
.
+Aborts if i
is out of bounds.
+
+
+public fun borrow_mut<T>(v: &mut big_vector::BigVector<T>, i: u64): &mut T
+
+
+
+
+public fun borrow_mut<T>(v: &mut BigVector<T>, i: u64): &mut T {
+ assert!(i < length(v), error::invalid_argument(EINDEX_OUT_OF_BOUNDS));
+ vector::borrow_mut(table_with_length::borrow_mut(&mut v.buckets, i / v.bucket_size), i % v.bucket_size)
+}
+
+
+
+
+public fun append<T: store>(lhs: &mut big_vector::BigVector<T>, other: big_vector::BigVector<T>)
+
+
+
+
+public fun append<T: store>(lhs: &mut BigVector<T>, other: BigVector<T>) {
+ let other_len = length(&other);
+ let half_other_len = other_len / 2;
+ let i = 0;
+ while (i < half_other_len) {
+ push_back(lhs, swap_remove(&mut other, i));
+ i = i + 1;
+ };
+ while (i < other_len) {
+ push_back(lhs, pop_back(&mut other));
+ i = i + 1;
+ };
+ destroy_empty(other);
+}
+
+
+
+
+val
to the end of the vector v
. It grows the buckets when the current buckets are full.
+This operation will cost more gas when it adds new bucket.
+
+
+public fun push_back<T: store>(v: &mut big_vector::BigVector<T>, val: T)
+
+
+
+
+public fun push_back<T: store>(v: &mut BigVector<T>, val: T) {
+ let num_buckets = table_with_length::length(&v.buckets);
+ if (v.end_index == num_buckets * v.bucket_size) {
+ table_with_length::add(&mut v.buckets, num_buckets, vector::empty());
+ vector::push_back(table_with_length::borrow_mut(&mut v.buckets, num_buckets), val);
+ } else {
+ vector::push_back(table_with_length::borrow_mut(&mut v.buckets, num_buckets - 1), val);
+ };
+ v.end_index = v.end_index + 1;
+}
+
+
+
+
+v
. It doesn't shrink the buckets even if they're empty.
+Call shrink_to_fit
explicity to deallocate empty buckets.
+Aborts if v
is empty.
+
+
+public fun pop_back<T>(v: &mut big_vector::BigVector<T>): T
+
+
+
+
+public fun pop_back<T>(v: &mut BigVector<T>): T {
+ assert!(!is_empty(v), error::invalid_state(EVECTOR_EMPTY));
+ let num_buckets = table_with_length::length(&v.buckets);
+ let last_bucket = table_with_length::borrow_mut(&mut v.buckets, num_buckets - 1);
+ let val = vector::pop_back(last_bucket);
+ // Shrink the table if the last vector is empty.
+ if (vector::is_empty(last_bucket)) {
+ move last_bucket;
+ vector::destroy_empty(table_with_length::remove(&mut v.buckets, num_buckets - 1));
+ };
+ v.end_index = v.end_index - 1;
+ val
+}
+
+
+
+
+public fun remove<T>(v: &mut big_vector::BigVector<T>, i: u64): T
+
+
+
+
+public fun remove<T>(v: &mut BigVector<T>, i: u64): T {
+ let len = length(v);
+ assert!(i < len, error::invalid_argument(EINDEX_OUT_OF_BOUNDS));
+ let num_buckets = table_with_length::length(&v.buckets);
+ let cur_bucket_index = i / v.bucket_size + 1;
+ let cur_bucket = table_with_length::borrow_mut(&mut v.buckets, cur_bucket_index - 1);
+ let res = vector::remove(cur_bucket, i % v.bucket_size);
+ v.end_index = v.end_index - 1;
+ move cur_bucket;
+ while ({
+ spec {
+ invariant cur_bucket_index <= num_buckets;
+ invariant table_with_length::spec_len(v.buckets) == num_buckets;
+ };
+ (cur_bucket_index < num_buckets)
+ }) {
+ // remove one element from the start of current vector
+ let cur_bucket = table_with_length::borrow_mut(&mut v.buckets, cur_bucket_index);
+ let t = vector::remove(cur_bucket, 0);
+ move cur_bucket;
+ // and put it at the end of the last one
+ let prev_bucket = table_with_length::borrow_mut(&mut v.buckets, cur_bucket_index - 1);
+ vector::push_back(prev_bucket, t);
+ cur_bucket_index = cur_bucket_index + 1;
+ };
+ spec {
+ assert cur_bucket_index == num_buckets;
+ };
+
+ // Shrink the table if the last vector is empty.
+ let last_bucket = table_with_length::borrow_mut(&mut v.buckets, num_buckets - 1);
+ if (vector::is_empty(last_bucket)) {
+ move last_bucket;
+ vector::destroy_empty(table_with_length::remove(&mut v.buckets, num_buckets - 1));
+ };
+
+ res
+}
+
+
+
+
+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<T>(v: &mut big_vector::BigVector<T>, i: u64): T
+
+
+
+
+public fun swap_remove<T>(v: &mut BigVector<T>, i: u64): T {
+ assert!(i < length(v), error::invalid_argument(EINDEX_OUT_OF_BOUNDS));
+ let last_val = pop_back(v);
+ // if the requested value is the last one, return it
+ if (v.end_index == i) {
+ return last_val
+ };
+ // because the lack of mem::swap, here we swap remove the requested value from the bucket
+ // and append the last_val to the bucket then swap the last bucket val back
+ let bucket = table_with_length::borrow_mut(&mut v.buckets, i / v.bucket_size);
+ let bucket_len = vector::length(bucket);
+ let val = vector::swap_remove(bucket, i % v.bucket_size);
+ vector::push_back(bucket, last_val);
+ vector::swap(bucket, i % v.bucket_size, bucket_len - 1);
+ val
+}
+
+
+
+
+public fun swap<T>(v: &mut big_vector::BigVector<T>, i: u64, j: u64)
+
+
+
+
+public fun swap<T>(v: &mut BigVector<T>, i: u64, j: u64) {
+ assert!(i < length(v) && j < length(v), error::invalid_argument(EINDEX_OUT_OF_BOUNDS));
+ let i_bucket_index = i / v.bucket_size;
+ let j_bucket_index = j / v.bucket_size;
+ let i_vector_index = i % v.bucket_size;
+ let j_vector_index = j % v.bucket_size;
+ if (i_bucket_index == j_bucket_index) {
+ vector::swap(table_with_length::borrow_mut(&mut v.buckets, i_bucket_index), i_vector_index, j_vector_index);
+ return
+ };
+ // If i and j are in different buckets, take the buckets out first for easy mutation.
+ let bucket_i = table_with_length::remove(&mut v.buckets, i_bucket_index);
+ let bucket_j = table_with_length::remove(&mut v.buckets, j_bucket_index);
+ // Get the elements from buckets by calling `swap_remove`.
+ let element_i = vector::swap_remove(&mut bucket_i, i_vector_index);
+ let element_j = vector::swap_remove(&mut bucket_j, j_vector_index);
+ // Swap the elements and push back to the other bucket.
+ vector::push_back(&mut bucket_i, element_j);
+ vector::push_back(&mut bucket_j, element_i);
+ let last_index_in_bucket_i = vector::length(&bucket_i) - 1;
+ let last_index_in_bucket_j = vector::length(&bucket_j) - 1;
+ // Re-position the swapped elements to the right index.
+ vector::swap(&mut bucket_i, i_vector_index, last_index_in_bucket_i);
+ vector::swap(&mut bucket_j, j_vector_index, last_index_in_bucket_j);
+ // Add back the buckets.
+ table_with_length::add(&mut v.buckets, i_bucket_index, bucket_i);
+ table_with_length::add(&mut v.buckets, j_bucket_index, bucket_j);
+}
+
+
+
+
+public fun reverse<T>(v: &mut big_vector::BigVector<T>)
+
+
+
+
+public fun reverse<T>(v: &mut BigVector<T>) {
+ let new_buckets = vector[];
+ let push_bucket = vector[];
+ let num_buckets = table_with_length::length(&v.buckets);
+ let num_buckets_left = num_buckets;
+
+ while (num_buckets_left > 0) {
+ let pop_bucket = table_with_length::remove(&mut v.buckets, num_buckets_left - 1);
+ vector::for_each_reverse(pop_bucket, |val| {
+ vector::push_back(&mut push_bucket, val);
+ if (vector::length(&push_bucket) == v.bucket_size) {
+ vector::push_back(&mut new_buckets, push_bucket);
+ push_bucket = vector[];
+ };
+ });
+ num_buckets_left = num_buckets_left - 1;
+ };
+
+ if (vector::length(&push_bucket) > 0) {
+ vector::push_back(&mut new_buckets, push_bucket);
+ } else {
+ vector::destroy_empty(push_bucket);
+ };
+
+ vector::reverse(&mut new_buckets);
+ let i = 0;
+ assert!(table_with_length::length(&v.buckets) == 0, 0);
+ while (i < num_buckets) {
+ table_with_length::add(&mut v.buckets, i, vector::pop_back(&mut new_buckets));
+ i = i + 1;
+ };
+ vector::destroy_empty(new_buckets);
+}
+
+
+
+
+public fun index_of<T>(v: &big_vector::BigVector<T>, val: &T): (bool, u64)
+
+
+
+
+public fun index_of<T>(v: &BigVector<T>, val: &T): (bool, u64) {
+ let num_buckets = table_with_length::length(&v.buckets);
+ let bucket_index = 0;
+ while (bucket_index < num_buckets) {
+ let cur = table_with_length::borrow(&v.buckets, bucket_index);
+ let (found, i) = vector::index_of(cur, val);
+ if (found) {
+ return (true, bucket_index * v.bucket_size + i)
+ };
+ bucket_index = bucket_index + 1;
+ };
+ (false, 0)
+}
+
+
+
+
+public fun contains<T>(v: &big_vector::BigVector<T>, val: &T): bool
+
+
+
+
+public fun contains<T>(v: &BigVector<T>, val: &T): bool {
+ if (is_empty(v)) return false;
+ let (exist, _) = index_of(v, val);
+ exist
+}
+
+
+
+
+public fun to_vector<T: copy>(v: &big_vector::BigVector<T>): vector<T>
+
+
+
+
+public fun to_vector<T: copy>(v: &BigVector<T>): vector<T> {
+ let res = vector[];
+ let num_buckets = table_with_length::length(&v.buckets);
+ let i = 0;
+ while (i < num_buckets) {
+ vector::append(&mut res, *table_with_length::borrow(&v.buckets, i));
+ i = i + 1;
+ };
+ res
+}
+
+
+
+
+public fun length<T>(v: &big_vector::BigVector<T>): u64
+
+
+
+
+public fun length<T>(v: &BigVector<T>): u64 {
+ v.end_index
+}
+
+
+
+
+true
if the vector v
has no elements and false
otherwise.
+
+
+public fun is_empty<T>(v: &big_vector::BigVector<T>): bool
+
+
+
+
+public fun is_empty<T>(v: &BigVector<T>): bool {
+ length(v) == 0
+}
+
+
+
+
+struct BigVector<T> has store
+
+
+
+
+buckets: table_with_length::TableWithLength<u64, vector<T>>
+end_index: u64
+bucket_size: u64
+invariant bucket_size != 0;
+invariant spec_table_len(buckets) == 0 ==> end_index == 0;
+invariant end_index == 0 ==> spec_table_len(buckets) == 0;
+invariant end_index <= spec_table_len(buckets) * bucket_size;
+invariant spec_table_len(buckets) == 0
+ || (forall i in 0..spec_table_len(buckets)-1: len(table_with_length::spec_get(buckets, i)) == bucket_size);
+invariant spec_table_len(buckets) == 0
+ || len(table_with_length::spec_get(buckets, spec_table_len(buckets) -1 )) <= bucket_size;
+invariant forall i in 0..spec_table_len(buckets): spec_table_contains(buckets, i);
+invariant spec_table_len(buckets) == (end_index + bucket_size - 1) / bucket_size;
+invariant (spec_table_len(buckets) == 0 && end_index == 0)
+ || (spec_table_len(buckets) != 0 && ((spec_table_len(buckets) - 1) * bucket_size) + (len(table_with_length::spec_get(buckets, spec_table_len(buckets) - 1))) == end_index);
+invariant forall i: u64 where i >= spec_table_len(buckets): {
+ !spec_table_contains(buckets, i)
+};
+invariant forall i: u64 where i < spec_table_len(buckets): {
+ spec_table_contains(buckets, i)
+};
+invariant spec_table_len(buckets) == 0
+ || (len(table_with_length::spec_get(buckets, spec_table_len(buckets) - 1)) > 0);
+
+
+
+
+
+
+### Function `empty`
+
+
+public(friend) fun empty<T: store>(bucket_size: u64): big_vector::BigVector<T>
+
+
+
+
+
+aborts_if bucket_size == 0;
+ensures length(result) == 0;
+ensures result.bucket_size == bucket_size;
+
+
+
+
+
+
+### Function `singleton`
+
+
+public(friend) fun singleton<T: store>(element: T, bucket_size: u64): big_vector::BigVector<T>
+
+
+
+
+
+aborts_if bucket_size == 0;
+ensures length(result) == 1;
+ensures result.bucket_size == bucket_size;
+
+
+
+
+
+
+### Function `destroy_empty`
+
+
+public fun destroy_empty<T>(v: big_vector::BigVector<T>)
+
+
+
+
+
+aborts_if !is_empty(v);
+
+
+
+
+
+
+### Function `borrow`
+
+
+public fun borrow<T>(v: &big_vector::BigVector<T>, i: u64): &T
+
+
+
+
+
+aborts_if i >= length(v);
+ensures result == spec_at(v, i);
+
+
+
+
+
+
+### Function `borrow_mut`
+
+
+public fun borrow_mut<T>(v: &mut big_vector::BigVector<T>, i: u64): &mut T
+
+
+
+
+
+aborts_if i >= length(v);
+ensures result == spec_at(v, i);
+
+
+
+
+
+
+### Function `append`
+
+
+public fun append<T: store>(lhs: &mut big_vector::BigVector<T>, other: big_vector::BigVector<T>)
+
+
+
+
+
+pragma verify=false;
+
+
+
+
+
+
+### Function `push_back`
+
+
+public fun push_back<T: store>(v: &mut big_vector::BigVector<T>, val: T)
+
+
+
+
+
+let num_buckets = spec_table_len(v.buckets);
+include PushbackAbortsIf<T>;
+ensures length(v) == length(old(v)) + 1;
+ensures v.end_index == old(v.end_index) + 1;
+ensures spec_at(v, v.end_index-1) == val;
+ensures forall i in 0..v.end_index-1: spec_at(v, i) == spec_at(old(v), i);
+ensures v.bucket_size == old(v).bucket_size;
+
+
+
+
+
+
+
+
+schema PushbackAbortsIf<T> {
+ v: BigVector<T>;
+ let num_buckets = spec_table_len(v.buckets);
+ aborts_if num_buckets * v.bucket_size > MAX_U64;
+ aborts_if v.end_index + 1 > MAX_U64;
+}
+
+
+
+
+
+
+### Function `pop_back`
+
+
+public fun pop_back<T>(v: &mut big_vector::BigVector<T>): T
+
+
+
+
+
+aborts_if is_empty(v);
+ensures length(v) == length(old(v)) - 1;
+ensures result == old(spec_at(v, v.end_index-1));
+ensures forall i in 0..v.end_index: spec_at(v, i) == spec_at(old(v), i);
+
+
+
+
+
+
+### Function `remove`
+
+
+public fun remove<T>(v: &mut big_vector::BigVector<T>, i: u64): T
+
+
+
+
+
+pragma verify=false;
+
+
+
+
+
+
+### Function `swap_remove`
+
+
+public fun swap_remove<T>(v: &mut big_vector::BigVector<T>, i: u64): T
+
+
+
+
+
+pragma verify_duration_estimate = 120;
+aborts_if i >= length(v);
+ensures length(v) == length(old(v)) - 1;
+ensures result == spec_at(old(v), i);
+
+
+
+
+
+
+### Function `swap`
+
+
+public fun swap<T>(v: &mut big_vector::BigVector<T>, i: u64, j: u64)
+
+
+
+
+
+pragma verify_duration_estimate = 1000;
+aborts_if i >= length(v) || j >= length(v);
+ensures length(v) == length(old(v));
+ensures spec_at(v, i) == spec_at(old(v), j);
+ensures spec_at(v, j) == spec_at(old(v), i);
+ensures forall idx in 0..length(v)
+ where idx != i && idx != j:
+ spec_at(v, idx) == spec_at(old(v), idx);
+
+
+
+
+
+
+### Function `reverse`
+
+
+public fun reverse<T>(v: &mut big_vector::BigVector<T>)
+
+
+
+
+
+pragma verify=false;
+
+
+
+
+
+
+### Function `index_of`
+
+
+public fun index_of<T>(v: &big_vector::BigVector<T>, val: &T): (bool, u64)
+
+
+
+
+
+pragma verify=false;
+
+
+
+
+
+
+
+
+fun spec_table_len<K, V>(t: TableWithLength<K, V>): u64 {
+ table_with_length::spec_len(t)
+}
+
+
+
+
+
+
+
+
+fun spec_table_contains<K, V>(t: TableWithLength<K, V>, k: K): bool {
+ table_with_length::spec_contains(t, k)
+}
+
+
+
+
+
+
+
+
+fun spec_at<T>(v: BigVector<T>, i: u64): T {
+ let bucket = i / v.bucket_size;
+ let idx = i % v.bucket_size;
+ let v = table_with_length::spec_get(v.buckets, bucket);
+ v[idx]
+}
+
+
+
+[move-book]: https://aptos.dev/move/book/SUMMARY
diff --git a/aptos-move/framework/aptos-stdlib/tests/compiler-v2-doc/bls12381.md b/aptos-move/framework/aptos-stdlib/tests/compiler-v2-doc/bls12381.md
new file mode 100644
index 0000000000000..edab19cccb525
--- /dev/null
+++ b/aptos-move/framework/aptos-stdlib/tests/compiler-v2-doc/bls12381.md
@@ -0,0 +1,1661 @@
+
+
+
+# Module `0x1::bls12381`
+
+Contains functions for:
+
+The minimum-pubkey-size variant of [Boneh-Lynn-Shacham (BLS) signatures](https://en.wikipedia.org/wiki/BLS_digital_signature),
+where public keys are BLS12-381 elliptic-curve points in $\mathbb{G}_1$ and signatures are in $\mathbb{G}_2$,
+as per the [IETF BLS draft standard](https://datatracker.ietf.org/doc/html/draft-irtf-cfrg-bls-signature#section-2.1).
+
+
+- [Struct `PublicKey`](#0x1_bls12381_PublicKey)
+- [Struct `ProofOfPossession`](#0x1_bls12381_ProofOfPossession)
+- [Struct `PublicKeyWithPoP`](#0x1_bls12381_PublicKeyWithPoP)
+- [Struct `AggrPublicKeysWithPoP`](#0x1_bls12381_AggrPublicKeysWithPoP)
+- [Struct `Signature`](#0x1_bls12381_Signature)
+- [Struct `AggrOrMultiSignature`](#0x1_bls12381_AggrOrMultiSignature)
+- [Constants](#@Constants_0)
+- [Function `public_key_from_bytes`](#0x1_bls12381_public_key_from_bytes)
+- [Function `public_key_to_bytes`](#0x1_bls12381_public_key_to_bytes)
+- [Function `proof_of_possession_from_bytes`](#0x1_bls12381_proof_of_possession_from_bytes)
+- [Function `proof_of_possession_to_bytes`](#0x1_bls12381_proof_of_possession_to_bytes)
+- [Function `public_key_from_bytes_with_pop`](#0x1_bls12381_public_key_from_bytes_with_pop)
+- [Function `public_key_with_pop_to_normal`](#0x1_bls12381_public_key_with_pop_to_normal)
+- [Function `public_key_with_pop_to_bytes`](#0x1_bls12381_public_key_with_pop_to_bytes)
+- [Function `signature_from_bytes`](#0x1_bls12381_signature_from_bytes)
+- [Function `signature_to_bytes`](#0x1_bls12381_signature_to_bytes)
+- [Function `signature_subgroup_check`](#0x1_bls12381_signature_subgroup_check)
+- [Function `aggregate_pubkeys`](#0x1_bls12381_aggregate_pubkeys)
+- [Function `aggregate_pubkey_to_bytes`](#0x1_bls12381_aggregate_pubkey_to_bytes)
+- [Function `aggregate_signatures`](#0x1_bls12381_aggregate_signatures)
+- [Function `aggr_or_multi_signature_to_bytes`](#0x1_bls12381_aggr_or_multi_signature_to_bytes)
+- [Function `aggr_or_multi_signature_from_bytes`](#0x1_bls12381_aggr_or_multi_signature_from_bytes)
+- [Function `aggr_or_multi_signature_subgroup_check`](#0x1_bls12381_aggr_or_multi_signature_subgroup_check)
+- [Function `verify_aggregate_signature`](#0x1_bls12381_verify_aggregate_signature)
+- [Function `verify_multisignature`](#0x1_bls12381_verify_multisignature)
+- [Function `verify_normal_signature`](#0x1_bls12381_verify_normal_signature)
+- [Function `verify_signature_share`](#0x1_bls12381_verify_signature_share)
+- [Function `aggregate_pubkeys_internal`](#0x1_bls12381_aggregate_pubkeys_internal)
+- [Function `aggregate_signatures_internal`](#0x1_bls12381_aggregate_signatures_internal)
+- [Function `validate_pubkey_internal`](#0x1_bls12381_validate_pubkey_internal)
+- [Function `signature_subgroup_check_internal`](#0x1_bls12381_signature_subgroup_check_internal)
+- [Function `verify_aggregate_signature_internal`](#0x1_bls12381_verify_aggregate_signature_internal)
+- [Function `verify_multisignature_internal`](#0x1_bls12381_verify_multisignature_internal)
+- [Function `verify_normal_signature_internal`](#0x1_bls12381_verify_normal_signature_internal)
+- [Function `verify_proof_of_possession_internal`](#0x1_bls12381_verify_proof_of_possession_internal)
+- [Function `verify_signature_share_internal`](#0x1_bls12381_verify_signature_share_internal)
+- [Specification](#@Specification_1)
+ - [Function `public_key_from_bytes`](#@Specification_1_public_key_from_bytes)
+ - [Function `public_key_from_bytes_with_pop`](#@Specification_1_public_key_from_bytes_with_pop)
+ - [Function `aggregate_pubkeys`](#@Specification_1_aggregate_pubkeys)
+ - [Function `aggregate_signatures`](#@Specification_1_aggregate_signatures)
+ - [Function `aggr_or_multi_signature_from_bytes`](#@Specification_1_aggr_or_multi_signature_from_bytes)
+ - [Function `aggr_or_multi_signature_subgroup_check`](#@Specification_1_aggr_or_multi_signature_subgroup_check)
+ - [Function `verify_aggregate_signature`](#@Specification_1_verify_aggregate_signature)
+ - [Function `verify_multisignature`](#@Specification_1_verify_multisignature)
+ - [Function `verify_normal_signature`](#@Specification_1_verify_normal_signature)
+ - [Function `verify_signature_share`](#@Specification_1_verify_signature_share)
+ - [Function `aggregate_pubkeys_internal`](#@Specification_1_aggregate_pubkeys_internal)
+ - [Function `aggregate_signatures_internal`](#@Specification_1_aggregate_signatures_internal)
+ - [Function `validate_pubkey_internal`](#@Specification_1_validate_pubkey_internal)
+ - [Function `signature_subgroup_check_internal`](#@Specification_1_signature_subgroup_check_internal)
+ - [Function `verify_aggregate_signature_internal`](#@Specification_1_verify_aggregate_signature_internal)
+ - [Function `verify_multisignature_internal`](#@Specification_1_verify_multisignature_internal)
+ - [Function `verify_normal_signature_internal`](#@Specification_1_verify_normal_signature_internal)
+ - [Function `verify_proof_of_possession_internal`](#@Specification_1_verify_proof_of_possession_internal)
+ - [Function `verify_signature_share_internal`](#@Specification_1_verify_signature_share_internal)
+ - [Helper functions](#@Helper_functions_2)
+
+
+use 0x1::error;
+use 0x1::option;
+
+
+
+
+
+
+## Struct `PublicKey`
+
+A *validated* public key that:
+(1) is a point in the prime-order subgroup of the BLS12-381 elliptic curve, and
+(2) is not the identity point
+
+This struct can be used to verify a normal (non-aggregated) signature.
+
+This struct can be combined with a ProofOfPossession struct in order to create a PublicKeyWithPop struct, which
+can be used to verify a multisignature.
+
+
+struct PublicKey has copy, drop, store
+
+
+
+
+bytes: vector<u8>
+struct ProofOfPossession has copy, drop, store
+
+
+
+
+bytes: vector<u8>
+struct PublicKeyWithPoP has copy, drop, store
+
+
+
+
+bytes: vector<u8>
+struct AggrPublicKeysWithPoP has copy, drop, store
+
+
+
+
+bytes: vector<u8>
+struct Signature has copy, drop, store
+
+
+
+
+bytes: vector<u8>
+verify_multisignature
and verify_signature_share
to verify both multisignatures and signature shares,
+which could create problems down the line.
+
+
+struct AggrOrMultiSignature has copy, drop, store
+
+
+
+
+bytes: vector<u8>
+const EWRONG_SIZE: u64 = 2;
+
+
+
+
+
+
+The caller was supposed to input one or more public keys.
+
+
+const EZERO_PUBKEYS: u64 = 1;
+
+
+
+
+
+
+The number of signers does not match the number of messages to be signed.
+
+
+const E_NUM_SIGNERS_MUST_EQ_NUM_MESSAGES: u64 = 3;
+
+
+
+
+
+
+The public key size, in bytes
+
+
+const PUBLIC_KEY_NUM_BYTES: u64 = 48;
+
+
+
+
+
+
+Random signature generated by running cargo test -- bls12381_sample_signature --nocapture --include-ignored
in crates/aptos-crypto
.
+The associated SK is 07416693b6b32c84abe45578728e2379f525729e5b94762435a31e65ecc728da.
+
+
+const RANDOM_PK: vector<u8> = [138, 83, 231, 174, 82, 112, 227, 231, 101, 205, 138, 64, 50, 194, 231, 124, 111, 126, 135, 164, 78, 187, 133, 191, 40, 164, 215, 134, 85, 101, 105, 143, 151, 83, 70, 113, 66, 98, 249, 228, 124, 111, 62, 13, 93, 149, 22, 96];
+
+
+
+
+
+
+Random signature generated by running cargo test -- bls12381_sample_signature --nocapture --include-ignored
in crates/aptos-crypto
.
+The message signed is "Hello Aptos!" and the associated SK is 07416693b6b32c84abe45578728e2379f525729e5b94762435a31e65ecc728da.
+
+
+const RANDOM_SIGNATURE: vector<u8> = [160, 26, 101, 133, 79, 152, 125, 52, 52, 20, 155, 127, 8, 247, 7, 48, 227, 11, 36, 25, 132, 232, 113, 43, 194, 172, 168, 133, 214, 50, 170, 252, 237, 76, 63, 102, 18, 9, 222, 187, 107, 28, 134, 1, 50, 102, 35, 204, 22, 202, 47, 108, 158, 220, 83, 183, 184, 139, 116, 53, 251, 107, 5, 221, 236, 228, 24, 210, 195, 77, 198, 172, 162, 245, 161, 26, 121, 230, 119, 116, 88, 44, 20, 8, 74, 1, 220, 183, 130, 14, 76, 180, 186, 208, 234, 141];
+
+
+
+
+
+
+The signature size, in bytes
+
+
+const SIGNATURE_SIZE: u64 = 96;
+
+
+
+
+
+
+## Function `public_key_from_bytes`
+
+Creates a new public key from a sequence of bytes.
+
+
+public fun public_key_from_bytes(bytes: vector<u8>): option::Option<bls12381::PublicKey>
+
+
+
+
+public fun public_key_from_bytes(bytes: vector<u8>): Option<PublicKey> {
+ if (validate_pubkey_internal(bytes)) {
+ option::some(PublicKey {
+ bytes
+ })
+ } else {
+ option::none<PublicKey>()
+ }
+}
+
+
+
+
+public fun public_key_to_bytes(pk: &bls12381::PublicKey): vector<u8>
+
+
+
+
+public fun public_key_to_bytes(pk: &PublicKey): vector<u8> {
+ pk.bytes
+}
+
+
+
+
+public fun proof_of_possession_from_bytes(bytes: vector<u8>): bls12381::ProofOfPossession
+
+
+
+
+public fun proof_of_possession_from_bytes(bytes: vector<u8>): ProofOfPossession {
+ ProofOfPossession {
+ bytes
+ }
+}
+
+
+
+
+public fun proof_of_possession_to_bytes(pop: &bls12381::ProofOfPossession): vector<u8>
+
+
+
+
+public fun proof_of_possession_to_bytes(pop: &ProofOfPossession): vector<u8> {
+ pop.bytes
+}
+
+
+
+
+public fun public_key_from_bytes_with_pop(pk_bytes: vector<u8>, pop: &bls12381::ProofOfPossession): option::Option<bls12381::PublicKeyWithPoP>
+
+
+
+
+public fun public_key_from_bytes_with_pop(pk_bytes: vector<u8>, pop: &ProofOfPossession): Option<PublicKeyWithPoP> {
+ if (verify_proof_of_possession_internal(pk_bytes, pop.bytes)) {
+ option::some(PublicKeyWithPoP {
+ bytes: pk_bytes
+ })
+ } else {
+ option::none<PublicKeyWithPoP>()
+ }
+}
+
+
+
+
+public fun public_key_with_pop_to_normal(pkpop: &bls12381::PublicKeyWithPoP): bls12381::PublicKey
+
+
+
+
+public fun public_key_with_pop_to_normal(pkpop: &PublicKeyWithPoP): PublicKey {
+ PublicKey {
+ bytes: pkpop.bytes
+ }
+}
+
+
+
+
+public fun public_key_with_pop_to_bytes(pk: &bls12381::PublicKeyWithPoP): vector<u8>
+
+
+
+
+public fun public_key_with_pop_to_bytes(pk: &PublicKeyWithPoP): vector<u8> {
+ pk.bytes
+}
+
+
+
+
+public fun signature_from_bytes(bytes: vector<u8>): bls12381::Signature
+
+
+
+
+public fun signature_from_bytes(bytes: vector<u8>): Signature {
+ Signature {
+ bytes
+ }
+}
+
+
+
+
+public fun signature_to_bytes(sig: &bls12381::Signature): vector<u8>
+
+
+
+
+public fun signature_to_bytes(sig: &Signature): vector<u8> {
+ sig.bytes
+}
+
+
+
+
+public fun signature_subgroup_check(signature: &bls12381::Signature): bool
+
+
+
+
+public fun signature_subgroup_check(signature: &Signature): bool {
+ signature_subgroup_check_internal(signature.bytes)
+}
+
+
+
+
+verify_multisignature
and aggregate signatures using verify_aggregate_signature
.
+Aborts if no public keys are given as input.
+
+
+public fun aggregate_pubkeys(public_keys: vector<bls12381::PublicKeyWithPoP>): bls12381::AggrPublicKeysWithPoP
+
+
+
+
+public fun aggregate_pubkeys(public_keys: vector<PublicKeyWithPoP>): AggrPublicKeysWithPoP {
+ let (bytes, success) = aggregate_pubkeys_internal(public_keys);
+ assert!(success, std::error::invalid_argument(EZERO_PUBKEYS));
+
+ AggrPublicKeysWithPoP {
+ bytes
+ }
+}
+
+
+
+
+public fun aggregate_pubkey_to_bytes(apk: &bls12381::AggrPublicKeysWithPoP): vector<u8>
+
+
+
+
+public fun aggregate_pubkey_to_bytes(apk: &AggrPublicKeysWithPoP): vector<u8> {
+ apk.bytes
+}
+
+
+
+
+verify_aggregate_signature
or verify_multisignature
. Returns None
if zero signatures are given as input
+or if some of the signatures are not valid group elements.
+
+
+public fun aggregate_signatures(signatures: vector<bls12381::Signature>): option::Option<bls12381::AggrOrMultiSignature>
+
+
+
+
+public fun aggregate_signatures(signatures: vector<Signature>): Option<AggrOrMultiSignature> {
+ let (bytes, success) = aggregate_signatures_internal(signatures);
+ if (success) {
+ option::some(
+ AggrOrMultiSignature {
+ bytes
+ }
+ )
+ } else {
+ option::none<AggrOrMultiSignature>()
+ }
+}
+
+
+
+
+public fun aggr_or_multi_signature_to_bytes(sig: &bls12381::AggrOrMultiSignature): vector<u8>
+
+
+
+
+public fun aggr_or_multi_signature_to_bytes(sig: &AggrOrMultiSignature): vector<u8> {
+ sig.bytes
+}
+
+
+
+
+public fun aggr_or_multi_signature_from_bytes(bytes: vector<u8>): bls12381::AggrOrMultiSignature
+
+
+
+
+public fun aggr_or_multi_signature_from_bytes(bytes: vector<u8>): AggrOrMultiSignature {
+ assert!(std::vector::length(&bytes) == SIGNATURE_SIZE, std::error::invalid_argument(EWRONG_SIZE));
+
+ AggrOrMultiSignature {
+ bytes
+ }
+}
+
+
+
+
+public fun aggr_or_multi_signature_subgroup_check(signature: &bls12381::AggrOrMultiSignature): bool
+
+
+
+
+public fun aggr_or_multi_signature_subgroup_check(signature: &AggrOrMultiSignature): bool {
+ signature_subgroup_check_internal(signature.bytes)
+}
+
+
+
+
+s_i
, each on a different message m_i
.
+
+
+public fun verify_aggregate_signature(aggr_sig: &bls12381::AggrOrMultiSignature, public_keys: vector<bls12381::PublicKeyWithPoP>, messages: vector<vector<u8>>): bool
+
+
+
+
+public fun verify_aggregate_signature(
+ aggr_sig: &AggrOrMultiSignature,
+ public_keys: vector<PublicKeyWithPoP>,
+ messages: vector<vector<u8>>,
+): bool {
+ verify_aggregate_signature_internal(aggr_sig.bytes, public_keys, messages)
+}
+
+
+
+
+m
.
+
+
+public fun verify_multisignature(multisig: &bls12381::AggrOrMultiSignature, aggr_public_key: &bls12381::AggrPublicKeysWithPoP, message: vector<u8>): bool
+
+
+
+
+public fun verify_multisignature(
+ multisig: &AggrOrMultiSignature,
+ aggr_public_key: &AggrPublicKeysWithPoP,
+ message: vector<u8>
+): bool {
+ verify_multisignature_internal(multisig.bytes, aggr_public_key.bytes, message)
+}
+
+
+
+
+public fun verify_normal_signature(signature: &bls12381::Signature, public_key: &bls12381::PublicKey, message: vector<u8>): bool
+
+
+
+
+public fun verify_normal_signature(
+ signature: &Signature,
+ public_key: &PublicKey,
+ message: vector<u8>
+): bool {
+ verify_normal_signature_internal(signature.bytes, public_key.bytes, message)
+}
+
+
+
+
+public fun verify_signature_share(signature_share: &bls12381::Signature, public_key: &bls12381::PublicKeyWithPoP, message: vector<u8>): bool
+
+
+
+
+public fun verify_signature_share(
+ signature_share: &Signature,
+ public_key: &PublicKeyWithPoP,
+ message: vector<u8>
+): bool {
+ verify_signature_share_internal(signature_share.bytes, public_key.bytes, message)
+}
+
+
+
+
+verify_proof_of_possession
.
+
+Given a vector of serialized public keys, combines them into an aggregated public key, returning (bytes, true)
,
+where bytes
store the serialized public key.
+Aborts if no public keys are given as input.
+
+
+fun aggregate_pubkeys_internal(public_keys: vector<bls12381::PublicKeyWithPoP>): (vector<u8>, bool)
+
+
+
+
+native fun aggregate_pubkeys_internal(public_keys: vector<PublicKeyWithPoP>): (vector<u8>, bool);
+
+
+
+
+(bytes, true)
,
+where bytes
store the serialized signature.
+Does not check the input signatures nor the final aggregated signatures for prime-order subgroup membership.
+Returns (_, false)
if no signatures are given as input.
+Does not abort.
+
+
+fun aggregate_signatures_internal(signatures: vector<bls12381::Signature>): (vector<u8>, bool)
+
+
+
+
+native fun aggregate_signatures_internal(signatures: vector<Signature>): (vector<u8>, bool);
+
+
+
+
+true
if the bytes in public_key
are a valid BLS12-381 public key:
+(1) it is NOT the identity point, and
+(2) it is a BLS12-381 elliptic curve point, and
+(3) it is a prime-order point
+Return false
otherwise.
+Does not abort.
+
+
+fun validate_pubkey_internal(public_key: vector<u8>): bool
+
+
+
+
+native fun validate_pubkey_internal(public_key: vector<u8>): bool;
+
+
+
+
+true
if the elliptic curve point serialized in signature
:
+(1) is NOT the identity point, and
+(2) is a BLS12-381 elliptic curve point, and
+(3) is a prime-order point
+Return false
otherwise.
+Does not abort.
+
+
+fun signature_subgroup_check_internal(signature: vector<u8>): bool
+
+
+
+
+native fun signature_subgroup_check_internal(signature: vector<u8>): bool;
+
+
+
+
+true
if the aggregate signature aggsig
on messages
under public_keys
verifies (where messages[i]
+should be signed by public_keys[i]
).
+
+Returns false
if either:
+- no public keys or messages are given as input,
+- number of messages does not equal number of public keys
+- aggsig
(1) is the identity point, or (2) is NOT a BLS12-381 elliptic curve point, or (3) is NOT a
+prime-order point
+Does not abort.
+
+
+fun verify_aggregate_signature_internal(aggsig: vector<u8>, public_keys: vector<bls12381::PublicKeyWithPoP>, messages: vector<vector<u8>>): bool
+
+
+
+
+native fun verify_aggregate_signature_internal(
+ aggsig: vector<u8>,
+ public_keys: vector<PublicKeyWithPoP>,
+ messages: vector<vector<u8>>,
+): bool;
+
+
+
+
+true
if the BLS multisignature
on message
verifies against the BLS aggregate public key agg_public_key
.
+Returns false
otherwise.
+Does not abort.
+
+
+fun verify_multisignature_internal(multisignature: vector<u8>, agg_public_key: vector<u8>, message: vector<u8>): bool
+
+
+
+
+native fun verify_multisignature_internal(
+ multisignature: vector<u8>,
+ agg_public_key: vector<u8>,
+ message: vector<u8>
+): bool;
+
+
+
+
+true
if the signature
on message
verifies under public key
.
+Returns false
otherwise.
+Does not abort.
+
+
+fun verify_normal_signature_internal(signature: vector<u8>, public_key: vector<u8>, message: vector<u8>): bool
+
+
+
+
+native fun verify_normal_signature_internal(
+ signature: vector<u8>,
+ public_key: vector<u8>,
+ message: vector<u8>
+): bool;
+
+
+
+
+true
if the bytes in public_key
are a valid bls12381 public key (as per validate_pubkey
)
+*and* this public key has a valid proof-of-possesion (PoP).
+Return false
otherwise.
+Does not abort.
+
+
+fun verify_proof_of_possession_internal(public_key: vector<u8>, proof_of_possesion: vector<u8>): bool
+
+
+
+
+native fun verify_proof_of_possession_internal(
+ public_key: vector<u8>,
+ proof_of_possesion: vector<u8>
+): bool;
+
+
+
+
+true
if the signature_share
on message
verifies under public key
.
+Returns false
otherwise, similar to verify_multisignature
.
+Does not abort.
+
+
+fun verify_signature_share_internal(signature_share: vector<u8>, public_key: vector<u8>, message: vector<u8>): bool
+
+
+
+
+native fun verify_signature_share_internal(
+ signature_share: vector<u8>,
+ public_key: vector<u8>,
+ message: vector<u8>
+): bool;
+
+
+
+
+public fun public_key_from_bytes(bytes: vector<u8>): option::Option<bls12381::PublicKey>
+
+
+
+
+
+aborts_if false;
+ensures spec_validate_pubkey_internal(bytes) ==> (std::option::spec_is_some(result) && std::option::spec_borrow(result).bytes == bytes);
+ensures !spec_validate_pubkey_internal(bytes) ==> std::option::spec_is_none(result);
+
+
+
+
+
+
+### Function `public_key_from_bytes_with_pop`
+
+
+public fun public_key_from_bytes_with_pop(pk_bytes: vector<u8>, pop: &bls12381::ProofOfPossession): option::Option<bls12381::PublicKeyWithPoP>
+
+
+
+
+
+pragma opaque;
+aborts_if false;
+ensures spec_verify_proof_of_possession_internal(pk_bytes, pop.bytes) ==> (std::option::spec_is_some(result) && std::option::spec_borrow(result).bytes == pk_bytes);
+ensures !spec_verify_proof_of_possession_internal(pk_bytes, pop.bytes) ==> std::option::spec_is_none(result);
+ensures [abstract] result == spec_public_key_from_bytes_with_pop(pk_bytes, pop);
+
+
+
+
+
+
+### Function `aggregate_pubkeys`
+
+
+public fun aggregate_pubkeys(public_keys: vector<bls12381::PublicKeyWithPoP>): bls12381::AggrPublicKeysWithPoP
+
+
+
+
+
+let bytes = spec_aggregate_pubkeys_internal_1(public_keys);
+let success = spec_aggregate_pubkeys_internal_2(public_keys);
+aborts_if !success;
+ensures result.bytes == bytes;
+
+
+
+
+
+
+### Function `aggregate_signatures`
+
+
+public fun aggregate_signatures(signatures: vector<bls12381::Signature>): option::Option<bls12381::AggrOrMultiSignature>
+
+
+
+
+
+aborts_if false;
+let bytes = spec_aggregate_signatures_internal_1(signatures);
+let success = spec_aggregate_signatures_internal_2(signatures);
+ensures success ==> (std::option::spec_is_some(result) && std::option::spec_borrow(result).bytes == bytes);
+ensures !success ==> std::option::spec_is_none(result);
+
+
+
+
+
+
+### Function `aggr_or_multi_signature_from_bytes`
+
+
+public fun aggr_or_multi_signature_from_bytes(bytes: vector<u8>): bls12381::AggrOrMultiSignature
+
+
+
+
+
+aborts_if len(bytes) != SIGNATURE_SIZE;
+ensures result.bytes == bytes;
+
+
+
+
+
+
+### Function `aggr_or_multi_signature_subgroup_check`
+
+
+public fun aggr_or_multi_signature_subgroup_check(signature: &bls12381::AggrOrMultiSignature): bool
+
+
+
+
+
+aborts_if false;
+ensures result == spec_signature_subgroup_check_internal(signature.bytes);
+
+
+
+
+
+
+### Function `verify_aggregate_signature`
+
+
+public fun verify_aggregate_signature(aggr_sig: &bls12381::AggrOrMultiSignature, public_keys: vector<bls12381::PublicKeyWithPoP>, messages: vector<vector<u8>>): bool
+
+
+
+
+
+aborts_if false;
+ensures result == spec_verify_aggregate_signature_internal(aggr_sig.bytes, public_keys, messages);
+
+
+
+
+
+
+### Function `verify_multisignature`
+
+
+public fun verify_multisignature(multisig: &bls12381::AggrOrMultiSignature, aggr_public_key: &bls12381::AggrPublicKeysWithPoP, message: vector<u8>): bool
+
+
+
+
+
+aborts_if false;
+ensures result == spec_verify_multisignature_internal(multisig.bytes, aggr_public_key.bytes, message);
+
+
+
+
+
+
+### Function `verify_normal_signature`
+
+
+public fun verify_normal_signature(signature: &bls12381::Signature, public_key: &bls12381::PublicKey, message: vector<u8>): bool
+
+
+
+
+
+aborts_if false;
+ensures result == spec_verify_normal_signature_internal(signature.bytes, public_key.bytes, message);
+
+
+
+
+
+
+### Function `verify_signature_share`
+
+
+public fun verify_signature_share(signature_share: &bls12381::Signature, public_key: &bls12381::PublicKeyWithPoP, message: vector<u8>): bool
+
+
+
+
+
+aborts_if false;
+ensures result == spec_verify_signature_share_internal(signature_share.bytes, public_key.bytes, message);
+
+
+
+
+
+
+### Function `aggregate_pubkeys_internal`
+
+
+fun aggregate_pubkeys_internal(public_keys: vector<bls12381::PublicKeyWithPoP>): (vector<u8>, bool)
+
+
+
+
+
+pragma opaque;
+aborts_if [abstract] false;
+ensures result_1 == spec_aggregate_pubkeys_internal_1(public_keys);
+ensures result_2 == spec_aggregate_pubkeys_internal_2(public_keys);
+
+
+
+
+
+
+### Function `aggregate_signatures_internal`
+
+
+fun aggregate_signatures_internal(signatures: vector<bls12381::Signature>): (vector<u8>, bool)
+
+
+
+
+
+pragma opaque;
+aborts_if [abstract] false;
+ensures result_1 == spec_aggregate_signatures_internal_1(signatures);
+ensures result_2 == spec_aggregate_signatures_internal_2(signatures);
+
+
+
+
+
+
+### Function `validate_pubkey_internal`
+
+
+fun validate_pubkey_internal(public_key: vector<u8>): bool
+
+
+
+
+
+pragma opaque;
+aborts_if [abstract] false;
+ensures result == spec_validate_pubkey_internal(public_key);
+
+
+
+
+
+
+### Function `signature_subgroup_check_internal`
+
+
+fun signature_subgroup_check_internal(signature: vector<u8>): bool
+
+
+
+
+
+pragma opaque;
+aborts_if [abstract] false;
+ensures result == spec_signature_subgroup_check_internal(signature);
+
+
+
+
+
+
+### Function `verify_aggregate_signature_internal`
+
+
+fun verify_aggregate_signature_internal(aggsig: vector<u8>, public_keys: vector<bls12381::PublicKeyWithPoP>, messages: vector<vector<u8>>): bool
+
+
+
+
+
+pragma opaque;
+aborts_if [abstract] false;
+ensures result == spec_verify_aggregate_signature_internal(aggsig, public_keys, messages);
+
+
+
+
+
+
+### Function `verify_multisignature_internal`
+
+
+fun verify_multisignature_internal(multisignature: vector<u8>, agg_public_key: vector<u8>, message: vector<u8>): bool
+
+
+
+
+
+pragma opaque;
+aborts_if [abstract] false;
+ensures result == spec_verify_multisignature_internal(multisignature, agg_public_key, message);
+
+
+
+
+
+
+### Function `verify_normal_signature_internal`
+
+
+fun verify_normal_signature_internal(signature: vector<u8>, public_key: vector<u8>, message: vector<u8>): bool
+
+
+
+
+
+pragma opaque;
+aborts_if [abstract] false;
+ensures result == spec_verify_normal_signature_internal(signature, public_key, message);
+
+
+
+
+
+
+### Function `verify_proof_of_possession_internal`
+
+
+fun verify_proof_of_possession_internal(public_key: vector<u8>, proof_of_possesion: vector<u8>): bool
+
+
+
+
+
+pragma opaque;
+aborts_if [abstract] false;
+ensures result == spec_verify_proof_of_possession_internal(public_key, proof_of_possesion);
+
+
+
+
+
+
+### Function `verify_signature_share_internal`
+
+
+fun verify_signature_share_internal(signature_share: vector<u8>, public_key: vector<u8>, message: vector<u8>): bool
+
+
+
+
+
+pragma opaque;
+aborts_if [abstract] false;
+ensures result == spec_verify_signature_share_internal(signature_share, public_key, message);
+
+
+
+
+
+
+### Helper functions
+
+
+
+
+
+
+fun spec_aggregate_pubkeys_internal_1(public_keys: vector<PublicKeyWithPoP>): vector<u8>;
+
+
+
+
+
+
+
+
+fun spec_public_key_from_bytes_with_pop(pk_bytes: vector<u8>, pop: ProofOfPossession): Option<PublicKeyWithPoP>;
+
+
+
+
+
+
+
+
+fun spec_aggregate_pubkeys_internal_2(public_keys: vector<PublicKeyWithPoP>): bool;
+
+
+
+
+
+
+
+
+fun spec_aggregate_signatures_internal_1(signatures: vector<Signature>): vector<u8>;
+
+
+
+
+
+
+
+
+fun spec_aggregate_signatures_internal_2(signatures: vector<Signature>): bool;
+
+
+
+
+
+
+
+
+fun spec_validate_pubkey_internal(public_key: vector<u8>): bool;
+
+
+
+
+
+
+
+
+fun spec_signature_subgroup_check_internal(signature: vector<u8>): bool;
+
+
+
+
+
+
+
+
+fun spec_verify_aggregate_signature_internal(
+ aggsig: vector<u8>,
+ public_keys: vector<PublicKeyWithPoP>,
+ messages: vector<vector<u8>>,
+): bool;
+
+
+
+
+
+
+
+
+fun spec_verify_multisignature_internal(
+ multisignature: vector<u8>,
+ agg_public_key: vector<u8>,
+ message: vector<u8>
+): bool;
+
+
+
+
+
+
+
+
+fun spec_verify_normal_signature_internal(
+ signature: vector<u8>,
+ public_key: vector<u8>,
+ message: vector<u8>
+): bool;
+
+
+
+
+
+
+
+
+fun spec_verify_proof_of_possession_internal(
+ public_key: vector<u8>,
+ proof_of_possesion: vector<u8>
+): bool;
+
+
+
+
+
+
+
+
+fun spec_verify_signature_share_internal(
+ signature_share: vector<u8>,
+ public_key: vector<u8>,
+ message: vector<u8>
+): bool;
+
+
+
+[move-book]: https://aptos.dev/move/book/SUMMARY
diff --git a/aptos-move/framework/aptos-stdlib/tests/compiler-v2-doc/bls12381_algebra.md b/aptos-move/framework/aptos-stdlib/tests/compiler-v2-doc/bls12381_algebra.md
new file mode 100644
index 0000000000000..9a6a39faec328
--- /dev/null
+++ b/aptos-move/framework/aptos-stdlib/tests/compiler-v2-doc/bls12381_algebra.md
@@ -0,0 +1,631 @@
+
+
+
+# Module `0x1::bls12381_algebra`
+
+This module defines marker types, constants and test cases for working with BLS12-381 curves
+using the generic API defined in algebra.move
.
+See https://datatracker.ietf.org/doc/html/draft-irtf-cfrg-pairing-friendly-curves-11#name-bls-curves-for-the-128-bit-
+for the full specification of BLS12-381 curves.
+
+Currently-supported BLS12-381 structures include Fq12
, Fr
, G1
, G2
and Gt
,
+along with their widely-used serialization formats,
+the pairing between G1
, G2
and Gt
,
+and the hash-to-curve operations for G1
and G2
defined in https://datatracker.ietf.org/doc/html/draft-irtf-cfrg-hash-to-curve-16.
+
+Other unimplemented BLS12-381 structures and serialization formats are also listed here,
+as they help define some of the currently supported structures.
+Their implementation may also be added in the future.
+
+Fq
: the finite field $F_q$ used in BLS12-381 curves with a prime order $q$ equal to
+0x1a0111ea397fe69a4b1ba7b6434bacd764774b84f38512bf6730d2a0f6b0f6241eabfffeb153ffffb9feffffffffaaab.
+
+FormatFqLsb
: a serialization format for Fq
elements,
+where an element is represented by a byte array b[]
of size 48 with the least significant byte (LSB) coming first.
+
+FormatFqMsb
: a serialization format for Fq
elements,
+where an element is represented by a byte array b[]
of size 48 with the most significant byte (MSB) coming first.
+
+Fq2
: the finite field $F_{q^2}$ used in BLS12-381 curves,
+which is an extension field of Fq
, constructed as $F_{q^2}=F_q[u]/(u^2+1)$.
+
+FormatFq2LscLsb
: a serialization format for Fq2
elements,
+where an element in the form $(c_0+c_1\cdot u)$ is represented by a byte array b[]
of size 96,
+which is a concatenation of its coefficients serialized, with the least significant coefficient (LSC) coming first:
+- b[0..48]
is $c_0$ serialized using FormatFqLsb
.
+- b[48..96]
is $c_1$ serialized using FormatFqLsb
.
+
+FormatFq2MscMsb
: a serialization format for Fq2
elements,
+where an element in the form $(c_0+c_1\cdot u)$ is represented by a byte array b[]
of size 96,
+which is a concatenation of its coefficients serialized, with the most significant coefficient (MSC) coming first:
+- b[0..48]
is $c_1$ serialized using FormatFqLsb
.
+- b[48..96]
is $c_0$ serialized using FormatFqLsb
.
+
+Fq6
: the finite field $F_{q^6}$ used in BLS12-381 curves,
+which is an extension field of Fq2
, constructed as $F_{q^6}=F_{q^2}[v]/(v^3-u-1)$.
+
+FormatFq6LscLsb
: a serialization scheme for Fq6
elements,
+where an element in the form $(c_0+c_1\cdot v+c_2\cdot v^2)$ is represented by a byte array b[]
of size 288,
+which is a concatenation of its coefficients serialized, with the least significant coefficient (LSC) coming first:
+- b[0..96]
is $c_0$ serialized using FormatFq2LscLsb
.
+- b[96..192]
is $c_1$ serialized using FormatFq2LscLsb
.
+- b[192..288]
is $c_2$ serialized using FormatFq2LscLsb
.
+
+G1Full
: a group constructed by the points on the BLS12-381 curve $E(F_q): y^2=x^3+4$ and the point at infinity,
+under the elliptic curve point addition.
+It contains the prime-order subgroup $G_1$ used in pairing.
+
+G2Full
: a group constructed by the points on a curve $E'(F_{q^2}): y^2=x^3+4(u+1)$ and the point at infinity,
+under the elliptic curve point addition.
+It contains the prime-order subgroup $G_2$ used in pairing.
+
+
+- [Struct `Fq12`](#0x1_bls12381_algebra_Fq12)
+- [Struct `FormatFq12LscLsb`](#0x1_bls12381_algebra_FormatFq12LscLsb)
+- [Struct `G1`](#0x1_bls12381_algebra_G1)
+- [Struct `FormatG1Uncompr`](#0x1_bls12381_algebra_FormatG1Uncompr)
+- [Struct `FormatG1Compr`](#0x1_bls12381_algebra_FormatG1Compr)
+- [Struct `G2`](#0x1_bls12381_algebra_G2)
+- [Struct `FormatG2Uncompr`](#0x1_bls12381_algebra_FormatG2Uncompr)
+- [Struct `FormatG2Compr`](#0x1_bls12381_algebra_FormatG2Compr)
+- [Struct `Gt`](#0x1_bls12381_algebra_Gt)
+- [Struct `FormatGt`](#0x1_bls12381_algebra_FormatGt)
+- [Struct `Fr`](#0x1_bls12381_algebra_Fr)
+- [Struct `FormatFrLsb`](#0x1_bls12381_algebra_FormatFrLsb)
+- [Struct `FormatFrMsb`](#0x1_bls12381_algebra_FormatFrMsb)
+- [Struct `HashG1XmdSha256SswuRo`](#0x1_bls12381_algebra_HashG1XmdSha256SswuRo)
+- [Struct `HashG2XmdSha256SswuRo`](#0x1_bls12381_algebra_HashG2XmdSha256SswuRo)
+
+
+
+
+
+
+
+
+## Struct `Fq12`
+
+The finite field $F_{q^12}$ used in BLS12-381 curves,
+which is an extension field of Fq6
(defined in the module documentation), constructed as $F_{q^12}=F_{q^6}[w]/(w^2-v)$.
+
+
+struct Fq12
+
+
+
+
+dummy_field: bool
+Fq12
elements,
+where an element $(c_0+c_1\cdot w)$ is represented by a byte array b[]
of size 576,
+which is a concatenation of its coefficients serialized, with the least significant coefficient (LSC) coming first.
+- b[0..288]
is $c_0$ serialized using FormatFq6LscLsb
(defined in the module documentation).
+- b[288..576]
is $c_1$ serialized using FormatFq6LscLsb
.
+
+NOTE: other implementation(s) using this format: ark-bls12-381-0.4.0.
+
+
+struct FormatFq12LscLsb
+
+
+
+
+dummy_field: bool
+G1Full
(defined in the module documentation) with a prime order $r$
+equal to 0x73eda753299d7d483339d80809a1d80553bda402fffe5bfeffffffff00000001.
+(so Fr
is the associated scalar field).
+
+
+struct G1
+
+
+
+
+dummy_field: bool
+G1
elements derived from
+https://www.ietf.org/archive/id/draft-irtf-cfrg-pairing-friendly-curves-11.html#name-zcash-serialization-format-.
+
+Below is the serialization procedure that takes a G1
element p
and outputs a byte array of size 96.
+1. Let (x,y)
be the coordinates of p
if p
is on the curve, or (0,0)
otherwise.
+1. Serialize x
and y
into b_x[]
and b_y[]
respectively using FormatFqMsb
(defined in the module documentation).
+1. Concatenate b_x[]
and b_y[]
into b[]
.
+1. If p
is the point at infinity, set the infinity bit: b[0]: = b[0] | 0x40
.
+1. Return b[]
.
+
+Below is the deserialization procedure that takes a byte array b[]
and outputs either a G1
element or none.
+1. If the size of b[]
is not 96, return none.
+1. Compute the compression flag as b[0] & 0x80 != 0
.
+1. If the compression flag is true, return none.
+1. Compute the infinity flag as b[0] & 0x40 != 0
.
+1. If the infinity flag is set, return the point at infinity.
+1. Deserialize [b[0] & 0x1f, b[1], ..., b[47]]
to x
using FormatFqMsb
. If x
is none, return none.
+1. Deserialize [b[48], ..., b[95]]
to y
using FormatFqMsb
. If y
is none, return none.
+1. Check if (x,y)
is on curve E
. If not, return none.
+1. Check if (x,y)
is in the subgroup of order r
. If not, return none.
+1. Return (x,y)
.
+
+NOTE: other implementation(s) using this format: ark-bls12-381-0.4.0.
+
+
+struct FormatG1Uncompr
+
+
+
+
+dummy_field: bool
+G1
elements derived from
+https://www.ietf.org/archive/id/draft-irtf-cfrg-pairing-friendly-curves-11.html#name-zcash-serialization-format-.
+
+Below is the serialization procedure that takes a G1
element p
and outputs a byte array of size 48.
+1. Let (x,y)
be the coordinates of p
if p
is on the curve, or (0,0)
otherwise.
+1. Serialize x
into b[]
using FormatFqMsb
(defined in the module documentation).
+1. Set the compression bit: b[0] := b[0] | 0x80
.
+1. If p
is the point at infinity, set the infinity bit: b[0]: = b[0] | 0x40
.
+1. If y > -y
, set the lexicographical flag: b[0] := b[0] | 0x20
.
+1. Return b[]
.
+
+Below is the deserialization procedure that takes a byte array b[]
and outputs either a G1
element or none.
+1. If the size of b[]
is not 48, return none.
+1. Compute the compression flag as b[0] & 0x80 != 0
.
+1. If the compression flag is false, return none.
+1. Compute the infinity flag as b[0] & 0x40 != 0
.
+1. If the infinity flag is set, return the point at infinity.
+1. Compute the lexicographical flag as b[0] & 0x20 != 0
.
+1. Deserialize [b[0] & 0x1f, b[1], ..., b[47]]
to x
using FormatFqMsb
. If x
is none, return none.
+1. Solve the curve equation with x
for y
. If no such y
exists, return none.
+1. Let y'
be max(y,-y)
if the lexicographical flag is set, or min(y,-y)
otherwise.
+1. Check if (x,y')
is in the subgroup of order r
. If not, return none.
+1. Return (x,y')
.
+
+NOTE: other implementation(s) using this format: ark-bls12-381-0.4.0.
+
+
+struct FormatG1Compr
+
+
+
+
+dummy_field: bool
+G2Full
(defined in the module documentation) with a prime order $r$ equal to
+0x73eda753299d7d483339d80809a1d80553bda402fffe5bfeffffffff00000001.
+(so Fr
is the scalar field).
+
+
+struct G2
+
+
+
+
+dummy_field: bool
+G2
elements derived from
+https://www.ietf.org/archive/id/draft-irtf-cfrg-pairing-friendly-curves-11.html#name-zcash-serialization-format-.
+
+Below is the serialization procedure that takes a G2
element p
and outputs a byte array of size 192.
+1. Let (x,y)
be the coordinates of p
if p
is on the curve, or (0,0)
otherwise.
+1. Serialize x
and y
into b_x[]
and b_y[]
respectively using FormatFq2MscMsb
(defined in the module documentation).
+1. Concatenate b_x[]
and b_y[]
into b[]
.
+1. If p
is the point at infinity, set the infinity bit in b[]
: b[0]: = b[0] | 0x40
.
+1. Return b[]
.
+
+Below is the deserialization procedure that takes a byte array b[]
and outputs either a G2
element or none.
+1. If the size of b[]
is not 192, return none.
+1. Compute the compression flag as b[0] & 0x80 != 0
.
+1. If the compression flag is true, return none.
+1. Compute the infinity flag as b[0] & 0x40 != 0
.
+1. If the infinity flag is set, return the point at infinity.
+1. Deserialize [b[0] & 0x1f, ..., b[95]]
to x
using FormatFq2MscMsb
. If x
is none, return none.
+1. Deserialize [b[96], ..., b[191]]
to y
using FormatFq2MscMsb
. If y
is none, return none.
+1. Check if (x,y)
is on the curve E'
. If not, return none.
+1. Check if (x,y)
is in the subgroup of order r
. If not, return none.
+1. Return (x,y)
.
+
+NOTE: other implementation(s) using this format: ark-bls12-381-0.4.0.
+
+
+struct FormatG2Uncompr
+
+
+
+
+dummy_field: bool
+G2
elements derived from
+https://www.ietf.org/archive/id/draft-irtf-cfrg-pairing-friendly-curves-11.html#name-zcash-serialization-format-.
+
+Below is the serialization procedure that takes a G2
element p
and outputs a byte array of size 96.
+1. Let (x,y)
be the coordinates of p
if p
is on the curve, or (0,0)
otherwise.
+1. Serialize x
into b[]
using FormatFq2MscMsb
(defined in the module documentation).
+1. Set the compression bit: b[0] := b[0] | 0x80
.
+1. If p
is the point at infinity, set the infinity bit: b[0]: = b[0] | 0x40
.
+1. If y > -y
, set the lexicographical flag: b[0] := b[0] | 0x20
.
+1. Return b[]
.
+
+Below is the deserialization procedure that takes a byte array b[]
and outputs either a G2
element or none.
+1. If the size of b[]
is not 96, return none.
+1. Compute the compression flag as b[0] & 0x80 != 0
.
+1. If the compression flag is false, return none.
+1. Compute the infinity flag as b[0] & 0x40 != 0
.
+1. If the infinity flag is set, return the point at infinity.
+1. Compute the lexicographical flag as b[0] & 0x20 != 0
.
+1. Deserialize [b[0] & 0x1f, b[1], ..., b[95]]
to x
using FormatFq2MscMsb
. If x
is none, return none.
+1. Solve the curve equation with x
for y
. If no such y
exists, return none.
+1. Let y'
be max(y,-y)
if the lexicographical flag is set, or min(y,-y)
otherwise.
+1. Check if (x,y')
is in the subgroup of order r
. If not, return none.
+1. Return (x,y')
.
+
+NOTE: other implementation(s) using this format: ark-bls12-381-0.4.0.
+
+
+struct FormatG2Compr
+
+
+
+
+dummy_field: bool
+Fq12
,
+with a prime order $r$ equal to 0x73eda753299d7d483339d80809a1d80553bda402fffe5bfeffffffff00000001.
+(so Fr
is the scalar field).
+The identity of Gt
is 1.
+
+
+struct Gt
+
+
+
+
+dummy_field: bool
+Gt
elements.
+
+To serialize, it treats a Gt
element p
as an Fq12
element and serialize it using FormatFq12LscLsb
.
+
+To deserialize, it uses FormatFq12LscLsb
to try deserializing to an Fq12
element then test the membership in Gt
.
+
+NOTE: other implementation(s) using this format: ark-bls12-381-0.4.0.
+
+
+struct FormatGt
+
+
+
+
+dummy_field: bool
+struct Fr
+
+
+
+
+dummy_field: bool
+Fr
elements,
+where an element is represented by a byte array b[]
of size 32 with the least significant byte (LSB) coming first.
+
+NOTE: other implementation(s) using this format: ark-bls12-381-0.4.0, blst-0.3.7.
+
+
+struct FormatFrLsb
+
+
+
+
+dummy_field: bool
+Fr
elements,
+where an element is represented by a byte array b[]
of size 32 with the most significant byte (MSB) coming first.
+
+NOTE: other implementation(s) using this format: ark-bls12-381-0.4.0, blst-0.3.7.
+
+
+struct FormatFrMsb
+
+
+
+
+dummy_field: bool
+BLS12381G1_XMD:SHA-256_SSWU_RO_
that hashes a byte array into G1
elements.
+
+Full specification is defined in https://datatracker.ietf.org/doc/html/draft-irtf-cfrg-hash-to-curve-16#name-bls12-381-g1.
+
+
+struct HashG1XmdSha256SswuRo
+
+
+
+
+dummy_field: bool
+BLS12381G2_XMD:SHA-256_SSWU_RO_
that hashes a byte array into G2
elements.
+
+Full specification is defined in https://datatracker.ietf.org/doc/html/draft-irtf-cfrg-hash-to-curve-16#name-bls12-381-g2.
+
+
+struct HashG2XmdSha256SswuRo
+
+
+
+
+dummy_field: bool
+algebra.move
.
+BN254 was sampled as part of the [\[BCTV14\]](https://eprint.iacr.org/2013/879.pdf) paper .
+The name denotes that it is a Barreto-Naehrig curve of embedding degree 12, defined over a 254-bit (prime) field.
+The scalar field is highly 2-adic which supports subgroups of roots of unity of size <= 2^28.
+(as (21888242871839275222246405745257275088548364400416034343698204186575808495617 - 1) mod 2^28 = 0)
+
+This curve is also implemented in [libff](https://github.com/scipr-lab/libff/tree/master/libff/algebra/curves/alt_bn128) under the name bn128
.
+It is the same as the bn254
curve used in Ethereum (eg: [go-ethereum](https://github.com/ethereum/go-ethereum/tree/master/crypto/bn254/cloudflare)).
+
+
+
+
+## CAUTION
+
+**This curve does not satisfy the 128-bit security level anymore.**
+
+Its current security is estimated at 128-bits (see "Updating Key Size Estimations for Pairings"; by Barbulescu, Razvan and Duquesne, Sylvain; in Journal of Cryptology; 2019; https://doi.org/10.1007/s00145-018-9280-5)
+
+
+Curve information:
+* Base field: q =
+21888242871839275222246405745257275088696311157297823662689037894645226208583
+* Scalar field: r =
+21888242871839275222246405745257275088548364400416034343698204186575808495617
+* valuation(q - 1, 2) = 1
+* valuation(r - 1, 2) = 28
+* G1 curve equation: y^2 = x^3 + 3
+* G2 curve equation: y^2 = x^3 + B, where
+* B = 3/(u+9) where Fq2 is represented as Fq\[u\]/(u^2+1) =
+Fq2(19485874751759354771024239261021720505790618469301721065564631296452457478373,
+266929791119991161246907387137283842545076965332900288569378510910307636690)
+
+
+Currently-supported BN254 structures include Fq12
, Fr
, Fq
, Fq2
, G1
, G2
and Gt
,
+along with their widely-used serialization formats,
+the pairing between G1
, G2
and Gt
.
+
+Other unimplemented BN254 structures and serialization formats are also listed here,
+as they help define some of the currently supported structures.
+Their implementation may also be added in the future.
+
+Fq2
: The finite field $F_{q^2}$ that can be used as the base field of $G_2$
+which is an extension field of Fq
, constructed as $F_{q^2}=F_{q}[u]/(u^2+1)$.
+
+FormatFq2LscLsb
: A serialization scheme for Fq2
elements,
+where an element $(c_0+c_1\cdot u)$ is represented by a byte array b[]
of size N=64,
+which is a concatenation of its coefficients serialized, with the least significant coefficient (LSC) coming first.
+- b[0..32]
is $c_0$ serialized using FormatFqLscLsb
.
+- b[32..64]
is $c_1$ serialized using FormatFqLscLsb
.
+
+Fq6
: the finite field $F_{q^6}$ used in BN254 curves,
+which is an extension field of Fq2
, constructed as $F_{q^6}=F_{q^2}[v]/(v^3-u-9)$.
+
+FormatFq6LscLsb
: a serialization scheme for Fq6
elements,
+where an element in the form $(c_0+c_1\cdot v+c_2\cdot v^2)$ is represented by a byte array b[]
of size 192,
+which is a concatenation of its coefficients serialized, with the least significant coefficient (LSC) coming first:
+- b[0..64]
is $c_0$ serialized using FormatFq2LscLsb
.
+- b[64..128]
is $c_1$ serialized using FormatFq2LscLsb
.
+- b[128..192]
is $c_2$ serialized using FormatFq2LscLsb
.
+
+G1Full
: a group constructed by the points on the BN254 curve $E(F_q): y^2=x^3+3$ and the point at infinity,
+under the elliptic curve point addition.
+It contains the prime-order subgroup $G_1$ used in pairing.
+
+G2Full
: a group constructed by the points on a curve $E'(F_{q^2}): y^2=x^3+3/(u+9)$ and the point at infinity,
+under the elliptic curve point addition.
+It contains the prime-order subgroup $G_2$ used in pairing.
+
+
+- [CAUTION](#@CAUTION_0)
+- [Struct `Fr`](#0x1_bn254_algebra_Fr)
+- [Struct `FormatFrLsb`](#0x1_bn254_algebra_FormatFrLsb)
+- [Struct `FormatFrMsb`](#0x1_bn254_algebra_FormatFrMsb)
+- [Struct `Fq`](#0x1_bn254_algebra_Fq)
+- [Struct `FormatFqLsb`](#0x1_bn254_algebra_FormatFqLsb)
+- [Struct `FormatFqMsb`](#0x1_bn254_algebra_FormatFqMsb)
+- [Struct `Fq12`](#0x1_bn254_algebra_Fq12)
+- [Struct `FormatFq12LscLsb`](#0x1_bn254_algebra_FormatFq12LscLsb)
+- [Struct `G1`](#0x1_bn254_algebra_G1)
+- [Struct `FormatG1Uncompr`](#0x1_bn254_algebra_FormatG1Uncompr)
+- [Struct `FormatG1Compr`](#0x1_bn254_algebra_FormatG1Compr)
+- [Struct `G2`](#0x1_bn254_algebra_G2)
+- [Struct `FormatG2Uncompr`](#0x1_bn254_algebra_FormatG2Uncompr)
+- [Struct `FormatG2Compr`](#0x1_bn254_algebra_FormatG2Compr)
+- [Struct `Gt`](#0x1_bn254_algebra_Gt)
+- [Struct `FormatGt`](#0x1_bn254_algebra_FormatGt)
+
+
+
+
+
+
+
+
+## Struct `Fr`
+
+The finite field $F_r$ that can be used as the scalar fields
+associated with the groups $G_1$, $G_2$, $G_t$ in BN254-based pairing.
+
+
+struct Fr
+
+
+
+
+dummy_field: bool
+Fr
elements,
+where an element is represented by a byte array b[]
of size 32 with the least significant byte (LSB) coming first.
+
+NOTE: other implementation(s) using this format: ark-bn254-0.4.0.
+
+
+struct FormatFrLsb
+
+
+
+
+dummy_field: bool
+Fr
elements,
+where an element is represented by a byte array b[]
of size 32 with the most significant byte (MSB) coming first.
+
+NOTE: other implementation(s) using this format: ark-bn254-0.4.0.
+
+
+struct FormatFrMsb
+
+
+
+
+dummy_field: bool
+struct Fq
+
+
+
+
+dummy_field: bool
+Fq
elements,
+where an element is represented by a byte array b[]
of size 32 with the least significant byte (LSB) coming first.
+
+NOTE: other implementation(s) using this format: ark-bn254-0.4.0.
+
+
+struct FormatFqLsb
+
+
+
+
+dummy_field: bool
+Fq
elements,
+where an element is represented by a byte array b[]
of size 32 with the most significant byte (MSB) coming first.
+
+NOTE: other implementation(s) using this format: ark-bn254-0.4.0.
+
+
+struct FormatFqMsb
+
+
+
+
+dummy_field: bool
+Fq6
(defined in the module documentation), constructed as $F_{q^12}=F_{q^6}[w]/(w^2-v)$.
+The field can downcast to Gt
if it's an element of the multiplicative subgroup Gt
of Fq12
+with a prime order $r$ = 0x73eda753299d7d483339d80809a1d80553bda402fffe5bfeffffffff00000001.
+
+
+struct Fq12
+
+
+
+
+dummy_field: bool
+Fq12
elements,
+where an element $(c_0+c_1\cdot w)$ is represented by a byte array b[]
of size 384,
+which is a concatenation of its coefficients serialized, with the least significant coefficient (LSC) coming first.
+- b[0..192]
is $c_0$ serialized using FormatFq6LscLsb
(defined in the module documentation).
+- b[192..384]
is $c_1$ serialized using FormatFq6LscLsb
.
+
+NOTE: other implementation(s) using this format: ark-bn254-0.4.0.
+
+
+struct FormatFq12LscLsb
+
+
+
+
+dummy_field: bool
+G1Full
(defined in the module documentation) with a prime order $r$
+equal to 0x73eda753299d7d483339d80809a1d80553bda402fffe5bfeffffffff00000001.
+(so Fr
is the associated scalar field).
+
+
+struct G1
+
+
+
+
+dummy_field: bool
+G1
elements derived from arkworks.rs.
+
+Below is the serialization procedure that takes a G1
element p
and outputs a byte array of size N=64.
+1. Let (x,y)
be the coordinates of p
if p
is on the curve, or (0,0)
otherwise.
+1. Serialize x
and y
into b_x[]
and b_y[]
respectively using FormatFqLsb
(defined in the module documentation).
+1. Concatenate b_x[]
and b_y[]
into b[]
.
+1. If p
is the point at infinity, set the infinity bit: b[N-1]: = b[N-1] | 0b0100_0000
.
+1. If y > -y
, set the lexicographical bit: b[N-1]: = b[N-1] | 0b1000_0000
.
+1. Return b[]
.
+
+Below is the deserialization procedure that takes a byte array b[]
and outputs either a G1
element or none.
+1. If the size of b[]
is not N, return none.
+1. Compute the infinity flag as b[N-1] & 0b0100_0000 != 0
.
+1. If the infinity flag is set, return the point at infinity.
+1. Deserialize [b[0], b[1], ..., b[N/2-1]]
to x
using FormatFqLsb
. If x
is none, return none.
+1. Deserialize [b[N/2], ..., b[N] & 0b0011_1111]
to y
using FormatFqLsb
. If y
is none, return none.
+1. Check if (x,y)
is on curve E
. If not, return none.
+1. Check if (x,y)
is in the subgroup of order r
. If not, return none.
+1. Return (x,y)
.
+
+NOTE: other implementation(s) using this format: ark-bn254-0.4.0.
+
+
+struct FormatG1Uncompr
+
+
+
+
+dummy_field: bool
+G1
elements derived from arkworks.rs
+
+Below is the serialization procedure that takes a G1
element p
and outputs a byte array of size N=32.
+1. Let (x,y)
be the coordinates of p
if p
is on the curve, or (0,0)
otherwise.
+1. Serialize x
into b[]
using FormatFqLsb
(defined in the module documentation).
+1. If p
is the point at infinity, set the infinity bit: b[N-1]: = b[N-1] | 0b0100_0000
.
+1. If y > -y
, set the lexicographical flag: b[N-1] := b[N-1] | 0x1000_0000
.
+1. Return b[]
.
+
+Below is the deserialization procedure that takes a byte array b[]
and outputs either a G1
element or none.
+1. If the size of b[]
is not N, return none.
+1. Compute the infinity flag as b[N-1] & 0b0100_0000 != 0
.
+1. If the infinity flag is set, return the point at infinity.
+1. Compute the lexicographical flag as b[N-1] & 0b1000_0000 != 0
.
+1. Deserialize [b[0], b[1], ..., b[N/2-1] & 0b0011_1111]
to x
using FormatFqLsb
. If x
is none, return none.
+1. Solve the curve equation with x
for y
. If no such y
exists, return none.
+1. Let y'
be max(y,-y)
if the lexicographical flag is set, or min(y,-y)
otherwise.
+1. Check if (x,y')
is in the subgroup of order r
. If not, return none.
+1. Return (x,y')
.
+
+NOTE: other implementation(s) using this format: ark-bn254-0.4.0.
+
+
+struct FormatG1Compr
+
+
+
+
+dummy_field: bool
+G2Full
(defined in the module documentation) with a prime order $r$ equal to
+0x73eda753299d7d483339d80809a1d80553bda402fffe5bfeffffffff00000001.
+(so Fr
is the scalar field).
+
+
+struct G2
+
+
+
+
+dummy_field: bool
+G2
elements derived from arkworks.rs.
+
+Below is the serialization procedure that takes a G2
element p
and outputs a byte array of size N=128.
+1. Let (x,y)
be the coordinates of p
if p
is on the curve, or (0,0)
otherwise.
+1. Serialize x
and y
into b_x[]
and b_y[]
respectively using FormatFq2LscLsb
(defined in the module documentation).
+1. Concatenate b_x[]
and b_y[]
into b[]
.
+1. If p
is the point at infinity, set the infinity bit: b[N-1]: = b[N-1] | 0b0100_0000
.
+1. If y > -y
, set the lexicographical bit: b[N-1]: = b[N-1] | 0b1000_0000
.
+1. Return b[]
.
+
+Below is the deserialization procedure that takes a byte array b[]
and outputs either a G1
element or none.
+1. If the size of b[]
is not N, return none.
+1. Compute the infinity flag as b[N-1] & 0b0100_0000 != 0
.
+1. If the infinity flag is set, return the point at infinity.
+1. Deserialize [b[0], b[1], ..., b[N/2-1]]
to x
using FormatFq2LscLsb
. If x
is none, return none.
+1. Deserialize [b[N/2], ..., b[N] & 0b0011_1111]
to y
using FormatFq2LscLsb
. If y
is none, return none.
+1. Check if (x,y)
is on curve E
. If not, return none.
+1. Check if (x,y)
is in the subgroup of order r
. If not, return none.
+1. Return (x,y)
.
+
+NOTE: other implementation(s) using this format: ark-bn254-0.4.0.
+
+
+struct FormatG2Uncompr
+
+
+
+
+dummy_field: bool
+G1
elements derived from arkworks.rs
+
+Below is the serialization procedure that takes a G1
element p
and outputs a byte array of size N=64.
+1. Let (x,y)
be the coordinates of p
if p
is on the curve, or (0,0)
otherwise.
+1. Serialize x
into b[]
using FormatFq2LscLsb
(defined in the module documentation).
+1. If p
is the point at infinity, set the infinity bit: b[N-1]: = b[N-1] | 0b0100_0000
.
+1. If y > -y
, set the lexicographical flag: b[N-1] := b[N-1] | 0x1000_0000
.
+1. Return b[]
.
+
+Below is the deserialization procedure that takes a byte array b[]
and outputs either a G1
element or none.
+1. If the size of b[]
is not N, return none.
+1. Compute the infinity flag as b[N-1] & 0b0100_0000 != 0
.
+1. If the infinity flag is set, return the point at infinity.
+1. Compute the lexicographical flag as b[N-1] & 0b1000_0000 != 0
.
+1. Deserialize [b[0], b[1], ..., b[N/2-1] & 0b0011_1111]
to x
using FormatFq2LscLsb
. If x
is none, return none.
+1. Solve the curve equation with x
for y
. If no such y
exists, return none.
+1. Let y'
be max(y,-y)
if the lexicographical flag is set, or min(y,-y)
otherwise.
+1. Check if (x,y')
is in the subgroup of order r
. If not, return none.
+1. Return (x,y')
.
+
+NOTE: other implementation(s) using this format: ark-bn254-0.4.0.
+
+
+struct FormatG2Compr
+
+
+
+
+dummy_field: bool
+Fq12
, so it can upcast to Fq12
.
+with a prime order $r$ equal to 0x73eda753299d7d483339d80809a1d80553bda402fffe5bfeffffffff00000001.
+(so Fr
is the scalar field).
+The identity of Gt
is 1.
+
+
+struct Gt
+
+
+
+
+dummy_field: bool
+Gt
elements.
+
+To serialize, it treats a Gt
element p
as an Fq12
element and serialize it using FormatFq12LscLsb
.
+
+To deserialize, it uses FormatFq12LscLsb
to try deserializing to an Fq12
element then test the membership in Gt
.
+
+NOTE: other implementation(s) using this format: ark-bn254-0.4.0.
+
+
+struct FormatGt
+
+
+
+
+dummy_field: bool
+capability::Cap
has
+no ability to be stored in global memory, capabilities cannot leak out of a transaction. For every function
+called within a transaction which has a capability as a parameter, it is guaranteed that the capability
+has been obtained via a proper signer-based authorization step previously in the transaction's execution.
+
+
+
+
+### Usage
+
+
+Initializing and acquiring capabilities is usually encapsulated in a module with a type
+tag which can only be constructed by this module.
+
+```
+module Pkg::Feature {
+use std::capability::Cap;
+
+/// A type tag used in CapSelf::delegate
, an owner of a capability
+can designate another signer to be also capable of acquiring the capability. Like the original creator,
+the delegate needs to present his signer to obtain the capability in his transactions. Delegation can
+be revoked via Self::revoke
, removing this access right from the delegate.
+
+While the basic authorization mechanism for delegates is the same as with core capabilities, the
+target of delegation might be subject of restrictions which need to be specified and verified. This can
+be done via global invariants in the specification language. For example, in order to prevent delegation
+all together for a capability, one can use the following invariant:
+
+```
+invariant forall a: address where capability::spec_has_capuse 0x1::error;
+use 0x1::signer;
+use 0x1::vector;
+
+
+
+
+
+
+## Struct `Cap`
+
+The token representing an acquired capability. Cannot be stored in memory, but copied and dropped freely.
+
+
+struct Cap<Feature> has copy, drop
+
+
+
+
+root: address
+struct LinearCap<Feature> has drop
+
+
+
+
+root: address
+struct CapState<Feature> has key
+
+
+
+
+delegates: vector<address>
+struct CapDelegateState<Feature> has key
+
+
+
+
+root: address
+const ECAPABILITY_ALREADY_EXISTS: u64 = 1;
+
+
+
+
+
+
+Capability resource not found
+
+
+const ECAPABILITY_NOT_FOUND: u64 = 2;
+
+
+
+
+
+
+Account does not have delegated permissions
+
+
+const EDELEGATE: u64 = 3;
+
+
+
+
+
+
+## Function `create`
+
+Creates a new capability class, owned by the passed signer. A caller must pass a witness that
+they own the Feature
type parameter.
+
+
+public fun create<Feature>(owner: &signer, _feature_witness: &Feature)
+
+
+
+
+public fun create<Feature>(owner: &signer, _feature_witness: &Feature) {
+ let addr = signer::address_of(owner);
+ assert!(!exists<CapState<Feature>>(addr), error::already_exists(ECAPABILITY_ALREADY_EXISTS));
+ move_to<CapState<Feature>>(owner, CapState { delegates: vector::empty() });
+}
+
+
+
+
+Feature
type
+parameter.
+
+
+public fun acquire<Feature>(requester: &signer, _feature_witness: &Feature): capability::Cap<Feature>
+
+
+
+
+public fun acquire<Feature>(requester: &signer, _feature_witness: &Feature): Cap<Feature>
+acquires CapState, CapDelegateState {
+ Cap<Feature> { root: validate_acquire<Feature>(requester) }
+}
+
+
+
+
+Feature
to decide
+whether to expose a linear or non-linear capability.
+
+
+public fun acquire_linear<Feature>(requester: &signer, _feature_witness: &Feature): capability::LinearCap<Feature>
+
+
+
+
+public fun acquire_linear<Feature>(requester: &signer, _feature_witness: &Feature): LinearCap<Feature>
+acquires CapState, CapDelegateState {
+ LinearCap<Feature> { root: validate_acquire<Feature>(requester) }
+}
+
+
+
+
+fun validate_acquire<Feature>(requester: &signer): address
+
+
+
+
+fun validate_acquire<Feature>(requester: &signer): address
+acquires CapState, CapDelegateState {
+ let addr = signer::address_of(requester);
+ if (exists<CapDelegateState<Feature>>(addr)) {
+ let root_addr = borrow_global<CapDelegateState<Feature>>(addr).root;
+ // double check that requester is actually registered as a delegate
+ assert!(exists<CapState<Feature>>(root_addr), error::invalid_state(EDELEGATE));
+ assert!(vector::contains(&borrow_global<CapState<Feature>>(root_addr).delegates, &addr),
+ error::invalid_state(EDELEGATE));
+ root_addr
+ } else {
+ assert!(exists<CapState<Feature>>(addr), error::not_found(ECAPABILITY_NOT_FOUND));
+ addr
+ }
+}
+
+
+
+
+public fun root_addr<Feature>(cap: capability::Cap<Feature>, _feature_witness: &Feature): address
+
+
+
+
+public fun root_addr<Feature>(cap: Cap<Feature>, _feature_witness: &Feature): address {
+ cap.root
+}
+
+
+
+
+public fun linear_root_addr<Feature>(cap: capability::LinearCap<Feature>, _feature_witness: &Feature): address
+
+
+
+
+public fun linear_root_addr<Feature>(cap: LinearCap<Feature>, _feature_witness: &Feature): address {
+ cap.root
+}
+
+
+
+
+public fun delegate<Feature>(cap: capability::Cap<Feature>, _feature_witness: &Feature, to: &signer)
+
+
+
+
+public fun delegate<Feature>(cap: Cap<Feature>, _feature_witness: &Feature, to: &signer)
+acquires CapState {
+ let addr = signer::address_of(to);
+ if (exists<CapDelegateState<Feature>>(addr)) return;
+ move_to(to, CapDelegateState<Feature> { root: cap.root });
+ add_element(&mut borrow_global_mut<CapState<Feature>>(cap.root).delegates, addr);
+}
+
+
+
+
+public fun revoke<Feature>(cap: capability::Cap<Feature>, _feature_witness: &Feature, from: address)
+
+
+
+
+public fun revoke<Feature>(cap: Cap<Feature>, _feature_witness: &Feature, from: address)
+acquires CapState, CapDelegateState
+{
+ if (!exists<CapDelegateState<Feature>>(from)) return;
+ let CapDelegateState { root: _root } = move_from<CapDelegateState<Feature>>(from);
+ remove_element(&mut borrow_global_mut<CapState<Feature>>(cap.root).delegates, &from);
+}
+
+
+
+
+fun remove_element<E: drop>(v: &mut vector<E>, x: &E)
+
+
+
+
+fun remove_element<E: drop>(v: &mut vector<E>, x: &E) {
+ let (found, index) = vector::index_of(v, x);
+ if (found) {
+ vector::remove(v, index);
+ }
+}
+
+
+
+
+fun add_element<E: drop>(v: &mut vector<E>, x: E)
+
+
+
+
+fun add_element<E: drop>(v: &mut vector<E>, x: E) {
+ if (!vector::contains(v, &x)) {
+ vector::push_back(v, x)
+ }
+}
+
+
+
+
+fun spec_has_cap<Feature>(addr: address): bool {
+ exists<CapState<Feature>>(addr)
+}
+
+
+
+Helper specification function to obtain the delegates of a capability.
+
+
+
+
+
+fun spec_delegates<Feature>(addr: address): vector<address> {
+ global<CapState<Feature>>(addr).delegates
+}
+
+
+
+Helper specification function to check whether a delegated capability exists at address.
+
+
+
+
+
+fun spec_has_delegate_cap<Feature>(addr: address): bool {
+ exists<CapDelegateState<Feature>>(addr)
+}
+
+
+
+
+
+
+### Function `create`
+
+
+public fun create<Feature>(owner: &signer, _feature_witness: &Feature)
+
+
+
+
+
+let addr = signer::address_of(owner);
+aborts_if spec_has_cap<Feature>(addr);
+ensures spec_has_cap<Feature>(addr);
+
+
+
+
+
+
+### Function `acquire`
+
+
+public fun acquire<Feature>(requester: &signer, _feature_witness: &Feature): capability::Cap<Feature>
+
+
+
+
+
+let addr = signer::address_of(requester);
+let root_addr = global<CapDelegateState<Feature>>(addr).root;
+include AcquireSchema<Feature>;
+ensures spec_has_delegate_cap<Feature>(addr) ==> result.root == root_addr;
+ensures !spec_has_delegate_cap<Feature>(addr) ==> result.root == addr;
+
+
+
+
+
+
+### Function `acquire_linear`
+
+
+public fun acquire_linear<Feature>(requester: &signer, _feature_witness: &Feature): capability::LinearCap<Feature>
+
+
+
+
+
+let addr = signer::address_of(requester);
+let root_addr = global<CapDelegateState<Feature>>(addr).root;
+include AcquireSchema<Feature>;
+ensures spec_has_delegate_cap<Feature>(addr) ==> result.root == root_addr;
+ensures !spec_has_delegate_cap<Feature>(addr) ==> result.root == addr;
+
+
+
+
+
+
+
+
+schema AcquireSchema<Feature> {
+ addr: address;
+ root_addr: address;
+ aborts_if spec_has_delegate_cap<Feature>(addr) && !spec_has_cap<Feature>(root_addr);
+ aborts_if spec_has_delegate_cap<Feature>(addr) && !vector::spec_contains(spec_delegates<Feature>(root_addr), addr);
+ aborts_if !spec_has_delegate_cap<Feature>(addr) && !spec_has_cap<Feature>(addr);
+}
+
+
+
+
+
+
+### Function `delegate`
+
+
+public fun delegate<Feature>(cap: capability::Cap<Feature>, _feature_witness: &Feature, to: &signer)
+
+
+
+
+
+let addr = signer::address_of(to);
+ensures spec_has_delegate_cap<Feature>(addr);
+ensures !old(spec_has_delegate_cap<Feature>(addr)) ==> global<CapDelegateState<Feature>>(addr).root == cap.root;
+ensures !old(spec_has_delegate_cap<Feature>(addr)) ==> vector::spec_contains(spec_delegates<Feature>(cap.root), addr);
+
+
+
+
+
+
+### Function `revoke`
+
+
+public fun revoke<Feature>(cap: capability::Cap<Feature>, _feature_witness: &Feature, from: address)
+
+
+
+
+
+ensures !spec_has_delegate_cap<Feature>(from);
+
+
+
+
+
+
+### Function `remove_element`
+
+
+fun remove_element<E: drop>(v: &mut vector<E>, x: &E)
+
+
+
+
+
+
+
+### Function `add_element`
+
+
+fun add_element<E: drop>(v: &mut vector<E>, x: E)
+
+
+
+
+
+ensures vector::spec_contains(v, x);
+
+
+
+[move-book]: https://aptos.dev/move/book/SUMMARY
diff --git a/aptos-move/framework/aptos-stdlib/tests/compiler-v2-doc/comparator.md b/aptos-move/framework/aptos-stdlib/tests/compiler-v2-doc/comparator.md
new file mode 100644
index 0000000000000..24aa41f3b8400
--- /dev/null
+++ b/aptos-move/framework/aptos-stdlib/tests/compiler-v2-doc/comparator.md
@@ -0,0 +1,387 @@
+
+
+
+# Module `0x1::comparator`
+
+Provides a framework for comparing two elements
+
+
+- [Struct `Result`](#0x1_comparator_Result)
+- [Constants](#@Constants_0)
+- [Function `is_equal`](#0x1_comparator_is_equal)
+- [Function `is_smaller_than`](#0x1_comparator_is_smaller_than)
+- [Function `is_greater_than`](#0x1_comparator_is_greater_than)
+- [Function `compare`](#0x1_comparator_compare)
+- [Function `compare_u8_vector`](#0x1_comparator_compare_u8_vector)
+- [Specification](#@Specification_1)
+ - [Struct `Result`](#@Specification_1_Result)
+ - [Function `is_equal`](#@Specification_1_is_equal)
+ - [Function `is_smaller_than`](#@Specification_1_is_smaller_than)
+ - [Function `is_greater_than`](#@Specification_1_is_greater_than)
+ - [Function `compare`](#@Specification_1_compare)
+ - [Function `compare_u8_vector`](#@Specification_1_compare_u8_vector)
+
+
+use 0x1::bcs;
+
+
+
+
+
+
+## Struct `Result`
+
+
+
+struct Result has drop
+
+
+
+
+inner: u8
+const EQUAL: u8 = 0;
+
+
+
+
+
+
+
+
+const GREATER: u8 = 2;
+
+
+
+
+
+
+
+
+const SMALLER: u8 = 1;
+
+
+
+
+
+
+## Function `is_equal`
+
+
+
+public fun is_equal(result: &comparator::Result): bool
+
+
+
+
+public fun is_equal(result: &Result): bool {
+ result.inner == EQUAL
+}
+
+
+
+
+public fun is_smaller_than(result: &comparator::Result): bool
+
+
+
+
+public fun is_smaller_than(result: &Result): bool {
+ result.inner == SMALLER
+}
+
+
+
+
+public fun is_greater_than(result: &comparator::Result): bool
+
+
+
+
+public fun is_greater_than(result: &Result): bool {
+ result.inner == GREATER
+}
+
+
+
+
+public fun compare<T>(left: &T, right: &T): comparator::Result
+
+
+
+
+public fun compare<T>(left: &T, right: &T): Result {
+ let left_bytes = bcs::to_bytes(left);
+ let right_bytes = bcs::to_bytes(right);
+
+ compare_u8_vector(left_bytes, right_bytes)
+}
+
+
+
+
+public fun compare_u8_vector(left: vector<u8>, right: vector<u8>): comparator::Result
+
+
+
+
+public fun compare_u8_vector(left: vector<u8>, right: vector<u8>): Result {
+ let left_length = vector::length(&left);
+ let right_length = vector::length(&right);
+
+ let idx = 0;
+
+ while (idx < left_length && idx < right_length) {
+ let left_byte = *vector::borrow(&left, idx);
+ let right_byte = *vector::borrow(&right, idx);
+
+ if (left_byte < right_byte) {
+ return Result { inner: SMALLER }
+ } else if (left_byte > right_byte) {
+ return Result { inner: GREATER }
+ };
+ idx = idx + 1;
+ };
+
+ if (left_length < right_length) {
+ Result { inner: SMALLER }
+ } else if (left_length > right_length) {
+ Result { inner: GREATER }
+ } else {
+ Result { inner: EQUAL }
+ }
+}
+
+
+
+
+struct Result has drop
+
+
+
+
+inner: u8
+invariant inner == EQUAL || inner == SMALLER || inner == GREATER;
+
+
+
+
+
+
+### Function `is_equal`
+
+
+public fun is_equal(result: &comparator::Result): bool
+
+
+
+
+
+aborts_if false;
+let res = result;
+ensures result == (res.inner == EQUAL);
+
+
+
+
+
+
+### Function `is_smaller_than`
+
+
+public fun is_smaller_than(result: &comparator::Result): bool
+
+
+
+
+
+aborts_if false;
+let res = result;
+ensures result == (res.inner == SMALLER);
+
+
+
+
+
+
+### Function `is_greater_than`
+
+
+public fun is_greater_than(result: &comparator::Result): bool
+
+
+
+
+
+aborts_if false;
+let res = result;
+ensures result == (res.inner == GREATER);
+
+
+
+
+
+
+### Function `compare`
+
+
+public fun compare<T>(left: &T, right: &T): comparator::Result
+
+
+
+
+
+let left_bytes = bcs::to_bytes(left);
+let right_bytes = bcs::to_bytes(right);
+ensures result == spec_compare_u8_vector(left_bytes, right_bytes);
+
+
+
+
+
+
+
+
+fun spec_compare_u8_vector(left: vector<u8>, right: vector<u8>): Result;
+
+
+
+
+
+
+### Function `compare_u8_vector`
+
+
+public fun compare_u8_vector(left: vector<u8>, right: vector<u8>): comparator::Result
+
+
+
+
+
+pragma unroll = 5;
+pragma opaque;
+aborts_if false;
+let left_length = len(left);
+let right_length = len(right);
+ensures (result.inner == EQUAL) ==> (
+ (left_length == right_length) &&
+ (forall i: u64 where i < left_length: left[i] == right[i])
+);
+ensures (result.inner == SMALLER) ==> (
+ (exists i: u64 where i < left_length:
+ (i < right_length) &&
+ (left[i] < right[i]) &&
+ (forall j: u64 where j < i: left[j] == right[j])
+ ) ||
+ (left_length < right_length)
+);
+ensures (result.inner == GREATER) ==> (
+ (exists i: u64 where i < left_length:
+ (i < right_length) &&
+ (left[i] > right[i]) &&
+ (forall j: u64 where j < i: left[j] == right[j])
+ ) ||
+ (left_length > right_length)
+);
+ensures [abstract] result == spec_compare_u8_vector(left, right);
+
+
+
+[move-book]: https://aptos.dev/move/book/SUMMARY
diff --git a/aptos-move/framework/aptos-stdlib/tests/compiler-v2-doc/copyable_any.md b/aptos-move/framework/aptos-stdlib/tests/compiler-v2-doc/copyable_any.md
new file mode 100644
index 0000000000000..7e6c8f613383f
--- /dev/null
+++ b/aptos-move/framework/aptos-stdlib/tests/compiler-v2-doc/copyable_any.md
@@ -0,0 +1,230 @@
+
+
+
+# Module `0x1::copyable_any`
+
+
+
+- [Struct `Any`](#0x1_copyable_any_Any)
+- [Constants](#@Constants_0)
+- [Function `pack`](#0x1_copyable_any_pack)
+- [Function `unpack`](#0x1_copyable_any_unpack)
+- [Function `type_name`](#0x1_copyable_any_type_name)
+- [Specification](#@Specification_1)
+ - [Function `pack`](#@Specification_1_pack)
+ - [Function `unpack`](#@Specification_1_unpack)
+ - [Function `type_name`](#@Specification_1_type_name)
+
+
+use 0x1::bcs;
+use 0x1::error;
+use 0x1::from_bcs;
+use 0x1::string;
+use 0x1::type_info;
+
+
+
+
+
+
+## Struct `Any`
+
+The same as any::Any
but with the copy ability.
+
+
+struct Any has copy, drop, store
+
+
+
+
+type_name: string::String
+data: vector<u8>
+unpack
is not the same as was given for pack
.
+
+
+const ETYPE_MISMATCH: u64 = 0;
+
+
+
+
+
+
+## Function `pack`
+
+Pack a value into the Any
representation. Because Any can be stored, dropped, and copied this is
+also required from T
.
+
+
+public fun pack<T: copy, drop, store>(x: T): copyable_any::Any
+
+
+
+
+public fun pack<T: drop + store + copy>(x: T): Any {
+ Any {
+ type_name: type_info::type_name<T>(),
+ data: bcs::to_bytes(&x)
+ }
+}
+
+
+
+
+Any
representation. This aborts if the value has not the expected type T
.
+
+
+public fun unpack<T>(x: copyable_any::Any): T
+
+
+
+
+public fun unpack<T>(x: Any): T {
+ assert!(type_info::type_name<T>() == x.type_name, error::invalid_argument(ETYPE_MISMATCH));
+ from_bytes<T>(x.data)
+}
+
+
+
+
+public fun type_name(x: ©able_any::Any): &string::String
+
+
+
+
+public fun type_name(x: &Any): &String {
+ &x.type_name
+}
+
+
+
+
+public fun pack<T: copy, drop, store>(x: T): copyable_any::Any
+
+
+
+
+
+aborts_if false;
+pragma opaque;
+ensures result == Any {
+ type_name: type_info::type_name<T>(),
+ data: bcs::serialize<T>(x)
+};
+ensures [abstract] from_bcs::deserializable<T>(result.data);
+
+
+
+
+
+
+### Function `unpack`
+
+
+public fun unpack<T>(x: copyable_any::Any): T
+
+
+
+
+
+include UnpackAbortsIf<T>;
+ensures result == from_bcs::deserialize<T>(x.data);
+
+
+
+
+
+
+
+
+schema UnpackAbortsIf<T> {
+ x: Any;
+ aborts_if type_info::type_name<T>() != x.type_name;
+ aborts_if !from_bcs::deserializable<T>(x.data);
+}
+
+
+
+
+
+
+### Function `type_name`
+
+
+public fun type_name(x: ©able_any::Any): &string::String
+
+
+
+
+
+aborts_if false;
+ensures result == x.type_name;
+
+
+
+[move-book]: https://aptos.dev/move/book/SUMMARY
diff --git a/aptos-move/framework/aptos-stdlib/tests/compiler-v2-doc/crypto_algebra.md b/aptos-move/framework/aptos-stdlib/tests/compiler-v2-doc/crypto_algebra.md
new file mode 100644
index 0000000000000..4aa0fab2c7833
--- /dev/null
+++ b/aptos-move/framework/aptos-stdlib/tests/compiler-v2-doc/crypto_algebra.md
@@ -0,0 +1,1755 @@
+
+
+
+# Module `0x1::crypto_algebra`
+
+This module provides generic structs/functions for operations of algebraic structures (e.g. fields and groups),
+which can be used to build generic cryptographic schemes atop.
+E.g., a Groth16 ZK proof verifier can be built to work over any pairing supported in this module.
+
+In general, every structure implements basic operations like (de)serialization, equality check, random sampling.
+
+A group may also implement the following operations. (Additive group notation is assumed.)
+- order()
for getting the group order.
+- zero()
for getting the group identity.
+- one()
for getting the group generator (if exists).
+- neg()
for group element inversion.
+- add()
for group operation (i.e., a group addition).
+- sub()
for group element subtraction.
+- double()
for efficient doubling.
+- scalar_mul()
for group scalar multiplication.
+- multi_scalar_mul()
for efficient group multi-scalar multiplication.
+- hash_to()
for hash-to-group.
+
+A field may also implement the following operations.
+- zero()
for getting the field additive identity.
+- one()
for getting the field multiplicative identity.
+- add()
for field addition.
+- sub()
for field subtraction.
+- mul()
for field multiplication.
+- div()
for field division.
+- neg()
for field negation.
+- inv()
for field inversion.
+- sqr()
for efficient field element squaring.
+- from_u64()
for quick conversion from u64 to field element.
+
+For 3 groups that admit a bilinear map, pairing()
and multi_pairing()
may be implemented.
+
+For a subset/superset relationship between 2 structures, upcast()
and downcast()
may be implemented.
+E.g., in BLS12-381 pairing, since Gt
is a subset of Fq12
,
+upcast<Gt, Fq12>()
and downcast<Fq12, Gt>()
will be supported.
+
+See *_algebra.move
for currently implemented algebraic structures.
+
+
+- [Struct `Element`](#0x1_crypto_algebra_Element)
+- [Constants](#@Constants_0)
+- [Function `eq`](#0x1_crypto_algebra_eq)
+- [Function `from_u64`](#0x1_crypto_algebra_from_u64)
+- [Function `zero`](#0x1_crypto_algebra_zero)
+- [Function `one`](#0x1_crypto_algebra_one)
+- [Function `neg`](#0x1_crypto_algebra_neg)
+- [Function `add`](#0x1_crypto_algebra_add)
+- [Function `sub`](#0x1_crypto_algebra_sub)
+- [Function `mul`](#0x1_crypto_algebra_mul)
+- [Function `div`](#0x1_crypto_algebra_div)
+- [Function `sqr`](#0x1_crypto_algebra_sqr)
+- [Function `inv`](#0x1_crypto_algebra_inv)
+- [Function `double`](#0x1_crypto_algebra_double)
+- [Function `multi_scalar_mul`](#0x1_crypto_algebra_multi_scalar_mul)
+- [Function `scalar_mul`](#0x1_crypto_algebra_scalar_mul)
+- [Function `multi_pairing`](#0x1_crypto_algebra_multi_pairing)
+- [Function `pairing`](#0x1_crypto_algebra_pairing)
+- [Function `deserialize`](#0x1_crypto_algebra_deserialize)
+- [Function `serialize`](#0x1_crypto_algebra_serialize)
+- [Function `order`](#0x1_crypto_algebra_order)
+- [Function `upcast`](#0x1_crypto_algebra_upcast)
+- [Function `downcast`](#0x1_crypto_algebra_downcast)
+- [Function `hash_to`](#0x1_crypto_algebra_hash_to)
+- [Function `abort_unless_cryptography_algebra_natives_enabled`](#0x1_crypto_algebra_abort_unless_cryptography_algebra_natives_enabled)
+- [Function `handles_from_elements`](#0x1_crypto_algebra_handles_from_elements)
+- [Function `add_internal`](#0x1_crypto_algebra_add_internal)
+- [Function `deserialize_internal`](#0x1_crypto_algebra_deserialize_internal)
+- [Function `div_internal`](#0x1_crypto_algebra_div_internal)
+- [Function `double_internal`](#0x1_crypto_algebra_double_internal)
+- [Function `downcast_internal`](#0x1_crypto_algebra_downcast_internal)
+- [Function `from_u64_internal`](#0x1_crypto_algebra_from_u64_internal)
+- [Function `eq_internal`](#0x1_crypto_algebra_eq_internal)
+- [Function `hash_to_internal`](#0x1_crypto_algebra_hash_to_internal)
+- [Function `inv_internal`](#0x1_crypto_algebra_inv_internal)
+- [Function `mul_internal`](#0x1_crypto_algebra_mul_internal)
+- [Function `multi_pairing_internal`](#0x1_crypto_algebra_multi_pairing_internal)
+- [Function `multi_scalar_mul_internal`](#0x1_crypto_algebra_multi_scalar_mul_internal)
+- [Function `neg_internal`](#0x1_crypto_algebra_neg_internal)
+- [Function `one_internal`](#0x1_crypto_algebra_one_internal)
+- [Function `order_internal`](#0x1_crypto_algebra_order_internal)
+- [Function `pairing_internal`](#0x1_crypto_algebra_pairing_internal)
+- [Function `scalar_mul_internal`](#0x1_crypto_algebra_scalar_mul_internal)
+- [Function `serialize_internal`](#0x1_crypto_algebra_serialize_internal)
+- [Function `sqr_internal`](#0x1_crypto_algebra_sqr_internal)
+- [Function `sub_internal`](#0x1_crypto_algebra_sub_internal)
+- [Function `upcast_internal`](#0x1_crypto_algebra_upcast_internal)
+- [Function `zero_internal`](#0x1_crypto_algebra_zero_internal)
+- [Specification](#@Specification_1)
+ - [Function `handles_from_elements`](#@Specification_1_handles_from_elements)
+ - [Function `add_internal`](#@Specification_1_add_internal)
+ - [Function `deserialize_internal`](#@Specification_1_deserialize_internal)
+ - [Function `div_internal`](#@Specification_1_div_internal)
+ - [Function `double_internal`](#@Specification_1_double_internal)
+ - [Function `downcast_internal`](#@Specification_1_downcast_internal)
+ - [Function `from_u64_internal`](#@Specification_1_from_u64_internal)
+ - [Function `eq_internal`](#@Specification_1_eq_internal)
+ - [Function `hash_to_internal`](#@Specification_1_hash_to_internal)
+ - [Function `inv_internal`](#@Specification_1_inv_internal)
+ - [Function `mul_internal`](#@Specification_1_mul_internal)
+ - [Function `multi_pairing_internal`](#@Specification_1_multi_pairing_internal)
+ - [Function `multi_scalar_mul_internal`](#@Specification_1_multi_scalar_mul_internal)
+ - [Function `neg_internal`](#@Specification_1_neg_internal)
+ - [Function `one_internal`](#@Specification_1_one_internal)
+ - [Function `order_internal`](#@Specification_1_order_internal)
+ - [Function `pairing_internal`](#@Specification_1_pairing_internal)
+ - [Function `scalar_mul_internal`](#@Specification_1_scalar_mul_internal)
+ - [Function `serialize_internal`](#@Specification_1_serialize_internal)
+ - [Function `sqr_internal`](#@Specification_1_sqr_internal)
+ - [Function `sub_internal`](#@Specification_1_sub_internal)
+ - [Function `upcast_internal`](#@Specification_1_upcast_internal)
+ - [Function `zero_internal`](#@Specification_1_zero_internal)
+
+
+use 0x1::error;
+use 0x1::features;
+use 0x1::option;
+
+
+
+
+
+
+## Struct `Element`
+
+This struct represents an element of a structure S
.
+
+
+struct Element<S> has copy, drop
+
+
+
+
+handle: u64
+const E_NON_EQUAL_LENGTHS: u64 = 2;
+
+
+
+
+
+
+
+
+const E_NOT_IMPLEMENTED: u64 = 1;
+
+
+
+
+
+
+
+
+const E_TOO_MUCH_MEMORY_USED: u64 = 3;
+
+
+
+
+
+
+## Function `eq`
+
+Check if x == y
for elements x
and y
of a structure S
.
+
+
+public fun eq<S>(x: &crypto_algebra::Element<S>, y: &crypto_algebra::Element<S>): bool
+
+
+
+
+public fun eq<S>(x: &Element<S>, y: &Element<S>): bool {
+ abort_unless_cryptography_algebra_natives_enabled();
+ eq_internal<S>(x.handle, y.handle)
+}
+
+
+
+
+S
.
+
+
+public fun from_u64<S>(value: u64): crypto_algebra::Element<S>
+
+
+
+
+public fun from_u64<S>(value: u64): Element<S> {
+ abort_unless_cryptography_algebra_natives_enabled();
+ Element<S> {
+ handle: from_u64_internal<S>(value)
+ }
+}
+
+
+
+
+S
, or the identity of group S
.
+
+
+public fun zero<S>(): crypto_algebra::Element<S>
+
+
+
+
+public fun zero<S>(): Element<S> {
+ abort_unless_cryptography_algebra_natives_enabled();
+ Element<S> {
+ handle: zero_internal<S>()
+ }
+}
+
+
+
+
+S
, or a fixed generator of group S
.
+
+
+public fun one<S>(): crypto_algebra::Element<S>
+
+
+
+
+public fun one<S>(): Element<S> {
+ abort_unless_cryptography_algebra_natives_enabled();
+ Element<S> {
+ handle: one_internal<S>()
+ }
+}
+
+
+
+
+-x
for an element x
of a structure S
.
+
+
+public fun neg<S>(x: &crypto_algebra::Element<S>): crypto_algebra::Element<S>
+
+
+
+
+public fun neg<S>(x: &Element<S>): Element<S> {
+ abort_unless_cryptography_algebra_natives_enabled();
+ Element<S> {
+ handle: neg_internal<S>(x.handle)
+ }
+}
+
+
+
+
+x + y
for elements x
and y
of structure S
.
+
+
+public fun add<S>(x: &crypto_algebra::Element<S>, y: &crypto_algebra::Element<S>): crypto_algebra::Element<S>
+
+
+
+
+public fun add<S>(x: &Element<S>, y: &Element<S>): Element<S> {
+ abort_unless_cryptography_algebra_natives_enabled();
+ Element<S> {
+ handle: add_internal<S>(x.handle, y.handle)
+ }
+}
+
+
+
+
+x - y
for elements x
and y
of a structure S
.
+
+
+public fun sub<S>(x: &crypto_algebra::Element<S>, y: &crypto_algebra::Element<S>): crypto_algebra::Element<S>
+
+
+
+
+public fun sub<S>(x: &Element<S>, y: &Element<S>): Element<S> {
+ abort_unless_cryptography_algebra_natives_enabled();
+ Element<S> {
+ handle: sub_internal<S>(x.handle, y.handle)
+ }
+}
+
+
+
+
+x * y
for elements x
and y
of a structure S
.
+
+
+public fun mul<S>(x: &crypto_algebra::Element<S>, y: &crypto_algebra::Element<S>): crypto_algebra::Element<S>
+
+
+
+
+public fun mul<S>(x: &Element<S>, y: &Element<S>): Element<S> {
+ abort_unless_cryptography_algebra_natives_enabled();
+ Element<S> {
+ handle: mul_internal<S>(x.handle, y.handle)
+ }
+}
+
+
+
+
+x / y
for elements x
and y
of a structure S
.
+Return none if y
does not have a multiplicative inverse in the structure S
+(e.g., when S
is a field, and y
is zero).
+
+
+public fun div<S>(x: &crypto_algebra::Element<S>, y: &crypto_algebra::Element<S>): option::Option<crypto_algebra::Element<S>>
+
+
+
+
+public fun div<S>(x: &Element<S>, y: &Element<S>): Option<Element<S>> {
+ abort_unless_cryptography_algebra_natives_enabled();
+ let (succ, handle) = div_internal<S>(x.handle, y.handle);
+ if (succ) {
+ some(Element<S> { handle })
+ } else {
+ none()
+ }
+}
+
+
+
+
+x^2
for an element x
of a structure S
. Faster and cheaper than mul(x, x)
.
+
+
+public fun sqr<S>(x: &crypto_algebra::Element<S>): crypto_algebra::Element<S>
+
+
+
+
+public fun sqr<S>(x: &Element<S>): Element<S> {
+ abort_unless_cryptography_algebra_natives_enabled();
+ Element<S> {
+ handle: sqr_internal<S>(x.handle)
+ }
+}
+
+
+
+
+x^(-1)
for an element x
of a structure S
.
+Return none if x
does not have a multiplicative inverse in the structure S
+(e.g., when S
is a field, and x
is zero).
+
+
+public fun inv<S>(x: &crypto_algebra::Element<S>): option::Option<crypto_algebra::Element<S>>
+
+
+
+
+public fun inv<S>(x: &Element<S>): Option<Element<S>> {
+ abort_unless_cryptography_algebra_natives_enabled();
+ let (succeeded, handle) = inv_internal<S>(x.handle);
+ if (succeeded) {
+ let scalar = Element<S> { handle };
+ some(scalar)
+ } else {
+ none()
+ }
+}
+
+
+
+
+2*P
for an element P
of a structure S
. Faster and cheaper than add(P, P)
.
+
+
+public fun double<S>(element_p: &crypto_algebra::Element<S>): crypto_algebra::Element<S>
+
+
+
+
+public fun double<S>(element_p: &Element<S>): Element<S> {
+ abort_unless_cryptography_algebra_natives_enabled();
+ Element<S> {
+ handle: double_internal<S>(element_p.handle)
+ }
+}
+
+
+
+
+k[0]*P[0]+...+k[n-1]*P[n-1]
, where
+P[]
are n
elements of group G
represented by parameter elements
, and
+k[]
are n
elements of the scalarfield S
of group G
represented by parameter scalars
.
+
+Abort with code std::error::invalid_argument(E_NON_EQUAL_LENGTHS)
if the sizes of elements
and scalars
do not match.
+
+
+public fun multi_scalar_mul<G, S>(elements: &vector<crypto_algebra::Element<G>>, scalars: &vector<crypto_algebra::Element<S>>): crypto_algebra::Element<G>
+
+
+
+
+public fun multi_scalar_mul<G, S>(elements: &vector<Element<G>>, scalars: &vector<Element<S>>): Element<G> {
+ let element_handles = handles_from_elements(elements);
+ let scalar_handles = handles_from_elements(scalars);
+ Element<G> {
+ handle: multi_scalar_mul_internal<G, S>(element_handles, scalar_handles)
+ }
+}
+
+
+
+
+k*P
, where P
is an element of a group G
and k
is an element of the scalar field S
associated to the group G
.
+
+
+public fun scalar_mul<G, S>(element_p: &crypto_algebra::Element<G>, scalar_k: &crypto_algebra::Element<S>): crypto_algebra::Element<G>
+
+
+
+
+public fun scalar_mul<G, S>(element_p: &Element<G>, scalar_k: &Element<S>): Element<G> {
+ abort_unless_cryptography_algebra_natives_enabled();
+ Element<G> {
+ handle: scalar_mul_internal<G, S>(element_p.handle, scalar_k.handle)
+ }
+}
+
+
+
+
+e(P[0],Q[0])+...+e(P[n-1],Q[n-1])
,
+where e: (G1,G2) -> (Gt)
is the pairing function from groups (G1,G2)
to group Gt
,
+P[]
are n
elements of group G1
represented by parameter g1_elements
, and
+Q[]
are n
elements of group G2
represented by parameter g2_elements
.
+
+Abort with code std::error::invalid_argument(E_NON_EQUAL_LENGTHS)
if the sizes of g1_elements
and g2_elements
do not match.
+
+NOTE: we are viewing the target group Gt
of the pairing as an additive group,
+rather than a multiplicative one (which is typically the case).
+
+
+public fun multi_pairing<G1, G2, Gt>(g1_elements: &vector<crypto_algebra::Element<G1>>, g2_elements: &vector<crypto_algebra::Element<G2>>): crypto_algebra::Element<Gt>
+
+
+
+
+public fun multi_pairing<G1,G2,Gt>(g1_elements: &vector<Element<G1>>, g2_elements: &vector<Element<G2>>): Element<Gt> {
+ abort_unless_cryptography_algebra_natives_enabled();
+ let g1_handles = handles_from_elements(g1_elements);
+ let g2_handles = handles_from_elements(g2_elements);
+ Element<Gt> {
+ handle: multi_pairing_internal<G1,G2,Gt>(g1_handles, g2_handles)
+ }
+}
+
+
+
+
+G1
element and a G2
element.
+Return an element in the target group Gt
.
+
+
+public fun pairing<G1, G2, Gt>(element_1: &crypto_algebra::Element<G1>, element_2: &crypto_algebra::Element<G2>): crypto_algebra::Element<Gt>
+
+
+
+
+public fun pairing<G1,G2,Gt>(element_1: &Element<G1>, element_2: &Element<G2>): Element<Gt> {
+ abort_unless_cryptography_algebra_natives_enabled();
+ Element<Gt> {
+ handle: pairing_internal<G1,G2,Gt>(element_1.handle, element_2.handle)
+ }
+}
+
+
+
+
+S
using a given serialization format F
.
+Return none if the deserialization failed.
+
+
+public fun deserialize<S, F>(bytes: &vector<u8>): option::Option<crypto_algebra::Element<S>>
+
+
+
+
+public fun deserialize<S, F>(bytes: &vector<u8>): Option<Element<S>> {
+ abort_unless_cryptography_algebra_natives_enabled();
+ let (succeeded, handle) = deserialize_internal<S, F>(bytes);
+ if (succeeded) {
+ some(Element<S> { handle })
+ } else {
+ none()
+ }
+}
+
+
+
+
+S
to a byte array using a given serialization format F
.
+
+
+public fun serialize<S, F>(element: &crypto_algebra::Element<S>): vector<u8>
+
+
+
+
+public fun serialize<S, F>(element: &Element<S>): vector<u8> {
+ abort_unless_cryptography_algebra_natives_enabled();
+ serialize_internal<S, F>(element.handle)
+}
+
+
+
+
+S
, a big integer little-endian encoded as a byte array.
+
+
+public fun order<S>(): vector<u8>
+
+
+
+
+public fun order<S>(): vector<u8> {
+ abort_unless_cryptography_algebra_natives_enabled();
+ order_internal<S>()
+}
+
+
+
+
+S
to a parent structure L
.
+
+
+public fun upcast<S, L>(element: &crypto_algebra::Element<S>): crypto_algebra::Element<L>
+
+
+
+
+public fun upcast<S,L>(element: &Element<S>): Element<L> {
+ abort_unless_cryptography_algebra_natives_enabled();
+ Element<L> {
+ handle: upcast_internal<S,L>(element.handle)
+ }
+}
+
+
+
+
+x
of a structure L
to a sub-structure S
.
+Return none if x
is not a member of S
.
+
+NOTE: Membership check in S
is performed inside, which can be expensive, depending on the structures L
and S
.
+
+
+public fun downcast<L, S>(element_x: &crypto_algebra::Element<L>): option::Option<crypto_algebra::Element<S>>
+
+
+
+
+public fun downcast<L,S>(element_x: &Element<L>): Option<Element<S>> {
+ abort_unless_cryptography_algebra_natives_enabled();
+ let (succ, new_handle) = downcast_internal<L,S>(element_x.handle);
+ if (succ) {
+ some(Element<S> { handle: new_handle })
+ } else {
+ none()
+ }
+}
+
+
+
+
+msg
into structure S
with a domain separation tag dst
+using the given hash-to-structure suite H
.
+
+NOTE: some hashing methods do not accept a dst
and will abort if a non-empty one is provided.
+
+
+public fun hash_to<S, H>(dst: &vector<u8>, msg: &vector<u8>): crypto_algebra::Element<S>
+
+
+
+
+public fun hash_to<S, H>(dst: &vector<u8>, msg: &vector<u8>): Element<S> {
+ abort_unless_cryptography_algebra_natives_enabled();
+ Element {
+ handle: hash_to_internal<S, H>(dst, msg)
+ }
+}
+
+
+
+
+fun abort_unless_cryptography_algebra_natives_enabled()
+
+
+
+
+fun abort_unless_cryptography_algebra_natives_enabled() {
+ if (features::cryptography_algebra_enabled()) return;
+ abort(std::error::not_implemented(0))
+}
+
+
+
+
+fun handles_from_elements<S>(elements: &vector<crypto_algebra::Element<S>>): vector<u64>
+
+
+
+
+fun handles_from_elements<S>(elements: &vector<Element<S>>): vector<u64> {
+ let num_elements = std::vector::length(elements);
+ let element_handles = std::vector::empty();
+ let i = 0;
+ while ({
+ spec {
+ invariant len(element_handles) == i;
+ invariant forall k in 0..i: element_handles[k] == elements[k].handle;
+ };
+ i < num_elements
+ }) {
+ std::vector::push_back(&mut element_handles, std::vector::borrow(elements, i).handle);
+ i = i + 1;
+ };
+ element_handles
+}
+
+
+
+
+fun add_internal<S>(handle_1: u64, handle_2: u64): u64
+
+
+
+
+native fun add_internal<S>(handle_1: u64, handle_2: u64): u64;
+
+
+
+
+fun deserialize_internal<S, F>(bytes: &vector<u8>): (bool, u64)
+
+
+
+
+native fun deserialize_internal<S, F>(bytes: &vector<u8>): (bool, u64);
+
+
+
+
+fun div_internal<F>(handle_1: u64, handle_2: u64): (bool, u64)
+
+
+
+
+native fun div_internal<F>(handle_1: u64, handle_2: u64): (bool, u64);
+
+
+
+
+fun double_internal<G>(element_handle: u64): u64
+
+
+
+
+native fun double_internal<G>(element_handle: u64): u64;
+
+
+
+
+fun downcast_internal<L, S>(handle: u64): (bool, u64)
+
+
+
+
+native fun downcast_internal<L,S>(handle: u64): (bool, u64);
+
+
+
+
+fun from_u64_internal<S>(value: u64): u64
+
+
+
+
+native fun from_u64_internal<S>(value: u64): u64;
+
+
+
+
+fun eq_internal<S>(handle_1: u64, handle_2: u64): bool
+
+
+
+
+native fun eq_internal<S>(handle_1: u64, handle_2: u64): bool;
+
+
+
+
+fun hash_to_internal<S, H>(dst: &vector<u8>, bytes: &vector<u8>): u64
+
+
+
+
+native fun hash_to_internal<S, H>(dst: &vector<u8>, bytes: &vector<u8>): u64;
+
+
+
+
+fun inv_internal<F>(handle: u64): (bool, u64)
+
+
+
+
+native fun inv_internal<F>(handle: u64): (bool, u64);
+
+
+
+
+fun mul_internal<F>(handle_1: u64, handle_2: u64): u64
+
+
+
+
+native fun mul_internal<F>(handle_1: u64, handle_2: u64): u64;
+
+
+
+
+fun multi_pairing_internal<G1, G2, Gt>(g1_handles: vector<u64>, g2_handles: vector<u64>): u64
+
+
+
+
+native fun multi_pairing_internal<G1,G2,Gt>(g1_handles: vector<u64>, g2_handles: vector<u64>): u64;
+
+
+
+
+fun multi_scalar_mul_internal<G, S>(element_handles: vector<u64>, scalar_handles: vector<u64>): u64
+
+
+
+
+native fun multi_scalar_mul_internal<G, S>(element_handles: vector<u64>, scalar_handles: vector<u64>): u64;
+
+
+
+
+fun neg_internal<F>(handle: u64): u64
+
+
+
+
+native fun neg_internal<F>(handle: u64): u64;
+
+
+
+
+fun one_internal<S>(): u64
+
+
+
+
+native fun one_internal<S>(): u64;
+
+
+
+
+fun order_internal<G>(): vector<u8>
+
+
+
+
+native fun order_internal<G>(): vector<u8>;
+
+
+
+
+fun pairing_internal<G1, G2, Gt>(g1_handle: u64, g2_handle: u64): u64
+
+
+
+
+native fun pairing_internal<G1,G2,Gt>(g1_handle: u64, g2_handle: u64): u64;
+
+
+
+
+fun scalar_mul_internal<G, S>(element_handle: u64, scalar_handle: u64): u64
+
+
+
+
+native fun scalar_mul_internal<G, S>(element_handle: u64, scalar_handle: u64): u64;
+
+
+
+
+fun serialize_internal<S, F>(handle: u64): vector<u8>
+
+
+
+
+native fun serialize_internal<S, F>(handle: u64): vector<u8>;
+
+
+
+
+fun sqr_internal<G>(handle: u64): u64
+
+
+
+
+native fun sqr_internal<G>(handle: u64): u64;
+
+
+
+
+fun sub_internal<G>(handle_1: u64, handle_2: u64): u64
+
+
+
+
+native fun sub_internal<G>(handle_1: u64, handle_2: u64): u64;
+
+
+
+
+fun upcast_internal<S, L>(handle: u64): u64
+
+
+
+
+native fun upcast_internal<S,L>(handle: u64): u64;
+
+
+
+
+fun zero_internal<S>(): u64
+
+
+
+
+native fun zero_internal<S>(): u64;
+
+
+
+
+fun handles_from_elements<S>(elements: &vector<crypto_algebra::Element<S>>): vector<u64>
+
+
+
+
+
+aborts_if false;
+ensures forall i in 0..len(elements): result[i] == elements[i].handle;
+
+
+
+
+
+
+### Function `add_internal`
+
+
+fun add_internal<S>(handle_1: u64, handle_2: u64): u64
+
+
+
+
+
+pragma opaque;
+
+
+
+
+
+
+### Function `deserialize_internal`
+
+
+fun deserialize_internal<S, F>(bytes: &vector<u8>): (bool, u64)
+
+
+
+
+
+pragma opaque;
+
+
+
+
+
+
+### Function `div_internal`
+
+
+fun div_internal<F>(handle_1: u64, handle_2: u64): (bool, u64)
+
+
+
+
+
+pragma opaque;
+
+
+
+
+
+
+### Function `double_internal`
+
+
+fun double_internal<G>(element_handle: u64): u64
+
+
+
+
+
+pragma opaque;
+
+
+
+
+
+
+### Function `downcast_internal`
+
+
+fun downcast_internal<L, S>(handle: u64): (bool, u64)
+
+
+
+
+
+pragma opaque;
+
+
+
+
+
+
+### Function `from_u64_internal`
+
+
+fun from_u64_internal<S>(value: u64): u64
+
+
+
+
+
+pragma opaque;
+
+
+
+
+
+
+### Function `eq_internal`
+
+
+fun eq_internal<S>(handle_1: u64, handle_2: u64): bool
+
+
+
+
+
+pragma opaque;
+
+
+
+
+
+
+### Function `hash_to_internal`
+
+
+fun hash_to_internal<S, H>(dst: &vector<u8>, bytes: &vector<u8>): u64
+
+
+
+
+
+pragma opaque;
+
+
+
+
+
+
+### Function `inv_internal`
+
+
+fun inv_internal<F>(handle: u64): (bool, u64)
+
+
+
+
+
+pragma opaque;
+
+
+
+
+
+
+### Function `mul_internal`
+
+
+fun mul_internal<F>(handle_1: u64, handle_2: u64): u64
+
+
+
+
+
+pragma opaque;
+
+
+
+
+
+
+### Function `multi_pairing_internal`
+
+
+fun multi_pairing_internal<G1, G2, Gt>(g1_handles: vector<u64>, g2_handles: vector<u64>): u64
+
+
+
+
+
+pragma opaque;
+
+
+
+
+
+
+### Function `multi_scalar_mul_internal`
+
+
+fun multi_scalar_mul_internal<G, S>(element_handles: vector<u64>, scalar_handles: vector<u64>): u64
+
+
+
+
+
+pragma opaque;
+
+
+
+
+
+
+### Function `neg_internal`
+
+
+fun neg_internal<F>(handle: u64): u64
+
+
+
+
+
+pragma opaque;
+
+
+
+
+
+
+### Function `one_internal`
+
+
+fun one_internal<S>(): u64
+
+
+
+
+
+pragma opaque;
+
+
+
+
+
+
+### Function `order_internal`
+
+
+fun order_internal<G>(): vector<u8>
+
+
+
+
+
+pragma opaque;
+
+
+
+
+
+
+### Function `pairing_internal`
+
+
+fun pairing_internal<G1, G2, Gt>(g1_handle: u64, g2_handle: u64): u64
+
+
+
+
+
+pragma opaque;
+
+
+
+
+
+
+### Function `scalar_mul_internal`
+
+
+fun scalar_mul_internal<G, S>(element_handle: u64, scalar_handle: u64): u64
+
+
+
+
+
+pragma opaque;
+
+
+
+
+
+
+### Function `serialize_internal`
+
+
+fun serialize_internal<S, F>(handle: u64): vector<u8>
+
+
+
+
+
+pragma opaque;
+
+
+
+
+
+
+### Function `sqr_internal`
+
+
+fun sqr_internal<G>(handle: u64): u64
+
+
+
+
+
+pragma opaque;
+
+
+
+
+
+
+### Function `sub_internal`
+
+
+fun sub_internal<G>(handle_1: u64, handle_2: u64): u64
+
+
+
+
+
+pragma opaque;
+
+
+
+
+
+
+### Function `upcast_internal`
+
+
+fun upcast_internal<S, L>(handle: u64): u64
+
+
+
+
+
+pragma opaque;
+
+
+
+
+
+
+### Function `zero_internal`
+
+
+fun zero_internal<S>(): u64
+
+
+
+
+
+pragma opaque;
+
+
+
+[move-book]: https://aptos.dev/move/book/SUMMARY
diff --git a/aptos-move/framework/aptos-stdlib/tests/compiler-v2-doc/debug.md b/aptos-move/framework/aptos-stdlib/tests/compiler-v2-doc/debug.md
new file mode 100644
index 0000000000000..3fe59b4b97ca4
--- /dev/null
+++ b/aptos-move/framework/aptos-stdlib/tests/compiler-v2-doc/debug.md
@@ -0,0 +1,237 @@
+
+
+
+# Module `0x1::debug`
+
+Module providing debug functionality.
+
+
+- [Constants](#@Constants_0)
+- [Function `print`](#0x1_debug_print)
+- [Function `print_stack_trace`](#0x1_debug_print_stack_trace)
+- [Function `format`](#0x1_debug_format)
+- [Function `native_print`](#0x1_debug_native_print)
+- [Function `native_stack_trace`](#0x1_debug_native_stack_trace)
+- [Specification](#@Specification_1)
+ - [Function `print`](#@Specification_1_print)
+ - [Function `print_stack_trace`](#@Specification_1_print_stack_trace)
+ - [Function `native_print`](#@Specification_1_native_print)
+ - [Function `native_stack_trace`](#@Specification_1_native_stack_trace)
+
+
+use 0x1::string;
+use 0x1::string_utils;
+
+
+
+
+
+
+## Constants
+
+
+
+
+
+
+const MSG_1: vector<u8> = [97, 98, 99, 100, 101, 102];
+
+
+
+
+
+
+
+
+const MSG_2: vector<u8> = [49, 50, 51, 52, 53, 54];
+
+
+
+
+
+
+## Function `print`
+
+
+
+public fun print<T>(x: &T)
+
+
+
+
+public fun print<T>(x: &T) {
+ native_print(format(x));
+}
+
+
+
+
+public fun print_stack_trace()
+
+
+
+
+public fun print_stack_trace() {
+ native_print(native_stack_trace());
+}
+
+
+
+
+fun format<T>(x: &T): string::String
+
+
+
+
+inline fun format<T>(x: &T): String {
+ aptos_std::string_utils::debug_string(x)
+}
+
+
+
+
+fun native_print(x: string::String)
+
+
+
+
+native fun native_print(x: String);
+
+
+
+
+fun native_stack_trace(): string::String
+
+
+
+
+native fun native_stack_trace(): String;
+
+
+
+
+public fun print<T>(x: &T)
+
+
+
+
+
+aborts_if false;
+
+
+
+
+
+
+### Function `print_stack_trace`
+
+
+public fun print_stack_trace()
+
+
+
+
+
+aborts_if false;
+
+
+
+
+
+
+### Function `native_print`
+
+
+fun native_print(x: string::String)
+
+
+
+
+
+pragma opaque;
+aborts_if false;
+
+
+
+
+
+
+### Function `native_stack_trace`
+
+
+fun native_stack_trace(): string::String
+
+
+
+
+
+pragma opaque;
+aborts_if false;
+
+
+
+[move-book]: https://aptos.dev/move/book/SUMMARY
diff --git a/aptos-move/framework/aptos-stdlib/tests/compiler-v2-doc/ed25519.md b/aptos-move/framework/aptos-stdlib/tests/compiler-v2-doc/ed25519.md
new file mode 100644
index 0000000000000..5122e741ba6a6
--- /dev/null
+++ b/aptos-move/framework/aptos-stdlib/tests/compiler-v2-doc/ed25519.md
@@ -0,0 +1,876 @@
+
+
+
+# Module `0x1::ed25519`
+
+Contains functions for:
+
+1. [Ed25519](https://en.wikipedia.org/wiki/EdDSA#Ed25519) digital signatures: i.e., EdDSA signatures over Edwards25519 curves with co-factor 8
+
+
+- [Struct `SignedMessage`](#0x1_ed25519_SignedMessage)
+- [Struct `UnvalidatedPublicKey`](#0x1_ed25519_UnvalidatedPublicKey)
+- [Struct `ValidatedPublicKey`](#0x1_ed25519_ValidatedPublicKey)
+- [Struct `Signature`](#0x1_ed25519_Signature)
+- [Constants](#@Constants_0)
+- [Function `new_unvalidated_public_key_from_bytes`](#0x1_ed25519_new_unvalidated_public_key_from_bytes)
+- [Function `new_validated_public_key_from_bytes`](#0x1_ed25519_new_validated_public_key_from_bytes)
+- [Function `new_signature_from_bytes`](#0x1_ed25519_new_signature_from_bytes)
+- [Function `public_key_to_unvalidated`](#0x1_ed25519_public_key_to_unvalidated)
+- [Function `public_key_into_unvalidated`](#0x1_ed25519_public_key_into_unvalidated)
+- [Function `unvalidated_public_key_to_bytes`](#0x1_ed25519_unvalidated_public_key_to_bytes)
+- [Function `validated_public_key_to_bytes`](#0x1_ed25519_validated_public_key_to_bytes)
+- [Function `signature_to_bytes`](#0x1_ed25519_signature_to_bytes)
+- [Function `public_key_validate`](#0x1_ed25519_public_key_validate)
+- [Function `signature_verify_strict`](#0x1_ed25519_signature_verify_strict)
+- [Function `signature_verify_strict_t`](#0x1_ed25519_signature_verify_strict_t)
+- [Function `new_signed_message`](#0x1_ed25519_new_signed_message)
+- [Function `unvalidated_public_key_to_authentication_key`](#0x1_ed25519_unvalidated_public_key_to_authentication_key)
+- [Function `validated_public_key_to_authentication_key`](#0x1_ed25519_validated_public_key_to_authentication_key)
+- [Function `public_key_bytes_to_authentication_key`](#0x1_ed25519_public_key_bytes_to_authentication_key)
+- [Function `public_key_validate_internal`](#0x1_ed25519_public_key_validate_internal)
+- [Function `signature_verify_strict_internal`](#0x1_ed25519_signature_verify_strict_internal)
+- [Specification](#@Specification_1)
+ - [Function `new_unvalidated_public_key_from_bytes`](#@Specification_1_new_unvalidated_public_key_from_bytes)
+ - [Function `new_validated_public_key_from_bytes`](#@Specification_1_new_validated_public_key_from_bytes)
+ - [Function `new_signature_from_bytes`](#@Specification_1_new_signature_from_bytes)
+ - [Function `public_key_bytes_to_authentication_key`](#@Specification_1_public_key_bytes_to_authentication_key)
+ - [Function `public_key_validate_internal`](#@Specification_1_public_key_validate_internal)
+ - [Function `signature_verify_strict_internal`](#@Specification_1_signature_verify_strict_internal)
+ - [Helper functions](#@Helper_functions_2)
+
+
+use 0x1::bcs;
+use 0x1::error;
+use 0x1::hash;
+use 0x1::option;
+use 0x1::type_info;
+
+
+
+
+
+
+## Struct `SignedMessage`
+
+A BCS-serializable message, which one can verify signatures on via signature_verify_strict_t
+
+
+struct SignedMessage<MessageType> has drop
+
+
+
+
+type_info: type_info::TypeInfo
+inner: MessageType
+struct UnvalidatedPublicKey has copy, drop, store
+
+
+
+
+bytes: vector<u8>
+struct ValidatedPublicKey has copy, drop, store
+
+
+
+
+bytes: vector<u8>
+signature_verify_strict
or signature_verify_strict_t
.
+
+
+struct Signature has copy, drop, store
+
+
+
+
+bytes: vector<u8>
+const PUBLIC_KEY_NUM_BYTES: u64 = 32;
+
+
+
+
+
+
+Wrong number of bytes were given as input when deserializing an Ed25519 public key.
+
+
+const E_WRONG_PUBKEY_SIZE: u64 = 1;
+
+
+
+
+
+
+Wrong number of bytes were given as input when deserializing an Ed25519 signature.
+
+
+const E_WRONG_SIGNATURE_SIZE: u64 = 2;
+
+
+
+
+
+
+The size of a serialized signature, in bytes.
+
+
+const SIGNATURE_NUM_BYTES: u64 = 64;
+
+
+
+
+
+
+The identifier of the Ed25519 signature scheme, which is used when deriving Aptos authentication keys by hashing
+it together with an Ed25519 public key.
+
+
+const SIGNATURE_SCHEME_ID: u8 = 0;
+
+
+
+
+
+
+## Function `new_unvalidated_public_key_from_bytes`
+
+Parses the input 32 bytes as an *unvalidated* Ed25519 public key.
+
+
+public fun new_unvalidated_public_key_from_bytes(bytes: vector<u8>): ed25519::UnvalidatedPublicKey
+
+
+
+
+public fun new_unvalidated_public_key_from_bytes(bytes: vector<u8>): UnvalidatedPublicKey {
+ assert!(std::vector::length(&bytes) == PUBLIC_KEY_NUM_BYTES, std::error::invalid_argument(E_WRONG_PUBKEY_SIZE));
+ UnvalidatedPublicKey { bytes }
+}
+
+
+
+
+public fun new_validated_public_key_from_bytes(bytes: vector<u8>): option::Option<ed25519::ValidatedPublicKey>
+
+
+
+
+public fun new_validated_public_key_from_bytes(bytes: vector<u8>): Option<ValidatedPublicKey> {
+ if (public_key_validate_internal(bytes)) {
+ option::some(ValidatedPublicKey {
+ bytes
+ })
+ } else {
+ option::none<ValidatedPublicKey>()
+ }
+}
+
+
+
+
+public fun new_signature_from_bytes(bytes: vector<u8>): ed25519::Signature
+
+
+
+
+public fun new_signature_from_bytes(bytes: vector<u8>): Signature {
+ assert!(std::vector::length(&bytes) == SIGNATURE_NUM_BYTES, std::error::invalid_argument(E_WRONG_SIGNATURE_SIZE));
+ Signature { bytes }
+}
+
+
+
+
+public fun public_key_to_unvalidated(pk: &ed25519::ValidatedPublicKey): ed25519::UnvalidatedPublicKey
+
+
+
+
+public fun public_key_to_unvalidated(pk: &ValidatedPublicKey): UnvalidatedPublicKey {
+ UnvalidatedPublicKey {
+ bytes: pk.bytes
+ }
+}
+
+
+
+
+public fun public_key_into_unvalidated(pk: ed25519::ValidatedPublicKey): ed25519::UnvalidatedPublicKey
+
+
+
+
+public fun public_key_into_unvalidated(pk: ValidatedPublicKey): UnvalidatedPublicKey {
+ UnvalidatedPublicKey {
+ bytes: pk.bytes
+ }
+}
+
+
+
+
+public fun unvalidated_public_key_to_bytes(pk: &ed25519::UnvalidatedPublicKey): vector<u8>
+
+
+
+
+public fun unvalidated_public_key_to_bytes(pk: &UnvalidatedPublicKey): vector<u8> {
+ pk.bytes
+}
+
+
+
+
+public fun validated_public_key_to_bytes(pk: &ed25519::ValidatedPublicKey): vector<u8>
+
+
+
+
+public fun validated_public_key_to_bytes(pk: &ValidatedPublicKey): vector<u8> {
+ pk.bytes
+}
+
+
+
+
+public fun signature_to_bytes(sig: &ed25519::Signature): vector<u8>
+
+
+
+
+public fun signature_to_bytes(sig: &Signature): vector<u8> {
+ sig.bytes
+}
+
+
+
+
+Some(ValidatedPublicKey)
if successful and None
otherwise.
+
+
+public fun public_key_validate(pk: &ed25519::UnvalidatedPublicKey): option::Option<ed25519::ValidatedPublicKey>
+
+
+
+
+public fun public_key_validate(pk: &UnvalidatedPublicKey): Option<ValidatedPublicKey> {
+ new_validated_public_key_from_bytes(pk.bytes)
+}
+
+
+
+
+signature
under an *unvalidated* public_key
on the specified message
.
+This call will validate the public key by checking it is NOT in the small subgroup.
+
+
+public fun signature_verify_strict(signature: &ed25519::Signature, public_key: &ed25519::UnvalidatedPublicKey, message: vector<u8>): bool
+
+
+
+
+public fun signature_verify_strict(
+ signature: &Signature,
+ public_key: &UnvalidatedPublicKey,
+ message: vector<u8>
+): bool {
+ signature_verify_strict_internal(signature.bytes, public_key.bytes, message)
+}
+
+
+
+
+public fun signature_verify_strict_t<T: drop>(signature: &ed25519::Signature, public_key: &ed25519::UnvalidatedPublicKey, data: T): bool
+
+
+
+
+public fun signature_verify_strict_t<T: drop>(signature: &Signature, public_key: &UnvalidatedPublicKey, data: T): bool {
+ let encoded = SignedMessage {
+ type_info: type_info::type_of<T>(),
+ inner: data,
+ };
+
+ signature_verify_strict_internal(signature.bytes, public_key.bytes, bcs::to_bytes(&encoded))
+}
+
+
+
+
+public fun new_signed_message<T: drop>(data: T): ed25519::SignedMessage<T>
+
+
+
+
+public fun new_signed_message<T: drop>(data: T): SignedMessage<T> {
+ SignedMessage {
+ type_info: type_info::type_of<T>(),
+ inner: data,
+ }
+}
+
+
+
+
+public fun unvalidated_public_key_to_authentication_key(pk: &ed25519::UnvalidatedPublicKey): vector<u8>
+
+
+
+
+public fun unvalidated_public_key_to_authentication_key(pk: &UnvalidatedPublicKey): vector<u8> {
+ public_key_bytes_to_authentication_key(pk.bytes)
+}
+
+
+
+
+public fun validated_public_key_to_authentication_key(pk: &ed25519::ValidatedPublicKey): vector<u8>
+
+
+
+
+public fun validated_public_key_to_authentication_key(pk: &ValidatedPublicKey): vector<u8> {
+ public_key_bytes_to_authentication_key(pk.bytes)
+}
+
+
+
+
+fun public_key_bytes_to_authentication_key(pk_bytes: vector<u8>): vector<u8>
+
+
+
+
+fun public_key_bytes_to_authentication_key(pk_bytes: vector<u8>): vector<u8> {
+ std::vector::push_back(&mut pk_bytes, SIGNATURE_SCHEME_ID);
+ std::hash::sha3_256(pk_bytes)
+}
+
+
+
+
+true
if the bytes in public_key
can be parsed as a valid Ed25519 public key: i.e., it passes
+points-on-curve and not-in-small-subgroup checks.
+Returns false
otherwise.
+
+
+fun public_key_validate_internal(bytes: vector<u8>): bool
+
+
+
+
+native fun public_key_validate_internal(bytes: vector<u8>): bool;
+
+
+
+
+signature
on message
verifies against the Ed25519 public_key
.
+Returns false
if either:
+- signature
or public key
are of wrong sizes
+- public_key
does not pass points-on-curve or not-in-small-subgroup checks,
+- signature
does not pass points-on-curve or not-in-small-subgroup checks,
+- the signature on message
does not verify.
+
+
+fun signature_verify_strict_internal(signature: vector<u8>, public_key: vector<u8>, message: vector<u8>): bool
+
+
+
+
+native fun signature_verify_strict_internal(
+ signature: vector<u8>,
+ public_key: vector<u8>,
+ message: vector<u8>
+): bool;
+
+
+
+
+public fun new_unvalidated_public_key_from_bytes(bytes: vector<u8>): ed25519::UnvalidatedPublicKey
+
+
+
+
+
+include NewUnvalidatedPublicKeyFromBytesAbortsIf;
+ensures result == UnvalidatedPublicKey { bytes };
+
+
+
+
+
+
+
+
+schema NewUnvalidatedPublicKeyFromBytesAbortsIf {
+ bytes: vector<u8>;
+ aborts_if len(bytes) != PUBLIC_KEY_NUM_BYTES;
+}
+
+
+
+
+
+
+### Function `new_validated_public_key_from_bytes`
+
+
+public fun new_validated_public_key_from_bytes(bytes: vector<u8>): option::Option<ed25519::ValidatedPublicKey>
+
+
+
+
+
+aborts_if false;
+let cond = spec_public_key_validate_internal(bytes);
+ensures cond ==> result == option::spec_some(ValidatedPublicKey{bytes});
+ensures !cond ==> result == option::spec_none<ValidatedPublicKey>();
+
+
+
+
+
+
+### Function `new_signature_from_bytes`
+
+
+public fun new_signature_from_bytes(bytes: vector<u8>): ed25519::Signature
+
+
+
+
+
+include NewSignatureFromBytesAbortsIf;
+ensures result == Signature { bytes };
+
+
+
+
+
+
+
+
+schema NewSignatureFromBytesAbortsIf {
+ bytes: vector<u8>;
+ aborts_if len(bytes) != SIGNATURE_NUM_BYTES;
+}
+
+
+
+
+
+
+### Function `public_key_bytes_to_authentication_key`
+
+
+fun public_key_bytes_to_authentication_key(pk_bytes: vector<u8>): vector<u8>
+
+
+
+
+
+pragma opaque;
+aborts_if false;
+ensures [abstract] result == spec_public_key_bytes_to_authentication_key(pk_bytes);
+
+
+
+
+
+
+### Function `public_key_validate_internal`
+
+
+fun public_key_validate_internal(bytes: vector<u8>): bool
+
+
+
+
+
+pragma opaque;
+aborts_if false;
+ensures result == spec_public_key_validate_internal(bytes);
+
+
+
+
+
+
+### Function `signature_verify_strict_internal`
+
+
+fun signature_verify_strict_internal(signature: vector<u8>, public_key: vector<u8>, message: vector<u8>): bool
+
+
+
+
+
+pragma opaque;
+aborts_if false;
+ensures result == spec_signature_verify_strict_internal(signature, public_key, message);
+
+
+
+
+
+
+### Helper functions
+
+
+
+
+
+
+fun spec_signature_verify_strict_internal(
+ signature: vector<u8>,
+ public_key: vector<u8>,
+ message: vector<u8>
+): bool;
+
+
+
+
+
+
+
+
+fun spec_public_key_validate_internal(bytes: vector<u8>): bool;
+
+
+
+
+
+
+
+
+fun spec_public_key_bytes_to_authentication_key(pk_bytes: vector<u8>): vector<u8>;
+
+
+
+
+
+
+
+
+fun spec_signature_verify_strict_t<T>(signature: Signature, public_key: UnvalidatedPublicKey, data: T): bool {
+ let encoded = SignedMessage<T> {
+ type_info: type_info::type_of<T>(),
+ inner: data,
+ };
+ let message = bcs::serialize(encoded);
+ spec_signature_verify_strict_internal(signature.bytes, public_key.bytes, message)
+}
+
+
+
+[move-book]: https://aptos.dev/move/book/SUMMARY
diff --git a/aptos-move/framework/aptos-stdlib/tests/compiler-v2-doc/fixed_point64.md b/aptos-move/framework/aptos-stdlib/tests/compiler-v2-doc/fixed_point64.md
new file mode 100644
index 0000000000000..d455b0a8f8548
--- /dev/null
+++ b/aptos-move/framework/aptos-stdlib/tests/compiler-v2-doc/fixed_point64.md
@@ -0,0 +1,1333 @@
+
+
+
+# Module `0x1::fixed_point64`
+
+Defines a fixed-point numeric type with a 64-bit integer part and
+a 64-bit fractional part.
+
+
+- [Struct `FixedPoint64`](#0x1_fixed_point64_FixedPoint64)
+- [Constants](#@Constants_0)
+- [Function `sub`](#0x1_fixed_point64_sub)
+- [Function `add`](#0x1_fixed_point64_add)
+- [Function `multiply_u128`](#0x1_fixed_point64_multiply_u128)
+- [Function `divide_u128`](#0x1_fixed_point64_divide_u128)
+- [Function `create_from_rational`](#0x1_fixed_point64_create_from_rational)
+- [Function `create_from_raw_value`](#0x1_fixed_point64_create_from_raw_value)
+- [Function `get_raw_value`](#0x1_fixed_point64_get_raw_value)
+- [Function `is_zero`](#0x1_fixed_point64_is_zero)
+- [Function `min`](#0x1_fixed_point64_min)
+- [Function `max`](#0x1_fixed_point64_max)
+- [Function `less_or_equal`](#0x1_fixed_point64_less_or_equal)
+- [Function `less`](#0x1_fixed_point64_less)
+- [Function `greater_or_equal`](#0x1_fixed_point64_greater_or_equal)
+- [Function `greater`](#0x1_fixed_point64_greater)
+- [Function `equal`](#0x1_fixed_point64_equal)
+- [Function `almost_equal`](#0x1_fixed_point64_almost_equal)
+- [Function `create_from_u128`](#0x1_fixed_point64_create_from_u128)
+- [Function `floor`](#0x1_fixed_point64_floor)
+- [Function `ceil`](#0x1_fixed_point64_ceil)
+- [Function `round`](#0x1_fixed_point64_round)
+- [Specification](#@Specification_1)
+ - [Function `sub`](#@Specification_1_sub)
+ - [Function `add`](#@Specification_1_add)
+ - [Function `multiply_u128`](#@Specification_1_multiply_u128)
+ - [Function `divide_u128`](#@Specification_1_divide_u128)
+ - [Function `create_from_rational`](#@Specification_1_create_from_rational)
+ - [Function `create_from_raw_value`](#@Specification_1_create_from_raw_value)
+ - [Function `min`](#@Specification_1_min)
+ - [Function `max`](#@Specification_1_max)
+ - [Function `less_or_equal`](#@Specification_1_less_or_equal)
+ - [Function `less`](#@Specification_1_less)
+ - [Function `greater_or_equal`](#@Specification_1_greater_or_equal)
+ - [Function `greater`](#@Specification_1_greater)
+ - [Function `equal`](#@Specification_1_equal)
+ - [Function `almost_equal`](#@Specification_1_almost_equal)
+ - [Function `create_from_u128`](#@Specification_1_create_from_u128)
+ - [Function `floor`](#@Specification_1_floor)
+ - [Function `ceil`](#@Specification_1_ceil)
+ - [Function `round`](#@Specification_1_round)
+
+
+
+
+
+
+
+
+## Struct `FixedPoint64`
+
+Define a fixed-point numeric type with 64 fractional bits.
+This is just a u128 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.
+
+
+struct FixedPoint64 has copy, drop, store
+
+
+
+
+value: u128
+const MAX_U128: u256 = 340282366920938463463374607431768211455;
+
+
+
+
+
+
+The denominator provided was zero
+
+
+const EDENOMINATOR: u64 = 65537;
+
+
+
+
+
+
+The quotient value would be too large to be held in a u128
+
+
+const EDIVISION: u64 = 131074;
+
+
+
+
+
+
+A division by zero was encountered
+
+
+const EDIVISION_BY_ZERO: u64 = 65540;
+
+
+
+
+
+
+The multiplied value would be too large to be held in a u128
+
+
+const EMULTIPLICATION: u64 = 131075;
+
+
+
+
+
+
+Abort code on calculation result is negative.
+
+
+const ENEGATIVE_RESULT: u64 = 65542;
+
+
+
+
+
+
+The computed ratio when converting to a FixedPoint64
would be unrepresentable
+
+
+const ERATIO_OUT_OF_RANGE: u64 = 131077;
+
+
+
+
+
+
+## Function `sub`
+
+Returns x - y. x must be not less than y.
+
+
+public fun sub(x: fixed_point64::FixedPoint64, y: fixed_point64::FixedPoint64): fixed_point64::FixedPoint64
+
+
+
+
+public fun sub(x: FixedPoint64, y: FixedPoint64): FixedPoint64 {
+ let x_raw = get_raw_value(x);
+ let y_raw = get_raw_value(y);
+ assert!(x_raw >= y_raw, ENEGATIVE_RESULT);
+ create_from_raw_value(x_raw - y_raw)
+}
+
+
+
+
+public fun add(x: fixed_point64::FixedPoint64, y: fixed_point64::FixedPoint64): fixed_point64::FixedPoint64
+
+
+
+
+public fun add(x: FixedPoint64, y: FixedPoint64): FixedPoint64 {
+ let x_raw = get_raw_value(x);
+ let y_raw = get_raw_value(y);
+ let result = (x_raw as u256) + (y_raw as u256);
+ assert!(result <= MAX_U128, ERATIO_OUT_OF_RANGE);
+ create_from_raw_value((result as u128))
+}
+
+
+
+
+public fun multiply_u128(val: u128, multiplier: fixed_point64::FixedPoint64): u128
+
+
+
+
+public fun multiply_u128(val: u128, multiplier: FixedPoint64): u128 {
+ // The product of two 128 bit values has 256 bits, so perform the
+ // multiplication with u256 types and keep the full 256 bit product
+ // to avoid losing accuracy.
+ let unscaled_product = (val as u256) * (multiplier.value as u256);
+ // The unscaled product has 64 fractional bits (from the multiplier)
+ // so rescale it by shifting away the low bits.
+ let product = unscaled_product >> 64;
+ // Check whether the value is too large.
+ assert!(product <= MAX_U128, EMULTIPLICATION);
+ (product as u128)
+}
+
+
+
+
+public fun divide_u128(val: u128, divisor: fixed_point64::FixedPoint64): u128
+
+
+
+
+public fun divide_u128(val: u128, divisor: FixedPoint64): u128 {
+ // Check for division by zero.
+ assert!(divisor.value != 0, EDIVISION_BY_ZERO);
+ // First convert to 256 bits and then shift left to
+ // add 64 fractional zero bits to the dividend.
+ let scaled_value = (val as u256) << 64;
+ let quotient = scaled_value / (divisor.value as u256);
+ // Check whether the value is too large.
+ assert!(quotient <= MAX_U128, EDIVISION);
+ // the value may be too large, which will cause the cast to fail
+ // with an arithmetic error.
+ (quotient as u128)
+}
+
+
+
+
+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^-64 .. 2^64-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: u128, denominator: u128): fixed_point64::FixedPoint64
+
+
+
+
+public fun create_from_rational(numerator: u128, denominator: u128): FixedPoint64 {
+ // If the denominator is zero, this will abort.
+ // Scale the numerator to have 64 fractional bits, so that the quotient will have 64
+ // fractional bits.
+ let scaled_numerator = (numerator as u256) << 64;
+ assert!(denominator != 0, EDENOMINATOR);
+ let quotient = scaled_numerator / (denominator as u256);
+ 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_U128, ERATIO_OUT_OF_RANGE);
+ FixedPoint64 { value: (quotient as u128) }
+}
+
+
+
+
+public fun create_from_raw_value(value: u128): fixed_point64::FixedPoint64
+
+
+
+
+public fun create_from_raw_value(value: u128): FixedPoint64 {
+ FixedPoint64 { value }
+}
+
+
+
+
+public fun get_raw_value(num: fixed_point64::FixedPoint64): u128
+
+
+
+
+public fun get_raw_value(num: FixedPoint64): u128 {
+ num.value
+}
+
+
+
+
+public fun is_zero(num: fixed_point64::FixedPoint64): bool
+
+
+
+
+public fun is_zero(num: FixedPoint64): bool {
+ num.value == 0
+}
+
+
+
+
+public fun min(num1: fixed_point64::FixedPoint64, num2: fixed_point64::FixedPoint64): fixed_point64::FixedPoint64
+
+
+
+
+public fun min(num1: FixedPoint64, num2: FixedPoint64): FixedPoint64 {
+ if (num1.value < num2.value) {
+ num1
+ } else {
+ num2
+ }
+}
+
+
+
+
+public fun max(num1: fixed_point64::FixedPoint64, num2: fixed_point64::FixedPoint64): fixed_point64::FixedPoint64
+
+
+
+
+public fun max(num1: FixedPoint64, num2: FixedPoint64): FixedPoint64 {
+ if (num1.value > num2.value) {
+ num1
+ } else {
+ num2
+ }
+}
+
+
+
+
+public fun less_or_equal(num1: fixed_point64::FixedPoint64, num2: fixed_point64::FixedPoint64): bool
+
+
+
+
+public fun less_or_equal(num1: FixedPoint64, num2: FixedPoint64): bool {
+ num1.value <= num2.value
+}
+
+
+
+
+public fun less(num1: fixed_point64::FixedPoint64, num2: fixed_point64::FixedPoint64): bool
+
+
+
+
+public fun less(num1: FixedPoint64, num2: FixedPoint64): bool {
+ num1.value < num2.value
+}
+
+
+
+
+public fun greater_or_equal(num1: fixed_point64::FixedPoint64, num2: fixed_point64::FixedPoint64): bool
+
+
+
+
+public fun greater_or_equal(num1: FixedPoint64, num2: FixedPoint64): bool {
+ num1.value >= num2.value
+}
+
+
+
+
+public fun greater(num1: fixed_point64::FixedPoint64, num2: fixed_point64::FixedPoint64): bool
+
+
+
+
+public fun greater(num1: FixedPoint64, num2: FixedPoint64): bool {
+ num1.value > num2.value
+}
+
+
+
+
+public fun equal(num1: fixed_point64::FixedPoint64, num2: fixed_point64::FixedPoint64): bool
+
+
+
+
+public fun equal(num1: FixedPoint64, num2: FixedPoint64): bool {
+ num1.value == num2.value
+}
+
+
+
+
+public fun almost_equal(num1: fixed_point64::FixedPoint64, num2: fixed_point64::FixedPoint64, precision: fixed_point64::FixedPoint64): bool
+
+
+
+
+public fun almost_equal(num1: FixedPoint64, num2: FixedPoint64, precision: FixedPoint64): bool {
+ if (num1.value > num2.value) {
+ (num1.value - num2.value <= precision.value)
+ } else {
+ (num2.value - num1.value <= precision.value)
+ }
+}
+
+
+
+
+public fun create_from_u128(val: u128): fixed_point64::FixedPoint64
+
+
+
+
+public fun create_from_u128(val: u128): FixedPoint64 {
+ let value = (val as u256) << 64;
+ assert!(value <= MAX_U128, ERATIO_OUT_OF_RANGE);
+ FixedPoint64 {value: (value as u128)}
+}
+
+
+
+
+public fun floor(num: fixed_point64::FixedPoint64): u128
+
+
+
+
+public fun floor(num: FixedPoint64): u128 {
+ num.value >> 64
+}
+
+
+
+
+public fun ceil(num: fixed_point64::FixedPoint64): u128
+
+
+
+
+public fun ceil(num: FixedPoint64): u128 {
+ let floored_num = floor(num) << 64;
+ if (num.value == floored_num) {
+ return floored_num >> 64
+ };
+ let val = ((floored_num as u256) + (1 << 64));
+ (val >> 64 as u128)
+}
+
+
+
+
+public fun round(num: fixed_point64::FixedPoint64): u128
+
+
+
+
+public fun round(num: FixedPoint64): u128 {
+ let floored_num = floor(num) << 64;
+ let boundary = floored_num + ((1 << 64) / 2);
+ if (num.value < boundary) {
+ floored_num >> 64
+ } else {
+ ceil(num)
+ }
+}
+
+
+
+
+pragma aborts_if_is_strict;
+
+
+
+
+
+
+### Function `sub`
+
+
+public fun sub(x: fixed_point64::FixedPoint64, y: fixed_point64::FixedPoint64): fixed_point64::FixedPoint64
+
+
+
+
+
+pragma opaque;
+aborts_if x.value < y.value with ENEGATIVE_RESULT;
+ensures result.value == x.value - y.value;
+
+
+
+
+
+
+### Function `add`
+
+
+public fun add(x: fixed_point64::FixedPoint64, y: fixed_point64::FixedPoint64): fixed_point64::FixedPoint64
+
+
+
+
+
+pragma opaque;
+aborts_if (x.value as u256) + (y.value as u256) > MAX_U128 with ERATIO_OUT_OF_RANGE;
+ensures result.value == x.value + y.value;
+
+
+
+
+
+
+### Function `multiply_u128`
+
+
+public fun multiply_u128(val: u128, multiplier: fixed_point64::FixedPoint64): u128
+
+
+
+
+
+pragma opaque;
+include MultiplyAbortsIf;
+ensures result == spec_multiply_u128(val, multiplier);
+
+
+
+
+
+
+
+
+schema MultiplyAbortsIf {
+ val: num;
+ multiplier: FixedPoint64;
+ aborts_if spec_multiply_u128(val, multiplier) > MAX_U128 with EMULTIPLICATION;
+}
+
+
+
+
+
+
+
+
+fun spec_multiply_u128(val: num, multiplier: FixedPoint64): num {
+ (val * multiplier.value) >> 64
+}
+
+
+
+
+
+
+### Function `divide_u128`
+
+
+public fun divide_u128(val: u128, divisor: fixed_point64::FixedPoint64): u128
+
+
+
+
+
+pragma opaque;
+include DivideAbortsIf;
+ensures result == spec_divide_u128(val, divisor);
+
+
+
+
+
+
+
+
+schema DivideAbortsIf {
+ val: num;
+ divisor: FixedPoint64;
+ aborts_if divisor.value == 0 with EDIVISION_BY_ZERO;
+ aborts_if spec_divide_u128(val, divisor) > MAX_U128 with EDIVISION;
+}
+
+
+
+
+
+
+
+
+fun spec_divide_u128(val: num, divisor: FixedPoint64): num {
+ (val << 64) / divisor.value
+}
+
+
+
+
+
+
+### Function `create_from_rational`
+
+
+public fun create_from_rational(numerator: u128, denominator: u128): fixed_point64::FixedPoint64
+
+
+
+
+
+pragma opaque;
+pragma verify_duration_estimate = 1000;
+include CreateFromRationalAbortsIf;
+ensures result == spec_create_from_rational(numerator, denominator);
+
+
+
+
+
+
+
+
+schema CreateFromRationalAbortsIf {
+ numerator: u128;
+ denominator: u128;
+ let scaled_numerator = (numerator as u256)<< 64;
+ let scaled_denominator = (denominator as u256);
+ let quotient = scaled_numerator / scaled_denominator;
+ aborts_if scaled_denominator == 0 with EDENOMINATOR;
+ aborts_if quotient == 0 && scaled_numerator != 0 with ERATIO_OUT_OF_RANGE;
+ aborts_if quotient > MAX_U128 with ERATIO_OUT_OF_RANGE;
+}
+
+
+
+
+
+
+
+
+fun spec_create_from_rational(numerator: num, denominator: num): FixedPoint64 {
+ FixedPoint64{value: (numerator << 128) / (denominator << 64)}
+}
+
+
+
+
+
+
+### Function `create_from_raw_value`
+
+
+public fun create_from_raw_value(value: u128): fixed_point64::FixedPoint64
+
+
+
+
+
+pragma opaque;
+aborts_if false;
+ensures result.value == value;
+
+
+
+
+
+
+### Function `min`
+
+
+public fun min(num1: fixed_point64::FixedPoint64, num2: fixed_point64::FixedPoint64): fixed_point64::FixedPoint64
+
+
+
+
+
+pragma opaque;
+aborts_if false;
+ensures result == spec_min(num1, num2);
+
+
+
+
+
+
+
+
+fun spec_min(num1: FixedPoint64, num2: FixedPoint64): FixedPoint64 {
+ if (num1.value < num2.value) {
+ num1
+ } else {
+ num2
+ }
+}
+
+
+
+
+
+
+### Function `max`
+
+
+public fun max(num1: fixed_point64::FixedPoint64, num2: fixed_point64::FixedPoint64): fixed_point64::FixedPoint64
+
+
+
+
+
+pragma opaque;
+aborts_if false;
+ensures result == spec_max(num1, num2);
+
+
+
+
+
+
+
+
+fun spec_max(num1: FixedPoint64, num2: FixedPoint64): FixedPoint64 {
+ if (num1.value > num2.value) {
+ num1
+ } else {
+ num2
+ }
+}
+
+
+
+
+
+
+### Function `less_or_equal`
+
+
+public fun less_or_equal(num1: fixed_point64::FixedPoint64, num2: fixed_point64::FixedPoint64): bool
+
+
+
+
+
+pragma opaque;
+aborts_if false;
+ensures result == spec_less_or_equal(num1, num2);
+
+
+
+
+
+
+
+
+fun spec_less_or_equal(num1: FixedPoint64, num2: FixedPoint64): bool {
+ num1.value <= num2.value
+}
+
+
+
+
+
+
+### Function `less`
+
+
+public fun less(num1: fixed_point64::FixedPoint64, num2: fixed_point64::FixedPoint64): bool
+
+
+
+
+
+pragma opaque;
+aborts_if false;
+ensures result == spec_less(num1, num2);
+
+
+
+
+
+
+
+
+fun spec_less(num1: FixedPoint64, num2: FixedPoint64): bool {
+ num1.value < num2.value
+}
+
+
+
+
+
+
+### Function `greater_or_equal`
+
+
+public fun greater_or_equal(num1: fixed_point64::FixedPoint64, num2: fixed_point64::FixedPoint64): bool
+
+
+
+
+
+pragma opaque;
+aborts_if false;
+ensures result == spec_greater_or_equal(num1, num2);
+
+
+
+
+
+
+
+
+fun spec_greater_or_equal(num1: FixedPoint64, num2: FixedPoint64): bool {
+ num1.value >= num2.value
+}
+
+
+
+
+
+
+### Function `greater`
+
+
+public fun greater(num1: fixed_point64::FixedPoint64, num2: fixed_point64::FixedPoint64): bool
+
+
+
+
+
+pragma opaque;
+aborts_if false;
+ensures result == spec_greater(num1, num2);
+
+
+
+
+
+
+
+
+fun spec_greater(num1: FixedPoint64, num2: FixedPoint64): bool {
+ num1.value > num2.value
+}
+
+
+
+
+
+
+### Function `equal`
+
+
+public fun equal(num1: fixed_point64::FixedPoint64, num2: fixed_point64::FixedPoint64): bool
+
+
+
+
+
+pragma opaque;
+aborts_if false;
+ensures result == spec_equal(num1, num2);
+
+
+
+
+
+
+
+
+fun spec_equal(num1: FixedPoint64, num2: FixedPoint64): bool {
+ num1.value == num2.value
+}
+
+
+
+
+
+
+### Function `almost_equal`
+
+
+public fun almost_equal(num1: fixed_point64::FixedPoint64, num2: fixed_point64::FixedPoint64, precision: fixed_point64::FixedPoint64): bool
+
+
+
+
+
+pragma opaque;
+aborts_if false;
+ensures result == spec_almost_equal(num1, num2, precision);
+
+
+
+
+
+
+
+
+fun spec_almost_equal(num1: FixedPoint64, num2: FixedPoint64, precision: FixedPoint64): bool {
+ if (num1.value > num2.value) {
+ (num1.value - num2.value <= precision.value)
+ } else {
+ (num2.value - num1.value <= precision.value)
+ }
+}
+
+
+
+
+
+
+### Function `create_from_u128`
+
+
+public fun create_from_u128(val: u128): fixed_point64::FixedPoint64
+
+
+
+
+
+pragma opaque;
+include CreateFromU64AbortsIf;
+ensures result == spec_create_from_u128(val);
+
+
+
+
+
+
+
+
+schema CreateFromU64AbortsIf {
+ val: num;
+ let scaled_value = (val as u256) << 64;
+ aborts_if scaled_value > MAX_U128;
+}
+
+
+
+
+
+
+
+
+fun spec_create_from_u128(val: num): FixedPoint64 {
+ FixedPoint64 {value: val << 64}
+}
+
+
+
+
+
+
+### Function `floor`
+
+
+public fun floor(num: fixed_point64::FixedPoint64): u128
+
+
+
+
+
+pragma opaque;
+aborts_if false;
+ensures result == spec_floor(num);
+
+
+
+
+
+
+
+
+fun spec_floor(val: FixedPoint64): u128 {
+ let fractional = val.value % (1 << 64);
+ if (fractional == 0) {
+ val.value >> 64
+ } else {
+ (val.value - fractional) >> 64
+ }
+}
+
+
+
+
+
+
+### Function `ceil`
+
+
+public fun ceil(num: fixed_point64::FixedPoint64): u128
+
+
+
+
+
+pragma verify_duration_estimate = 1000;
+pragma opaque;
+aborts_if false;
+ensures result == spec_ceil(num);
+
+
+
+
+
+
+
+
+fun spec_ceil(val: FixedPoint64): u128 {
+ let fractional = val.value % (1 << 64);
+ let one = 1 << 64;
+ if (fractional == 0) {
+ val.value >> 64
+ } else {
+ (val.value - fractional + one) >> 64
+ }
+}
+
+
+
+
+
+
+### Function `round`
+
+
+public fun round(num: fixed_point64::FixedPoint64): u128
+
+
+
+
+
+pragma opaque;
+aborts_if false;
+ensures result == spec_round(num);
+
+
+
+
+
+
+
+
+fun spec_round(val: FixedPoint64): u128 {
+ let fractional = val.value % (1 << 64);
+ let boundary = (1 << 64) / 2;
+ let one = 1 << 64;
+ if (fractional < boundary) {
+ (val.value - fractional) >> 64
+ } else {
+ (val.value - fractional + one) >> 64
+ }
+}
+
+
+
+[move-book]: https://aptos.dev/move/book/SUMMARY
diff --git a/aptos-move/framework/aptos-stdlib/tests/compiler-v2-doc/from_bcs.md b/aptos-move/framework/aptos-stdlib/tests/compiler-v2-doc/from_bcs.md
new file mode 100644
index 0000000000000..5838c78fdd758
--- /dev/null
+++ b/aptos-move/framework/aptos-stdlib/tests/compiler-v2-doc/from_bcs.md
@@ -0,0 +1,363 @@
+
+
+
+# Module `0x1::from_bcs`
+
+This module provides a number of functions to convert _primitive_ types from their representation in std::bcs
+to values. This is the opposite of bcs::to_bytes
. Note that it is not safe to define a generic public from_bytes
+function because this can violate implicit struct invariants, therefore only primitive types are offerred. If
+a general conversion back-and-force is needed, consider the aptos_std::Any
type which preserves invariants.
+
+Example:
+```
+use std::bcs;
+use aptos_std::from_bcs;
+
+assert!(from_bcs::to_address(bcs::to_bytes(&@0xabcdef)) == @0xabcdef, 0);
+```
+
+
+- [Constants](#@Constants_0)
+- [Function `to_bool`](#0x1_from_bcs_to_bool)
+- [Function `to_u8`](#0x1_from_bcs_to_u8)
+- [Function `to_u16`](#0x1_from_bcs_to_u16)
+- [Function `to_u32`](#0x1_from_bcs_to_u32)
+- [Function `to_u64`](#0x1_from_bcs_to_u64)
+- [Function `to_u128`](#0x1_from_bcs_to_u128)
+- [Function `to_u256`](#0x1_from_bcs_to_u256)
+- [Function `to_address`](#0x1_from_bcs_to_address)
+- [Function `to_bytes`](#0x1_from_bcs_to_bytes)
+- [Function `to_string`](#0x1_from_bcs_to_string)
+- [Function `from_bytes`](#0x1_from_bcs_from_bytes)
+- [Specification](#@Specification_1)
+ - [Function `from_bytes`](#@Specification_1_from_bytes)
+
+
+use 0x1::string;
+
+
+
+
+
+
+## Constants
+
+
+
+
+UTF8 check failed in conversion from bytes to string
+
+
+const EINVALID_UTF8: u64 = 1;
+
+
+
+
+
+
+## Function `to_bool`
+
+
+
+public fun to_bool(v: vector<u8>): bool
+
+
+
+
+public fun to_bool(v: vector<u8>): bool {
+ from_bytes<bool>(v)
+}
+
+
+
+
+public fun to_u8(v: vector<u8>): u8
+
+
+
+
+public fun to_u8(v: vector<u8>): u8 {
+ from_bytes<u8>(v)
+}
+
+
+
+
+public fun to_u16(v: vector<u8>): u16
+
+
+
+
+public fun to_u16(v: vector<u8>): u16 {
+ from_bytes<u16>(v)
+}
+
+
+
+
+public fun to_u32(v: vector<u8>): u32
+
+
+
+
+public fun to_u32(v: vector<u8>): u32 {
+ from_bytes<u32>(v)
+}
+
+
+
+
+public fun to_u64(v: vector<u8>): u64
+
+
+
+
+public fun to_u64(v: vector<u8>): u64 {
+ from_bytes<u64>(v)
+}
+
+
+
+
+public fun to_u128(v: vector<u8>): u128
+
+
+
+
+public fun to_u128(v: vector<u8>): u128 {
+ from_bytes<u128>(v)
+}
+
+
+
+
+public fun to_u256(v: vector<u8>): u256
+
+
+
+
+public fun to_u256(v: vector<u8>): u256 {
+ from_bytes<u256>(v)
+}
+
+
+
+
+public fun to_address(v: vector<u8>): address
+
+
+
+
+public fun to_address(v: vector<u8>): address {
+ from_bytes<address>(v)
+}
+
+
+
+
+public fun to_bytes(v: vector<u8>): vector<u8>
+
+
+
+
+public fun to_bytes(v: vector<u8>): vector<u8> {
+ from_bytes<vector<u8>>(v)
+}
+
+
+
+
+public fun to_string(v: vector<u8>): string::String
+
+
+
+
+public fun to_string(v: vector<u8>): String {
+ // To make this safe, we need to evaluate the utf8 invariant.
+ let s = from_bytes<String>(v);
+ assert!(string::internal_check_utf8(string::bytes(&s)), EINVALID_UTF8);
+ s
+}
+
+
+
+
+T
. If code uses this function to
+deserialize a linear value, its their responsibility that the data they deserialize is
+owned.
+
+
+public(friend) fun from_bytes<T>(bytes: vector<u8>): T
+
+
+
+
+public(friend) native fun from_bytes<T>(bytes: vector<u8>): T;
+
+
+
+
+fun deserialize<T>(bytes: vector<u8>): T;
+
+fun deserializable<T>(bytes: vector<u8>): bool;
+axiom<T> forall b1: vector<u8>, b2: vector<u8>:
+ ( b1 == b2 ==> deserializable<T>(b1) == deserializable<T>(b2) );
+axiom<T> forall b1: vector<u8>, b2: vector<u8>:
+ ( b1 == b2 ==> deserialize<T>(b1) == deserialize<T>(b2) );
+
+
+
+
+
+
+### Function `from_bytes`
+
+
+public(friend) fun from_bytes<T>(bytes: vector<u8>): T
+
+
+
+
+
+pragma opaque;
+aborts_if !deserializable<T>(bytes);
+ensures result == deserialize<T>(bytes);
+
+
+
+[move-book]: https://aptos.dev/move/book/SUMMARY
diff --git a/aptos-move/framework/aptos-stdlib/tests/compiler-v2-doc/hash.md b/aptos-move/framework/aptos-stdlib/tests/compiler-v2-doc/hash.md
new file mode 100644
index 0000000000000..908970ce7c41d
--- /dev/null
+++ b/aptos-move/framework/aptos-stdlib/tests/compiler-v2-doc/hash.md
@@ -0,0 +1,623 @@
+
+
+
+# Module `0x1::aptos_hash`
+
+Cryptographic hashes:
+- Keccak-256: see https://keccak.team/keccak.html
+
+In addition, SHA2-256 and SHA3-256 are available in std::hash
. Note that SHA3-256 is a variant of Keccak: it is
+NOT the same as Keccak-256.
+
+Non-cryptograhic hashes:
+- SipHash: an add-rotate-xor (ARX) based family of pseudorandom functions created by Jean-Philippe Aumasson and Daniel J. Bernstein in 2012
+
+
+- [Constants](#@Constants_0)
+- [Function `sip_hash`](#0x1_aptos_hash_sip_hash)
+- [Function `sip_hash_from_value`](#0x1_aptos_hash_sip_hash_from_value)
+- [Function `keccak256`](#0x1_aptos_hash_keccak256)
+- [Function `sha2_512`](#0x1_aptos_hash_sha2_512)
+- [Function `sha3_512`](#0x1_aptos_hash_sha3_512)
+- [Function `ripemd160`](#0x1_aptos_hash_ripemd160)
+- [Function `blake2b_256`](#0x1_aptos_hash_blake2b_256)
+- [Function `sha2_512_internal`](#0x1_aptos_hash_sha2_512_internal)
+- [Function `sha3_512_internal`](#0x1_aptos_hash_sha3_512_internal)
+- [Function `ripemd160_internal`](#0x1_aptos_hash_ripemd160_internal)
+- [Function `blake2b_256_internal`](#0x1_aptos_hash_blake2b_256_internal)
+- [Specification](#@Specification_1)
+ - [Function `sip_hash`](#@Specification_1_sip_hash)
+ - [Function `sip_hash_from_value`](#@Specification_1_sip_hash_from_value)
+ - [Function `keccak256`](#@Specification_1_keccak256)
+ - [Function `sha2_512`](#@Specification_1_sha2_512)
+ - [Function `sha3_512`](#@Specification_1_sha3_512)
+ - [Function `ripemd160`](#@Specification_1_ripemd160)
+ - [Function `blake2b_256`](#@Specification_1_blake2b_256)
+ - [Function `sha2_512_internal`](#@Specification_1_sha2_512_internal)
+ - [Function `sha3_512_internal`](#@Specification_1_sha3_512_internal)
+ - [Function `ripemd160_internal`](#@Specification_1_ripemd160_internal)
+ - [Function `blake2b_256_internal`](#@Specification_1_blake2b_256_internal)
+
+
+use 0x1::bcs;
+use 0x1::error;
+use 0x1::features;
+
+
+
+
+
+
+## Constants
+
+
+
+
+A newly-added native function is not yet enabled.
+
+
+const E_NATIVE_FUN_NOT_AVAILABLE: u64 = 1;
+
+
+
+
+
+
+## Function `sip_hash`
+
+Returns the (non-cryptographic) SipHash of bytes
. See https://en.wikipedia.org/wiki/SipHash
+
+
+public fun sip_hash(bytes: vector<u8>): u64
+
+
+
+
+native public fun sip_hash(bytes: vector<u8>): u64;
+
+
+
+
+v
. See https://en.wikipedia.org/wiki/SipHash
+
+
+public fun sip_hash_from_value<MoveValue>(v: &MoveValue): u64
+
+
+
+
+public fun sip_hash_from_value<MoveValue>(v: &MoveValue): u64 {
+ let bytes = bcs::to_bytes(v);
+
+ sip_hash(bytes)
+}
+
+
+
+
+bytes
.
+
+
+public fun keccak256(bytes: vector<u8>): vector<u8>
+
+
+
+
+native public fun keccak256(bytes: vector<u8>): vector<u8>;
+
+
+
+
+bytes
.
+
+
+public fun sha2_512(bytes: vector<u8>): vector<u8>
+
+
+
+
+public fun sha2_512(bytes: vector<u8>): vector<u8> {
+ if(!features::sha_512_and_ripemd_160_enabled()) {
+ abort(std::error::invalid_state(E_NATIVE_FUN_NOT_AVAILABLE))
+ };
+
+ sha2_512_internal(bytes)
+}
+
+
+
+
+bytes
.
+
+
+public fun sha3_512(bytes: vector<u8>): vector<u8>
+
+
+
+
+public fun sha3_512(bytes: vector<u8>): vector<u8> {
+ if(!features::sha_512_and_ripemd_160_enabled()) {
+ abort(std::error::invalid_state(E_NATIVE_FUN_NOT_AVAILABLE))
+ };
+
+ sha3_512_internal(bytes)
+}
+
+
+
+
+bytes
.
+
+WARNING: Only 80-bit security is provided by this function. This means an adversary who can compute roughly 2^80
+hashes will, with high probability, find a collision x_1 != x_2 such that RIPEMD-160(x_1) = RIPEMD-160(x_2).
+
+
+public fun ripemd160(bytes: vector<u8>): vector<u8>
+
+
+
+
+public fun ripemd160(bytes: vector<u8>): vector<u8> {
+ if(!features::sha_512_and_ripemd_160_enabled()) {
+ abort(std::error::invalid_state(E_NATIVE_FUN_NOT_AVAILABLE))
+ };
+
+ ripemd160_internal(bytes)
+}
+
+
+
+
+bytes
.
+
+
+public fun blake2b_256(bytes: vector<u8>): vector<u8>
+
+
+
+
+public fun blake2b_256(bytes: vector<u8>): vector<u8> {
+ if(!features::blake2b_256_enabled()) {
+ abort(std::error::invalid_state(E_NATIVE_FUN_NOT_AVAILABLE))
+ };
+
+ blake2b_256_internal(bytes)
+}
+
+
+
+
+bytes
.
+
+
+fun sha2_512_internal(bytes: vector<u8>): vector<u8>
+
+
+
+
+native fun sha2_512_internal(bytes: vector<u8>): vector<u8>;
+
+
+
+
+bytes
.
+
+
+fun sha3_512_internal(bytes: vector<u8>): vector<u8>
+
+
+
+
+native fun sha3_512_internal(bytes: vector<u8>): vector<u8>;
+
+
+
+
+bytes
.
+
+WARNING: Only 80-bit security is provided by this function. This means an adversary who can compute roughly 2^80
+hashes will, with high probability, find a collision x_1 != x_2 such that RIPEMD-160(x_1) = RIPEMD-160(x_2).
+
+
+fun ripemd160_internal(bytes: vector<u8>): vector<u8>
+
+
+
+
+native fun ripemd160_internal(bytes: vector<u8>): vector<u8>;
+
+
+
+
+bytes
.
+
+
+fun blake2b_256_internal(bytes: vector<u8>): vector<u8>
+
+
+
+
+native fun blake2b_256_internal(bytes: vector<u8>): vector<u8>;
+
+
+
+
+spec_sip_hash
is not assumed to be injective.
+
+
+
+
+
+fun spec_sip_hash(bytes: vector<u8>): u64;
+
+
+
+spec_keccak256
is an injective function.
+
+
+
+
+
+fun spec_keccak256(bytes: vector<u8>): vector<u8>;
+axiom forall b1: vector<u8>, b2: vector<u8>:
+ (spec_keccak256(b1) == spec_keccak256(b2) ==> b1 == b2);
+
+
+
+spec_sha2_512_internal
is an injective function.
+
+
+
+
+
+fun spec_sha2_512_internal(bytes: vector<u8>): vector<u8>;
+axiom forall b1: vector<u8>, b2: vector<u8>:
+ (spec_sha2_512_internal(b1) == spec_sha2_512_internal(b2) ==> b1 == b2);
+
+
+
+spec_sha3_512_internal
is an injective function.
+
+
+
+
+
+fun spec_sha3_512_internal(bytes: vector<u8>): vector<u8>;
+axiom forall b1: vector<u8>, b2: vector<u8>:
+ (spec_sha3_512_internal(b1) == spec_sha3_512_internal(b2) ==> b1 == b2);
+
+
+
+spec_ripemd160_internal
is an injective function.
+
+
+
+
+
+fun spec_ripemd160_internal(bytes: vector<u8>): vector<u8>;
+axiom forall b1: vector<u8>, b2: vector<u8>:
+ (spec_ripemd160_internal(b1) == spec_ripemd160_internal(b2) ==> b1 == b2);
+
+
+
+spec_blake2b_256_internal
is an injective function.
+
+
+
+
+
+fun spec_blake2b_256_internal(bytes: vector<u8>): vector<u8>;
+axiom forall b1: vector<u8>, b2: vector<u8>:
+ (spec_blake2b_256_internal(b1) == spec_blake2b_256_internal(b2) ==> b1 == b2);
+
+
+
+
+
+
+### Function `sip_hash`
+
+
+public fun sip_hash(bytes: vector<u8>): u64
+
+
+
+
+
+pragma opaque;
+aborts_if [abstract] false;
+ensures [abstract] result == spec_sip_hash(bytes);
+
+
+
+
+
+
+### Function `sip_hash_from_value`
+
+
+public fun sip_hash_from_value<MoveValue>(v: &MoveValue): u64
+
+
+
+
+
+pragma opaque;
+ensures result == spec_sip_hash(bcs::serialize(v));
+
+
+
+
+
+
+### Function `keccak256`
+
+
+public fun keccak256(bytes: vector<u8>): vector<u8>
+
+
+
+
+
+pragma opaque;
+aborts_if [abstract] false;
+ensures [abstract] result == spec_keccak256(bytes);
+
+
+
+
+
+
+### Function `sha2_512`
+
+
+public fun sha2_512(bytes: vector<u8>): vector<u8>
+
+
+
+
+
+pragma opaque;
+aborts_if !features::spec_is_enabled(features::SHA_512_AND_RIPEMD_160_NATIVES);
+ensures result == spec_sha2_512_internal(bytes);
+
+
+
+
+
+
+### Function `sha3_512`
+
+
+public fun sha3_512(bytes: vector<u8>): vector<u8>
+
+
+
+
+
+pragma opaque;
+aborts_if !features::spec_is_enabled(features::SHA_512_AND_RIPEMD_160_NATIVES);
+ensures result == spec_sha3_512_internal(bytes);
+
+
+
+
+
+
+### Function `ripemd160`
+
+
+public fun ripemd160(bytes: vector<u8>): vector<u8>
+
+
+
+
+
+pragma opaque;
+aborts_if !features::spec_is_enabled(features::SHA_512_AND_RIPEMD_160_NATIVES);
+ensures result == spec_ripemd160_internal(bytes);
+
+
+
+
+
+
+### Function `blake2b_256`
+
+
+public fun blake2b_256(bytes: vector<u8>): vector<u8>
+
+
+
+
+
+pragma opaque;
+aborts_if !features::spec_is_enabled(features::BLAKE2B_256_NATIVE);
+ensures result == spec_blake2b_256_internal(bytes);
+
+
+
+
+
+
+### Function `sha2_512_internal`
+
+
+fun sha2_512_internal(bytes: vector<u8>): vector<u8>
+
+
+
+
+
+pragma opaque;
+aborts_if [abstract] false;
+ensures [abstract] result == spec_sha2_512_internal(bytes);
+
+
+
+
+
+
+### Function `sha3_512_internal`
+
+
+fun sha3_512_internal(bytes: vector<u8>): vector<u8>
+
+
+
+
+
+pragma opaque;
+aborts_if [abstract] false;
+ensures [abstract] result == spec_sha3_512_internal(bytes);
+
+
+
+
+
+
+### Function `ripemd160_internal`
+
+
+fun ripemd160_internal(bytes: vector<u8>): vector<u8>
+
+
+
+
+
+pragma opaque;
+aborts_if [abstract] false;
+ensures [abstract] result == spec_ripemd160_internal(bytes);
+
+
+
+
+
+
+### Function `blake2b_256_internal`
+
+
+fun blake2b_256_internal(bytes: vector<u8>): vector<u8>
+
+
+
+
+
+pragma opaque;
+aborts_if false;
+ensures result == spec_blake2b_256_internal(bytes);
+
+
+
+[move-book]: https://aptos.dev/move/book/SUMMARY
diff --git a/aptos-move/framework/aptos-stdlib/tests/compiler-v2-doc/math128.md b/aptos-move/framework/aptos-stdlib/tests/compiler-v2-doc/math128.md
new file mode 100644
index 0000000000000..d8e0fa05716df
--- /dev/null
+++ b/aptos-move/framework/aptos-stdlib/tests/compiler-v2-doc/math128.md
@@ -0,0 +1,594 @@
+
+
+
+# Module `0x1::math128`
+
+Standard math utilities missing in the Move Language.
+
+
+- [Constants](#@Constants_0)
+- [Function `max`](#0x1_math128_max)
+- [Function `min`](#0x1_math128_min)
+- [Function `average`](#0x1_math128_average)
+- [Function `gcd`](#0x1_math128_gcd)
+- [Function `mul_div`](#0x1_math128_mul_div)
+- [Function `clamp`](#0x1_math128_clamp)
+- [Function `pow`](#0x1_math128_pow)
+- [Function `floor_log2`](#0x1_math128_floor_log2)
+- [Function `log2`](#0x1_math128_log2)
+- [Function `log2_64`](#0x1_math128_log2_64)
+- [Function `sqrt`](#0x1_math128_sqrt)
+- [Function `ceil_div`](#0x1_math128_ceil_div)
+- [Specification](#@Specification_1)
+ - [Function `max`](#@Specification_1_max)
+ - [Function `min`](#@Specification_1_min)
+ - [Function `average`](#@Specification_1_average)
+ - [Function `clamp`](#@Specification_1_clamp)
+ - [Function `pow`](#@Specification_1_pow)
+ - [Function `floor_log2`](#@Specification_1_floor_log2)
+ - [Function `sqrt`](#@Specification_1_sqrt)
+
+
+use 0x1::error;
+use 0x1::fixed_point32;
+use 0x1::fixed_point64;
+
+
+
+
+
+
+## Constants
+
+
+
+
+Cannot log2 the value 0
+
+
+const EINVALID_ARG_FLOOR_LOG2: u64 = 1;
+
+
+
+
+
+
+## Function `max`
+
+Return the largest of two numbers.
+
+
+public fun max(a: u128, b: u128): u128
+
+
+
+
+public fun max(a: u128, b: u128): u128 {
+ if (a >= b) a else b
+}
+
+
+
+
+public fun min(a: u128, b: u128): u128
+
+
+
+
+public fun min(a: u128, b: u128): u128 {
+ if (a < b) a else b
+}
+
+
+
+
+public fun average(a: u128, b: u128): u128
+
+
+
+
+public fun average(a: u128, b: u128): u128 {
+ if (a < b) {
+ a + (b - a) / 2
+ } else {
+ b + (a - b) / 2
+ }
+}
+
+
+
+
+a
& b
, via the Euclidean algorithm.
+
+
+public fun gcd(a: u128, b: u128): u128
+
+
+
+
+public inline fun gcd(a: u128, b: u128): u128 {
+ let (large, small) = if (a > b) (a, b) else (b, a);
+ while (small != 0) {
+ let tmp = small;
+ small = large % small;
+ large = tmp;
+ };
+ large
+}
+
+
+
+
+public fun mul_div(a: u128, b: u128, c: u128): u128
+
+
+
+
+public inline fun mul_div(a: u128, b: u128, c: u128): u128 {
+ // Inline functions cannot take constants, as then every module using it needs the constant
+ assert!(c != 0, std::error::invalid_argument(4));
+ (((a as u256) * (b as u256) / (c as u256)) as u128)
+}
+
+
+
+
+public fun clamp(x: u128, lower: u128, upper: u128): u128
+
+
+
+
+public fun clamp(x: u128, lower: u128, upper: u128): u128 {
+ min(upper, max(lower, x))
+}
+
+
+
+
+public fun pow(n: u128, e: u128): u128
+
+
+
+
+public fun pow(n: u128, e: u128): u128 {
+ if (e == 0) {
+ 1
+ } else {
+ let p = 1;
+ while (e > 1) {
+ if (e % 2 == 1) {
+ p = p * n;
+ };
+ e = e / 2;
+ n = n * n;
+ };
+ p * n
+ }
+}
+
+
+
+
+public fun floor_log2(x: u128): u8
+
+
+
+
+public fun floor_log2(x: u128): u8 {
+ let res = 0;
+ assert!(x != 0, std::error::invalid_argument(EINVALID_ARG_FLOOR_LOG2));
+ // Effectively the position of the most significant set bit
+ let n = 64;
+ while (n > 0) {
+ if (x >= (1 << n)) {
+ x = x >> n;
+ res = res + n;
+ };
+ n = n >> 1;
+ };
+ res
+}
+
+
+
+
+public fun log2(x: u128): fixed_point32::FixedPoint32
+
+
+
+
+public fun log2(x: u128): FixedPoint32 {
+ let integer_part = floor_log2(x);
+ // Normalize x to [1, 2) in fixed point 32.
+ if (x >= 1 << 32) {
+ x = x >> (integer_part - 32);
+ } else {
+ x = x << (32 - integer_part);
+ };
+ let frac = 0;
+ let delta = 1 << 31;
+ while (delta != 0) {
+ // log x = 1/2 log x^2
+ // x in [1, 2)
+ x = (x * x) >> 32;
+ // x is now in [1, 4)
+ // if x in [2, 4) then log x = 1 + log (x / 2)
+ if (x >= (2 << 32)) { frac = frac + delta; x = x >> 1; };
+ delta = delta >> 1;
+ };
+ fixed_point32::create_from_raw_value (((integer_part as u64) << 32) + frac)
+}
+
+
+
+
+public fun log2_64(x: u128): fixed_point64::FixedPoint64
+
+
+
+
+public fun log2_64(x: u128): FixedPoint64 {
+ let integer_part = floor_log2(x);
+ // Normalize x to [1, 2) in fixed point 63. To ensure x is smaller then 1<<64
+ if (x >= 1 << 63) {
+ x = x >> (integer_part - 63);
+ } else {
+ x = x << (63 - integer_part);
+ };
+ let frac = 0;
+ let delta = 1 << 63;
+ while (delta != 0) {
+ // log x = 1/2 log x^2
+ // x in [1, 2)
+ x = (x * x) >> 63;
+ // x is now in [1, 4)
+ // if x in [2, 4) then log x = 1 + log (x / 2)
+ if (x >= (2 << 63)) { frac = frac + delta; x = x >> 1; };
+ delta = delta >> 1;
+ };
+ fixed_point64::create_from_raw_value (((integer_part as u128) << 64) + frac)
+}
+
+
+
+
+public fun sqrt(x: u128): u128
+
+
+
+
+public fun sqrt(x: u128): u128 {
+ if (x == 0) return 0;
+ // Note the plus 1 in the expression. Let n = floor_lg2(x) we have x in [2^n, 2^{n+1}) and thus the answer in
+ // the half-open interval [2^(n/2), 2^{(n+1)/2}). For even n we can write this as [2^(n/2), sqrt(2) 2^{n/2})
+ // for odd n [2^((n+1)/2)/sqrt(2), 2^((n+1)/2). For even n the left end point is integer for odd the right
+ // end point is integer. If we choose as our first approximation the integer end point we have as maximum
+ // relative error either (sqrt(2) - 1) or (1 - 1/sqrt(2)) both are smaller then 1/2.
+ let res = 1 << ((floor_log2(x) + 1) >> 1);
+ // We use standard newton-rhapson iteration to improve the initial approximation.
+ // The error term evolves as delta_i+1 = delta_i^2 / 2 (quadratic convergence).
+ // It turns out that after 5 iterations the delta is smaller than 2^-64 and thus below the treshold.
+ res = (res + x / res) >> 1;
+ res = (res + x / res) >> 1;
+ res = (res + x / res) >> 1;
+ res = (res + x / res) >> 1;
+ res = (res + x / res) >> 1;
+ min(res, x / res)
+}
+
+
+
+
+public fun ceil_div(x: u128, y: u128): u128
+
+
+
+
+public inline fun ceil_div(x: u128, y: u128): u128 {
+ // ceil_div(x, y) = floor((x + y - 1) / y) = floor((x - 1) / y) + 1
+ // (x + y - 1) could spuriously overflow. so we use the later version
+ if (x == 0) {
+ // Inline functions cannot take constants, as then every module using it needs the constant
+ assert!(y != 0, std::error::invalid_argument(4));
+ 0
+ }
+ else (x - 1) / y + 1
+}
+
+
+
+
+public fun max(a: u128, b: u128): u128
+
+
+
+
+
+aborts_if false;
+ensures a >= b ==> result == a;
+ensures a < b ==> result == b;
+
+
+
+
+
+
+### Function `min`
+
+
+public fun min(a: u128, b: u128): u128
+
+
+
+
+
+aborts_if false;
+ensures a < b ==> result == a;
+ensures a >= b ==> result == b;
+
+
+
+
+
+
+### Function `average`
+
+
+public fun average(a: u128, b: u128): u128
+
+
+
+
+
+pragma opaque;
+aborts_if false;
+ensures result == (a + b) / 2;
+
+
+
+
+
+
+### Function `clamp`
+
+
+public fun clamp(x: u128, lower: u128, upper: u128): u128
+
+
+
+
+
+requires (lower <= upper);
+aborts_if false;
+ensures (lower <=x && x <= upper) ==> result == x;
+ensures (x < lower) ==> result == lower;
+ensures (upper < x) ==> result == upper;
+
+
+
+
+
+
+### Function `pow`
+
+
+public fun pow(n: u128, e: u128): u128
+
+
+
+
+
+pragma opaque;
+aborts_if [abstract] spec_pow(n, e) > MAX_U128;
+ensures [abstract] result == spec_pow(n, e);
+
+
+
+
+
+
+### Function `floor_log2`
+
+
+public fun floor_log2(x: u128): u8
+
+
+
+
+
+pragma opaque;
+aborts_if [abstract] x == 0;
+ensures [abstract] spec_pow(2, result) <= x;
+ensures [abstract] x < spec_pow(2, result+1);
+
+
+
+
+
+
+### Function `sqrt`
+
+
+public fun sqrt(x: u128): u128
+
+
+
+
+
+pragma opaque;
+aborts_if [abstract] false;
+ensures [abstract] x > 0 ==> result * result <= x;
+ensures [abstract] x > 0 ==> x < (result+1) * (result+1);
+
+
+
+
+
+
+
+
+fun spec_pow(n: u128, e: u128): u128 {
+ if (e == 0) {
+ 1
+ }
+ else {
+ n * spec_pow(n, e-1)
+ }
+}
+
+
+
+[move-book]: https://aptos.dev/move/book/SUMMARY
diff --git a/aptos-move/framework/aptos-stdlib/tests/compiler-v2-doc/math64.md b/aptos-move/framework/aptos-stdlib/tests/compiler-v2-doc/math64.md
new file mode 100644
index 0000000000000..9a9e0b90a77f8
--- /dev/null
+++ b/aptos-move/framework/aptos-stdlib/tests/compiler-v2-doc/math64.md
@@ -0,0 +1,549 @@
+
+
+
+# Module `0x1::math64`
+
+Standard math utilities missing in the Move Language.
+
+
+- [Constants](#@Constants_0)
+- [Function `max`](#0x1_math64_max)
+- [Function `min`](#0x1_math64_min)
+- [Function `average`](#0x1_math64_average)
+- [Function `gcd`](#0x1_math64_gcd)
+- [Function `mul_div`](#0x1_math64_mul_div)
+- [Function `clamp`](#0x1_math64_clamp)
+- [Function `pow`](#0x1_math64_pow)
+- [Function `floor_log2`](#0x1_math64_floor_log2)
+- [Function `log2`](#0x1_math64_log2)
+- [Function `sqrt`](#0x1_math64_sqrt)
+- [Function `ceil_div`](#0x1_math64_ceil_div)
+- [Specification](#@Specification_1)
+ - [Function `max`](#@Specification_1_max)
+ - [Function `min`](#@Specification_1_min)
+ - [Function `average`](#@Specification_1_average)
+ - [Function `clamp`](#@Specification_1_clamp)
+ - [Function `pow`](#@Specification_1_pow)
+ - [Function `floor_log2`](#@Specification_1_floor_log2)
+ - [Function `sqrt`](#@Specification_1_sqrt)
+
+
+use 0x1::error;
+use 0x1::fixed_point32;
+
+
+
+
+
+
+## Constants
+
+
+
+
+Cannot log2 the value 0
+
+
+const EINVALID_ARG_FLOOR_LOG2: u64 = 1;
+
+
+
+
+
+
+## Function `max`
+
+Return the largest of two numbers.
+
+
+public fun max(a: u64, b: u64): u64
+
+
+
+
+public fun max(a: u64, b: u64): u64 {
+ if (a >= b) a else b
+}
+
+
+
+
+public fun min(a: u64, b: u64): u64
+
+
+
+
+public fun min(a: u64, b: u64): u64 {
+ if (a < b) a else b
+}
+
+
+
+
+public fun average(a: u64, b: u64): u64
+
+
+
+
+public fun average(a: u64, b: u64): u64 {
+ if (a < b) {
+ a + (b - a) / 2
+ } else {
+ b + (a - b) / 2
+ }
+}
+
+
+
+
+a
& b
, via the Euclidean algorithm.
+
+
+public fun gcd(a: u64, b: u64): u64
+
+
+
+
+public inline fun gcd(a: u64, b: u64): u64 {
+ let (large, small) = if (a > b) (a, b) else (b, a);
+ while (small != 0) {
+ let tmp = small;
+ small = large % small;
+ large = tmp;
+ };
+ large
+}
+
+
+
+
+public fun mul_div(a: u64, b: u64, c: u64): u64
+
+
+
+
+public inline fun mul_div(a: u64, b: u64, c: u64): u64 {
+ // Inline functions cannot take constants, as then every module using it needs the constant
+ assert!(c != 0, std::error::invalid_argument(4));
+ (((a as u128) * (b as u128) / (c as u128)) as u64)
+}
+
+
+
+
+public fun clamp(x: u64, lower: u64, upper: u64): u64
+
+
+
+
+public fun clamp(x: u64, lower: u64, upper: u64): u64 {
+ min(upper, max(lower, x))
+}
+
+
+
+
+public fun pow(n: u64, e: u64): u64
+
+
+
+
+public fun pow(n: u64, e: u64): u64 {
+ if (e == 0) {
+ 1
+ } else {
+ let p = 1;
+ while (e > 1) {
+ if (e % 2 == 1) {
+ p = p * n;
+ };
+ e = e / 2;
+ n = n * n;
+ };
+ p * n
+ }
+}
+
+
+
+
+public fun floor_log2(x: u64): u8
+
+
+
+
+public fun floor_log2(x: u64): u8 {
+ let res = 0;
+ assert!(x != 0, std::error::invalid_argument(EINVALID_ARG_FLOOR_LOG2));
+ // Effectively the position of the most significant set bit
+ let n = 32;
+ while (n > 0) {
+ if (x >= (1 << n)) {
+ x = x >> n;
+ res = res + n;
+ };
+ n = n >> 1;
+ };
+ res
+}
+
+
+
+
+public fun log2(x: u64): fixed_point32::FixedPoint32
+
+
+
+
+public fun log2(x: u64): FixedPoint32 {
+ let integer_part = floor_log2(x);
+ // Normalize x to [1, 2) in fixed point 32.
+ let y = (if (x >= 1 << 32) {
+ x >> (integer_part - 32)
+ } else {
+ x << (32 - integer_part)
+ } as u128);
+ let frac = 0;
+ let delta = 1 << 31;
+ while (delta != 0) {
+ // log x = 1/2 log x^2
+ // x in [1, 2)
+ y = (y * y) >> 32;
+ // x is now in [1, 4)
+ // if x in [2, 4) then log x = 1 + log (x / 2)
+ if (y >= (2 << 32)) { frac = frac + delta; y = y >> 1; };
+ delta = delta >> 1;
+ };
+ fixed_point32::create_from_raw_value (((integer_part as u64) << 32) + frac)
+}
+
+
+
+
+public fun sqrt(x: u64): u64
+
+
+
+
+public fun sqrt(x: u64): u64 {
+ if (x == 0) return 0;
+ // Note the plus 1 in the expression. Let n = floor_lg2(x) we have x in [2^n, 2^(n+1)> and thus the answer in
+ // the half-open interval [2^(n/2), 2^((n+1)/2)>. For even n we can write this as [2^(n/2), sqrt(2) 2^(n/2)>
+ // for odd n [2^((n+1)/2)/sqrt(2), 2^((n+1)/2>. For even n the left end point is integer for odd the right
+ // end point is integer. If we choose as our first approximation the integer end point we have as maximum
+ // relative error either (sqrt(2) - 1) or (1 - 1/sqrt(2)) both are smaller then 1/2.
+ let res = 1 << ((floor_log2(x) + 1) >> 1);
+ // We use standard newton-rhapson iteration to improve the initial approximation.
+ // The error term evolves as delta_i+1 = delta_i^2 / 2 (quadratic convergence).
+ // It turns out that after 4 iterations the delta is smaller than 2^-32 and thus below the treshold.
+ res = (res + x / res) >> 1;
+ res = (res + x / res) >> 1;
+ res = (res + x / res) >> 1;
+ res = (res + x / res) >> 1;
+ min(res, x / res)
+}
+
+
+
+
+public fun ceil_div(x: u64, y: u64): u64
+
+
+
+
+public inline fun ceil_div(x: u64, y: u64): u64 {
+ // ceil_div(x, y) = floor((x + y - 1) / y) = floor((x - 1) / y) + 1
+ // (x + y - 1) could spuriously overflow. so we use the later version
+ if (x == 0) {
+ // Inline functions cannot take constants, as then every module using it needs the constant
+ assert!(y != 0, std::error::invalid_argument(4));
+ 0
+ }
+ else (x - 1) / y + 1
+}
+
+
+
+
+public fun max(a: u64, b: u64): u64
+
+
+
+
+
+aborts_if false;
+ensures a >= b ==> result == a;
+ensures a < b ==> result == b;
+
+
+
+
+
+
+### Function `min`
+
+
+public fun min(a: u64, b: u64): u64
+
+
+
+
+
+aborts_if false;
+ensures a < b ==> result == a;
+ensures a >= b ==> result == b;
+
+
+
+
+
+
+### Function `average`
+
+
+public fun average(a: u64, b: u64): u64
+
+
+
+
+
+pragma opaque;
+aborts_if false;
+ensures result == (a + b) / 2;
+
+
+
+
+
+
+### Function `clamp`
+
+
+public fun clamp(x: u64, lower: u64, upper: u64): u64
+
+
+
+
+
+requires (lower <= upper);
+aborts_if false;
+ensures (lower <=x && x <= upper) ==> result == x;
+ensures (x < lower) ==> result == lower;
+ensures (upper < x) ==> result == upper;
+
+
+
+
+
+
+### Function `pow`
+
+
+public fun pow(n: u64, e: u64): u64
+
+
+
+
+
+pragma opaque;
+aborts_if [abstract] spec_pow(n, e) > MAX_U64;
+ensures [abstract] result == spec_pow(n, e);
+
+
+
+
+
+
+### Function `floor_log2`
+
+
+public fun floor_log2(x: u64): u8
+
+
+
+
+
+pragma opaque;
+aborts_if [abstract] x == 0;
+ensures [abstract] spec_pow(2, result) <= x;
+ensures [abstract] x < spec_pow(2, result+1);
+
+
+
+
+
+
+### Function `sqrt`
+
+
+public fun sqrt(x: u64): u64
+
+
+
+
+
+pragma opaque;
+aborts_if [abstract] false;
+ensures [abstract] x > 0 ==> result * result <= x;
+ensures [abstract] x > 0 ==> x < (result+1) * (result+1);
+
+
+
+
+
+
+
+
+fun spec_pow(n: u64, e: u64): u64 {
+ if (e == 0) {
+ 1
+ }
+ else {
+ n * spec_pow(n, e-1)
+ }
+}
+
+
+
+[move-book]: https://aptos.dev/move/book/SUMMARY
diff --git a/aptos-move/framework/aptos-stdlib/tests/compiler-v2-doc/math_fixed.md b/aptos-move/framework/aptos-stdlib/tests/compiler-v2-doc/math_fixed.md
new file mode 100644
index 0000000000000..b1099005aad39
--- /dev/null
+++ b/aptos-move/framework/aptos-stdlib/tests/compiler-v2-doc/math_fixed.md
@@ -0,0 +1,299 @@
+
+
+
+# Module `0x1::math_fixed`
+
+Standard math utilities missing in the Move Language.
+
+
+- [Constants](#@Constants_0)
+- [Function `sqrt`](#0x1_math_fixed_sqrt)
+- [Function `exp`](#0x1_math_fixed_exp)
+- [Function `log2_plus_32`](#0x1_math_fixed_log2_plus_32)
+- [Function `ln_plus_32ln2`](#0x1_math_fixed_ln_plus_32ln2)
+- [Function `pow`](#0x1_math_fixed_pow)
+- [Function `mul_div`](#0x1_math_fixed_mul_div)
+- [Function `exp_raw`](#0x1_math_fixed_exp_raw)
+- [Function `pow_raw`](#0x1_math_fixed_pow_raw)
+
+
+use 0x1::error;
+use 0x1::fixed_point32;
+use 0x1::math128;
+
+
+
+
+
+
+## Constants
+
+
+
+
+Abort code on overflow
+
+
+const EOVERFLOW_EXP: u64 = 1;
+
+
+
+
+
+
+Natural log 2 in 32 bit fixed point
+
+
+const LN2: u128 = 2977044472;
+
+
+
+
+
+
+
+
+const LN2_X_32: u64 = 95265423104;
+
+
+
+
+
+
+## Function `sqrt`
+
+Square root of fixed point number
+
+
+public fun sqrt(x: fixed_point32::FixedPoint32): fixed_point32::FixedPoint32
+
+
+
+
+public fun sqrt(x: FixedPoint32): FixedPoint32 {
+ let y = (fixed_point32::get_raw_value(x) as u128);
+ fixed_point32::create_from_raw_value((math128::sqrt(y << 32) as u64))
+}
+
+
+
+
+public fun exp(x: fixed_point32::FixedPoint32): fixed_point32::FixedPoint32
+
+
+
+
+public fun exp(x: FixedPoint32): FixedPoint32 {
+ let raw_value = (fixed_point32::get_raw_value(x) as u128);
+ fixed_point32::create_from_raw_value((exp_raw(raw_value) as u64))
+}
+
+
+
+
+public fun log2_plus_32(x: fixed_point32::FixedPoint32): fixed_point32::FixedPoint32
+
+
+
+
+public fun log2_plus_32(x: FixedPoint32): FixedPoint32 {
+ let raw_value = (fixed_point32::get_raw_value(x) as u128);
+ math128::log2(raw_value)
+}
+
+
+
+
+public fun ln_plus_32ln2(x: fixed_point32::FixedPoint32): fixed_point32::FixedPoint32
+
+
+
+
+public fun ln_plus_32ln2(x: FixedPoint32): FixedPoint32 {
+ let raw_value = (fixed_point32::get_raw_value(x) as u128);
+ let x = (fixed_point32::get_raw_value(math128::log2(raw_value)) as u128);
+ fixed_point32::create_from_raw_value((x * LN2 >> 32 as u64))
+}
+
+
+
+
+public fun pow(x: fixed_point32::FixedPoint32, n: u64): fixed_point32::FixedPoint32
+
+
+
+
+public fun pow(x: FixedPoint32, n: u64): FixedPoint32 {
+ let raw_value = (fixed_point32::get_raw_value(x) as u128);
+ fixed_point32::create_from_raw_value((pow_raw(raw_value, (n as u128)) as u64))
+}
+
+
+
+
+public fun mul_div(x: fixed_point32::FixedPoint32, y: fixed_point32::FixedPoint32, z: fixed_point32::FixedPoint32): fixed_point32::FixedPoint32
+
+
+
+
+public fun mul_div(x: FixedPoint32, y: FixedPoint32, z: FixedPoint32): FixedPoint32 {
+ let a = fixed_point32::get_raw_value(x);
+ let b = fixed_point32::get_raw_value(y);
+ let c = fixed_point32::get_raw_value(z);
+ fixed_point32::create_from_raw_value (math64::mul_div(a, b, c))
+}
+
+
+
+
+fun exp_raw(x: u128): u128
+
+
+
+
+fun exp_raw(x: u128): u128 {
+ // exp(x / 2^32) = 2^(x / (2^32 * ln(2))) = 2^(floor(x / (2^32 * ln(2))) + frac(x / (2^32 * ln(2))))
+ let shift_long = x / LN2;
+ assert!(shift_long <= 31, std::error::invalid_state(EOVERFLOW_EXP));
+ let shift = (shift_long as u8);
+ let remainder = x % LN2;
+ // At this point we want to calculate 2^(remainder / ln2) << shift
+ // ln2 = 595528 * 4999 which means
+ let bigfactor = 595528;
+ let exponent = remainder / bigfactor;
+ let x = remainder % bigfactor;
+ // 2^(remainder / ln2) = (2^(1/4999))^exponent * exp(x / 2^32)
+ let roottwo = 4295562865; // fixed point representation of 2^(1/4999)
+ // This has an error of 5000 / 4 10^9 roughly 6 digits of precission
+ let power = pow_raw(roottwo, exponent);
+ let eps_correction = 1241009291;
+ power = power + ((power * eps_correction * exponent) >> 64);
+ // x is fixed point number smaller than 595528/2^32 < 0.00014 so we need only 2 tayler steps
+ // to get the 6 digits of precission
+ let taylor1 = (power * x) >> (32 - shift);
+ let taylor2 = (taylor1 * x) >> 32;
+ let taylor3 = (taylor2 * x) >> 32;
+ (power << shift) + taylor1 + taylor2 / 2 + taylor3 / 6
+}
+
+
+
+
+fun pow_raw(x: u128, n: u128): u128
+
+
+
+
+fun pow_raw(x: u128, n: u128): u128 {
+ let res: u256 = 1 << 64;
+ x = x << 32;
+ while (n != 0) {
+ if (n & 1 != 0) {
+ res = (res * (x as u256)) >> 64;
+ };
+ n = n >> 1;
+ x = ((((x as u256) * (x as u256)) >> 64) as u128);
+ };
+ ((res >> 32) as u128)
+}
+
+
+
+
+use 0x1::error;
+use 0x1::fixed_point64;
+use 0x1::math128;
+
+
+
+
+
+
+## Constants
+
+
+
+
+Abort code on overflow
+
+
+const EOVERFLOW_EXP: u64 = 1;
+
+
+
+
+
+
+Natural log 2 in 32 bit fixed point
+
+
+const LN2: u256 = 12786308645202655660;
+
+
+
+
+
+
+## Function `sqrt`
+
+Square root of fixed point number
+
+
+public fun sqrt(x: fixed_point64::FixedPoint64): fixed_point64::FixedPoint64
+
+
+
+
+public fun sqrt(x: FixedPoint64): FixedPoint64 {
+ let y = fixed_point64::get_raw_value(x);
+ let z = (math128::sqrt(y) << 32 as u256);
+ z = (z + ((y as u256) << 64) / z) >> 1;
+ fixed_point64::create_from_raw_value((z as u128))
+}
+
+
+
+
+public fun exp(x: fixed_point64::FixedPoint64): fixed_point64::FixedPoint64
+
+
+
+
+public fun exp(x: FixedPoint64): FixedPoint64 {
+ let raw_value = (fixed_point64::get_raw_value(x) as u256);
+ fixed_point64::create_from_raw_value((exp_raw(raw_value) as u128))
+}
+
+
+
+
+public fun log2_plus_64(x: fixed_point64::FixedPoint64): fixed_point64::FixedPoint64
+
+
+
+
+public fun log2_plus_64(x: FixedPoint64): FixedPoint64 {
+ let raw_value = (fixed_point64::get_raw_value(x) as u128);
+ math128::log2_64(raw_value)
+}
+
+
+
+
+public fun ln_plus_32ln2(x: fixed_point64::FixedPoint64): fixed_point64::FixedPoint64
+
+
+
+
+public fun ln_plus_32ln2(x: FixedPoint64): FixedPoint64 {
+ let raw_value = fixed_point64::get_raw_value(x);
+ let x = (fixed_point64::get_raw_value(math128::log2_64(raw_value)) as u256);
+ fixed_point64::create_from_raw_value(((x * LN2) >> 64 as u128))
+}
+
+
+
+
+public fun pow(x: fixed_point64::FixedPoint64, n: u64): fixed_point64::FixedPoint64
+
+
+
+
+public fun pow(x: FixedPoint64, n: u64): FixedPoint64 {
+ let raw_value = (fixed_point64::get_raw_value(x) as u256);
+ fixed_point64::create_from_raw_value((pow_raw(raw_value, (n as u128)) as u128))
+}
+
+
+
+
+public fun mul_div(x: fixed_point64::FixedPoint64, y: fixed_point64::FixedPoint64, z: fixed_point64::FixedPoint64): fixed_point64::FixedPoint64
+
+
+
+
+public fun mul_div(x: FixedPoint64, y: FixedPoint64, z: FixedPoint64): FixedPoint64 {
+ let a = fixed_point64::get_raw_value(x);
+ let b = fixed_point64::get_raw_value(y);
+ let c = fixed_point64::get_raw_value(z);
+ fixed_point64::create_from_raw_value (math128::mul_div(a, b, c))
+}
+
+
+
+
+fun exp_raw(x: u256): u256
+
+
+
+
+fun exp_raw(x: u256): u256 {
+ // exp(x / 2^64) = 2^(x / (2^64 * ln(2))) = 2^(floor(x / (2^64 * ln(2))) + frac(x / (2^64 * ln(2))))
+ let shift_long = x / LN2;
+ assert!(shift_long <= 63, std::error::invalid_state(EOVERFLOW_EXP));
+ let shift = (shift_long as u8);
+ let remainder = x % LN2;
+ // At this point we want to calculate 2^(remainder / ln2) << shift
+ // ln2 = 580 * 22045359733108027
+ let bigfactor = 22045359733108027;
+ let exponent = remainder / bigfactor;
+ let x = remainder % bigfactor;
+ // 2^(remainder / ln2) = (2^(1/580))^exponent * exp(x / 2^64)
+ let roottwo = 18468802611690918839; // fixed point representation of 2^(1/580)
+ // 2^(1/580) = roottwo(1 - eps), so the number we seek is roottwo^exponent (1 - eps * exponent)
+ let power = pow_raw(roottwo, (exponent as u128));
+ let eps_correction = 219071715585908898;
+ power = power - ((power * eps_correction * exponent) >> 128);
+ // x is fixed point number smaller than bigfactor/2^64 < 0.0011 so we need only 5 tayler steps
+ // to get the 15 digits of precission
+ let taylor1 = (power * x) >> (64 - shift);
+ let taylor2 = (taylor1 * x) >> 64;
+ let taylor3 = (taylor2 * x) >> 64;
+ let taylor4 = (taylor3 * x) >> 64;
+ let taylor5 = (taylor4 * x) >> 64;
+ let taylor6 = (taylor5 * x) >> 64;
+ (power << shift) + taylor1 + taylor2 / 2 + taylor3 / 6 + taylor4 / 24 + taylor5 / 120 + taylor6 / 720
+}
+
+
+
+
+fun pow_raw(x: u256, n: u128): u256
+
+
+
+
+fun pow_raw(x: u256, n: u128): u256 {
+ let res: u256 = 1 << 64;
+ while (n != 0) {
+ if (n & 1 != 0) {
+ res = (res * x) >> 64;
+ };
+ n = n >> 1;
+ x = (x * x) >> 64;
+ };
+ res
+}
+
+
+
+
+use 0x1::bcs;
+use 0x1::ed25519;
+use 0x1::error;
+use 0x1::features;
+use 0x1::hash;
+use 0x1::option;
+
+
+
+
+
+
+## Struct `UnvalidatedPublicKey`
+
+An *unvalidated*, k out of n MultiEd25519 public key. The bytes
field contains (1) several chunks of
+ed25519::PUBLIC_KEY_NUM_BYTES
bytes, each encoding a Ed25519 PK, and (2) a single byte encoding the threshold k.
+*Unvalidated* means there is no guarantee that the underlying PKs are valid elliptic curve points of non-small
+order.
+
+
+struct UnvalidatedPublicKey has copy, drop, store
+
+
+
+
+bytes: vector<u8>
+UnvalidatedPublicKey
.
+
+For now, this struct is not used in any verification functions, but it might be in the future.
+
+
+struct ValidatedPublicKey has copy, drop, store
+
+
+
+
+bytes: vector<u8>
+signature_verify_strict
or
+signature_verify_strict_t
. The bytes
field contains (1) several chunks of ed25519::SIGNATURE_NUM_BYTES
+bytes, each encoding a Ed25519 signature, and (2) a BITMAP_NUM_OF_BYTES
-byte bitmap encoding the signer
+identities.
+
+
+struct Signature has copy, drop, store
+
+
+
+
+bytes: vector<u8>
+const E_NATIVE_FUN_NOT_AVAILABLE: u64 = 4;
+
+
+
+
+
+
+Wrong number of bytes were given as input when deserializing an Ed25519 public key.
+
+
+const E_WRONG_PUBKEY_SIZE: u64 = 1;
+
+
+
+
+
+
+Wrong number of bytes were given as input when deserializing an Ed25519 signature.
+
+
+const E_WRONG_SIGNATURE_SIZE: u64 = 2;
+
+
+
+
+
+
+The identifier of the MultiEd25519 signature scheme, which is used when deriving Aptos authentication keys by hashing
+it together with an MultiEd25519 public key.
+
+
+const SIGNATURE_SCHEME_ID: u8 = 1;
+
+
+
+
+
+
+When serializing a MultiEd25519 signature, the bitmap that indicates the signers will be encoded using this many
+bytes.
+
+
+const BITMAP_NUM_OF_BYTES: u64 = 4;
+
+
+
+
+
+
+The threshold must be in the range [1, n]
, where n is the total number of signers.
+
+
+const E_INVALID_THRESHOLD_OR_NUMBER_OF_SIGNERS: u64 = 3;
+
+
+
+
+
+
+The size of an individual Ed25519 public key, in bytes.
+(A MultiEd25519 public key consists of several of these, plus the threshold.)
+
+
+const INDIVIDUAL_PUBLIC_KEY_NUM_BYTES: u64 = 32;
+
+
+
+
+
+
+The size of an individual Ed25519 signature, in bytes.
+(A MultiEd25519 signature consists of several of these, plus the signer bitmap.)
+
+
+const INDIVIDUAL_SIGNATURE_NUM_BYTES: u64 = 64;
+
+
+
+
+
+
+Max number of ed25519 public keys allowed in multi-ed25519 keys
+
+
+const MAX_NUMBER_OF_PUBLIC_KEYS: u64 = 32;
+
+
+
+
+
+
+When serializing a MultiEd25519 public key, the threshold k will be encoded using this many bytes.
+
+
+const THRESHOLD_SIZE_BYTES: u64 = 1;
+
+
+
+
+
+
+## Function `new_unvalidated_public_key_from_bytes`
+
+Parses the input 32 bytes as an *unvalidated* MultiEd25519 public key.
+
+NOTE: This function could have also checked that the # of sub-PKs is > 0, but it did not. However, since such
+invalid PKs are rejected during signature verification (see bugfix_unvalidated_pk_from_zero_subpks
) they
+will not cause problems.
+
+We could fix this API by adding a new one that checks the # of sub-PKs is > 0, but it is likely not a good idea
+to reproduce the PK validation logic in Move. We should not have done so in the first place. Instead, we will
+leave it as is and continue assuming UnvalidatedPublicKey
objects could be invalid PKs that will safely be
+rejected during signature verification.
+
+
+public fun new_unvalidated_public_key_from_bytes(bytes: vector<u8>): multi_ed25519::UnvalidatedPublicKey
+
+
+
+
+public fun new_unvalidated_public_key_from_bytes(bytes: vector<u8>): UnvalidatedPublicKey {
+ let len = vector::length(&bytes);
+ let num_sub_pks = len / INDIVIDUAL_PUBLIC_KEY_NUM_BYTES;
+
+ assert!(num_sub_pks <= MAX_NUMBER_OF_PUBLIC_KEYS, error::invalid_argument(E_WRONG_PUBKEY_SIZE));
+ assert!(len % INDIVIDUAL_PUBLIC_KEY_NUM_BYTES == THRESHOLD_SIZE_BYTES, error::invalid_argument(E_WRONG_PUBKEY_SIZE));
+ UnvalidatedPublicKey { bytes }
+}
+
+
+
+
+new_validated_public_key_from_bytes_v2
instead. See public_key_validate_internal
comments.
+
+(Incorrectly) parses the input bytes as a *validated* MultiEd25519 public key.
+
+
+public fun new_validated_public_key_from_bytes(bytes: vector<u8>): option::Option<multi_ed25519::ValidatedPublicKey>
+
+
+
+
+public fun new_validated_public_key_from_bytes(bytes: vector<u8>): Option<ValidatedPublicKey> {
+ // Note that `public_key_validate_internal` will check that `vector::length(&bytes) / INDIVIDUAL_PUBLIC_KEY_NUM_BYTES <= MAX_NUMBER_OF_PUBLIC_KEYS`.
+ if (vector::length(&bytes) % INDIVIDUAL_PUBLIC_KEY_NUM_BYTES == THRESHOLD_SIZE_BYTES &&
+ public_key_validate_internal(bytes)) {
+ option::some(ValidatedPublicKey {
+ bytes
+ })
+ } else {
+ option::none<ValidatedPublicKey>()
+ }
+}
+
+
+
+
+public_key_validate_internal_v2
).
+
+
+public fun new_validated_public_key_from_bytes_v2(bytes: vector<u8>): option::Option<multi_ed25519::ValidatedPublicKey>
+
+
+
+
+public fun new_validated_public_key_from_bytes_v2(bytes: vector<u8>): Option<ValidatedPublicKey> {
+ if (!features::multi_ed25519_pk_validate_v2_enabled()) {
+ abort(error::invalid_state(E_NATIVE_FUN_NOT_AVAILABLE))
+ };
+
+ if (public_key_validate_v2_internal(bytes)) {
+ option::some(ValidatedPublicKey {
+ bytes
+ })
+ } else {
+ option::none<ValidatedPublicKey>()
+ }
+}
+
+
+
+
+public fun new_signature_from_bytes(bytes: vector<u8>): multi_ed25519::Signature
+
+
+
+
+public fun new_signature_from_bytes(bytes: vector<u8>): Signature {
+ assert!(vector::length(&bytes) % INDIVIDUAL_SIGNATURE_NUM_BYTES == BITMAP_NUM_OF_BYTES, error::invalid_argument(E_WRONG_SIGNATURE_SIZE));
+ Signature { bytes }
+}
+
+
+
+
+public fun public_key_to_unvalidated(pk: &multi_ed25519::ValidatedPublicKey): multi_ed25519::UnvalidatedPublicKey
+
+
+
+
+public fun public_key_to_unvalidated(pk: &ValidatedPublicKey): UnvalidatedPublicKey {
+ UnvalidatedPublicKey {
+ bytes: pk.bytes
+ }
+}
+
+
+
+
+public fun public_key_into_unvalidated(pk: multi_ed25519::ValidatedPublicKey): multi_ed25519::UnvalidatedPublicKey
+
+
+
+
+public fun public_key_into_unvalidated(pk: ValidatedPublicKey): UnvalidatedPublicKey {
+ UnvalidatedPublicKey {
+ bytes: pk.bytes
+ }
+}
+
+
+
+
+public fun unvalidated_public_key_to_bytes(pk: &multi_ed25519::UnvalidatedPublicKey): vector<u8>
+
+
+
+
+public fun unvalidated_public_key_to_bytes(pk: &UnvalidatedPublicKey): vector<u8> {
+ pk.bytes
+}
+
+
+
+
+public fun validated_public_key_to_bytes(pk: &multi_ed25519::ValidatedPublicKey): vector<u8>
+
+
+
+
+public fun validated_public_key_to_bytes(pk: &ValidatedPublicKey): vector<u8> {
+ pk.bytes
+}
+
+
+
+
+public fun signature_to_bytes(sig: &multi_ed25519::Signature): vector<u8>
+
+
+
+
+public fun signature_to_bytes(sig: &Signature): vector<u8> {
+ sig.bytes
+}
+
+
+
+
+public_key_validate_v2
instead. See public_key_validate_internal
comments.
+
+Takes in an *unvalidated* public key and attempts to validate it.
+Returns Some(ValidatedPublicKey)
if successful and None
otherwise.
+
+
+public fun public_key_validate(pk: &multi_ed25519::UnvalidatedPublicKey): option::Option<multi_ed25519::ValidatedPublicKey>
+
+
+
+
+public fun public_key_validate(pk: &UnvalidatedPublicKey): Option<ValidatedPublicKey> {
+ new_validated_public_key_from_bytes(pk.bytes)
+}
+
+
+
+
+Some(ValidatedPublicKey)
if successful and None
otherwise.
+
+
+public fun public_key_validate_v2(pk: &multi_ed25519::UnvalidatedPublicKey): option::Option<multi_ed25519::ValidatedPublicKey>
+
+
+
+
+public fun public_key_validate_v2(pk: &UnvalidatedPublicKey): Option<ValidatedPublicKey> {
+ new_validated_public_key_from_bytes_v2(pk.bytes)
+}
+
+
+
+
+multisignature
under an *unvalidated* public_key
on the specified message
.
+This call will validate the public key by checking it is NOT in the small subgroup.
+
+
+public fun signature_verify_strict(multisignature: &multi_ed25519::Signature, public_key: &multi_ed25519::UnvalidatedPublicKey, message: vector<u8>): bool
+
+
+
+
+public fun signature_verify_strict(
+ multisignature: &Signature,
+ public_key: &UnvalidatedPublicKey,
+ message: vector<u8>
+): bool {
+ signature_verify_strict_internal(multisignature.bytes, public_key.bytes, message)
+}
+
+
+
+
+public fun signature_verify_strict_t<T: drop>(multisignature: &multi_ed25519::Signature, public_key: &multi_ed25519::UnvalidatedPublicKey, data: T): bool
+
+
+
+
+public fun signature_verify_strict_t<T: drop>(multisignature: &Signature, public_key: &UnvalidatedPublicKey, data: T): bool {
+ let encoded = ed25519::new_signed_message(data);
+
+ signature_verify_strict_internal(multisignature.bytes, public_key.bytes, bcs::to_bytes(&encoded))
+}
+
+
+
+
+public fun unvalidated_public_key_to_authentication_key(pk: &multi_ed25519::UnvalidatedPublicKey): vector<u8>
+
+
+
+
+public fun unvalidated_public_key_to_authentication_key(pk: &UnvalidatedPublicKey): vector<u8> {
+ public_key_bytes_to_authentication_key(pk.bytes)
+}
+
+
+
+
+UnvalidatedPublicKey
would pass validation in public_key_validate
, then the returned # of sub-PKs
+can be relied upon as correct.
+
+We provide this API as a cheaper alternative to calling public_key_validate
and then validated_public_key_num_sub_pks
+when the input pk
is known to be valid.
+
+
+public fun unvalidated_public_key_num_sub_pks(pk: &multi_ed25519::UnvalidatedPublicKey): u8
+
+
+
+
+public fun unvalidated_public_key_num_sub_pks(pk: &UnvalidatedPublicKey): u8 {
+ let len = vector::length(&pk.bytes);
+
+ ((len / INDIVIDUAL_PUBLIC_KEY_NUM_BYTES) as u8)
+}
+
+
+
+
+None
+if bytes
does not correctly encode such a PK.
+
+
+public fun unvalidated_public_key_threshold(pk: &multi_ed25519::UnvalidatedPublicKey): option::Option<u8>
+
+
+
+
+public fun unvalidated_public_key_threshold(pk: &UnvalidatedPublicKey): Option<u8> {
+ check_and_get_threshold(pk.bytes)
+}
+
+
+
+
+public fun validated_public_key_to_authentication_key(pk: &multi_ed25519::ValidatedPublicKey): vector<u8>
+
+
+
+
+public fun validated_public_key_to_authentication_key(pk: &ValidatedPublicKey): vector<u8> {
+ public_key_bytes_to_authentication_key(pk.bytes)
+}
+
+
+
+
+public fun validated_public_key_num_sub_pks(pk: &multi_ed25519::ValidatedPublicKey): u8
+
+
+
+
+public fun validated_public_key_num_sub_pks(pk: &ValidatedPublicKey): u8 {
+ let len = vector::length(&pk.bytes);
+
+ ((len / INDIVIDUAL_PUBLIC_KEY_NUM_BYTES) as u8)
+}
+
+
+
+
+public fun validated_public_key_threshold(pk: &multi_ed25519::ValidatedPublicKey): u8
+
+
+
+
+public fun validated_public_key_threshold(pk: &ValidatedPublicKey): u8 {
+ let len = vector::length(&pk.bytes);
+ let threshold_byte = *vector::borrow(&pk.bytes, len - 1);
+
+ threshold_byte
+}
+
+
+
+
+ValidatedPublicKey
objects are guaranteed to pass this check.)
+Returns the threshold t <= n of the PK.
+
+
+public fun check_and_get_threshold(bytes: vector<u8>): option::Option<u8>
+
+
+
+
+public fun check_and_get_threshold(bytes: vector<u8>): Option<u8> {
+ let len = vector::length(&bytes);
+ if (len == 0) {
+ return option::none<u8>()
+ };
+
+ let threshold_num_of_bytes = len % INDIVIDUAL_PUBLIC_KEY_NUM_BYTES;
+ let num_of_keys = len / INDIVIDUAL_PUBLIC_KEY_NUM_BYTES;
+ let threshold_byte = *vector::borrow(&bytes, len - 1);
+
+ if (num_of_keys == 0 || num_of_keys > MAX_NUMBER_OF_PUBLIC_KEYS || threshold_num_of_bytes != 1) {
+ return option::none<u8>()
+ } else if (threshold_byte == 0 || threshold_byte > (num_of_keys as u8)) {
+ return option::none<u8>()
+ } else {
+ return option::some(threshold_byte)
+ }
+}
+
+
+
+
+fun public_key_bytes_to_authentication_key(pk_bytes: vector<u8>): vector<u8>
+
+
+
+
+fun public_key_bytes_to_authentication_key(pk_bytes: vector<u8>): vector<u8> {
+ vector::push_back(&mut pk_bytes, SIGNATURE_SCHEME_ID);
+ std::hash::sha3_256(pk_bytes)
+}
+
+
+
+
+public_key_validate_internal_v2
instead. This function was NOT correctly implemented:
+
+1. It does not check that the # of sub public keys is > 0, which leads to invalid ValidatedPublicKey
objects
+against which no signature will verify, since signature_verify_strict_internal
will reject such invalid PKs.
+This is not a security issue, but a correctness issue. See bugfix_validated_pk_from_zero_subpks
.
+2. It charges too much gas: if the first sub-PK is invalid, it will charge for verifying all remaining sub-PKs.
+
+DEPRECATES:
+- new_validated_public_key_from_bytes
+- public_key_validate
+
+Return true
if the bytes in public_key
can be parsed as a valid MultiEd25519 public key: i.e., all underlying
+PKs pass point-on-curve and not-in-small-subgroup checks.
+Returns false
otherwise.
+
+
+fun public_key_validate_internal(bytes: vector<u8>): bool
+
+
+
+
+native fun public_key_validate_internal(bytes: vector<u8>): bool;
+
+
+
+
+true
if the bytes in public_key
can be parsed as a valid MultiEd25519 public key: i.e., all underlying
+sub-PKs pass point-on-curve and not-in-small-subgroup checks.
+Returns false
otherwise.
+
+
+fun public_key_validate_v2_internal(bytes: vector<u8>): bool
+
+
+
+
+native fun public_key_validate_v2_internal(bytes: vector<u8>): bool;
+
+
+
+
+multisignature
on message
verifies against the MultiEd25519 public_key
.
+Returns false
if either:
+- The PKs in public_key
do not all pass points-on-curve or not-in-small-subgroup checks,
+- The signatures in multisignature
do not all pass points-on-curve or not-in-small-subgroup checks,
+- the multisignature
on message
does not verify.
+
+
+fun signature_verify_strict_internal(multisignature: vector<u8>, public_key: vector<u8>, message: vector<u8>): bool
+
+
+
+
+native fun signature_verify_strict_internal(
+ multisignature: vector<u8>,
+ public_key: vector<u8>,
+ message: vector<u8>
+): bool;
+
+
+
+
+public fun new_unvalidated_public_key_from_bytes(bytes: vector<u8>): multi_ed25519::UnvalidatedPublicKey
+
+
+
+
+
+include NewUnvalidatedPublicKeyFromBytesAbortsIf;
+ensures result == UnvalidatedPublicKey { bytes };
+
+
+
+
+
+
+
+
+schema NewUnvalidatedPublicKeyFromBytesAbortsIf {
+ bytes: vector<u8>;
+ let length = len(bytes);
+ aborts_if length / INDIVIDUAL_PUBLIC_KEY_NUM_BYTES > MAX_NUMBER_OF_PUBLIC_KEYS;
+ aborts_if length % INDIVIDUAL_PUBLIC_KEY_NUM_BYTES != THRESHOLD_SIZE_BYTES;
+}
+
+
+
+
+
+
+### Function `new_validated_public_key_from_bytes`
+
+
+public fun new_validated_public_key_from_bytes(bytes: vector<u8>): option::Option<multi_ed25519::ValidatedPublicKey>
+
+
+
+
+
+aborts_if false;
+let cond = len(bytes) % INDIVIDUAL_PUBLIC_KEY_NUM_BYTES == THRESHOLD_SIZE_BYTES
+ && spec_public_key_validate_internal(bytes);
+ensures cond ==> result == option::spec_some(ValidatedPublicKey{bytes});
+ensures !cond ==> result == option::spec_none<ValidatedPublicKey>();
+
+
+
+
+
+
+### Function `new_validated_public_key_from_bytes_v2`
+
+
+public fun new_validated_public_key_from_bytes_v2(bytes: vector<u8>): option::Option<multi_ed25519::ValidatedPublicKey>
+
+
+
+
+
+let cond = spec_public_key_validate_v2_internal(bytes);
+ensures cond ==> result == option::spec_some(ValidatedPublicKey{bytes});
+ensures !cond ==> result == option::spec_none<ValidatedPublicKey>();
+
+
+
+
+
+
+### Function `new_signature_from_bytes`
+
+
+public fun new_signature_from_bytes(bytes: vector<u8>): multi_ed25519::Signature
+
+
+
+
+
+include NewSignatureFromBytesAbortsIf;
+ensures result == Signature { bytes };
+
+
+
+
+
+
+
+
+schema NewSignatureFromBytesAbortsIf {
+ bytes: vector<u8>;
+ aborts_if len(bytes) % INDIVIDUAL_SIGNATURE_NUM_BYTES != BITMAP_NUM_OF_BYTES;
+}
+
+
+
+
+
+
+### Function `unvalidated_public_key_num_sub_pks`
+
+
+public fun unvalidated_public_key_num_sub_pks(pk: &multi_ed25519::UnvalidatedPublicKey): u8
+
+
+
+
+
+let bytes = pk.bytes;
+include PkDivision;
+
+
+
+
+
+
+### Function `unvalidated_public_key_threshold`
+
+
+public fun unvalidated_public_key_threshold(pk: &multi_ed25519::UnvalidatedPublicKey): option::Option<u8>
+
+
+
+
+
+aborts_if false;
+ensures result == spec_check_and_get_threshold(pk.bytes);
+
+
+
+
+
+
+### Function `validated_public_key_num_sub_pks`
+
+
+public fun validated_public_key_num_sub_pks(pk: &multi_ed25519::ValidatedPublicKey): u8
+
+
+
+
+
+let bytes = pk.bytes;
+include PkDivision;
+
+
+
+
+
+
+### Function `validated_public_key_threshold`
+
+
+public fun validated_public_key_threshold(pk: &multi_ed25519::ValidatedPublicKey): u8
+
+
+
+
+
+aborts_if len(pk.bytes) == 0;
+ensures result == pk.bytes[len(pk.bytes) - 1];
+
+
+
+
+
+
+### Function `check_and_get_threshold`
+
+
+public fun check_and_get_threshold(bytes: vector<u8>): option::Option<u8>
+
+
+
+
+
+aborts_if false;
+ensures result == spec_check_and_get_threshold(bytes);
+
+
+
+
+
+
+
+
+schema PkDivision {
+ bytes: vector<u8>;
+ result: u8;
+ aborts_if len(bytes) / INDIVIDUAL_PUBLIC_KEY_NUM_BYTES > MAX_U8;
+ ensures result == len(bytes) / INDIVIDUAL_PUBLIC_KEY_NUM_BYTES;
+}
+
+
+
+
+
+
+### Function `public_key_bytes_to_authentication_key`
+
+
+fun public_key_bytes_to_authentication_key(pk_bytes: vector<u8>): vector<u8>
+
+
+
+
+
+pragma opaque;
+aborts_if false;
+ensures [abstract] result == spec_public_key_bytes_to_authentication_key(pk_bytes);
+
+
+
+
+
+
+### Function `public_key_validate_internal`
+
+
+fun public_key_validate_internal(bytes: vector<u8>): bool
+
+
+
+
+
+pragma opaque;
+aborts_if false;
+ensures (len(bytes) / INDIVIDUAL_PUBLIC_KEY_NUM_BYTES > MAX_NUMBER_OF_PUBLIC_KEYS) ==> (result == false);
+ensures result == spec_public_key_validate_internal(bytes);
+
+
+
+
+
+
+### Function `public_key_validate_v2_internal`
+
+
+fun public_key_validate_v2_internal(bytes: vector<u8>): bool
+
+
+
+
+
+pragma opaque;
+ensures result == spec_public_key_validate_v2_internal(bytes);
+
+
+
+
+
+
+### Function `signature_verify_strict_internal`
+
+
+fun signature_verify_strict_internal(multisignature: vector<u8>, public_key: vector<u8>, message: vector<u8>): bool
+
+
+
+
+
+pragma opaque;
+aborts_if false;
+ensures result == spec_signature_verify_strict_internal(multisignature, public_key, message);
+
+
+
+
+
+
+### Helper functions
+
+
+
+
+
+
+fun spec_check_and_get_threshold(bytes: vector<u8>): Option<u8> {
+ let len = len(bytes);
+ if (len == 0) {
+ option::none<u8>()
+ } else {
+ let threshold_num_of_bytes = len % INDIVIDUAL_PUBLIC_KEY_NUM_BYTES;
+ let num_of_keys = len / INDIVIDUAL_PUBLIC_KEY_NUM_BYTES;
+ let threshold_byte = bytes[len - 1];
+ if (num_of_keys == 0 || num_of_keys > MAX_NUMBER_OF_PUBLIC_KEYS || len % INDIVIDUAL_PUBLIC_KEY_NUM_BYTES != 1) {
+ option::none<u8>()
+ } else if (threshold_byte == 0 || threshold_byte > (num_of_keys as u8)) {
+ option::none<u8>()
+ } else {
+ option::spec_some(threshold_byte)
+ }
+ }
+}
+
+
+
+
+
+
+
+
+fun spec_signature_verify_strict_internal(
+ multisignature: vector<u8>,
+ public_key: vector<u8>,
+ message: vector<u8>
+): bool;
+
+
+
+
+
+
+
+
+fun spec_public_key_validate_internal(bytes: vector<u8>): bool;
+
+
+
+
+
+
+
+
+fun spec_public_key_validate_v2_internal(bytes: vector<u8>): bool;
+
+
+
+
+
+
+
+
+fun spec_public_key_bytes_to_authentication_key(pk_bytes: vector<u8>): vector<u8>;
+
+
+
+
+
+
+
+
+fun spec_signature_verify_strict_t<T>(signature: Signature, public_key: UnvalidatedPublicKey, data: T): bool {
+ let encoded = ed25519::new_signed_message<T>(data);
+ let message = bcs::serialize(encoded);
+ spec_signature_verify_strict_internal(signature.bytes, public_key.bytes, message)
+}
+
+
+
+[move-book]: https://aptos.dev/move/book/SUMMARY
diff --git a/aptos-move/framework/aptos-stdlib/tests/compiler-v2-doc/overview.md b/aptos-move/framework/aptos-stdlib/tests/compiler-v2-doc/overview.md
new file mode 100644
index 0000000000000..6176385db1d97
--- /dev/null
+++ b/aptos-move/framework/aptos-stdlib/tests/compiler-v2-doc/overview.md
@@ -0,0 +1,50 @@
+
+
+
+# Aptos Standard Library
+
+
+This is the reference documentation of the Aptos standard library.
+
+
+
+
+## Index
+
+
+- [`0x1::any`](any.md#0x1_any)
+- [`0x1::aptos_hash`](hash.md#0x1_aptos_hash)
+- [`0x1::big_vector`](big_vector.md#0x1_big_vector)
+- [`0x1::bls12381`](bls12381.md#0x1_bls12381)
+- [`0x1::bls12381_algebra`](bls12381_algebra.md#0x1_bls12381_algebra)
+- [`0x1::bn254_algebra`](bn254_algebra.md#0x1_bn254_algebra)
+- [`0x1::capability`](capability.md#0x1_capability)
+- [`0x1::comparator`](comparator.md#0x1_comparator)
+- [`0x1::copyable_any`](copyable_any.md#0x1_copyable_any)
+- [`0x1::crypto_algebra`](crypto_algebra.md#0x1_crypto_algebra)
+- [`0x1::debug`](debug.md#0x1_debug)
+- [`0x1::ed25519`](ed25519.md#0x1_ed25519)
+- [`0x1::fixed_point64`](fixed_point64.md#0x1_fixed_point64)
+- [`0x1::from_bcs`](from_bcs.md#0x1_from_bcs)
+- [`0x1::math128`](math128.md#0x1_math128)
+- [`0x1::math64`](math64.md#0x1_math64)
+- [`0x1::math_fixed`](math_fixed.md#0x1_math_fixed)
+- [`0x1::math_fixed64`](math_fixed64.md#0x1_math_fixed64)
+- [`0x1::multi_ed25519`](multi_ed25519.md#0x1_multi_ed25519)
+- [`0x1::pool_u64`](pool_u64.md#0x1_pool_u64)
+- [`0x1::pool_u64_unbound`](pool_u64_unbound.md#0x1_pool_u64_unbound)
+- [`0x1::ristretto255`](ristretto255.md#0x1_ristretto255)
+- [`0x1::ristretto255_bulletproofs`](ristretto255_bulletproofs.md#0x1_ristretto255_bulletproofs)
+- [`0x1::ristretto255_elgamal`](ristretto255_elgamal.md#0x1_ristretto255_elgamal)
+- [`0x1::ristretto255_pedersen`](ristretto255_pedersen.md#0x1_ristretto255_pedersen)
+- [`0x1::secp256k1`](secp256k1.md#0x1_secp256k1)
+- [`0x1::simple_map`](simple_map.md#0x1_simple_map)
+- [`0x1::smart_table`](smart_table.md#0x1_smart_table)
+- [`0x1::smart_vector`](smart_vector.md#0x1_smart_vector)
+- [`0x1::string_utils`](string_utils.md#0x1_string_utils)
+- [`0x1::table`](table.md#0x1_table)
+- [`0x1::table_with_length`](table_with_length.md#0x1_table_with_length)
+- [`0x1::type_info`](type_info.md#0x1_type_info)
+
+
+[move-book]: https://aptos.dev/move/book/SUMMARY
diff --git a/aptos-move/framework/aptos-stdlib/tests/compiler-v2-doc/pool_u64.md b/aptos-move/framework/aptos-stdlib/tests/compiler-v2-doc/pool_u64.md
new file mode 100644
index 0000000000000..14616c6041e08
--- /dev/null
+++ b/aptos-move/framework/aptos-stdlib/tests/compiler-v2-doc/pool_u64.md
@@ -0,0 +1,1290 @@
+
+
+
+# Module `0x1::pool_u64`
+
+
+Simple module for tracking and calculating shares of a pool of coins. The shares are worth more as the total coins in
+the pool increases. New shareholder can buy more shares or redeem their existing shares.
+
+Example flow:
+1. Pool start outs empty.
+2. Shareholder A buys in with 1000 coins. A will receive 1000 shares in the pool. Pool now has 1000 total coins and
+1000 total shares.
+3. Pool appreciates in value from rewards and now has 2000 coins. A's 1000 shares are now worth 2000 coins.
+4. Shareholder B now buys in with 1000 coins. Since before the buy in, each existing share is worth 2 coins, B will
+receive 500 shares in exchange for 1000 coins. Pool now has 1500 shares and 3000 coins.
+5. Pool appreciates in value from rewards and now has 6000 coins.
+6. A redeems 500 shares. Each share is worth 6000 / 1500 = 4. A receives 2000 coins. Pool has 4000 coins and 1000
+shares left.
+
+
+- [Struct `Pool`](#0x1_pool_u64_Pool)
+- [Constants](#@Constants_0)
+- [Function `new`](#0x1_pool_u64_new)
+- [Function `create`](#0x1_pool_u64_create)
+- [Function `create_with_scaling_factor`](#0x1_pool_u64_create_with_scaling_factor)
+- [Function `destroy_empty`](#0x1_pool_u64_destroy_empty)
+- [Function `total_coins`](#0x1_pool_u64_total_coins)
+- [Function `total_shares`](#0x1_pool_u64_total_shares)
+- [Function `contains`](#0x1_pool_u64_contains)
+- [Function `shares`](#0x1_pool_u64_shares)
+- [Function `balance`](#0x1_pool_u64_balance)
+- [Function `shareholders`](#0x1_pool_u64_shareholders)
+- [Function `shareholders_count`](#0x1_pool_u64_shareholders_count)
+- [Function `update_total_coins`](#0x1_pool_u64_update_total_coins)
+- [Function `buy_in`](#0x1_pool_u64_buy_in)
+- [Function `add_shares`](#0x1_pool_u64_add_shares)
+- [Function `redeem_shares`](#0x1_pool_u64_redeem_shares)
+- [Function `transfer_shares`](#0x1_pool_u64_transfer_shares)
+- [Function `deduct_shares`](#0x1_pool_u64_deduct_shares)
+- [Function `amount_to_shares`](#0x1_pool_u64_amount_to_shares)
+- [Function `amount_to_shares_with_total_coins`](#0x1_pool_u64_amount_to_shares_with_total_coins)
+- [Function `shares_to_amount`](#0x1_pool_u64_shares_to_amount)
+- [Function `shares_to_amount_with_total_coins`](#0x1_pool_u64_shares_to_amount_with_total_coins)
+- [Function `multiply_then_divide`](#0x1_pool_u64_multiply_then_divide)
+- [Function `to_u128`](#0x1_pool_u64_to_u128)
+- [Specification](#@Specification_1)
+ - [Struct `Pool`](#@Specification_1_Pool)
+ - [Function `contains`](#@Specification_1_contains)
+ - [Function `shares`](#@Specification_1_shares)
+ - [Function `balance`](#@Specification_1_balance)
+ - [Function `buy_in`](#@Specification_1_buy_in)
+ - [Function `add_shares`](#@Specification_1_add_shares)
+ - [Function `redeem_shares`](#@Specification_1_redeem_shares)
+ - [Function `transfer_shares`](#@Specification_1_transfer_shares)
+ - [Function `deduct_shares`](#@Specification_1_deduct_shares)
+ - [Function `amount_to_shares_with_total_coins`](#@Specification_1_amount_to_shares_with_total_coins)
+ - [Function `shares_to_amount_with_total_coins`](#@Specification_1_shares_to_amount_with_total_coins)
+ - [Function `multiply_then_divide`](#@Specification_1_multiply_then_divide)
+
+
+use 0x1::error;
+use 0x1::simple_map;
+use 0x1::vector;
+
+
+
+
+
+
+## Struct `Pool`
+
+
+
+struct Pool has store
+
+
+
+
+shareholders_limit: u64
+total_coins: u64
+total_shares: u64
+shares: simple_map::SimpleMap<address, u64>
+shareholders: vector<address>
+scaling_factor: u64
+const MAX_U64: u64 = 18446744073709551615;
+
+
+
+
+
+
+Cannot redeem more shares than the shareholder has in the pool.
+
+
+const EINSUFFICIENT_SHARES: u64 = 4;
+
+
+
+
+
+
+Cannot destroy non-empty pool.
+
+
+const EPOOL_IS_NOT_EMPTY: u64 = 3;
+
+
+
+
+
+
+Pool's total coins cannot exceed u64.max.
+
+
+const EPOOL_TOTAL_COINS_OVERFLOW: u64 = 6;
+
+
+
+
+
+
+Pool's total shares cannot exceed u64.max.
+
+
+const EPOOL_TOTAL_SHARES_OVERFLOW: u64 = 7;
+
+
+
+
+
+
+Shareholder not present in pool.
+
+
+const ESHAREHOLDER_NOT_FOUND: u64 = 1;
+
+
+
+
+
+
+Shareholder cannot have more than u64.max shares.
+
+
+const ESHAREHOLDER_SHARES_OVERFLOW: u64 = 5;
+
+
+
+
+
+
+There are too many shareholders in the pool.
+
+
+const ETOO_MANY_SHAREHOLDERS: u64 = 2;
+
+
+
+
+
+
+## Function `new`
+
+Create a new pool.
+
+
+public fun new(shareholders_limit: u64): pool_u64::Pool
+
+
+
+
+public fun new(shareholders_limit: u64): Pool {
+ // Default to a scaling factor of 1 (effectively no scaling).
+ create_with_scaling_factor(shareholders_limit, 1)
+}
+
+
+
+
+new
instead.
+Create a new pool.
+
+
+#[deprecated]
+public fun create(shareholders_limit: u64): pool_u64::Pool
+
+
+
+
+public fun create(shareholders_limit: u64): Pool {
+ new(shareholders_limit)
+}
+
+
+
+
+scaling_factor
.
+
+
+public fun create_with_scaling_factor(shareholders_limit: u64, scaling_factor: u64): pool_u64::Pool
+
+
+
+
+public fun create_with_scaling_factor(shareholders_limit: u64, scaling_factor: u64): Pool {
+ Pool {
+ shareholders_limit,
+ total_coins: 0,
+ total_shares: 0,
+ shares: simple_map::create<address, u64>(),
+ shareholders: vector::empty<address>(),
+ scaling_factor,
+ }
+}
+
+
+
+
+public fun destroy_empty(pool: pool_u64::Pool)
+
+
+
+
+public fun destroy_empty(pool: Pool) {
+ assert!(pool.total_coins == 0, error::invalid_state(EPOOL_IS_NOT_EMPTY));
+ let Pool {
+ shareholders_limit: _,
+ total_coins: _,
+ total_shares: _,
+ shares: _,
+ shareholders: _,
+ scaling_factor: _,
+ } = pool;
+}
+
+
+
+
+pool
's total balance of coins.
+
+
+public fun total_coins(pool: &pool_u64::Pool): u64
+
+
+
+
+public fun total_coins(pool: &Pool): u64 {
+ pool.total_coins
+}
+
+
+
+
+pool
.
+
+
+public fun total_shares(pool: &pool_u64::Pool): u64
+
+
+
+
+public fun total_shares(pool: &Pool): u64 {
+ pool.total_shares
+}
+
+
+
+
+shareholder
is in pool
.
+
+
+public fun contains(pool: &pool_u64::Pool, shareholder: address): bool
+
+
+
+
+public fun contains(pool: &Pool, shareholder: address): bool {
+ simple_map::contains_key(&pool.shares, &shareholder)
+}
+
+
+
+
+stakeholder
in pool
.
+
+
+public fun shares(pool: &pool_u64::Pool, shareholder: address): u64
+
+
+
+
+public fun shares(pool: &Pool, shareholder: address): u64 {
+ if (contains(pool, shareholder)) {
+ *simple_map::borrow(&pool.shares, &shareholder)
+ } else {
+ 0
+ }
+}
+
+
+
+
+shareholder
in pool.
+
+
+public fun balance(pool: &pool_u64::Pool, shareholder: address): u64
+
+
+
+
+public fun balance(pool: &Pool, shareholder: address): u64 {
+ let num_shares = shares(pool, shareholder);
+ shares_to_amount(pool, num_shares)
+}
+
+
+
+
+pool
.
+
+
+public fun shareholders(pool: &pool_u64::Pool): vector<address>
+
+
+
+
+public fun shareholders(pool: &Pool): vector<address> {
+ pool.shareholders
+}
+
+
+
+
+pool
.
+
+
+public fun shareholders_count(pool: &pool_u64::Pool): u64
+
+
+
+
+public fun shareholders_count(pool: &Pool): u64 {
+ vector::length(&pool.shareholders)
+}
+
+
+
+
+pool
's total balance of coins.
+
+
+public fun update_total_coins(pool: &mut pool_u64::Pool, new_total_coins: u64)
+
+
+
+
+public fun update_total_coins(pool: &mut Pool, new_total_coins: u64) {
+ pool.total_coins = new_total_coins;
+}
+
+
+
+
+public fun buy_in(pool: &mut pool_u64::Pool, shareholder: address, coins_amount: u64): u64
+
+
+
+
+public fun buy_in(pool: &mut Pool, shareholder: address, coins_amount: u64): u64 {
+ if (coins_amount == 0) return 0;
+
+ let new_shares = amount_to_shares(pool, coins_amount);
+ assert!(MAX_U64 - pool.total_coins >= coins_amount, error::invalid_argument(EPOOL_TOTAL_COINS_OVERFLOW));
+ assert!(MAX_U64 - pool.total_shares >= new_shares, error::invalid_argument(EPOOL_TOTAL_COINS_OVERFLOW));
+
+ pool.total_coins = pool.total_coins + coins_amount;
+ pool.total_shares = pool.total_shares + new_shares;
+ add_shares(pool, shareholder, new_shares);
+ new_shares
+}
+
+
+
+
+shareholder
in pool
.
+This would dilute other shareholders if the pool's balance of coins didn't change.
+
+
+fun add_shares(pool: &mut pool_u64::Pool, shareholder: address, new_shares: u64): u64
+
+
+
+
+fun add_shares(pool: &mut Pool, shareholder: address, new_shares: u64): u64 {
+ if (contains(pool, shareholder)) {
+ let existing_shares = simple_map::borrow_mut(&mut pool.shares, &shareholder);
+ let current_shares = *existing_shares;
+ assert!(MAX_U64 - current_shares >= new_shares, error::invalid_argument(ESHAREHOLDER_SHARES_OVERFLOW));
+
+ *existing_shares = current_shares + new_shares;
+ *existing_shares
+ } else if (new_shares > 0) {
+ assert!(
+ vector::length(&pool.shareholders) < pool.shareholders_limit,
+ error::invalid_state(ETOO_MANY_SHAREHOLDERS),
+ );
+
+ vector::push_back(&mut pool.shareholders, shareholder);
+ simple_map::add(&mut pool.shares, shareholder, new_shares);
+ new_shares
+ } else {
+ new_shares
+ }
+}
+
+
+
+
+shareholder
to redeem their shares in pool
for coins.
+
+
+public fun redeem_shares(pool: &mut pool_u64::Pool, shareholder: address, shares_to_redeem: u64): u64
+
+
+
+
+public fun redeem_shares(pool: &mut Pool, shareholder: address, shares_to_redeem: u64): u64 {
+ assert!(contains(pool, shareholder), error::invalid_argument(ESHAREHOLDER_NOT_FOUND));
+ assert!(shares(pool, shareholder) >= shares_to_redeem, error::invalid_argument(EINSUFFICIENT_SHARES));
+
+ if (shares_to_redeem == 0) return 0;
+
+ let redeemed_coins = shares_to_amount(pool, shares_to_redeem);
+ pool.total_coins = pool.total_coins - redeemed_coins;
+ pool.total_shares = pool.total_shares - shares_to_redeem;
+ deduct_shares(pool, shareholder, shares_to_redeem);
+
+ redeemed_coins
+}
+
+
+
+
+shareholder_1
to shareholder_2
.
+
+
+public fun transfer_shares(pool: &mut pool_u64::Pool, shareholder_1: address, shareholder_2: address, shares_to_transfer: u64)
+
+
+
+
+public fun transfer_shares(
+ pool: &mut Pool,
+ shareholder_1: address,
+ shareholder_2: address,
+ shares_to_transfer: u64,
+) {
+ assert!(contains(pool, shareholder_1), error::invalid_argument(ESHAREHOLDER_NOT_FOUND));
+ assert!(shares(pool, shareholder_1) >= shares_to_transfer, error::invalid_argument(EINSUFFICIENT_SHARES));
+ if (shares_to_transfer == 0) return;
+
+ deduct_shares(pool, shareholder_1, shares_to_transfer);
+ add_shares(pool, shareholder_2, shares_to_transfer);
+}
+
+
+
+
+shareholder
's number of shares in pool
and return the number of remaining shares.
+
+
+fun deduct_shares(pool: &mut pool_u64::Pool, shareholder: address, num_shares: u64): u64
+
+
+
+
+fun deduct_shares(pool: &mut Pool, shareholder: address, num_shares: u64): u64 {
+ assert!(contains(pool, shareholder), error::invalid_argument(ESHAREHOLDER_NOT_FOUND));
+ assert!(shares(pool, shareholder) >= num_shares, error::invalid_argument(EINSUFFICIENT_SHARES));
+
+ let existing_shares = simple_map::borrow_mut(&mut pool.shares, &shareholder);
+ *existing_shares = *existing_shares - num_shares;
+
+ // Remove the shareholder completely if they have no shares left.
+ let remaining_shares = *existing_shares;
+ if (remaining_shares == 0) {
+ let (_, shareholder_index) = vector::index_of(&pool.shareholders, &shareholder);
+ vector::remove(&mut pool.shareholders, shareholder_index);
+ simple_map::remove(&mut pool.shares, &shareholder);
+ };
+
+ remaining_shares
+}
+
+
+
+
+coins_amount
can buy in pool
.
+amount
needs to big enough to avoid rounding number.
+
+
+public fun amount_to_shares(pool: &pool_u64::Pool, coins_amount: u64): u64
+
+
+
+
+public fun amount_to_shares(pool: &Pool, coins_amount: u64): u64 {
+ amount_to_shares_with_total_coins(pool, coins_amount, pool.total_coins)
+}
+
+
+
+
+coins_amount
can buy in pool
with a custom total coins number.
+amount
needs to big enough to avoid rounding number.
+
+
+public fun amount_to_shares_with_total_coins(pool: &pool_u64::Pool, coins_amount: u64, total_coins: u64): u64
+
+
+
+
+public fun amount_to_shares_with_total_coins(pool: &Pool, coins_amount: u64, total_coins: u64): u64 {
+ // No shares yet so amount is worth the same number of shares.
+ if (pool.total_coins == 0 || pool.total_shares == 0) {
+ // Multiply by scaling factor to minimize rounding errors during internal calculations for buy ins/redeems.
+ // This can overflow but scaling factor is expected to be chosen carefully so this would not overflow.
+ coins_amount * pool.scaling_factor
+ } else {
+ // Shares price = total_coins / total existing shares.
+ // New number of shares = new_amount / shares_price = new_amount * existing_shares / total_amount.
+ // We rearrange the calc and do multiplication first to avoid rounding errors.
+ multiply_then_divide(pool, coins_amount, pool.total_shares, total_coins)
+ }
+}
+
+
+
+
+shares
are worth in pool
.
+shares
needs to big enough to avoid rounding number.
+
+
+public fun shares_to_amount(pool: &pool_u64::Pool, shares: u64): u64
+
+
+
+
+public fun shares_to_amount(pool: &Pool, shares: u64): u64 {
+ shares_to_amount_with_total_coins(pool, shares, pool.total_coins)
+}
+
+
+
+
+shares
are worth in pool
with a custom total coins number.
+shares
needs to big enough to avoid rounding number.
+
+
+public fun shares_to_amount_with_total_coins(pool: &pool_u64::Pool, shares: u64, total_coins: u64): u64
+
+
+
+
+public fun shares_to_amount_with_total_coins(pool: &Pool, shares: u64, total_coins: u64): u64 {
+ // No shares or coins yet so shares are worthless.
+ if (pool.total_coins == 0 || pool.total_shares == 0) {
+ 0
+ } else {
+ // Shares price = total_coins / total existing shares.
+ // Shares worth = shares * shares price = shares * total_coins / total existing shares.
+ // We rearrange the calc and do multiplication first to avoid rounding errors.
+ multiply_then_divide(pool, shares, total_coins, pool.total_shares)
+ }
+}
+
+
+
+
+public fun multiply_then_divide(_pool: &pool_u64::Pool, x: u64, y: u64, z: u64): u64
+
+
+
+
+public fun multiply_then_divide(_pool: &Pool, x: u64, y: u64, z: u64): u64 {
+ let result = (to_u128(x) * to_u128(y)) / to_u128(z);
+ (result as u64)
+}
+
+
+
+
+fun to_u128(num: u64): u128
+
+
+
+
+fun to_u128(num: u64): u128 {
+ (num as u128)
+}
+
+
+
+
+pragma verify = false;
+
+
+
+
+
+
+### Struct `Pool`
+
+
+struct Pool has store
+
+
+
+
+shareholders_limit: u64
+total_coins: u64
+total_shares: u64
+shares: simple_map::SimpleMap<address, u64>
+shareholders: vector<address>
+scaling_factor: u64
+invariant forall addr: address:
+ (simple_map::spec_contains_key(shares, addr) == vector::spec_contains(shareholders, addr));
+invariant forall i in 0..len(shareholders), j in 0..len(shareholders):
+ shareholders[i] == shareholders[j] ==> i == j;
+
+
+
+
+
+
+
+
+fun spec_contains(pool: Pool, shareholder: address): bool {
+ simple_map::spec_contains_key(pool.shares, shareholder)
+}
+
+
+
+
+
+
+### Function `contains`
+
+
+public fun contains(pool: &pool_u64::Pool, shareholder: address): bool
+
+
+
+
+
+aborts_if false;
+ensures result == spec_contains(pool, shareholder);
+
+
+
+
+
+
+
+
+fun spec_shares(pool: Pool, shareholder: address): u64 {
+ if (simple_map::spec_contains_key(pool.shares, shareholder)) {
+ simple_map::spec_get(pool.shares, shareholder)
+ }
+ else {
+ 0
+ }
+}
+
+
+
+
+
+
+### Function `shares`
+
+
+public fun shares(pool: &pool_u64::Pool, shareholder: address): u64
+
+
+
+
+
+aborts_if false;
+ensures result == spec_shares(pool, shareholder);
+
+
+
+
+
+
+### Function `balance`
+
+
+public fun balance(pool: &pool_u64::Pool, shareholder: address): u64
+
+
+
+
+
+let shares = spec_shares(pool, shareholder);
+let total_coins = pool.total_coins;
+aborts_if pool.total_coins > 0 && pool.total_shares > 0 && (shares * total_coins) / pool.total_shares > MAX_U64;
+ensures result == spec_shares_to_amount_with_total_coins(pool, shares, total_coins);
+
+
+
+
+
+
+### Function `buy_in`
+
+
+public fun buy_in(pool: &mut pool_u64::Pool, shareholder: address, coins_amount: u64): u64
+
+
+
+
+
+let new_shares = spec_amount_to_shares_with_total_coins(pool, coins_amount, pool.total_coins);
+aborts_if pool.total_coins + coins_amount > MAX_U64;
+aborts_if pool.total_shares + new_shares > MAX_U64;
+include coins_amount > 0 ==> AddSharesAbortsIf { new_shares: new_shares };
+include coins_amount > 0 ==> AddSharesEnsures { new_shares: new_shares };
+ensures pool.total_coins == old(pool.total_coins) + coins_amount;
+ensures pool.total_shares == old(pool.total_shares) + new_shares;
+ensures result == new_shares;
+
+
+
+
+
+
+### Function `add_shares`
+
+
+fun add_shares(pool: &mut pool_u64::Pool, shareholder: address, new_shares: u64): u64
+
+
+
+
+
+include AddSharesAbortsIf;
+include AddSharesEnsures;
+let key_exists = simple_map::spec_contains_key(pool.shares, shareholder);
+ensures result == if (key_exists) { simple_map::spec_get(pool.shares, shareholder) }
+else { new_shares };
+
+
+
+
+
+
+
+
+schema AddSharesAbortsIf {
+ pool: Pool;
+ shareholder: address;
+ new_shares: u64;
+ let key_exists = simple_map::spec_contains_key(pool.shares, shareholder);
+ let current_shares = simple_map::spec_get(pool.shares, shareholder);
+ aborts_if key_exists && current_shares + new_shares > MAX_U64;
+ aborts_if !key_exists && new_shares > 0 && len(pool.shareholders) >= pool.shareholders_limit;
+}
+
+
+
+
+
+
+
+
+schema AddSharesEnsures {
+ pool: Pool;
+ shareholder: address;
+ new_shares: u64;
+ let key_exists = simple_map::spec_contains_key(pool.shares, shareholder);
+ let current_shares = simple_map::spec_get(pool.shares, shareholder);
+ ensures key_exists ==>
+ pool.shares == simple_map::spec_set(old(pool.shares), shareholder, current_shares + new_shares);
+ ensures (!key_exists && new_shares > 0) ==>
+ pool.shares == simple_map::spec_set(old(pool.shares), shareholder, new_shares);
+ ensures (!key_exists && new_shares > 0) ==>
+ vector::eq_push_back(pool.shareholders, old(pool.shareholders), shareholder);
+}
+
+
+
+
+
+
+
+
+fun spec_amount_to_shares_with_total_coins(pool: Pool, coins_amount: u64, total_coins: u64): u64 {
+ if (pool.total_coins == 0 || pool.total_shares == 0) {
+ coins_amount * pool.scaling_factor
+ }
+ else {
+ (coins_amount * pool.total_shares) / total_coins
+ }
+}
+
+
+
+
+
+
+### Function `redeem_shares`
+
+
+public fun redeem_shares(pool: &mut pool_u64::Pool, shareholder: address, shares_to_redeem: u64): u64
+
+
+
+
+
+let redeemed_coins = spec_shares_to_amount_with_total_coins(pool, shares_to_redeem, pool.total_coins);
+aborts_if !spec_contains(pool, shareholder);
+aborts_if spec_shares(pool, shareholder) < shares_to_redeem;
+aborts_if pool.total_coins < redeemed_coins;
+aborts_if pool.total_shares < shares_to_redeem;
+ensures pool.total_coins == old(pool.total_coins) - redeemed_coins;
+ensures pool.total_shares == old(pool.total_shares) - shares_to_redeem;
+include shares_to_redeem > 0 ==> DeductSharesEnsures { num_shares: shares_to_redeem };
+ensures result == redeemed_coins;
+
+
+
+
+
+
+### Function `transfer_shares`
+
+
+public fun transfer_shares(pool: &mut pool_u64::Pool, shareholder_1: address, shareholder_2: address, shares_to_transfer: u64)
+
+
+
+
+
+pragma aborts_if_is_partial;
+aborts_if !spec_contains(pool, shareholder_1);
+aborts_if spec_shares(pool, shareholder_1) < shares_to_transfer;
+
+
+
+
+
+
+### Function `deduct_shares`
+
+
+fun deduct_shares(pool: &mut pool_u64::Pool, shareholder: address, num_shares: u64): u64
+
+
+
+
+
+aborts_if !spec_contains(pool, shareholder);
+aborts_if spec_shares(pool, shareholder) < num_shares;
+include DeductSharesEnsures;
+let remaining_shares = simple_map::spec_get(pool.shares, shareholder) - num_shares;
+ensures remaining_shares > 0 ==> result == simple_map::spec_get(pool.shares, shareholder);
+ensures remaining_shares == 0 ==> result == 0;
+
+
+
+
+
+
+
+
+schema DeductSharesEnsures {
+ pool: Pool;
+ shareholder: address;
+ num_shares: u64;
+ let remaining_shares = simple_map::spec_get(pool.shares, shareholder) - num_shares;
+ ensures remaining_shares > 0 ==> simple_map::spec_get(pool.shares, shareholder) == remaining_shares;
+ ensures remaining_shares == 0 ==> !simple_map::spec_contains_key(pool.shares, shareholder);
+ ensures remaining_shares == 0 ==> !vector::spec_contains(pool.shareholders, shareholder);
+}
+
+
+
+
+
+
+### Function `amount_to_shares_with_total_coins`
+
+
+public fun amount_to_shares_with_total_coins(pool: &pool_u64::Pool, coins_amount: u64, total_coins: u64): u64
+
+
+
+
+
+aborts_if pool.total_coins > 0 && pool.total_shares > 0
+ && (coins_amount * pool.total_shares) / total_coins > MAX_U64;
+aborts_if (pool.total_coins == 0 || pool.total_shares == 0)
+ && coins_amount * pool.scaling_factor > MAX_U64;
+aborts_if pool.total_coins > 0 && pool.total_shares > 0 && total_coins == 0;
+ensures result == spec_amount_to_shares_with_total_coins(pool, coins_amount, total_coins);
+
+
+
+
+
+
+### Function `shares_to_amount_with_total_coins`
+
+
+public fun shares_to_amount_with_total_coins(pool: &pool_u64::Pool, shares: u64, total_coins: u64): u64
+
+
+
+
+
+aborts_if pool.total_coins > 0 && pool.total_shares > 0
+ && (shares * total_coins) / pool.total_shares > MAX_U64;
+ensures result == spec_shares_to_amount_with_total_coins(pool, shares, total_coins);
+
+
+
+
+
+
+
+
+fun spec_shares_to_amount_with_total_coins(pool: Pool, shares: u64, total_coins: u64): u64 {
+ if (pool.total_coins == 0 || pool.total_shares == 0) {
+ 0
+ }
+ else {
+ (shares * total_coins) / pool.total_shares
+ }
+}
+
+
+
+
+
+
+### Function `multiply_then_divide`
+
+
+public fun multiply_then_divide(_pool: &pool_u64::Pool, x: u64, y: u64, z: u64): u64
+
+
+
+
+
+aborts_if z == 0;
+aborts_if (x * y) / z > MAX_U64;
+ensures result == (x * y) / z;
+
+
+
+[move-book]: https://aptos.dev/move/book/SUMMARY
diff --git a/aptos-move/framework/aptos-stdlib/tests/compiler-v2-doc/pool_u64_unbound.md b/aptos-move/framework/aptos-stdlib/tests/compiler-v2-doc/pool_u64_unbound.md
new file mode 100644
index 0000000000000..6d23dd44b65b4
--- /dev/null
+++ b/aptos-move/framework/aptos-stdlib/tests/compiler-v2-doc/pool_u64_unbound.md
@@ -0,0 +1,1333 @@
+
+
+
+# Module `0x1::pool_u64_unbound`
+
+
+Simple module for tracking and calculating shares of a pool of coins. The shares are worth more as the total coins in
+the pool increases. New shareholder can buy more shares or redeem their existing shares.
+
+Example flow:
+1. Pool start outs empty.
+2. Shareholder A buys in with 1000 coins. A will receive 1000 shares in the pool. Pool now has 1000 total coins and
+1000 total shares.
+3. Pool appreciates in value from rewards and now has 2000 coins. A's 1000 shares are now worth 2000 coins.
+4. Shareholder B now buys in with 1000 coins. Since before the buy in, each existing share is worth 2 coins, B will
+receive 500 shares in exchange for 1000 coins. Pool now has 1500 shares and 3000 coins.
+5. Pool appreciates in value from rewards and now has 6000 coins.
+6. A redeems 500 shares. Each share is worth 6000 / 1500 = 4. A receives 2000 coins. Pool has 4000 coins and 1000
+shares left.
+
+
+- [Struct `Pool`](#0x1_pool_u64_unbound_Pool)
+- [Constants](#@Constants_0)
+- [Function `new`](#0x1_pool_u64_unbound_new)
+- [Function `create`](#0x1_pool_u64_unbound_create)
+- [Function `create_with_scaling_factor`](#0x1_pool_u64_unbound_create_with_scaling_factor)
+- [Function `destroy_empty`](#0x1_pool_u64_unbound_destroy_empty)
+- [Function `total_coins`](#0x1_pool_u64_unbound_total_coins)
+- [Function `total_shares`](#0x1_pool_u64_unbound_total_shares)
+- [Function `contains`](#0x1_pool_u64_unbound_contains)
+- [Function `shares`](#0x1_pool_u64_unbound_shares)
+- [Function `balance`](#0x1_pool_u64_unbound_balance)
+- [Function `shareholders_count`](#0x1_pool_u64_unbound_shareholders_count)
+- [Function `update_total_coins`](#0x1_pool_u64_unbound_update_total_coins)
+- [Function `buy_in`](#0x1_pool_u64_unbound_buy_in)
+- [Function `add_shares`](#0x1_pool_u64_unbound_add_shares)
+- [Function `redeem_shares`](#0x1_pool_u64_unbound_redeem_shares)
+- [Function `transfer_shares`](#0x1_pool_u64_unbound_transfer_shares)
+- [Function `deduct_shares`](#0x1_pool_u64_unbound_deduct_shares)
+- [Function `amount_to_shares`](#0x1_pool_u64_unbound_amount_to_shares)
+- [Function `amount_to_shares_with_total_coins`](#0x1_pool_u64_unbound_amount_to_shares_with_total_coins)
+- [Function `shares_to_amount`](#0x1_pool_u64_unbound_shares_to_amount)
+- [Function `shares_to_amount_with_total_coins`](#0x1_pool_u64_unbound_shares_to_amount_with_total_coins)
+- [Function `shares_to_amount_with_total_stats`](#0x1_pool_u64_unbound_shares_to_amount_with_total_stats)
+- [Function `multiply_then_divide`](#0x1_pool_u64_unbound_multiply_then_divide)
+- [Function `to_u128`](#0x1_pool_u64_unbound_to_u128)
+- [Function `to_u256`](#0x1_pool_u64_unbound_to_u256)
+- [Specification](#@Specification_1)
+ - [Struct `Pool`](#@Specification_1_Pool)
+ - [Function `contains`](#@Specification_1_contains)
+ - [Function `shares`](#@Specification_1_shares)
+ - [Function `balance`](#@Specification_1_balance)
+ - [Function `buy_in`](#@Specification_1_buy_in)
+ - [Function `add_shares`](#@Specification_1_add_shares)
+ - [Function `redeem_shares`](#@Specification_1_redeem_shares)
+ - [Function `transfer_shares`](#@Specification_1_transfer_shares)
+ - [Function `deduct_shares`](#@Specification_1_deduct_shares)
+ - [Function `amount_to_shares_with_total_coins`](#@Specification_1_amount_to_shares_with_total_coins)
+ - [Function `shares_to_amount_with_total_coins`](#@Specification_1_shares_to_amount_with_total_coins)
+ - [Function `multiply_then_divide`](#@Specification_1_multiply_then_divide)
+ - [Function `to_u128`](#@Specification_1_to_u128)
+ - [Function `to_u256`](#@Specification_1_to_u256)
+
+
+use 0x1::error;
+use 0x1::table_with_length;
+
+
+
+
+
+
+## Struct `Pool`
+
+
+
+struct Pool has store
+
+
+
+
+total_coins: u64
+total_shares: u128
+shares: table_with_length::TableWithLength<address, u128>
+scaling_factor: u64
+const MAX_U64: u64 = 18446744073709551615;
+
+
+
+
+
+
+
+
+const MAX_U128: u128 = 340282366920938463463374607431768211455;
+
+
+
+
+
+
+Cannot redeem more shares than the shareholder has in the pool.
+
+
+const EINSUFFICIENT_SHARES: u64 = 4;
+
+
+
+
+
+
+Cannot destroy non-empty pool.
+
+
+const EPOOL_IS_NOT_EMPTY: u64 = 3;
+
+
+
+
+
+
+Pool's total coins cannot exceed u64.max.
+
+
+const EPOOL_TOTAL_COINS_OVERFLOW: u64 = 6;
+
+
+
+
+
+
+Pool's total shares cannot exceed u64.max.
+
+
+const EPOOL_TOTAL_SHARES_OVERFLOW: u64 = 7;
+
+
+
+
+
+
+Shareholder not present in pool.
+
+
+const ESHAREHOLDER_NOT_FOUND: u64 = 1;
+
+
+
+
+
+
+Shareholder cannot have more than u64.max shares.
+
+
+const ESHAREHOLDER_SHARES_OVERFLOW: u64 = 5;
+
+
+
+
+
+
+There are too many shareholders in the pool.
+
+
+const ETOO_MANY_SHAREHOLDERS: u64 = 2;
+
+
+
+
+
+
+## Function `new`
+
+Create a new pool.
+
+
+public fun new(): pool_u64_unbound::Pool
+
+
+
+
+public fun new(): Pool {
+ // Default to a scaling factor of 1 (effectively no scaling).
+ create_with_scaling_factor(1)
+}
+
+
+
+
+new
instead.
+Create a new pool.
+
+
+#[deprecated]
+public fun create(): pool_u64_unbound::Pool
+
+
+
+
+public fun create(): Pool {
+ new()
+}
+
+
+
+
+scaling_factor
.
+
+
+public fun create_with_scaling_factor(scaling_factor: u64): pool_u64_unbound::Pool
+
+
+
+
+public fun create_with_scaling_factor(scaling_factor: u64): Pool {
+ Pool {
+ total_coins: 0,
+ total_shares: 0,
+ shares: table::new<address, u128>(),
+ scaling_factor,
+ }
+}
+
+
+
+
+public fun destroy_empty(pool: pool_u64_unbound::Pool)
+
+
+
+
+public fun destroy_empty(pool: Pool) {
+ assert!(pool.total_coins == 0, error::invalid_state(EPOOL_IS_NOT_EMPTY));
+ let Pool {
+ total_coins: _,
+ total_shares: _,
+ shares,
+ scaling_factor: _,
+ } = pool;
+ table::destroy_empty<address, u128>(shares);
+}
+
+
+
+
+pool
's total balance of coins.
+
+
+public fun total_coins(pool: &pool_u64_unbound::Pool): u64
+
+
+
+
+public fun total_coins(pool: &Pool): u64 {
+ pool.total_coins
+}
+
+
+
+
+pool
.
+
+
+public fun total_shares(pool: &pool_u64_unbound::Pool): u128
+
+
+
+
+public fun total_shares(pool: &Pool): u128 {
+ pool.total_shares
+}
+
+
+
+
+shareholder
is in pool
.
+
+
+public fun contains(pool: &pool_u64_unbound::Pool, shareholder: address): bool
+
+
+
+
+public fun contains(pool: &Pool, shareholder: address): bool {
+ table::contains(&pool.shares, shareholder)
+}
+
+
+
+
+stakeholder
in pool
.
+
+
+public fun shares(pool: &pool_u64_unbound::Pool, shareholder: address): u128
+
+
+
+
+public fun shares(pool: &Pool, shareholder: address): u128 {
+ if (contains(pool, shareholder)) {
+ *table::borrow(&pool.shares, shareholder)
+ } else {
+ 0
+ }
+}
+
+
+
+
+shareholder
in pool.
+
+
+public fun balance(pool: &pool_u64_unbound::Pool, shareholder: address): u64
+
+
+
+
+public fun balance(pool: &Pool, shareholder: address): u64 {
+ let num_shares = shares(pool, shareholder);
+ shares_to_amount(pool, num_shares)
+}
+
+
+
+
+pool
.
+
+
+public fun shareholders_count(pool: &pool_u64_unbound::Pool): u64
+
+
+
+
+public fun shareholders_count(pool: &Pool): u64 {
+ table::length(&pool.shares)
+}
+
+
+
+
+pool
's total balance of coins.
+
+
+public fun update_total_coins(pool: &mut pool_u64_unbound::Pool, new_total_coins: u64)
+
+
+
+
+public fun update_total_coins(pool: &mut Pool, new_total_coins: u64) {
+ pool.total_coins = new_total_coins;
+}
+
+
+
+
+public fun buy_in(pool: &mut pool_u64_unbound::Pool, shareholder: address, coins_amount: u64): u128
+
+
+
+
+public fun buy_in(pool: &mut Pool, shareholder: address, coins_amount: u64): u128 {
+ if (coins_amount == 0) return 0;
+
+ let new_shares = amount_to_shares(pool, coins_amount);
+ assert!(MAX_U64 - pool.total_coins >= coins_amount, error::invalid_argument(EPOOL_TOTAL_COINS_OVERFLOW));
+ assert!(MAX_U128 - pool.total_shares >= new_shares, error::invalid_argument(EPOOL_TOTAL_SHARES_OVERFLOW));
+
+ pool.total_coins = pool.total_coins + coins_amount;
+ pool.total_shares = pool.total_shares + new_shares;
+ add_shares(pool, shareholder, new_shares);
+ new_shares
+}
+
+
+
+
+shareholder
in pool
.
+This would dilute other shareholders if the pool's balance of coins didn't change.
+
+
+fun add_shares(pool: &mut pool_u64_unbound::Pool, shareholder: address, new_shares: u128): u128
+
+
+
+
+fun add_shares(pool: &mut Pool, shareholder: address, new_shares: u128): u128 {
+ if (contains(pool, shareholder)) {
+ let existing_shares = table::borrow_mut(&mut pool.shares, shareholder);
+ let current_shares = *existing_shares;
+ assert!(MAX_U128 - current_shares >= new_shares, error::invalid_argument(ESHAREHOLDER_SHARES_OVERFLOW));
+
+ *existing_shares = current_shares + new_shares;
+ *existing_shares
+ } else if (new_shares > 0) {
+ table::add(&mut pool.shares, shareholder, new_shares);
+ new_shares
+ } else {
+ new_shares
+ }
+}
+
+
+
+
+shareholder
to redeem their shares in pool
for coins.
+
+
+public fun redeem_shares(pool: &mut pool_u64_unbound::Pool, shareholder: address, shares_to_redeem: u128): u64
+
+
+
+
+public fun redeem_shares(pool: &mut Pool, shareholder: address, shares_to_redeem: u128): u64 {
+ assert!(contains(pool, shareholder), error::invalid_argument(ESHAREHOLDER_NOT_FOUND));
+ assert!(shares(pool, shareholder) >= shares_to_redeem, error::invalid_argument(EINSUFFICIENT_SHARES));
+
+ if (shares_to_redeem == 0) return 0;
+
+ let redeemed_coins = shares_to_amount(pool, shares_to_redeem);
+ pool.total_coins = pool.total_coins - redeemed_coins;
+ pool.total_shares = pool.total_shares - shares_to_redeem;
+ deduct_shares(pool, shareholder, shares_to_redeem);
+
+ redeemed_coins
+}
+
+
+
+
+shareholder_1
to shareholder_2
.
+
+
+public fun transfer_shares(pool: &mut pool_u64_unbound::Pool, shareholder_1: address, shareholder_2: address, shares_to_transfer: u128)
+
+
+
+
+public fun transfer_shares(
+ pool: &mut Pool,
+ shareholder_1: address,
+ shareholder_2: address,
+ shares_to_transfer: u128,
+) {
+ assert!(contains(pool, shareholder_1), error::invalid_argument(ESHAREHOLDER_NOT_FOUND));
+ assert!(shares(pool, shareholder_1) >= shares_to_transfer, error::invalid_argument(EINSUFFICIENT_SHARES));
+ if (shares_to_transfer == 0) return;
+
+ deduct_shares(pool, shareholder_1, shares_to_transfer);
+ add_shares(pool, shareholder_2, shares_to_transfer);
+}
+
+
+
+
+shareholder
's number of shares in pool
and return the number of remaining shares.
+
+
+fun deduct_shares(pool: &mut pool_u64_unbound::Pool, shareholder: address, num_shares: u128): u128
+
+
+
+
+fun deduct_shares(pool: &mut Pool, shareholder: address, num_shares: u128): u128 {
+ assert!(contains(pool, shareholder), error::invalid_argument(ESHAREHOLDER_NOT_FOUND));
+ assert!(shares(pool, shareholder) >= num_shares, error::invalid_argument(EINSUFFICIENT_SHARES));
+
+ let existing_shares = table::borrow_mut(&mut pool.shares, shareholder);
+ *existing_shares = *existing_shares - num_shares;
+
+ // Remove the shareholder completely if they have no shares left.
+ let remaining_shares = *existing_shares;
+ if (remaining_shares == 0) {
+ table::remove(&mut pool.shares, shareholder);
+ };
+
+ remaining_shares
+}
+
+
+
+
+coins_amount
can buy in pool
.
+amount
needs to big enough to avoid rounding number.
+
+
+public fun amount_to_shares(pool: &pool_u64_unbound::Pool, coins_amount: u64): u128
+
+
+
+
+public fun amount_to_shares(pool: &Pool, coins_amount: u64): u128 {
+ amount_to_shares_with_total_coins(pool, coins_amount, pool.total_coins)
+}
+
+
+
+
+coins_amount
can buy in pool
with a custom total coins number.
+amount
needs to big enough to avoid rounding number.
+
+
+public fun amount_to_shares_with_total_coins(pool: &pool_u64_unbound::Pool, coins_amount: u64, total_coins: u64): u128
+
+
+
+
+public fun amount_to_shares_with_total_coins(pool: &Pool, coins_amount: u64, total_coins: u64): u128 {
+ // No shares yet so amount is worth the same number of shares.
+ if (pool.total_coins == 0 || pool.total_shares == 0) {
+ // Multiply by scaling factor to minimize rounding errors during internal calculations for buy ins/redeems.
+ // This can overflow but scaling factor is expected to be chosen carefully so this would not overflow.
+ to_u128(coins_amount) * to_u128(pool.scaling_factor)
+ } else {
+ // Shares price = total_coins / total existing shares.
+ // New number of shares = new_amount / shares_price = new_amount * existing_shares / total_amount.
+ // We rearrange the calc and do multiplication first to avoid rounding errors.
+ multiply_then_divide(pool, to_u128(coins_amount), pool.total_shares, to_u128(total_coins))
+ }
+}
+
+
+
+
+shares
are worth in pool
.
+shares
needs to big enough to avoid rounding number.
+
+
+public fun shares_to_amount(pool: &pool_u64_unbound::Pool, shares: u128): u64
+
+
+
+
+public fun shares_to_amount(pool: &Pool, shares: u128): u64 {
+ shares_to_amount_with_total_coins(pool, shares, pool.total_coins)
+}
+
+
+
+
+shares
are worth in pool
with a custom total coins number.
+shares
needs to big enough to avoid rounding number.
+
+
+public fun shares_to_amount_with_total_coins(pool: &pool_u64_unbound::Pool, shares: u128, total_coins: u64): u64
+
+
+
+
+public fun shares_to_amount_with_total_coins(pool: &Pool, shares: u128, total_coins: u64): u64 {
+ // No shares or coins yet so shares are worthless.
+ if (pool.total_coins == 0 || pool.total_shares == 0) {
+ 0
+ } else {
+ // Shares price = total_coins / total existing shares.
+ // Shares worth = shares * shares price = shares * total_coins / total existing shares.
+ // We rearrange the calc and do multiplication first to avoid rounding errors.
+ (multiply_then_divide(pool, shares, to_u128(total_coins), pool.total_shares) as u64)
+ }
+}
+
+
+
+
+shares
are worth in pool
with custom total coins and shares numbers.
+
+
+public fun shares_to_amount_with_total_stats(pool: &pool_u64_unbound::Pool, shares: u128, total_coins: u64, total_shares: u128): u64
+
+
+
+
+public fun shares_to_amount_with_total_stats(
+ pool: &Pool,
+ shares: u128,
+ total_coins: u64,
+ total_shares: u128,
+): u64 {
+ if (pool.total_coins == 0 || total_shares == 0) {
+ 0
+ } else {
+ (multiply_then_divide(pool, shares, to_u128(total_coins), total_shares) as u64)
+ }
+}
+
+
+
+
+public fun multiply_then_divide(_pool: &pool_u64_unbound::Pool, x: u128, y: u128, z: u128): u128
+
+
+
+
+public fun multiply_then_divide(_pool: &Pool, x: u128, y: u128, z: u128): u128 {
+ let result = (to_u256(x) * to_u256(y)) / to_u256(z);
+ (result as u128)
+}
+
+
+
+
+fun to_u128(num: u64): u128
+
+
+
+
+fun to_u128(num: u64): u128 {
+ (num as u128)
+}
+
+
+
+
+fun to_u256(num: u128): u256
+
+
+
+
+fun to_u256(num: u128): u256 {
+ (num as u256)
+}
+
+
+
+
+struct Pool has store
+
+
+
+
+total_coins: u64
+total_shares: u128
+shares: table_with_length::TableWithLength<address, u128>
+scaling_factor: u64
+invariant forall addr: address:
+ table::spec_contains(shares, addr) ==> (table::spec_get(shares, addr) > 0);
+
+
+
+
+
+
+
+
+fun spec_contains(pool: Pool, shareholder: address): bool {
+ table::spec_contains(pool.shares, shareholder)
+}
+
+
+
+
+
+
+### Function `contains`
+
+
+public fun contains(pool: &pool_u64_unbound::Pool, shareholder: address): bool
+
+
+
+
+
+aborts_if false;
+ensures result == spec_contains(pool, shareholder);
+
+
+
+
+
+
+
+
+fun spec_shares(pool: Pool, shareholder: address): u64 {
+ if (spec_contains(pool, shareholder)) {
+ table::spec_get(pool.shares, shareholder)
+ }
+ else {
+ 0
+ }
+}
+
+
+
+
+
+
+### Function `shares`
+
+
+public fun shares(pool: &pool_u64_unbound::Pool, shareholder: address): u128
+
+
+
+
+
+aborts_if false;
+ensures result == spec_shares(pool, shareholder);
+
+
+
+
+
+
+### Function `balance`
+
+
+public fun balance(pool: &pool_u64_unbound::Pool, shareholder: address): u64
+
+
+
+
+
+let shares = spec_shares(pool, shareholder);
+let total_coins = pool.total_coins;
+aborts_if pool.total_coins > 0 && pool.total_shares > 0 && (shares * total_coins) / pool.total_shares > MAX_U64;
+ensures result == spec_shares_to_amount_with_total_coins(pool, shares, total_coins);
+
+
+
+
+
+
+### Function `buy_in`
+
+
+public fun buy_in(pool: &mut pool_u64_unbound::Pool, shareholder: address, coins_amount: u64): u128
+
+
+
+
+
+let new_shares = spec_amount_to_shares_with_total_coins(pool, coins_amount, pool.total_coins);
+aborts_if pool.total_coins + coins_amount > MAX_U64;
+aborts_if pool.total_shares + new_shares > MAX_U128;
+include coins_amount > 0 ==> AddSharesAbortsIf { new_shares: new_shares };
+include coins_amount > 0 ==> AddSharesEnsures { new_shares: new_shares };
+ensures pool.total_coins == old(pool.total_coins) + coins_amount;
+ensures pool.total_shares == old(pool.total_shares) + new_shares;
+ensures result == new_shares;
+
+
+
+
+
+
+### Function `add_shares`
+
+
+fun add_shares(pool: &mut pool_u64_unbound::Pool, shareholder: address, new_shares: u128): u128
+
+
+
+
+
+include AddSharesAbortsIf;
+include AddSharesEnsures;
+let key_exists = table::spec_contains(pool.shares, shareholder);
+ensures result == if (key_exists) { table::spec_get(pool.shares, shareholder) }
+else { new_shares };
+
+
+
+
+
+
+
+
+schema AddSharesAbortsIf {
+ pool: Pool;
+ shareholder: address;
+ new_shares: u64;
+ let key_exists = table::spec_contains(pool.shares, shareholder);
+ let current_shares = table::spec_get(pool.shares, shareholder);
+ aborts_if key_exists && current_shares + new_shares > MAX_U128;
+}
+
+
+
+
+
+
+
+
+schema AddSharesEnsures {
+ pool: Pool;
+ shareholder: address;
+ new_shares: u64;
+ let key_exists = table::spec_contains(pool.shares, shareholder);
+ let current_shares = table::spec_get(pool.shares, shareholder);
+ ensures key_exists ==>
+ pool.shares == table::spec_set(old(pool.shares), shareholder, current_shares + new_shares);
+ ensures (!key_exists && new_shares > 0) ==>
+ pool.shares == table::spec_set(old(pool.shares), shareholder, new_shares);
+}
+
+
+
+
+
+
+
+
+fun spec_amount_to_shares_with_total_coins(pool: Pool, coins_amount: u64, total_coins: u64): u128 {
+ if (pool.total_coins == 0 || pool.total_shares == 0) {
+ coins_amount * pool.scaling_factor
+ }
+ else {
+ (coins_amount * pool.total_shares) / total_coins
+ }
+}
+
+
+
+
+
+
+### Function `redeem_shares`
+
+
+public fun redeem_shares(pool: &mut pool_u64_unbound::Pool, shareholder: address, shares_to_redeem: u128): u64
+
+
+
+
+
+let redeemed_coins = spec_shares_to_amount_with_total_coins(pool, shares_to_redeem, pool.total_coins);
+aborts_if !spec_contains(pool, shareholder);
+aborts_if spec_shares(pool, shareholder) < shares_to_redeem;
+aborts_if pool.total_coins < redeemed_coins;
+aborts_if pool.total_shares < shares_to_redeem;
+ensures pool.total_coins == old(pool.total_coins) - redeemed_coins;
+ensures pool.total_shares == old(pool.total_shares) - shares_to_redeem;
+include shares_to_redeem > 0 ==> DeductSharesEnsures { num_shares: shares_to_redeem };
+ensures result == redeemed_coins;
+
+
+
+
+
+
+### Function `transfer_shares`
+
+
+public fun transfer_shares(pool: &mut pool_u64_unbound::Pool, shareholder_1: address, shareholder_2: address, shares_to_transfer: u128)
+
+
+
+
+
+aborts_if (shareholder_1 != shareholder_2) && shares_to_transfer > 0 && spec_contains(pool, shareholder_2) &&
+ (spec_shares(pool, shareholder_2) + shares_to_transfer > MAX_U128);
+aborts_if !spec_contains(pool, shareholder_1);
+aborts_if spec_shares(pool, shareholder_1) < shares_to_transfer;
+ensures shareholder_1 == shareholder_2 ==> spec_shares(old(pool), shareholder_1) == spec_shares(pool, shareholder_1);
+ensures ((shareholder_1 != shareholder_2) && (spec_shares(old(pool), shareholder_1) == shares_to_transfer)) ==>
+ !spec_contains(pool, shareholder_1);
+ensures (shareholder_1 != shareholder_2 && shares_to_transfer > 0) ==>
+ (spec_contains(pool, shareholder_2));
+ensures (shareholder_1 != shareholder_2 && shares_to_transfer > 0 && !spec_contains(old(pool), shareholder_2)) ==>
+ (spec_contains(pool, shareholder_2) && spec_shares(pool, shareholder_2) == shares_to_transfer);
+ensures (shareholder_1 != shareholder_2 && shares_to_transfer > 0 && spec_contains(old(pool), shareholder_2)) ==>
+ (spec_contains(pool, shareholder_2) && spec_shares(pool, shareholder_2) == spec_shares(old(pool), shareholder_2) + shares_to_transfer);
+ensures ((shareholder_1 != shareholder_2) && (spec_shares(old(pool), shareholder_1) > shares_to_transfer)) ==>
+ (spec_contains(pool, shareholder_1) && (spec_shares(pool, shareholder_1) == spec_shares(old(pool), shareholder_1) - shares_to_transfer));
+
+
+
+
+
+
+### Function `deduct_shares`
+
+
+fun deduct_shares(pool: &mut pool_u64_unbound::Pool, shareholder: address, num_shares: u128): u128
+
+
+
+
+
+aborts_if !spec_contains(pool, shareholder);
+aborts_if spec_shares(pool, shareholder) < num_shares;
+include DeductSharesEnsures;
+let remaining_shares = table::spec_get(pool.shares, shareholder) - num_shares;
+ensures remaining_shares > 0 ==> result == table::spec_get(pool.shares, shareholder);
+ensures remaining_shares == 0 ==> result == 0;
+
+
+
+
+
+
+
+
+schema DeductSharesEnsures {
+ pool: Pool;
+ shareholder: address;
+ num_shares: u64;
+ let remaining_shares = table::spec_get(pool.shares, shareholder) - num_shares;
+ ensures remaining_shares > 0 ==> table::spec_get(pool.shares, shareholder) == remaining_shares;
+ ensures remaining_shares == 0 ==> !table::spec_contains(pool.shares, shareholder);
+}
+
+
+
+
+
+
+### Function `amount_to_shares_with_total_coins`
+
+
+public fun amount_to_shares_with_total_coins(pool: &pool_u64_unbound::Pool, coins_amount: u64, total_coins: u64): u128
+
+
+
+
+
+aborts_if pool.total_coins > 0 && pool.total_shares > 0
+ && (coins_amount * pool.total_shares) / total_coins > MAX_U128;
+aborts_if (pool.total_coins == 0 || pool.total_shares == 0)
+ && coins_amount * pool.scaling_factor > MAX_U128;
+aborts_if pool.total_coins > 0 && pool.total_shares > 0 && total_coins == 0;
+ensures result == spec_amount_to_shares_with_total_coins(pool, coins_amount, total_coins);
+
+
+
+
+
+
+### Function `shares_to_amount_with_total_coins`
+
+
+public fun shares_to_amount_with_total_coins(pool: &pool_u64_unbound::Pool, shares: u128, total_coins: u64): u64
+
+
+
+
+
+aborts_if pool.total_coins > 0 && pool.total_shares > 0
+ && (shares * total_coins) / pool.total_shares > MAX_U64;
+ensures result == spec_shares_to_amount_with_total_coins(pool, shares, total_coins);
+
+
+
+
+
+
+
+
+fun spec_shares_to_amount_with_total_coins(pool: Pool, shares: u128, total_coins: u64): u64 {
+ if (pool.total_coins == 0 || pool.total_shares == 0) {
+ 0
+ }
+ else {
+ (shares * total_coins) / pool.total_shares
+ }
+}
+
+
+
+
+
+
+### Function `multiply_then_divide`
+
+
+public fun multiply_then_divide(_pool: &pool_u64_unbound::Pool, x: u128, y: u128, z: u128): u128
+
+
+
+
+
+aborts_if z == 0;
+aborts_if (x * y) / z > MAX_U128;
+ensures result == (x * y) / z;
+
+
+
+
+
+
+### Function `to_u128`
+
+
+fun to_u128(num: u64): u128
+
+
+
+
+
+aborts_if false;
+ensures result == num;
+
+
+
+
+
+
+### Function `to_u256`
+
+
+fun to_u256(num: u128): u256
+
+
+
+
+
+aborts_if false;
+ensures result == num;
+
+
+
+[move-book]: https://aptos.dev/move/book/SUMMARY
diff --git a/aptos-move/framework/aptos-stdlib/tests/compiler-v2-doc/ristretto255.md b/aptos-move/framework/aptos-stdlib/tests/compiler-v2-doc/ristretto255.md
new file mode 100644
index 0000000000000..15b42e8e8f61e
--- /dev/null
+++ b/aptos-move/framework/aptos-stdlib/tests/compiler-v2-doc/ristretto255.md
@@ -0,0 +1,3463 @@
+
+
+
+# Module `0x1::ristretto255`
+
+This module contains functions for Ristretto255 curve arithmetic, assuming addition as the group operation.
+
+The order of the Ristretto255 elliptic curve group is $\ell = 2^252 + 27742317777372353535851937790883648493$, same
+as the order of the prime-order subgroup of Curve25519.
+
+This module provides two structs for encoding Ristretto elliptic curves to the developer:
+
+- First, a 32-byte-sized CompressedRistretto struct, which is used to persist points in storage.
+
+- Second, a larger, in-memory, RistrettoPoint struct, which is decompressable from a CompressedRistretto struct. This
+larger struct can be used for fast arithmetic operations (additions, multiplications, etc.). The results can be saved
+back into storage by compressing RistrettoPoint structs back to CompressedRistretto structs.
+
+This module also provides a Scalar struct for persisting scalars in storage and doing fast arithmetic on them.
+
+One invariant maintained by this module is that all CompressedRistretto structs store a canonically-encoded point,
+which can always be decompressed into a valid point on the curve as a RistrettoPoint struct. Unfortunately, due to
+limitations in our underlying curve25519-dalek elliptic curve library, this decompression will unnecessarily verify
+the validity of the point and thus slightly decrease performance.
+
+Similarly, all Scalar structs store a canonically-encoded scalar, which can always be safely operated on using
+arithmetic operations.
+
+In the future, we might support additional features:
+
+* For scalars:
+- batch_invert()
+
+* For points:
+- double()
++ The challenge is that curve25519-dalek does NOT export double for Ristretto points (nor for Edwards)
+
+- double_and_compress_batch()
+
+- fixed-base, variable-time via optional_mixed_multiscalar_mul() in VartimePrecomputedMultiscalarMul
++ This would require a storage-friendly RistrettoBasepointTable and an in-memory variant of it too
++ Similar to the CompressedRistretto and RistrettoPoint structs in this module
++ The challenge is that curve25519-dalek's RistrettoBasepointTable is not serializable
+
+
+- [Struct `Scalar`](#0x1_ristretto255_Scalar)
+- [Struct `CompressedRistretto`](#0x1_ristretto255_CompressedRistretto)
+- [Struct `RistrettoPoint`](#0x1_ristretto255_RistrettoPoint)
+- [Constants](#@Constants_0)
+- [Function `point_identity_compressed`](#0x1_ristretto255_point_identity_compressed)
+- [Function `point_identity`](#0x1_ristretto255_point_identity)
+- [Function `basepoint_compressed`](#0x1_ristretto255_basepoint_compressed)
+- [Function `hash_to_point_base`](#0x1_ristretto255_hash_to_point_base)
+- [Function `basepoint`](#0x1_ristretto255_basepoint)
+- [Function `basepoint_mul`](#0x1_ristretto255_basepoint_mul)
+- [Function `new_compressed_point_from_bytes`](#0x1_ristretto255_new_compressed_point_from_bytes)
+- [Function `new_point_from_bytes`](#0x1_ristretto255_new_point_from_bytes)
+- [Function `compressed_point_to_bytes`](#0x1_ristretto255_compressed_point_to_bytes)
+- [Function `new_point_from_sha512`](#0x1_ristretto255_new_point_from_sha512)
+- [Function `new_point_from_sha2_512`](#0x1_ristretto255_new_point_from_sha2_512)
+- [Function `new_point_from_64_uniform_bytes`](#0x1_ristretto255_new_point_from_64_uniform_bytes)
+- [Function `point_decompress`](#0x1_ristretto255_point_decompress)
+- [Function `point_clone`](#0x1_ristretto255_point_clone)
+- [Function `point_compress`](#0x1_ristretto255_point_compress)
+- [Function `point_to_bytes`](#0x1_ristretto255_point_to_bytes)
+- [Function `point_mul`](#0x1_ristretto255_point_mul)
+- [Function `point_mul_assign`](#0x1_ristretto255_point_mul_assign)
+- [Function `basepoint_double_mul`](#0x1_ristretto255_basepoint_double_mul)
+- [Function `point_add`](#0x1_ristretto255_point_add)
+- [Function `point_add_assign`](#0x1_ristretto255_point_add_assign)
+- [Function `point_sub`](#0x1_ristretto255_point_sub)
+- [Function `point_sub_assign`](#0x1_ristretto255_point_sub_assign)
+- [Function `point_neg`](#0x1_ristretto255_point_neg)
+- [Function `point_neg_assign`](#0x1_ristretto255_point_neg_assign)
+- [Function `point_equals`](#0x1_ristretto255_point_equals)
+- [Function `double_scalar_mul`](#0x1_ristretto255_double_scalar_mul)
+- [Function `multi_scalar_mul`](#0x1_ristretto255_multi_scalar_mul)
+- [Function `new_scalar_from_bytes`](#0x1_ristretto255_new_scalar_from_bytes)
+- [Function `new_scalar_from_sha512`](#0x1_ristretto255_new_scalar_from_sha512)
+- [Function `new_scalar_from_sha2_512`](#0x1_ristretto255_new_scalar_from_sha2_512)
+- [Function `new_scalar_from_u8`](#0x1_ristretto255_new_scalar_from_u8)
+- [Function `new_scalar_from_u32`](#0x1_ristretto255_new_scalar_from_u32)
+- [Function `new_scalar_from_u64`](#0x1_ristretto255_new_scalar_from_u64)
+- [Function `new_scalar_from_u128`](#0x1_ristretto255_new_scalar_from_u128)
+- [Function `new_scalar_reduced_from_32_bytes`](#0x1_ristretto255_new_scalar_reduced_from_32_bytes)
+- [Function `new_scalar_uniform_from_64_bytes`](#0x1_ristretto255_new_scalar_uniform_from_64_bytes)
+- [Function `scalar_zero`](#0x1_ristretto255_scalar_zero)
+- [Function `scalar_is_zero`](#0x1_ristretto255_scalar_is_zero)
+- [Function `scalar_one`](#0x1_ristretto255_scalar_one)
+- [Function `scalar_is_one`](#0x1_ristretto255_scalar_is_one)
+- [Function `scalar_equals`](#0x1_ristretto255_scalar_equals)
+- [Function `scalar_invert`](#0x1_ristretto255_scalar_invert)
+- [Function `scalar_mul`](#0x1_ristretto255_scalar_mul)
+- [Function `scalar_mul_assign`](#0x1_ristretto255_scalar_mul_assign)
+- [Function `scalar_add`](#0x1_ristretto255_scalar_add)
+- [Function `scalar_add_assign`](#0x1_ristretto255_scalar_add_assign)
+- [Function `scalar_sub`](#0x1_ristretto255_scalar_sub)
+- [Function `scalar_sub_assign`](#0x1_ristretto255_scalar_sub_assign)
+- [Function `scalar_neg`](#0x1_ristretto255_scalar_neg)
+- [Function `scalar_neg_assign`](#0x1_ristretto255_scalar_neg_assign)
+- [Function `scalar_to_bytes`](#0x1_ristretto255_scalar_to_bytes)
+- [Function `new_point_from_sha512_internal`](#0x1_ristretto255_new_point_from_sha512_internal)
+- [Function `new_point_from_64_uniform_bytes_internal`](#0x1_ristretto255_new_point_from_64_uniform_bytes_internal)
+- [Function `point_is_canonical_internal`](#0x1_ristretto255_point_is_canonical_internal)
+- [Function `point_identity_internal`](#0x1_ristretto255_point_identity_internal)
+- [Function `point_decompress_internal`](#0x1_ristretto255_point_decompress_internal)
+- [Function `point_clone_internal`](#0x1_ristretto255_point_clone_internal)
+- [Function `point_compress_internal`](#0x1_ristretto255_point_compress_internal)
+- [Function `point_mul_internal`](#0x1_ristretto255_point_mul_internal)
+- [Function `basepoint_mul_internal`](#0x1_ristretto255_basepoint_mul_internal)
+- [Function `basepoint_double_mul_internal`](#0x1_ristretto255_basepoint_double_mul_internal)
+- [Function `point_add_internal`](#0x1_ristretto255_point_add_internal)
+- [Function `point_sub_internal`](#0x1_ristretto255_point_sub_internal)
+- [Function `point_neg_internal`](#0x1_ristretto255_point_neg_internal)
+- [Function `double_scalar_mul_internal`](#0x1_ristretto255_double_scalar_mul_internal)
+- [Function `multi_scalar_mul_internal`](#0x1_ristretto255_multi_scalar_mul_internal)
+- [Function `scalar_is_canonical_internal`](#0x1_ristretto255_scalar_is_canonical_internal)
+- [Function `scalar_from_u64_internal`](#0x1_ristretto255_scalar_from_u64_internal)
+- [Function `scalar_from_u128_internal`](#0x1_ristretto255_scalar_from_u128_internal)
+- [Function `scalar_reduced_from_32_bytes_internal`](#0x1_ristretto255_scalar_reduced_from_32_bytes_internal)
+- [Function `scalar_uniform_from_64_bytes_internal`](#0x1_ristretto255_scalar_uniform_from_64_bytes_internal)
+- [Function `scalar_invert_internal`](#0x1_ristretto255_scalar_invert_internal)
+- [Function `scalar_from_sha512_internal`](#0x1_ristretto255_scalar_from_sha512_internal)
+- [Function `scalar_mul_internal`](#0x1_ristretto255_scalar_mul_internal)
+- [Function `scalar_add_internal`](#0x1_ristretto255_scalar_add_internal)
+- [Function `scalar_sub_internal`](#0x1_ristretto255_scalar_sub_internal)
+- [Function `scalar_neg_internal`](#0x1_ristretto255_scalar_neg_internal)
+- [Specification](#@Specification_1)
+ - [Helper functions](#@Helper_functions_2)
+ - [Function `point_equals`](#@Specification_1_point_equals)
+ - [Function `double_scalar_mul`](#@Specification_1_double_scalar_mul)
+ - [Function `multi_scalar_mul`](#@Specification_1_multi_scalar_mul)
+ - [Function `new_scalar_from_bytes`](#@Specification_1_new_scalar_from_bytes)
+ - [Function `new_scalar_from_sha2_512`](#@Specification_1_new_scalar_from_sha2_512)
+ - [Function `new_scalar_from_u8`](#@Specification_1_new_scalar_from_u8)
+ - [Function `new_scalar_from_u32`](#@Specification_1_new_scalar_from_u32)
+ - [Function `new_scalar_from_u64`](#@Specification_1_new_scalar_from_u64)
+ - [Function `new_scalar_from_u128`](#@Specification_1_new_scalar_from_u128)
+ - [Function `new_scalar_reduced_from_32_bytes`](#@Specification_1_new_scalar_reduced_from_32_bytes)
+ - [Function `new_scalar_uniform_from_64_bytes`](#@Specification_1_new_scalar_uniform_from_64_bytes)
+ - [Function `scalar_zero`](#@Specification_1_scalar_zero)
+ - [Function `scalar_is_zero`](#@Specification_1_scalar_is_zero)
+ - [Function `scalar_one`](#@Specification_1_scalar_one)
+ - [Function `scalar_is_one`](#@Specification_1_scalar_is_one)
+ - [Function `scalar_equals`](#@Specification_1_scalar_equals)
+ - [Function `scalar_invert`](#@Specification_1_scalar_invert)
+ - [Function `scalar_mul`](#@Specification_1_scalar_mul)
+ - [Function `scalar_mul_assign`](#@Specification_1_scalar_mul_assign)
+ - [Function `scalar_add`](#@Specification_1_scalar_add)
+ - [Function `scalar_add_assign`](#@Specification_1_scalar_add_assign)
+ - [Function `scalar_sub`](#@Specification_1_scalar_sub)
+ - [Function `scalar_sub_assign`](#@Specification_1_scalar_sub_assign)
+ - [Function `scalar_neg`](#@Specification_1_scalar_neg)
+ - [Function `scalar_neg_assign`](#@Specification_1_scalar_neg_assign)
+ - [Function `scalar_to_bytes`](#@Specification_1_scalar_to_bytes)
+ - [Function `new_point_from_sha512_internal`](#@Specification_1_new_point_from_sha512_internal)
+ - [Function `new_point_from_64_uniform_bytes_internal`](#@Specification_1_new_point_from_64_uniform_bytes_internal)
+ - [Function `point_is_canonical_internal`](#@Specification_1_point_is_canonical_internal)
+ - [Function `point_identity_internal`](#@Specification_1_point_identity_internal)
+ - [Function `point_decompress_internal`](#@Specification_1_point_decompress_internal)
+ - [Function `point_clone_internal`](#@Specification_1_point_clone_internal)
+ - [Function `point_compress_internal`](#@Specification_1_point_compress_internal)
+ - [Function `point_mul_internal`](#@Specification_1_point_mul_internal)
+ - [Function `basepoint_mul_internal`](#@Specification_1_basepoint_mul_internal)
+ - [Function `basepoint_double_mul_internal`](#@Specification_1_basepoint_double_mul_internal)
+ - [Function `point_add_internal`](#@Specification_1_point_add_internal)
+ - [Function `point_sub_internal`](#@Specification_1_point_sub_internal)
+ - [Function `point_neg_internal`](#@Specification_1_point_neg_internal)
+ - [Function `double_scalar_mul_internal`](#@Specification_1_double_scalar_mul_internal)
+ - [Function `multi_scalar_mul_internal`](#@Specification_1_multi_scalar_mul_internal)
+ - [Function `scalar_is_canonical_internal`](#@Specification_1_scalar_is_canonical_internal)
+ - [Function `scalar_from_u64_internal`](#@Specification_1_scalar_from_u64_internal)
+ - [Function `scalar_from_u128_internal`](#@Specification_1_scalar_from_u128_internal)
+ - [Function `scalar_reduced_from_32_bytes_internal`](#@Specification_1_scalar_reduced_from_32_bytes_internal)
+ - [Function `scalar_uniform_from_64_bytes_internal`](#@Specification_1_scalar_uniform_from_64_bytes_internal)
+ - [Function `scalar_invert_internal`](#@Specification_1_scalar_invert_internal)
+ - [Function `scalar_from_sha512_internal`](#@Specification_1_scalar_from_sha512_internal)
+ - [Function `scalar_mul_internal`](#@Specification_1_scalar_mul_internal)
+ - [Function `scalar_add_internal`](#@Specification_1_scalar_add_internal)
+ - [Function `scalar_sub_internal`](#@Specification_1_scalar_sub_internal)
+ - [Function `scalar_neg_internal`](#@Specification_1_scalar_neg_internal)
+
+
+use 0x1::error;
+use 0x1::features;
+use 0x1::option;
+use 0x1::vector;
+
+
+
+
+
+
+## Struct `Scalar`
+
+This struct represents a scalar as a little-endian byte encoding of an integer in $\mathbb{Z}_\ell$, which is
+stored in data
. Here, \ell denotes the order of the scalar field (and the underlying elliptic curve group).
+
+
+struct Scalar has copy, drop, store
+
+
+
+
+data: vector<u8>
+struct CompressedRistretto has copy, drop, store
+
+
+
+
+data: vector<u8>
+struct RistrettoPoint has drop
+
+
+
+
+handle: u64
+const E_NATIVE_FUN_NOT_AVAILABLE: u64 = 5;
+
+
+
+
+
+
+The basepoint (generator) of the Ristretto255 group
+
+
+const BASE_POINT: vector<u8> = [226, 242, 174, 10, 106, 188, 78, 113, 168, 132, 169, 97, 197, 0, 81, 95, 88, 227, 11, 106, 165, 130, 221, 141, 182, 166, 89, 69, 224, 141, 45, 118];
+
+
+
+
+
+
+The number of scalars does not match the number of points.
+
+
+const E_DIFFERENT_NUM_POINTS_AND_SCALARS: u64 = 1;
+
+
+
+
+
+
+Too many points have been created in the current transaction execution.
+
+
+const E_TOO_MANY_POINTS_CREATED: u64 = 4;
+
+
+
+
+
+
+Expected more than zero points as input.
+
+
+const E_ZERO_POINTS: u64 = 2;
+
+
+
+
+
+
+Expected more than zero scalars as input.
+
+
+const E_ZERO_SCALARS: u64 = 3;
+
+
+
+
+
+
+The hash of the basepoint of the Ristretto255 group using SHA3_512
+
+
+const HASH_BASE_POINT: vector<u8> = [140, 146, 64, 180, 86, 169, 230, 220, 101, 195, 119, 161, 4, 141, 116, 95, 148, 160, 140, 219, 127, 68, 203, 205, 123, 70, 243, 64, 72, 135, 17, 52];
+
+
+
+
+
+
+ORDER_ELL
- 1: i.e., the "largest", reduced scalar in the field
+
+
+const L_MINUS_ONE: vector<u8> = [236, 211, 245, 92, 26, 99, 18, 88, 214, 156, 247, 162, 222, 249, 222, 20, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 16];
+
+
+
+
+
+
+The maximum size in bytes of a canonically-encoded Ristretto255 point is 32 bytes.
+
+
+const MAX_POINT_NUM_BYTES: u64 = 32;
+
+
+
+
+
+
+The maximum size in bits of a canonically-encoded Scalar is 256 bits.
+
+
+const MAX_SCALAR_NUM_BITS: u64 = 256;
+
+
+
+
+
+
+The maximum size in bytes of a canonically-encoded Scalar is 32 bytes.
+
+
+const MAX_SCALAR_NUM_BYTES: u64 = 32;
+
+
+
+
+
+
+The order of the Ristretto255 group and its scalar field, in little-endian.
+
+
+const ORDER_ELL: vector<u8> = [237, 211, 245, 92, 26, 99, 18, 88, 214, 156, 247, 162, 222, 249, 222, 20, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 16];
+
+
+
+
+
+
+## Function `point_identity_compressed`
+
+Returns the identity point as a CompressedRistretto.
+
+
+public fun point_identity_compressed(): ristretto255::CompressedRistretto
+
+
+
+
+public fun point_identity_compressed(): CompressedRistretto {
+ CompressedRistretto {
+ data: x"0000000000000000000000000000000000000000000000000000000000000000"
+ }
+}
+
+
+
+
+public fun point_identity(): ristretto255::RistrettoPoint
+
+
+
+
+public fun point_identity(): RistrettoPoint {
+ RistrettoPoint {
+ handle: point_identity_internal()
+ }
+}
+
+
+
+
+public fun basepoint_compressed(): ristretto255::CompressedRistretto
+
+
+
+
+public fun basepoint_compressed(): CompressedRistretto {
+ CompressedRistretto {
+ data: BASE_POINT
+ }
+}
+
+
+
+
+public fun hash_to_point_base(): ristretto255::RistrettoPoint
+
+
+
+
+public fun hash_to_point_base(): RistrettoPoint {
+ let comp_res = CompressedRistretto { data: HASH_BASE_POINT };
+ point_decompress(&comp_res)
+}
+
+
+
+
+public fun basepoint(): ristretto255::RistrettoPoint
+
+
+
+
+public fun basepoint(): RistrettoPoint {
+ let (handle, _) = point_decompress_internal(BASE_POINT);
+
+ RistrettoPoint {
+ handle
+ }
+}
+
+
+
+
+point_mul(&basepoint(), &some_scalar)
because of precomputation tables.
+
+
+public fun basepoint_mul(a: &ristretto255::Scalar): ristretto255::RistrettoPoint
+
+
+
+
+public fun basepoint_mul(a: &Scalar): RistrettoPoint {
+ RistrettoPoint {
+ handle: basepoint_mul_internal(a.data)
+ }
+}
+
+
+
+
+public fun new_compressed_point_from_bytes(bytes: vector<u8>): option::Option<ristretto255::CompressedRistretto>
+
+
+
+
+public fun new_compressed_point_from_bytes(bytes: vector<u8>): Option<CompressedRistretto> {
+ if (point_is_canonical_internal(bytes)) {
+ std::option::some(CompressedRistretto {
+ data: bytes
+ })
+ } else {
+ std::option::none<CompressedRistretto>()
+ }
+}
+
+
+
+
+public fun new_point_from_bytes(bytes: vector<u8>): option::Option<ristretto255::RistrettoPoint>
+
+
+
+
+public fun new_point_from_bytes(bytes: vector<u8>): Option<RistrettoPoint> {
+ let (handle, is_canonical) = point_decompress_internal(bytes);
+ if (is_canonical) {
+ std::option::some(RistrettoPoint { handle })
+ } else {
+ std::option::none<RistrettoPoint>()
+ }
+}
+
+
+
+
+point
, returns the byte representation of that point
+
+
+public fun compressed_point_to_bytes(point: ristretto255::CompressedRistretto): vector<u8>
+
+
+
+
+public fun compressed_point_to_bytes(point: CompressedRistretto): vector<u8> {
+ point.data
+}
+
+
+
+
+new_point_from_sha2_512
+
+Hashes the input to a uniformly-at-random RistrettoPoint via SHA512.
+
+
+public fun new_point_from_sha512(sha2_512_input: vector<u8>): ristretto255::RistrettoPoint
+
+
+
+
+public fun new_point_from_sha512(sha2_512_input: vector<u8>): RistrettoPoint {
+ new_point_from_sha2_512(sha2_512_input)
+}
+
+
+
+
+public fun new_point_from_sha2_512(sha2_512_input: vector<u8>): ristretto255::RistrettoPoint
+
+
+
+
+public fun new_point_from_sha2_512(sha2_512_input: vector<u8>): RistrettoPoint {
+ RistrettoPoint {
+ handle: new_point_from_sha512_internal(sha2_512_input)
+ }
+}
+
+
+
+
+public fun new_point_from_64_uniform_bytes(bytes: vector<u8>): option::Option<ristretto255::RistrettoPoint>
+
+
+
+
+public fun new_point_from_64_uniform_bytes(bytes: vector<u8>): Option<RistrettoPoint> {
+ if (std::vector::length(&bytes) == 64) {
+ std::option::some(RistrettoPoint {
+ handle: new_point_from_64_uniform_bytes_internal(bytes)
+ })
+ } else {
+ std::option::none<RistrettoPoint>()
+ }
+}
+
+
+
+
+public fun point_decompress(point: &ristretto255::CompressedRistretto): ristretto255::RistrettoPoint
+
+
+
+
+public fun point_decompress(point: &CompressedRistretto): RistrettoPoint {
+ // NOTE: Our CompressedRistretto invariant assures us that every CompressedRistretto in storage is a valid
+ // RistrettoPoint
+ let (handle, _) = point_decompress_internal(point.data);
+ RistrettoPoint { handle }
+}
+
+
+
+
+public fun point_clone(point: &ristretto255::RistrettoPoint): ristretto255::RistrettoPoint
+
+
+
+
+public fun point_clone(point: &RistrettoPoint): RistrettoPoint {
+ if(!features::bulletproofs_enabled()) {
+ abort(std::error::invalid_state(E_NATIVE_FUN_NOT_AVAILABLE))
+ };
+
+ RistrettoPoint {
+ handle: point_clone_internal(point.handle)
+ }
+}
+
+
+
+
+public fun point_compress(point: &ristretto255::RistrettoPoint): ristretto255::CompressedRistretto
+
+
+
+
+public fun point_compress(point: &RistrettoPoint): CompressedRistretto {
+ CompressedRistretto {
+ data: point_compress_internal(point)
+ }
+}
+
+
+
+
+c = point_compress(&p)
, and then call this
+function on c
.
+
+
+public fun point_to_bytes(point: &ristretto255::CompressedRistretto): vector<u8>
+
+
+
+
+public fun point_to_bytes(point: &CompressedRistretto): vector<u8> {
+ point.data
+}
+
+
+
+
+public fun point_mul(point: &ristretto255::RistrettoPoint, a: &ristretto255::Scalar): ristretto255::RistrettoPoint
+
+
+
+
+public fun point_mul(point: &RistrettoPoint, a: &Scalar): RistrettoPoint {
+ RistrettoPoint {
+ handle: point_mul_internal(point, a.data, false)
+ }
+}
+
+
+
+
+public fun point_mul_assign(point: &mut ristretto255::RistrettoPoint, a: &ristretto255::Scalar): &mut ristretto255::RistrettoPoint
+
+
+
+
+public fun point_mul_assign(point: &mut RistrettoPoint, a: &Scalar): &mut RistrettoPoint {
+ point_mul_internal(point, a.data, true);
+ point
+}
+
+
+
+
+BASE_POINT
.
+
+
+public fun basepoint_double_mul(a: &ristretto255::Scalar, a_base: &ristretto255::RistrettoPoint, b: &ristretto255::Scalar): ristretto255::RistrettoPoint
+
+
+
+
+public fun basepoint_double_mul(a: &Scalar, a_base: &RistrettoPoint, b: &Scalar): RistrettoPoint {
+ RistrettoPoint {
+ handle: basepoint_double_mul_internal(a.data, a_base, b.data)
+ }
+}
+
+
+
+
+public fun point_add(a: &ristretto255::RistrettoPoint, b: &ristretto255::RistrettoPoint): ristretto255::RistrettoPoint
+
+
+
+
+public fun point_add(a: &RistrettoPoint, b: &RistrettoPoint): RistrettoPoint {
+ RistrettoPoint {
+ handle: point_add_internal(a, b, false)
+ }
+}
+
+
+
+
+public fun point_add_assign(a: &mut ristretto255::RistrettoPoint, b: &ristretto255::RistrettoPoint): &mut ristretto255::RistrettoPoint
+
+
+
+
+public fun point_add_assign(a: &mut RistrettoPoint, b: &RistrettoPoint): &mut RistrettoPoint {
+ point_add_internal(a, b, true);
+ a
+}
+
+
+
+
+public fun point_sub(a: &ristretto255::RistrettoPoint, b: &ristretto255::RistrettoPoint): ristretto255::RistrettoPoint
+
+
+
+
+public fun point_sub(a: &RistrettoPoint, b: &RistrettoPoint): RistrettoPoint {
+ RistrettoPoint {
+ handle: point_sub_internal(a, b, false)
+ }
+}
+
+
+
+
+public fun point_sub_assign(a: &mut ristretto255::RistrettoPoint, b: &ristretto255::RistrettoPoint): &mut ristretto255::RistrettoPoint
+
+
+
+
+public fun point_sub_assign(a: &mut RistrettoPoint, b: &RistrettoPoint): &mut RistrettoPoint {
+ point_sub_internal(a, b, true);
+ a
+}
+
+
+
+
+public fun point_neg(a: &ristretto255::RistrettoPoint): ristretto255::RistrettoPoint
+
+
+
+
+public fun point_neg(a: &RistrettoPoint): RistrettoPoint {
+ RistrettoPoint {
+ handle: point_neg_internal(a, false)
+ }
+}
+
+
+
+
+public fun point_neg_assign(a: &mut ristretto255::RistrettoPoint): &mut ristretto255::RistrettoPoint
+
+
+
+
+public fun point_neg_assign(a: &mut RistrettoPoint): &mut RistrettoPoint {
+ point_neg_internal(a, true);
+ a
+}
+
+
+
+
+public fun point_equals(g: &ristretto255::RistrettoPoint, h: &ristretto255::RistrettoPoint): bool
+
+
+
+
+native public fun point_equals(g: &RistrettoPoint, h: &RistrettoPoint): bool;
+
+
+
+
+point_mul
and adding up the results using point_add
.
+
+
+public fun double_scalar_mul(scalar1: &ristretto255::Scalar, point1: &ristretto255::RistrettoPoint, scalar2: &ristretto255::Scalar, point2: &ristretto255::RistrettoPoint): ristretto255::RistrettoPoint
+
+
+
+
+public fun double_scalar_mul(scalar1: &Scalar, point1: &RistrettoPoint, scalar2: &Scalar, point2: &RistrettoPoint): RistrettoPoint {
+ if(!features::bulletproofs_enabled()) {
+ abort(std::error::invalid_state(E_NATIVE_FUN_NOT_AVAILABLE))
+ };
+
+ RistrettoPoint {
+ handle: double_scalar_mul_internal(point1.handle, point2.handle, scalar1.data, scalar2.data)
+ }
+}
+
+
+
+
+point_mul
and adding up the results using point_add
.
+
+
+public fun multi_scalar_mul(points: &vector<ristretto255::RistrettoPoint>, scalars: &vector<ristretto255::Scalar>): ristretto255::RistrettoPoint
+
+
+
+
+public fun multi_scalar_mul(points: &vector<RistrettoPoint>, scalars: &vector<Scalar>): RistrettoPoint {
+ assert!(!std::vector::is_empty(points), std::error::invalid_argument(E_ZERO_POINTS));
+ assert!(!std::vector::is_empty(scalars), std::error::invalid_argument(E_ZERO_SCALARS));
+ assert!(std::vector::length(points) == std::vector::length(scalars), std::error::invalid_argument(E_DIFFERENT_NUM_POINTS_AND_SCALARS));
+
+ RistrettoPoint {
+ handle: multi_scalar_mul_internal<RistrettoPoint, Scalar>(points, scalars)
+ }
+}
+
+
+
+
+public fun new_scalar_from_bytes(bytes: vector<u8>): option::Option<ristretto255::Scalar>
+
+
+
+
+public fun new_scalar_from_bytes(bytes: vector<u8>): Option<Scalar> {
+ if (scalar_is_canonical_internal(bytes)) {
+ std::option::some(Scalar {
+ data: bytes
+ })
+ } else {
+ std::option::none<Scalar>()
+ }
+}
+
+
+
+
+new_scalar_from_sha2_512
+
+Hashes the input to a uniformly-at-random Scalar via SHA2-512
+
+
+public fun new_scalar_from_sha512(sha2_512_input: vector<u8>): ristretto255::Scalar
+
+
+
+
+public fun new_scalar_from_sha512(sha2_512_input: vector<u8>): Scalar {
+ new_scalar_from_sha2_512(sha2_512_input)
+}
+
+
+
+
+public fun new_scalar_from_sha2_512(sha2_512_input: vector<u8>): ristretto255::Scalar
+
+
+
+
+public fun new_scalar_from_sha2_512(sha2_512_input: vector<u8>): Scalar {
+ Scalar {
+ data: scalar_from_sha512_internal(sha2_512_input)
+ }
+}
+
+
+
+
+public fun new_scalar_from_u8(byte: u8): ristretto255::Scalar
+
+
+
+
+public fun new_scalar_from_u8(byte: u8): Scalar {
+ let s = scalar_zero();
+ let byte_zero = std::vector::borrow_mut(&mut s.data, 0);
+ *byte_zero = byte;
+
+ s
+}
+
+
+
+
+public fun new_scalar_from_u32(four_bytes: u32): ristretto255::Scalar
+
+
+
+
+public fun new_scalar_from_u32(four_bytes: u32): Scalar {
+ Scalar {
+ data: scalar_from_u64_internal((four_bytes as u64))
+ }
+}
+
+
+
+
+public fun new_scalar_from_u64(eight_bytes: u64): ristretto255::Scalar
+
+
+
+
+public fun new_scalar_from_u64(eight_bytes: u64): Scalar {
+ Scalar {
+ data: scalar_from_u64_internal(eight_bytes)
+ }
+}
+
+
+
+
+public fun new_scalar_from_u128(sixteen_bytes: u128): ristretto255::Scalar
+
+
+
+
+public fun new_scalar_from_u128(sixteen_bytes: u128): Scalar {
+ Scalar {
+ data: scalar_from_u128_internal(sixteen_bytes)
+ }
+}
+
+
+
+
+public fun new_scalar_reduced_from_32_bytes(bytes: vector<u8>): option::Option<ristretto255::Scalar>
+
+
+
+
+public fun new_scalar_reduced_from_32_bytes(bytes: vector<u8>): Option<Scalar> {
+ if (std::vector::length(&bytes) == 32) {
+ std::option::some(Scalar {
+ data: scalar_reduced_from_32_bytes_internal(bytes)
+ })
+ } else {
+ std::option::none()
+ }
+}
+
+
+
+
+public fun new_scalar_uniform_from_64_bytes(bytes: vector<u8>): option::Option<ristretto255::Scalar>
+
+
+
+
+public fun new_scalar_uniform_from_64_bytes(bytes: vector<u8>): Option<Scalar> {
+ if (std::vector::length(&bytes) == 64) {
+ std::option::some(Scalar {
+ data: scalar_uniform_from_64_bytes_internal(bytes)
+ })
+ } else {
+ std::option::none()
+ }
+}
+
+
+
+
+public fun scalar_zero(): ristretto255::Scalar
+
+
+
+
+public fun scalar_zero(): Scalar {
+ Scalar {
+ data: x"0000000000000000000000000000000000000000000000000000000000000000"
+ }
+}
+
+
+
+
+public fun scalar_is_zero(s: &ristretto255::Scalar): bool
+
+
+
+
+public fun scalar_is_zero(s: &Scalar): bool {
+ s.data == x"0000000000000000000000000000000000000000000000000000000000000000"
+}
+
+
+
+
+public fun scalar_one(): ristretto255::Scalar
+
+
+
+
+public fun scalar_one(): Scalar {
+ Scalar {
+ data: x"0100000000000000000000000000000000000000000000000000000000000000"
+ }
+}
+
+
+
+
+public fun scalar_is_one(s: &ristretto255::Scalar): bool
+
+
+
+
+public fun scalar_is_one(s: &Scalar): bool {
+ s.data == x"0100000000000000000000000000000000000000000000000000000000000000"
+}
+
+
+
+
+public fun scalar_equals(lhs: &ristretto255::Scalar, rhs: &ristretto255::Scalar): bool
+
+
+
+
+public fun scalar_equals(lhs: &Scalar, rhs: &Scalar): bool {
+ lhs.data == rhs.data
+}
+
+
+
+
+public fun scalar_invert(s: &ristretto255::Scalar): option::Option<ristretto255::Scalar>
+
+
+
+
+public fun scalar_invert(s: &Scalar): Option<Scalar> {
+ if (scalar_is_zero(s)) {
+ std::option::none<Scalar>()
+ } else {
+ std::option::some(Scalar {
+ data: scalar_invert_internal(s.data)
+ })
+ }
+}
+
+
+
+
+public fun scalar_mul(a: &ristretto255::Scalar, b: &ristretto255::Scalar): ristretto255::Scalar
+
+
+
+
+public fun scalar_mul(a: &Scalar, b: &Scalar): Scalar {
+ Scalar {
+ data: scalar_mul_internal(a.data, b.data)
+ }
+}
+
+
+
+
+public fun scalar_mul_assign(a: &mut ristretto255::Scalar, b: &ristretto255::Scalar): &mut ristretto255::Scalar
+
+
+
+
+public fun scalar_mul_assign(a: &mut Scalar, b: &Scalar): &mut Scalar {
+ a.data = scalar_mul(a, b).data;
+ a
+}
+
+
+
+
+public fun scalar_add(a: &ristretto255::Scalar, b: &ristretto255::Scalar): ristretto255::Scalar
+
+
+
+
+public fun scalar_add(a: &Scalar, b: &Scalar): Scalar {
+ Scalar {
+ data: scalar_add_internal(a.data, b.data)
+ }
+}
+
+
+
+
+public fun scalar_add_assign(a: &mut ristretto255::Scalar, b: &ristretto255::Scalar): &mut ristretto255::Scalar
+
+
+
+
+public fun scalar_add_assign(a: &mut Scalar, b: &Scalar): &mut Scalar {
+ a.data = scalar_add(a, b).data;
+ a
+}
+
+
+
+
+public fun scalar_sub(a: &ristretto255::Scalar, b: &ristretto255::Scalar): ristretto255::Scalar
+
+
+
+
+public fun scalar_sub(a: &Scalar, b: &Scalar): Scalar {
+ Scalar {
+ data: scalar_sub_internal(a.data, b.data)
+ }
+}
+
+
+
+
+public fun scalar_sub_assign(a: &mut ristretto255::Scalar, b: &ristretto255::Scalar): &mut ristretto255::Scalar
+
+
+
+
+public fun scalar_sub_assign(a: &mut Scalar, b: &Scalar): &mut Scalar {
+ a.data = scalar_sub(a, b).data;
+ a
+}
+
+
+
+
+public fun scalar_neg(a: &ristretto255::Scalar): ristretto255::Scalar
+
+
+
+
+public fun scalar_neg(a: &Scalar): Scalar {
+ Scalar {
+ data: scalar_neg_internal(a.data)
+ }
+}
+
+
+
+
+public fun scalar_neg_assign(a: &mut ristretto255::Scalar): &mut ristretto255::Scalar
+
+
+
+
+public fun scalar_neg_assign(a: &mut Scalar): &mut Scalar {
+ a.data = scalar_neg(a).data;
+ a
+}
+
+
+
+
+public fun scalar_to_bytes(s: &ristretto255::Scalar): vector<u8>
+
+
+
+
+public fun scalar_to_bytes(s: &Scalar): vector<u8> {
+ s.data
+}
+
+
+
+
+fun new_point_from_sha512_internal(sha2_512_input: vector<u8>): u64
+
+
+
+
+native fun new_point_from_sha512_internal(sha2_512_input: vector<u8>): u64;
+
+
+
+
+fun new_point_from_64_uniform_bytes_internal(bytes: vector<u8>): u64
+
+
+
+
+native fun new_point_from_64_uniform_bytes_internal(bytes: vector<u8>): u64;
+
+
+
+
+fun point_is_canonical_internal(bytes: vector<u8>): bool
+
+
+
+
+native fun point_is_canonical_internal(bytes: vector<u8>): bool;
+
+
+
+
+fun point_identity_internal(): u64
+
+
+
+
+native fun point_identity_internal(): u64;
+
+
+
+
+fun point_decompress_internal(maybe_non_canonical_bytes: vector<u8>): (u64, bool)
+
+
+
+
+native fun point_decompress_internal(maybe_non_canonical_bytes: vector<u8>): (u64, bool);
+
+
+
+
+fun point_clone_internal(point_handle: u64): u64
+
+
+
+
+native fun point_clone_internal(point_handle: u64): u64;
+
+
+
+
+fun point_compress_internal(point: &ristretto255::RistrettoPoint): vector<u8>
+
+
+
+
+native fun point_compress_internal(point: &RistrettoPoint): vector<u8>;
+
+
+
+
+fun point_mul_internal(point: &ristretto255::RistrettoPoint, a: vector<u8>, in_place: bool): u64
+
+
+
+
+native fun point_mul_internal(point: &RistrettoPoint, a: vector<u8>, in_place: bool): u64;
+
+
+
+
+fun basepoint_mul_internal(a: vector<u8>): u64
+
+
+
+
+native fun basepoint_mul_internal(a: vector<u8>): u64;
+
+
+
+
+fun basepoint_double_mul_internal(a: vector<u8>, some_point: &ristretto255::RistrettoPoint, b: vector<u8>): u64
+
+
+
+
+native fun basepoint_double_mul_internal(a: vector<u8>, some_point: &RistrettoPoint, b: vector<u8>): u64;
+
+
+
+
+fun point_add_internal(a: &ristretto255::RistrettoPoint, b: &ristretto255::RistrettoPoint, in_place: bool): u64
+
+
+
+
+native fun point_add_internal(a: &RistrettoPoint, b: &RistrettoPoint, in_place: bool): u64;
+
+
+
+
+fun point_sub_internal(a: &ristretto255::RistrettoPoint, b: &ristretto255::RistrettoPoint, in_place: bool): u64
+
+
+
+
+native fun point_sub_internal(a: &RistrettoPoint, b: &RistrettoPoint, in_place: bool): u64;
+
+
+
+
+fun point_neg_internal(a: &ristretto255::RistrettoPoint, in_place: bool): u64
+
+
+
+
+native fun point_neg_internal(a: &RistrettoPoint, in_place: bool): u64;
+
+
+
+
+fun double_scalar_mul_internal(point1: u64, point2: u64, scalar1: vector<u8>, scalar2: vector<u8>): u64
+
+
+
+
+native fun double_scalar_mul_internal(point1: u64, point2: u64, scalar1: vector<u8>, scalar2: vector<u8>): u64;
+
+
+
+
+fun multi_scalar_mul_internal<P, S>(points: &vector<P>, scalars: &vector<S>): u64
+
+
+
+
+native fun multi_scalar_mul_internal<P, S>(points: &vector<P>, scalars: &vector<S>): u64;
+
+
+
+
+fun scalar_is_canonical_internal(s: vector<u8>): bool
+
+
+
+
+native fun scalar_is_canonical_internal(s: vector<u8>): bool;
+
+
+
+
+fun scalar_from_u64_internal(num: u64): vector<u8>
+
+
+
+
+native fun scalar_from_u64_internal(num: u64): vector<u8>;
+
+
+
+
+fun scalar_from_u128_internal(num: u128): vector<u8>
+
+
+
+
+native fun scalar_from_u128_internal(num: u128): vector<u8>;
+
+
+
+
+fun scalar_reduced_from_32_bytes_internal(bytes: vector<u8>): vector<u8>
+
+
+
+
+native fun scalar_reduced_from_32_bytes_internal(bytes: vector<u8>): vector<u8>;
+
+
+
+
+fun scalar_uniform_from_64_bytes_internal(bytes: vector<u8>): vector<u8>
+
+
+
+
+native fun scalar_uniform_from_64_bytes_internal(bytes: vector<u8>): vector<u8>;
+
+
+
+
+fun scalar_invert_internal(bytes: vector<u8>): vector<u8>
+
+
+
+
+native fun scalar_invert_internal(bytes: vector<u8>): vector<u8>;
+
+
+
+
+fun scalar_from_sha512_internal(sha2_512_input: vector<u8>): vector<u8>
+
+
+
+
+native fun scalar_from_sha512_internal(sha2_512_input: vector<u8>): vector<u8>;
+
+
+
+
+fun scalar_mul_internal(a_bytes: vector<u8>, b_bytes: vector<u8>): vector<u8>
+
+
+
+
+native fun scalar_mul_internal(a_bytes: vector<u8>, b_bytes: vector<u8>): vector<u8>;
+
+
+
+
+fun scalar_add_internal(a_bytes: vector<u8>, b_bytes: vector<u8>): vector<u8>
+
+
+
+
+native fun scalar_add_internal(a_bytes: vector<u8>, b_bytes: vector<u8>): vector<u8>;
+
+
+
+
+fun scalar_sub_internal(a_bytes: vector<u8>, b_bytes: vector<u8>): vector<u8>
+
+
+
+
+native fun scalar_sub_internal(a_bytes: vector<u8>, b_bytes: vector<u8>): vector<u8>;
+
+
+
+
+fun scalar_neg_internal(a_bytes: vector<u8>): vector<u8>
+
+
+
+
+native fun scalar_neg_internal(a_bytes: vector<u8>): vector<u8>;
+
+
+
+
+fun spec_scalar_is_zero(s: Scalar): bool {
+ s.data == x"0000000000000000000000000000000000000000000000000000000000000000"
+}
+
+
+
+
+
+
+
+
+fun spec_scalar_is_one(s: Scalar): bool {
+ s.data == x"0100000000000000000000000000000000000000000000000000000000000000"
+}
+
+
+
+
+
+
+
+
+fun spec_point_is_canonical_internal(bytes: vector<u8>): bool;
+
+
+
+
+
+
+
+
+fun spec_double_scalar_mul_internal(point1: u64, point2: u64, scalar1: vector<u8>, scalar2: vector<u8>): u64;
+
+
+
+
+
+
+
+
+fun spec_multi_scalar_mul_internal<P, S>(points: vector<P>, scalars: vector<S>): u64;
+
+
+
+
+
+
+
+
+fun spec_scalar_is_canonical_internal(s: vector<u8>): bool;
+
+
+
+
+
+
+
+
+fun spec_scalar_from_u64_internal(num: u64): vector<u8>;
+
+
+
+
+
+
+
+
+fun spec_scalar_from_u128_internal(num: u128): vector<u8>;
+
+
+
+
+
+
+
+
+fun spec_scalar_reduced_from_32_bytes_internal(bytes: vector<u8>): vector<u8>;
+
+
+
+
+
+
+
+
+fun spec_scalar_uniform_from_64_bytes_internal(bytes: vector<u8>): vector<u8>;
+
+
+
+
+
+
+
+
+fun spec_scalar_invert_internal(bytes: vector<u8>): vector<u8>;
+
+
+
+
+
+
+
+
+fun spec_scalar_from_sha512_internal(sha2_512_input: vector<u8>): vector<u8>;
+
+
+
+
+
+
+
+
+fun spec_scalar_mul_internal(a_bytes: vector<u8>, b_bytes: vector<u8>): vector<u8>;
+
+
+
+
+
+
+
+
+fun spec_scalar_add_internal(a_bytes: vector<u8>, b_bytes: vector<u8>): vector<u8>;
+
+
+
+
+
+
+
+
+fun spec_scalar_sub_internal(a_bytes: vector<u8>, b_bytes: vector<u8>): vector<u8>;
+
+
+
+
+
+
+
+
+fun spec_scalar_neg_internal(a_bytes: vector<u8>): vector<u8>;
+
+
+
+
+
+
+### Function `point_equals`
+
+
+public fun point_equals(g: &ristretto255::RistrettoPoint, h: &ristretto255::RistrettoPoint): bool
+
+
+
+
+
+pragma opaque;
+
+
+
+
+
+
+### Function `double_scalar_mul`
+
+
+public fun double_scalar_mul(scalar1: &ristretto255::Scalar, point1: &ristretto255::RistrettoPoint, scalar2: &ristretto255::Scalar, point2: &ristretto255::RistrettoPoint): ristretto255::RistrettoPoint
+
+
+
+
+
+pragma opaque;
+
+
+
+
+
+
+### Function `multi_scalar_mul`
+
+
+public fun multi_scalar_mul(points: &vector<ristretto255::RistrettoPoint>, scalars: &vector<ristretto255::Scalar>): ristretto255::RistrettoPoint
+
+
+
+
+
+aborts_if len(points) == 0;
+aborts_if len(scalars) == 0;
+aborts_if len(points) != len(scalars);
+ensures result.handle == spec_multi_scalar_mul_internal(points, scalars);
+
+
+
+
+
+
+### Function `new_scalar_from_bytes`
+
+
+public fun new_scalar_from_bytes(bytes: vector<u8>): option::Option<ristretto255::Scalar>
+
+
+
+
+
+aborts_if false;
+ensures spec_scalar_is_canonical_internal(bytes) ==> (std::option::spec_is_some(result)
+ && std::option::spec_borrow(result).data == bytes);
+ensures !spec_scalar_is_canonical_internal(bytes) ==> std::option::spec_is_none(result);
+
+
+
+
+
+
+### Function `new_scalar_from_sha2_512`
+
+
+public fun new_scalar_from_sha2_512(sha2_512_input: vector<u8>): ristretto255::Scalar
+
+
+
+
+
+aborts_if false;
+ensures result.data == spec_scalar_from_sha512_internal(sha2_512_input);
+
+
+
+
+
+
+### Function `new_scalar_from_u8`
+
+
+public fun new_scalar_from_u8(byte: u8): ristretto255::Scalar
+
+
+
+
+
+aborts_if false;
+ensures result.data[0] == byte;
+ensures forall i in 1..len(result.data): result.data[i] == 0;
+
+
+
+
+
+
+### Function `new_scalar_from_u32`
+
+
+public fun new_scalar_from_u32(four_bytes: u32): ristretto255::Scalar
+
+
+
+
+
+aborts_if false;
+ensures result.data == spec_scalar_from_u64_internal(four_bytes);
+
+
+
+
+
+
+### Function `new_scalar_from_u64`
+
+
+public fun new_scalar_from_u64(eight_bytes: u64): ristretto255::Scalar
+
+
+
+
+
+aborts_if false;
+ensures result.data == spec_scalar_from_u64_internal(eight_bytes);
+
+
+
+
+
+
+### Function `new_scalar_from_u128`
+
+
+public fun new_scalar_from_u128(sixteen_bytes: u128): ristretto255::Scalar
+
+
+
+
+
+aborts_if false;
+ensures result.data == spec_scalar_from_u128_internal(sixteen_bytes);
+
+
+
+
+
+
+### Function `new_scalar_reduced_from_32_bytes`
+
+
+public fun new_scalar_reduced_from_32_bytes(bytes: vector<u8>): option::Option<ristretto255::Scalar>
+
+
+
+
+
+ensures len(bytes) != 32 ==> std::option::spec_is_none(result);
+ensures len(bytes) == 32 ==> std::option::spec_borrow(result).data == spec_scalar_reduced_from_32_bytes_internal(bytes);
+
+
+
+
+
+
+### Function `new_scalar_uniform_from_64_bytes`
+
+
+public fun new_scalar_uniform_from_64_bytes(bytes: vector<u8>): option::Option<ristretto255::Scalar>
+
+
+
+
+
+ensures len(bytes) != 64 ==> std::option::spec_is_none(result);
+ensures len(bytes) == 64 ==> std::option::spec_borrow(result).data == spec_scalar_uniform_from_64_bytes_internal(bytes);
+
+
+
+
+
+
+### Function `scalar_zero`
+
+
+public fun scalar_zero(): ristretto255::Scalar
+
+
+
+
+
+ensures spec_scalar_is_zero(result);
+
+
+
+
+
+
+### Function `scalar_is_zero`
+
+
+public fun scalar_is_zero(s: &ristretto255::Scalar): bool
+
+
+
+
+
+ensures result == spec_scalar_is_zero(s);
+
+
+
+
+
+
+### Function `scalar_one`
+
+
+public fun scalar_one(): ristretto255::Scalar
+
+
+
+
+
+ensures spec_scalar_is_one(result);
+
+
+
+
+
+
+### Function `scalar_is_one`
+
+
+public fun scalar_is_one(s: &ristretto255::Scalar): bool
+
+
+
+
+
+ensures result == spec_scalar_is_one(s);
+
+
+
+
+
+
+### Function `scalar_equals`
+
+
+public fun scalar_equals(lhs: &ristretto255::Scalar, rhs: &ristretto255::Scalar): bool
+
+
+
+
+
+aborts_if false;
+ensures result == (lhs.data == rhs.data);
+
+
+
+
+
+
+### Function `scalar_invert`
+
+
+public fun scalar_invert(s: &ristretto255::Scalar): option::Option<ristretto255::Scalar>
+
+
+
+
+
+aborts_if false;
+ensures spec_scalar_is_zero(s) ==> std::option::spec_is_none(result);
+ensures !spec_scalar_is_zero(s) ==> (std::option::spec_is_some(result) && std::option::spec_borrow(result).data == spec_scalar_invert_internal(s.data));
+
+
+
+
+
+
+### Function `scalar_mul`
+
+
+public fun scalar_mul(a: &ristretto255::Scalar, b: &ristretto255::Scalar): ristretto255::Scalar
+
+
+
+
+
+aborts_if false;
+ensures result.data == spec_scalar_mul_internal(a.data, b.data);
+
+
+
+
+
+
+### Function `scalar_mul_assign`
+
+
+public fun scalar_mul_assign(a: &mut ristretto255::Scalar, b: &ristretto255::Scalar): &mut ristretto255::Scalar
+
+
+
+
+
+aborts_if false;
+ensures a.data == spec_scalar_mul_internal(old(a).data, b.data);
+
+
+
+
+
+
+### Function `scalar_add`
+
+
+public fun scalar_add(a: &ristretto255::Scalar, b: &ristretto255::Scalar): ristretto255::Scalar
+
+
+
+
+
+aborts_if false;
+ensures result.data == spec_scalar_add_internal(a.data, b.data);
+
+
+
+
+
+
+### Function `scalar_add_assign`
+
+
+public fun scalar_add_assign(a: &mut ristretto255::Scalar, b: &ristretto255::Scalar): &mut ristretto255::Scalar
+
+
+
+
+
+aborts_if false;
+ensures a.data == spec_scalar_add_internal(old(a).data, b.data);
+
+
+
+
+
+
+### Function `scalar_sub`
+
+
+public fun scalar_sub(a: &ristretto255::Scalar, b: &ristretto255::Scalar): ristretto255::Scalar
+
+
+
+
+
+aborts_if false;
+ensures result.data == spec_scalar_sub_internal(a.data, b.data);
+
+
+
+
+
+
+### Function `scalar_sub_assign`
+
+
+public fun scalar_sub_assign(a: &mut ristretto255::Scalar, b: &ristretto255::Scalar): &mut ristretto255::Scalar
+
+
+
+
+
+aborts_if false;
+ensures a.data == spec_scalar_sub_internal(old(a).data, b.data);
+
+
+
+
+
+
+### Function `scalar_neg`
+
+
+public fun scalar_neg(a: &ristretto255::Scalar): ristretto255::Scalar
+
+
+
+
+
+pragma opaque;
+aborts_if false;
+ensures result.data == spec_scalar_neg_internal(a.data);
+
+
+
+
+
+
+### Function `scalar_neg_assign`
+
+
+public fun scalar_neg_assign(a: &mut ristretto255::Scalar): &mut ristretto255::Scalar
+
+
+
+
+
+aborts_if false;
+ensures a.data == spec_scalar_neg_internal(old(a).data);
+
+
+
+
+
+
+### Function `scalar_to_bytes`
+
+
+public fun scalar_to_bytes(s: &ristretto255::Scalar): vector<u8>
+
+
+
+
+
+aborts_if false;
+ensures result == s.data;
+
+
+
+
+
+
+### Function `new_point_from_sha512_internal`
+
+
+fun new_point_from_sha512_internal(sha2_512_input: vector<u8>): u64
+
+
+
+
+
+pragma opaque;
+
+
+
+
+
+
+### Function `new_point_from_64_uniform_bytes_internal`
+
+
+fun new_point_from_64_uniform_bytes_internal(bytes: vector<u8>): u64
+
+
+
+
+
+pragma opaque;
+
+
+
+
+
+
+### Function `point_is_canonical_internal`
+
+
+fun point_is_canonical_internal(bytes: vector<u8>): bool
+
+
+
+
+
+pragma opaque;
+aborts_if [abstract] false;
+ensures result == spec_point_is_canonical_internal(bytes);
+
+
+
+
+
+
+### Function `point_identity_internal`
+
+
+fun point_identity_internal(): u64
+
+
+
+
+
+pragma opaque;
+
+
+
+
+
+
+### Function `point_decompress_internal`
+
+
+fun point_decompress_internal(maybe_non_canonical_bytes: vector<u8>): (u64, bool)
+
+
+
+
+
+pragma opaque;
+
+
+
+
+
+
+### Function `point_clone_internal`
+
+
+fun point_clone_internal(point_handle: u64): u64
+
+
+
+
+
+pragma opaque;
+
+
+
+
+
+
+### Function `point_compress_internal`
+
+
+fun point_compress_internal(point: &ristretto255::RistrettoPoint): vector<u8>
+
+
+
+
+
+pragma opaque;
+
+
+
+
+
+
+### Function `point_mul_internal`
+
+
+fun point_mul_internal(point: &ristretto255::RistrettoPoint, a: vector<u8>, in_place: bool): u64
+
+
+
+
+
+pragma opaque;
+
+
+
+
+
+
+### Function `basepoint_mul_internal`
+
+
+fun basepoint_mul_internal(a: vector<u8>): u64
+
+
+
+
+
+pragma opaque;
+
+
+
+
+
+
+### Function `basepoint_double_mul_internal`
+
+
+fun basepoint_double_mul_internal(a: vector<u8>, some_point: &ristretto255::RistrettoPoint, b: vector<u8>): u64
+
+
+
+
+
+pragma opaque;
+
+
+
+
+
+
+### Function `point_add_internal`
+
+
+fun point_add_internal(a: &ristretto255::RistrettoPoint, b: &ristretto255::RistrettoPoint, in_place: bool): u64
+
+
+
+
+
+pragma opaque;
+
+
+
+
+
+
+### Function `point_sub_internal`
+
+
+fun point_sub_internal(a: &ristretto255::RistrettoPoint, b: &ristretto255::RistrettoPoint, in_place: bool): u64
+
+
+
+
+
+pragma opaque;
+
+
+
+
+
+
+### Function `point_neg_internal`
+
+
+fun point_neg_internal(a: &ristretto255::RistrettoPoint, in_place: bool): u64
+
+
+
+
+
+pragma opaque;
+
+
+
+
+
+
+### Function `double_scalar_mul_internal`
+
+
+fun double_scalar_mul_internal(point1: u64, point2: u64, scalar1: vector<u8>, scalar2: vector<u8>): u64
+
+
+
+
+
+pragma opaque;
+
+
+
+
+
+
+### Function `multi_scalar_mul_internal`
+
+
+fun multi_scalar_mul_internal<P, S>(points: &vector<P>, scalars: &vector<S>): u64
+
+
+
+
+
+pragma opaque;
+aborts_if [abstract] false;
+ensures result == spec_multi_scalar_mul_internal<P, S>(points, scalars);
+
+
+
+
+
+
+### Function `scalar_is_canonical_internal`
+
+
+fun scalar_is_canonical_internal(s: vector<u8>): bool
+
+
+
+
+
+pragma opaque;
+aborts_if [abstract] false;
+ensures result == spec_scalar_is_canonical_internal(s);
+
+
+
+
+
+
+### Function `scalar_from_u64_internal`
+
+
+fun scalar_from_u64_internal(num: u64): vector<u8>
+
+
+
+
+
+pragma opaque;
+aborts_if [abstract] false;
+ensures result == spec_scalar_from_u64_internal(num);
+
+
+
+
+
+
+### Function `scalar_from_u128_internal`
+
+
+fun scalar_from_u128_internal(num: u128): vector<u8>
+
+
+
+
+
+pragma opaque;
+aborts_if [abstract] false;
+ensures result == spec_scalar_from_u128_internal(num);
+
+
+
+
+
+
+### Function `scalar_reduced_from_32_bytes_internal`
+
+
+fun scalar_reduced_from_32_bytes_internal(bytes: vector<u8>): vector<u8>
+
+
+
+
+
+pragma opaque;
+ensures result == spec_scalar_reduced_from_32_bytes_internal(bytes);
+
+
+
+
+
+
+### Function `scalar_uniform_from_64_bytes_internal`
+
+
+fun scalar_uniform_from_64_bytes_internal(bytes: vector<u8>): vector<u8>
+
+
+
+
+
+pragma opaque;
+aborts_if [abstract] false;
+ensures result == spec_scalar_uniform_from_64_bytes_internal(bytes);
+
+
+
+
+
+
+### Function `scalar_invert_internal`
+
+
+fun scalar_invert_internal(bytes: vector<u8>): vector<u8>
+
+
+
+
+
+pragma opaque;
+aborts_if [abstract] false;
+ensures result == spec_scalar_invert_internal(bytes);
+
+
+
+
+
+
+### Function `scalar_from_sha512_internal`
+
+
+fun scalar_from_sha512_internal(sha2_512_input: vector<u8>): vector<u8>
+
+
+
+
+
+pragma opaque;
+aborts_if [abstract] false;
+ensures result == spec_scalar_from_sha512_internal(sha2_512_input);
+
+
+
+
+
+
+### Function `scalar_mul_internal`
+
+
+fun scalar_mul_internal(a_bytes: vector<u8>, b_bytes: vector<u8>): vector<u8>
+
+
+
+
+
+pragma opaque;
+aborts_if [abstract] false;
+ensures result == spec_scalar_mul_internal(a_bytes, b_bytes);
+
+
+
+
+
+
+### Function `scalar_add_internal`
+
+
+fun scalar_add_internal(a_bytes: vector<u8>, b_bytes: vector<u8>): vector<u8>
+
+
+
+
+
+pragma opaque;
+aborts_if [abstract] false;
+ensures result == spec_scalar_add_internal(a_bytes, b_bytes);
+
+
+
+
+
+
+### Function `scalar_sub_internal`
+
+
+fun scalar_sub_internal(a_bytes: vector<u8>, b_bytes: vector<u8>): vector<u8>
+
+
+
+
+
+pragma opaque;
+aborts_if [abstract] false;
+ensures result == spec_scalar_sub_internal(a_bytes, b_bytes);
+
+
+
+
+
+
+### Function `scalar_neg_internal`
+
+
+fun scalar_neg_internal(a_bytes: vector<u8>): vector<u8>
+
+
+
+
+
+pragma opaque;
+aborts_if [abstract] false;
+ensures result == spec_scalar_neg_internal(a_bytes);
+
+
+
+[move-book]: https://aptos.dev/move/book/SUMMARY
diff --git a/aptos-move/framework/aptos-stdlib/tests/compiler-v2-doc/ristretto255_bulletproofs.md b/aptos-move/framework/aptos-stdlib/tests/compiler-v2-doc/ristretto255_bulletproofs.md
new file mode 100644
index 0000000000000..e98a3e6acd194
--- /dev/null
+++ b/aptos-move/framework/aptos-stdlib/tests/compiler-v2-doc/ristretto255_bulletproofs.md
@@ -0,0 +1,321 @@
+
+
+
+# Module `0x1::ristretto255_bulletproofs`
+
+This module implements a Bulletproof range proof verifier on the Ristretto255 curve.
+
+A Bulletproof-based zero-knowledge range proof is a proof that a Pedersen commitment
+$c = v G + r H$ commits to an $n$-bit value $v$ (i.e., $v \in [0, 2^n)$). Currently, this module only supports
+$n \in \{8, 16, 32, 64\}$ for the number of bits.
+
+
+- [Struct `RangeProof`](#0x1_ristretto255_bulletproofs_RangeProof)
+- [Constants](#@Constants_0)
+- [Function `get_max_range_bits`](#0x1_ristretto255_bulletproofs_get_max_range_bits)
+- [Function `range_proof_from_bytes`](#0x1_ristretto255_bulletproofs_range_proof_from_bytes)
+- [Function `range_proof_to_bytes`](#0x1_ristretto255_bulletproofs_range_proof_to_bytes)
+- [Function `verify_range_proof_pedersen`](#0x1_ristretto255_bulletproofs_verify_range_proof_pedersen)
+- [Function `verify_range_proof`](#0x1_ristretto255_bulletproofs_verify_range_proof)
+- [Function `verify_range_proof_internal`](#0x1_ristretto255_bulletproofs_verify_range_proof_internal)
+- [Specification](#@Specification_1)
+ - [Function `verify_range_proof_internal`](#@Specification_1_verify_range_proof_internal)
+
+
+use 0x1::error;
+use 0x1::features;
+use 0x1::ristretto255;
+use 0x1::ristretto255_pedersen;
+
+
+
+
+
+
+## Struct `RangeProof`
+
+Represents a zero-knowledge range proof that a value committed inside a Pedersen commitment lies in
+[0, 2^{MAX_RANGE_BITS})
.
+
+
+struct RangeProof has copy, drop, store
+
+
+
+
+bytes: vector<u8>
+const E_NATIVE_FUN_NOT_AVAILABLE: u64 = 4;
+
+
+
+
+
+
+There was an error deserializing the range proof.
+
+
+const E_DESERIALIZE_RANGE_PROOF: u64 = 1;
+
+
+
+
+
+
+The range proof system only supports proving ranges of type $[0, 2^b)$ where $b \in \{8, 16, 32, 64\}$.
+
+
+const E_RANGE_NOT_SUPPORTED: u64 = 3;
+
+
+
+
+
+
+The committed value given to the prover is too large.
+
+
+const E_VALUE_OUTSIDE_RANGE: u64 = 2;
+
+
+
+
+
+
+The maximum range supported by the Bulletproofs library is $[0, 2^{64})$.
+
+
+const MAX_RANGE_BITS: u64 = 64;
+
+
+
+
+
+
+## Function `get_max_range_bits`
+
+Returns the maximum # of bits that the range proof system can verify proofs for.
+
+
+public fun get_max_range_bits(): u64
+
+
+
+
+public fun get_max_range_bits(): u64 {
+ MAX_RANGE_BITS
+}
+
+
+
+
+bulletproofs
library (https://docs.rs/bulletproofs/4.0.0/bulletproofs/struct.RangeProof.html#method.from_bytes).
+
+
+public fun range_proof_from_bytes(bytes: vector<u8>): ristretto255_bulletproofs::RangeProof
+
+
+
+
+public fun range_proof_from_bytes(bytes: vector<u8>): RangeProof {
+ RangeProof {
+ bytes
+ }
+}
+
+
+
+
+public fun range_proof_to_bytes(proof: &ristretto255_bulletproofs::RangeProof): vector<u8>
+
+
+
+
+public fun range_proof_to_bytes(proof: &RangeProof): vector<u8> {
+ proof.bytes
+}
+
+
+
+
+v
committed in com
(under the default Bulletproofs
+commitment key; see pedersen::new_commitment_for_bulletproof
) satisfies $v \in [0, 2^b)$. Only works
+for $b \in \{8, 16, 32, 64\}$. Additionally, checks that the prover used dst
as the domain-separation
+tag (DST).
+
+WARNING: The DST check is VERY important for security as it prevents proofs computed for one application
+(a.k.a., a _domain_) with dst_1
from verifying in a different application with dst_2 != dst_1
.
+
+
+public fun verify_range_proof_pedersen(com: &ristretto255_pedersen::Commitment, proof: &ristretto255_bulletproofs::RangeProof, num_bits: u64, dst: vector<u8>): bool
+
+
+
+
+public fun verify_range_proof_pedersen(com: &pedersen::Commitment, proof: &RangeProof, num_bits: u64, dst: vector<u8>): bool {
+ assert!(features::bulletproofs_enabled(), error::invalid_state(E_NATIVE_FUN_NOT_AVAILABLE));
+
+ verify_range_proof_internal(
+ ristretto255::point_to_bytes(&pedersen::commitment_as_compressed_point(com)),
+ &ristretto255::basepoint(), &ristretto255::hash_to_point_base(),
+ proof.bytes,
+ num_bits,
+ dst
+ )
+}
+
+
+
+
+v
committed in com
(as v * val_base + r * rand_base,
+for some randomness r
) satisfies v
in [0, 2^num_bits)
. Only works for num_bits
in {8, 16, 32, 64}
.
+
+
+public fun verify_range_proof(com: &ristretto255::RistrettoPoint, val_base: &ristretto255::RistrettoPoint, rand_base: &ristretto255::RistrettoPoint, proof: &ristretto255_bulletproofs::RangeProof, num_bits: u64, dst: vector<u8>): bool
+
+
+
+
+public fun verify_range_proof(
+ com: &RistrettoPoint,
+ val_base: &RistrettoPoint, rand_base: &RistrettoPoint,
+ proof: &RangeProof, num_bits: u64, dst: vector<u8>): bool
+{
+ assert!(features::bulletproofs_enabled(), error::invalid_state(E_NATIVE_FUN_NOT_AVAILABLE));
+
+ verify_range_proof_internal(
+ ristretto255::point_to_bytes(&ristretto255::point_compress(com)),
+ val_base, rand_base,
+ proof.bytes, num_bits, dst
+ )
+}
+
+
+
+
+error::invalid_argument(E_DESERIALIZE_RANGE_PROOF)
if proof
is not a valid serialization of a
+range proof.
+Aborts with error::invalid_argument(E_RANGE_NOT_SUPPORTED)
if an unsupported num_bits
is provided.
+
+
+fun verify_range_proof_internal(com: vector<u8>, val_base: &ristretto255::RistrettoPoint, rand_base: &ristretto255::RistrettoPoint, proof: vector<u8>, num_bits: u64, dst: vector<u8>): bool
+
+
+
+
+native fun verify_range_proof_internal(
+ com: vector<u8>,
+ val_base: &RistrettoPoint,
+ rand_base: &RistrettoPoint,
+ proof: vector<u8>,
+ num_bits: u64,
+ dst: vector<u8>): bool;
+
+
+
+
+fun verify_range_proof_internal(com: vector<u8>, val_base: &ristretto255::RistrettoPoint, rand_base: &ristretto255::RistrettoPoint, proof: vector<u8>, num_bits: u64, dst: vector<u8>): bool
+
+
+
+
+
+pragma opaque;
+
+
+
+[move-book]: https://aptos.dev/move/book/SUMMARY
diff --git a/aptos-move/framework/aptos-stdlib/tests/compiler-v2-doc/ristretto255_elgamal.md b/aptos-move/framework/aptos-stdlib/tests/compiler-v2-doc/ristretto255_elgamal.md
new file mode 100644
index 0000000000000..ec09869195b51
--- /dev/null
+++ b/aptos-move/framework/aptos-stdlib/tests/compiler-v2-doc/ristretto255_elgamal.md
@@ -0,0 +1,707 @@
+
+
+
+# Module `0x1::ristretto255_elgamal`
+
+This module implements an ElGamal encryption API, over the Ristretto255 curve, that can be used with the
+Bulletproofs module.
+
+An ElGamal *ciphertext* is an encryption of a value v
under a basepoint G
and public key Y = sk * G
, where sk
+is the corresponding secret key, is (v * G + r * Y, r * G)
, for a random scalar r
.
+
+Note that we place the value v
"in the exponent" of G
so that ciphertexts are additively homomorphic: i.e., so
+that Enc_Y(v, r) + Enc_Y(v', r') = Enc_Y(v + v', r + r')
where v, v'
are plaintext messages, Y
is a public key and r, r'
+are the randomness of the ciphertexts.
+
+
+- [Struct `Ciphertext`](#0x1_ristretto255_elgamal_Ciphertext)
+- [Struct `CompressedCiphertext`](#0x1_ristretto255_elgamal_CompressedCiphertext)
+- [Struct `CompressedPubkey`](#0x1_ristretto255_elgamal_CompressedPubkey)
+- [Function `new_pubkey_from_bytes`](#0x1_ristretto255_elgamal_new_pubkey_from_bytes)
+- [Function `pubkey_to_bytes`](#0x1_ristretto255_elgamal_pubkey_to_bytes)
+- [Function `pubkey_to_point`](#0x1_ristretto255_elgamal_pubkey_to_point)
+- [Function `pubkey_to_compressed_point`](#0x1_ristretto255_elgamal_pubkey_to_compressed_point)
+- [Function `new_ciphertext_from_bytes`](#0x1_ristretto255_elgamal_new_ciphertext_from_bytes)
+- [Function `new_ciphertext_no_randomness`](#0x1_ristretto255_elgamal_new_ciphertext_no_randomness)
+- [Function `ciphertext_from_points`](#0x1_ristretto255_elgamal_ciphertext_from_points)
+- [Function `ciphertext_from_compressed_points`](#0x1_ristretto255_elgamal_ciphertext_from_compressed_points)
+- [Function `ciphertext_to_bytes`](#0x1_ristretto255_elgamal_ciphertext_to_bytes)
+- [Function `ciphertext_into_points`](#0x1_ristretto255_elgamal_ciphertext_into_points)
+- [Function `ciphertext_as_points`](#0x1_ristretto255_elgamal_ciphertext_as_points)
+- [Function `compress_ciphertext`](#0x1_ristretto255_elgamal_compress_ciphertext)
+- [Function `decompress_ciphertext`](#0x1_ristretto255_elgamal_decompress_ciphertext)
+- [Function `ciphertext_add`](#0x1_ristretto255_elgamal_ciphertext_add)
+- [Function `ciphertext_add_assign`](#0x1_ristretto255_elgamal_ciphertext_add_assign)
+- [Function `ciphertext_sub`](#0x1_ristretto255_elgamal_ciphertext_sub)
+- [Function `ciphertext_sub_assign`](#0x1_ristretto255_elgamal_ciphertext_sub_assign)
+- [Function `ciphertext_clone`](#0x1_ristretto255_elgamal_ciphertext_clone)
+- [Function `ciphertext_equals`](#0x1_ristretto255_elgamal_ciphertext_equals)
+- [Function `get_value_component`](#0x1_ristretto255_elgamal_get_value_component)
+
+
+use 0x1::option;
+use 0x1::ristretto255;
+use 0x1::vector;
+
+
+
+
+
+
+## Struct `Ciphertext`
+
+An ElGamal ciphertext.
+
+
+struct Ciphertext has drop
+
+
+
+
+left: ristretto255::RistrettoPoint
+right: ristretto255::RistrettoPoint
+struct CompressedCiphertext has copy, drop, store
+
+
+
+
+left: ristretto255::CompressedRistretto
+right: ristretto255::CompressedRistretto
+struct CompressedPubkey has copy, drop, store
+
+
+
+
+point: ristretto255::CompressedRistretto
+public fun new_pubkey_from_bytes(bytes: vector<u8>): option::Option<ristretto255_elgamal::CompressedPubkey>
+
+
+
+
+public fun new_pubkey_from_bytes(bytes: vector<u8>): Option<CompressedPubkey> {
+ let point = ristretto255::new_compressed_point_from_bytes(bytes);
+ if (std::option::is_some(&mut point)) {
+ let pk = CompressedPubkey {
+ point: std::option::extract(&mut point)
+ };
+ std::option::some(pk)
+ } else {
+ std::option::none<CompressedPubkey>()
+ }
+}
+
+
+
+
+pubkey
, returns the byte representation of that public key.
+
+
+public fun pubkey_to_bytes(pubkey: &ristretto255_elgamal::CompressedPubkey): vector<u8>
+
+
+
+
+public fun pubkey_to_bytes(pubkey: &CompressedPubkey): vector<u8> {
+ ristretto255::compressed_point_to_bytes(pubkey.point)
+}
+
+
+
+
+pubkey
, returns the underlying RistrettoPoint
representing that key.
+
+
+public fun pubkey_to_point(pubkey: &ristretto255_elgamal::CompressedPubkey): ristretto255::RistrettoPoint
+
+
+
+
+public fun pubkey_to_point(pubkey: &CompressedPubkey): RistrettoPoint {
+ ristretto255::point_decompress(&pubkey.point)
+}
+
+
+
+
+CompressedRistretto
point representing that key.
+
+
+public fun pubkey_to_compressed_point(pubkey: &ristretto255_elgamal::CompressedPubkey): ristretto255::CompressedRistretto
+
+
+
+
+public fun pubkey_to_compressed_point(pubkey: &CompressedPubkey): CompressedRistretto {
+ pubkey.point
+}
+
+
+
+
+r * G
while the
+next 32 bytes store v * G + r * Y
, where Y
is the public key.
+
+
+public fun new_ciphertext_from_bytes(bytes: vector<u8>): option::Option<ristretto255_elgamal::Ciphertext>
+
+
+
+
+public fun new_ciphertext_from_bytes(bytes: vector<u8>): Option<Ciphertext> {
+ if(vector::length(&bytes) != 64) {
+ return std::option::none<Ciphertext>()
+ };
+
+ let bytes_right = vector::trim(&mut bytes, 32);
+
+ let left_point = ristretto255::new_point_from_bytes(bytes);
+ let right_point = ristretto255::new_point_from_bytes(bytes_right);
+
+ if (std::option::is_some<RistrettoPoint>(&mut left_point) && std::option::is_some<RistrettoPoint>(&mut right_point)) {
+ std::option::some<Ciphertext>(Ciphertext {
+ left: std::option::extract<RistrettoPoint>(&mut left_point),
+ right: std::option::extract<RistrettoPoint>(&mut right_point)
+ })
+ } else {
+ std::option::none<Ciphertext>()
+ }
+}
+
+
+
+
+(val * G + 0 * Y, 0 * G) = (val * G, 0 * G)
where G
is the Ristretto255 basepoint
+and the randomness is set to zero.
+
+
+public fun new_ciphertext_no_randomness(val: &ristretto255::Scalar): ristretto255_elgamal::Ciphertext
+
+
+
+
+public fun new_ciphertext_no_randomness(val: &Scalar): Ciphertext {
+ Ciphertext {
+ left: ristretto255::basepoint_mul(val),
+ right: ristretto255::point_identity(),
+ }
+}
+
+
+
+
+public fun ciphertext_from_points(left: ristretto255::RistrettoPoint, right: ristretto255::RistrettoPoint): ristretto255_elgamal::Ciphertext
+
+
+
+
+public fun ciphertext_from_points(left: RistrettoPoint, right: RistrettoPoint): Ciphertext {
+ Ciphertext {
+ left,
+ right,
+ }
+}
+
+
+
+
+CompressedRistretto
points into an ElGamal ciphertext.
+
+
+public fun ciphertext_from_compressed_points(left: ristretto255::CompressedRistretto, right: ristretto255::CompressedRistretto): ristretto255_elgamal::CompressedCiphertext
+
+
+
+
+public fun ciphertext_from_compressed_points(left: CompressedRistretto, right: CompressedRistretto): CompressedCiphertext {
+ CompressedCiphertext {
+ left,
+ right,
+ }
+}
+
+
+
+
+ct
, serializes that ciphertext into bytes.
+
+
+public fun ciphertext_to_bytes(ct: &ristretto255_elgamal::Ciphertext): vector<u8>
+
+
+
+
+public fun ciphertext_to_bytes(ct: &Ciphertext): vector<u8> {
+ let bytes_left = ristretto255::point_to_bytes(&ristretto255::point_compress(&ct.left));
+ let bytes_right = ristretto255::point_to_bytes(&ristretto255::point_compress(&ct.right));
+ let bytes = vector::empty<u8>();
+ vector::append<u8>(&mut bytes, bytes_left);
+ vector::append<u8>(&mut bytes, bytes_right);
+ bytes
+}
+
+
+
+
+RistrettoPoint
's.
+
+
+public fun ciphertext_into_points(c: ristretto255_elgamal::Ciphertext): (ristretto255::RistrettoPoint, ristretto255::RistrettoPoint)
+
+
+
+
+public fun ciphertext_into_points(c: Ciphertext): (RistrettoPoint, RistrettoPoint) {
+ let Ciphertext { left, right } = c;
+ (left, right)
+}
+
+
+
+
+RistrettoPoint
's representing the ciphertext.
+
+
+public fun ciphertext_as_points(c: &ristretto255_elgamal::Ciphertext): (&ristretto255::RistrettoPoint, &ristretto255::RistrettoPoint)
+
+
+
+
+public fun ciphertext_as_points(c: &Ciphertext): (&RistrettoPoint, &RistrettoPoint) {
+ (&c.left, &c.right)
+}
+
+
+
+
+public fun compress_ciphertext(ct: &ristretto255_elgamal::Ciphertext): ristretto255_elgamal::CompressedCiphertext
+
+
+
+
+public fun compress_ciphertext(ct: &Ciphertext): CompressedCiphertext {
+ CompressedCiphertext {
+ left: point_compress(&ct.left),
+ right: point_compress(&ct.right),
+ }
+}
+
+
+
+
+public fun decompress_ciphertext(ct: &ristretto255_elgamal::CompressedCiphertext): ristretto255_elgamal::Ciphertext
+
+
+
+
+public fun decompress_ciphertext(ct: &CompressedCiphertext): Ciphertext {
+ Ciphertext {
+ left: ristretto255::point_decompress(&ct.left),
+ right: ristretto255::point_decompress(&ct.right),
+ }
+}
+
+
+
+
+lhs
and rhs
as lhs + rhs
.
+Useful for re-randomizing the ciphertext or updating the committed value.
+
+
+public fun ciphertext_add(lhs: &ristretto255_elgamal::Ciphertext, rhs: &ristretto255_elgamal::Ciphertext): ristretto255_elgamal::Ciphertext
+
+
+
+
+public fun ciphertext_add(lhs: &Ciphertext, rhs: &Ciphertext): Ciphertext {
+ Ciphertext {
+ left: ristretto255::point_add(&lhs.left, &rhs.left),
+ right: ristretto255::point_add(&lhs.right, &rhs.right),
+ }
+}
+
+
+
+
+ciphertext_add
but assigns lhs = lhs + rhs
.
+
+
+public fun ciphertext_add_assign(lhs: &mut ristretto255_elgamal::Ciphertext, rhs: &ristretto255_elgamal::Ciphertext)
+
+
+
+
+public fun ciphertext_add_assign(lhs: &mut Ciphertext, rhs: &Ciphertext) {
+ ristretto255::point_add_assign(&mut lhs.left, &rhs.left);
+ ristretto255::point_add_assign(&mut lhs.right, &rhs.right);
+}
+
+
+
+
+lhs
and rhs
as lhs - rhs
.
+Useful for re-randomizing the ciphertext or updating the committed value.
+
+
+public fun ciphertext_sub(lhs: &ristretto255_elgamal::Ciphertext, rhs: &ristretto255_elgamal::Ciphertext): ristretto255_elgamal::Ciphertext
+
+
+
+
+public fun ciphertext_sub(lhs: &Ciphertext, rhs: &Ciphertext): Ciphertext {
+ Ciphertext {
+ left: ristretto255::point_sub(&lhs.left, &rhs.left),
+ right: ristretto255::point_sub(&lhs.right, &rhs.right),
+ }
+}
+
+
+
+
+ciphertext_add
but assigns lhs = lhs - rhs
.
+
+
+public fun ciphertext_sub_assign(lhs: &mut ristretto255_elgamal::Ciphertext, rhs: &ristretto255_elgamal::Ciphertext)
+
+
+
+
+public fun ciphertext_sub_assign(lhs: &mut Ciphertext, rhs: &Ciphertext) {
+ ristretto255::point_sub_assign(&mut lhs.left, &rhs.left);
+ ristretto255::point_sub_assign(&mut lhs.right, &rhs.right);
+}
+
+
+
+
+public fun ciphertext_clone(c: &ristretto255_elgamal::Ciphertext): ristretto255_elgamal::Ciphertext
+
+
+
+
+public fun ciphertext_clone(c: &Ciphertext): Ciphertext {
+ Ciphertext {
+ left: ristretto255::point_clone(&c.left),
+ right: ristretto255::point_clone(&c.right),
+ }
+}
+
+
+
+
+public fun ciphertext_equals(lhs: &ristretto255_elgamal::Ciphertext, rhs: &ristretto255_elgamal::Ciphertext): bool
+
+
+
+
+public fun ciphertext_equals(lhs: &Ciphertext, rhs: &Ciphertext): bool {
+ ristretto255::point_equals(&lhs.left, &rhs.left) &&
+ ristretto255::point_equals(&lhs.right, &rhs.right)
+}
+
+
+
+
+RistrettoPoint
in the ciphertext which contains the encrypted value in the exponent.
+
+
+public fun get_value_component(ct: &ristretto255_elgamal::Ciphertext): &ristretto255::RistrettoPoint
+
+
+
+
+public fun get_value_component(ct: &Ciphertext): &RistrettoPoint {
+ &ct.left
+}
+
+
+
+
+v
under _commitment key_ (g, h)
is v * g + r * h
, for a random scalar r
.
+
+
+- [Struct `Commitment`](#0x1_ristretto255_pedersen_Commitment)
+- [Constants](#@Constants_0)
+- [Function `new_commitment_from_bytes`](#0x1_ristretto255_pedersen_new_commitment_from_bytes)
+- [Function `commitment_to_bytes`](#0x1_ristretto255_pedersen_commitment_to_bytes)
+- [Function `commitment_from_point`](#0x1_ristretto255_pedersen_commitment_from_point)
+- [Function `commitment_from_compressed`](#0x1_ristretto255_pedersen_commitment_from_compressed)
+- [Function `new_commitment`](#0x1_ristretto255_pedersen_new_commitment)
+- [Function `new_commitment_with_basepoint`](#0x1_ristretto255_pedersen_new_commitment_with_basepoint)
+- [Function `new_commitment_for_bulletproof`](#0x1_ristretto255_pedersen_new_commitment_for_bulletproof)
+- [Function `commitment_add`](#0x1_ristretto255_pedersen_commitment_add)
+- [Function `commitment_add_assign`](#0x1_ristretto255_pedersen_commitment_add_assign)
+- [Function `commitment_sub`](#0x1_ristretto255_pedersen_commitment_sub)
+- [Function `commitment_sub_assign`](#0x1_ristretto255_pedersen_commitment_sub_assign)
+- [Function `commitment_clone`](#0x1_ristretto255_pedersen_commitment_clone)
+- [Function `commitment_equals`](#0x1_ristretto255_pedersen_commitment_equals)
+- [Function `commitment_as_point`](#0x1_ristretto255_pedersen_commitment_as_point)
+- [Function `commitment_as_compressed_point`](#0x1_ristretto255_pedersen_commitment_as_compressed_point)
+- [Function `commitment_into_point`](#0x1_ristretto255_pedersen_commitment_into_point)
+- [Function `commitment_into_compressed_point`](#0x1_ristretto255_pedersen_commitment_into_compressed_point)
+- [Function `randomness_base_for_bulletproof`](#0x1_ristretto255_pedersen_randomness_base_for_bulletproof)
+
+
+use 0x1::option;
+use 0x1::ristretto255;
+
+
+
+
+
+
+## Struct `Commitment`
+
+A Pedersen commitment to some value with some randomness.
+
+
+struct Commitment has drop
+
+
+
+
+point: ristretto255::RistrettoPoint
+h
used in our underlying Bulletproofs library.
+This is obtained by hashing the compressed Ristretto255 basepoint using SHA3-512 (not SHA2-512).
+
+
+const BULLETPROOF_DEFAULT_PEDERSEN_RAND_BASE: vector<u8> = [140, 146, 64, 180, 86, 169, 230, 220, 101, 195, 119, 161, 4, 141, 116, 95, 148, 160, 140, 219, 127, 68, 203, 205, 123, 70, 243, 64, 72, 135, 17, 52];
+
+
+
+
+
+
+## Function `new_commitment_from_bytes`
+
+Creates a new public key from a serialized Ristretto255 point.
+
+
+public fun new_commitment_from_bytes(bytes: vector<u8>): option::Option<ristretto255_pedersen::Commitment>
+
+
+
+
+public fun new_commitment_from_bytes(bytes: vector<u8>): Option<Commitment> {
+ let point = ristretto255::new_point_from_bytes(bytes);
+ if (std::option::is_some(&mut point)) {
+ let comm = Commitment {
+ point: std::option::extract(&mut point)
+ };
+ std::option::some(comm)
+ } else {
+ std::option::none<Commitment>()
+ }
+}
+
+
+
+
+public fun commitment_to_bytes(comm: &ristretto255_pedersen::Commitment): vector<u8>
+
+
+
+
+public fun commitment_to_bytes(comm: &Commitment): vector<u8> {
+ ristretto255::point_to_bytes(&ristretto255::point_compress(&comm.point))
+}
+
+
+
+
+public fun commitment_from_point(point: ristretto255::RistrettoPoint): ristretto255_pedersen::Commitment
+
+
+
+
+public fun commitment_from_point(point: RistrettoPoint): Commitment {
+ Commitment {
+ point
+ }
+}
+
+
+
+
+public fun commitment_from_compressed(point: &ristretto255::CompressedRistretto): ristretto255_pedersen::Commitment
+
+
+
+
+public fun commitment_from_compressed(point: &CompressedRistretto): Commitment {
+ Commitment {
+ point: ristretto255::point_decompress(point)
+ }
+}
+
+
+
+
+v * val_base + r * rand_base
where (val_base, rand_base)
is the commitment key.
+
+
+public fun new_commitment(v: &ristretto255::Scalar, val_base: &ristretto255::RistrettoPoint, r: &ristretto255::Scalar, rand_base: &ristretto255::RistrettoPoint): ristretto255_pedersen::Commitment
+
+
+
+
+public fun new_commitment(v: &Scalar, val_base: &RistrettoPoint, r: &Scalar, rand_base: &RistrettoPoint): Commitment {
+ Commitment {
+ point: ristretto255::double_scalar_mul(v, val_base, r, rand_base)
+ }
+}
+
+
+
+
+v * G + r * rand_base
where G
is the Ristretto255 basepoint.
+
+
+public fun new_commitment_with_basepoint(v: &ristretto255::Scalar, r: &ristretto255::Scalar, rand_base: &ristretto255::RistrettoPoint): ristretto255_pedersen::Commitment
+
+
+
+
+public fun new_commitment_with_basepoint(v: &Scalar, r: &Scalar, rand_base: &RistrettoPoint): Commitment {
+ Commitment {
+ point: ristretto255::basepoint_double_mul(r, rand_base, v)
+ }
+}
+
+
+
+
+v * G + r * H
where G
is the Ristretto255 basepoint and H
is the default randomness
+base used in the Bulletproofs library (i.e., BULLETPROOF_DEFAULT_PEDERSEN_RAND_BASE
).
+
+
+public fun new_commitment_for_bulletproof(v: &ristretto255::Scalar, r: &ristretto255::Scalar): ristretto255_pedersen::Commitment
+
+
+
+
+public fun new_commitment_for_bulletproof(v: &Scalar, r: &Scalar): Commitment {
+ let rand_base = ristretto255::new_point_from_bytes(BULLETPROOF_DEFAULT_PEDERSEN_RAND_BASE);
+ let rand_base = std::option::extract(&mut rand_base);
+
+ Commitment {
+ point: ristretto255::basepoint_double_mul(r, &rand_base, v)
+ }
+}
+
+
+
+
+lhs
and rhs
as lhs + rhs
.
+Useful for re-randomizing the commitment or updating the committed value.
+
+
+public fun commitment_add(lhs: &ristretto255_pedersen::Commitment, rhs: &ristretto255_pedersen::Commitment): ristretto255_pedersen::Commitment
+
+
+
+
+public fun commitment_add(lhs: &Commitment, rhs: &Commitment): Commitment {
+ Commitment {
+ point: ristretto255::point_add(&lhs.point, &rhs.point)
+ }
+}
+
+
+
+
+commitment_add
but assigns lhs = lhs + rhs
.
+
+
+public fun commitment_add_assign(lhs: &mut ristretto255_pedersen::Commitment, rhs: &ristretto255_pedersen::Commitment)
+
+
+
+
+public fun commitment_add_assign(lhs: &mut Commitment, rhs: &Commitment) {
+ ristretto255::point_add_assign(&mut lhs.point, &rhs.point);
+}
+
+
+
+
+lhs
and rhs
as lhs - rhs
.
+Useful for re-randomizing the commitment or updating the committed value.
+
+
+public fun commitment_sub(lhs: &ristretto255_pedersen::Commitment, rhs: &ristretto255_pedersen::Commitment): ristretto255_pedersen::Commitment
+
+
+
+
+public fun commitment_sub(lhs: &Commitment, rhs: &Commitment): Commitment {
+ Commitment {
+ point: ristretto255::point_sub(&lhs.point, &rhs.point)
+ }
+}
+
+
+
+
+commitment_add
but assigns lhs = lhs - rhs
.
+
+
+public fun commitment_sub_assign(lhs: &mut ristretto255_pedersen::Commitment, rhs: &ristretto255_pedersen::Commitment)
+
+
+
+
+public fun commitment_sub_assign(lhs: &mut Commitment, rhs: &Commitment) {
+ ristretto255::point_sub_assign(&mut lhs.point, &rhs.point);
+}
+
+
+
+
+public fun commitment_clone(c: &ristretto255_pedersen::Commitment): ristretto255_pedersen::Commitment
+
+
+
+
+public fun commitment_clone(c: &Commitment): Commitment {
+ Commitment {
+ point: ristretto255::point_clone(&c.point)
+ }
+}
+
+
+
+
+public fun commitment_equals(lhs: &ristretto255_pedersen::Commitment, rhs: &ristretto255_pedersen::Commitment): bool
+
+
+
+
+public fun commitment_equals(lhs: &Commitment, rhs: &Commitment): bool {
+ ristretto255::point_equals(&lhs.point, &rhs.point)
+}
+
+
+
+
+RistrettoPoint
.
+
+
+public fun commitment_as_point(c: &ristretto255_pedersen::Commitment): &ristretto255::RistrettoPoint
+
+
+
+
+public fun commitment_as_point(c: &Commitment): &RistrettoPoint {
+ &c.point
+}
+
+
+
+
+CompressedRistretto
point.
+
+
+public fun commitment_as_compressed_point(c: &ristretto255_pedersen::Commitment): ristretto255::CompressedRistretto
+
+
+
+
+public fun commitment_as_compressed_point(c: &Commitment): CompressedRistretto {
+ point_compress(&c.point)
+}
+
+
+
+
+public fun commitment_into_point(c: ristretto255_pedersen::Commitment): ristretto255::RistrettoPoint
+
+
+
+
+public fun commitment_into_point(c: Commitment): RistrettoPoint {
+ let Commitment { point } = c;
+ point
+}
+
+
+
+
+CompressedRistretto
point.
+
+
+public fun commitment_into_compressed_point(c: ristretto255_pedersen::Commitment): ristretto255::CompressedRistretto
+
+
+
+
+public fun commitment_into_compressed_point(c: Commitment): CompressedRistretto {
+ point_compress(&c.point)
+}
+
+
+
+
+v
inside a Pedersen commitment
+v * g + r * h
is sufficiently "small" (e.g., is 32-bits wide). Here, h
is referred to as the
+"randomness base" of the commitment scheme.
+
+Bulletproof has a default choice for g
and h
and this function returns the default h
as used in the
+Bulletproofs Move module.
+
+
+public fun randomness_base_for_bulletproof(): ristretto255::RistrettoPoint
+
+
+
+
+public fun randomness_base_for_bulletproof(): RistrettoPoint {
+ std::option::extract(&mut ristretto255::new_point_from_bytes(BULLETPROOF_DEFAULT_PEDERSEN_RAND_BASE))
+}
+
+
+
+
+use 0x1::error;
+use 0x1::option;
+
+
+
+
+
+
+## Struct `ECDSARawPublicKey`
+
+A 64-byte ECDSA public key.
+
+
+struct ECDSARawPublicKey has copy, drop, store
+
+
+
+
+bytes: vector<u8>
+struct ECDSASignature has copy, drop, store
+
+
+
+
+bytes: vector<u8>
+const SIGNATURE_NUM_BYTES: u64 = 64;
+
+
+
+
+
+
+An error occurred while deserializing, for example due to wrong input size.
+
+
+const E_DESERIALIZE: u64 = 1;
+
+
+
+
+
+
+The size of a secp256k1-based ECDSA public key, in bytes.
+
+
+const RAW_PUBLIC_KEY_NUM_BYTES: u64 = 64;
+
+
+
+
+
+
+## Function `ecdsa_signature_from_bytes`
+
+Constructs an ECDSASignature struct from the given 64 bytes.
+
+
+public fun ecdsa_signature_from_bytes(bytes: vector<u8>): secp256k1::ECDSASignature
+
+
+
+
+public fun ecdsa_signature_from_bytes(bytes: vector<u8>): ECDSASignature {
+ assert!(std::vector::length(&bytes) == SIGNATURE_NUM_BYTES, std::error::invalid_argument(E_DESERIALIZE));
+ ECDSASignature { bytes }
+}
+
+
+
+
+public fun ecdsa_raw_public_key_from_64_bytes(bytes: vector<u8>): secp256k1::ECDSARawPublicKey
+
+
+
+
+public fun ecdsa_raw_public_key_from_64_bytes(bytes: vector<u8>): ECDSARawPublicKey {
+ assert!(std::vector::length(&bytes) == RAW_PUBLIC_KEY_NUM_BYTES, std::error::invalid_argument(E_DESERIALIZE));
+ ECDSARawPublicKey { bytes }
+}
+
+
+
+
+public fun ecdsa_raw_public_key_to_bytes(pk: &secp256k1::ECDSARawPublicKey): vector<u8>
+
+
+
+
+public fun ecdsa_raw_public_key_to_bytes(pk: &ECDSARawPublicKey): vector<u8> {
+ pk.bytes
+}
+
+
+
+
+public fun ecdsa_signature_to_bytes(sig: &secp256k1::ECDSASignature): vector<u8>
+
+
+
+
+public fun ecdsa_signature_to_bytes(sig: &ECDSASignature): vector<u8> {
+ sig.bytes
+}
+
+
+
+
+signature
given the recovery_id
and the signed
+message
(32 byte digest).
+
+Note that an invalid signature, or a signature from a different message, will result in the recovery of an
+incorrect public key. This recovery algorithm can only be used to check validity of a signature if the signer's
+public key (or its hash) is known beforehand.
+
+
+public fun ecdsa_recover(message: vector<u8>, recovery_id: u8, signature: &secp256k1::ECDSASignature): option::Option<secp256k1::ECDSARawPublicKey>
+
+
+
+
+public fun ecdsa_recover(
+ message: vector<u8>,
+ recovery_id: u8,
+ signature: &ECDSASignature,
+): Option<ECDSARawPublicKey> {
+ let (pk, success) = ecdsa_recover_internal(message, recovery_id, signature.bytes);
+ if (success) {
+ std::option::some(ecdsa_raw_public_key_from_64_bytes(pk))
+ } else {
+ std::option::none<ECDSARawPublicKey>()
+ }
+}
+
+
+
+
+(public_key, true)
if signature
verifies on message
under the recovered public_key
+and returns ([], false)
otherwise.
+
+
+fun ecdsa_recover_internal(message: vector<u8>, recovery_id: u8, signature: vector<u8>): (vector<u8>, bool)
+
+
+
+
+native fun ecdsa_recover_internal(
+ message: vector<u8>,
+ recovery_id: u8,
+ signature: vector<u8>
+): (vector<u8>, bool);
+
+
+
+
+public fun ecdsa_signature_from_bytes(bytes: vector<u8>): secp256k1::ECDSASignature
+
+
+
+
+
+aborts_if len(bytes) != SIGNATURE_NUM_BYTES;
+ensures result == ECDSASignature { bytes };
+
+
+
+
+
+
+### Function `ecdsa_raw_public_key_from_64_bytes`
+
+
+public fun ecdsa_raw_public_key_from_64_bytes(bytes: vector<u8>): secp256k1::ECDSARawPublicKey
+
+
+
+
+
+aborts_if len(bytes) != RAW_PUBLIC_KEY_NUM_BYTES;
+ensures result == ECDSARawPublicKey { bytes };
+
+
+
+
+
+
+### Function `ecdsa_raw_public_key_to_bytes`
+
+
+public fun ecdsa_raw_public_key_to_bytes(pk: &secp256k1::ECDSARawPublicKey): vector<u8>
+
+
+
+
+
+aborts_if false;
+ensures result == pk.bytes;
+
+
+
+
+
+
+### Function `ecdsa_signature_to_bytes`
+
+
+public fun ecdsa_signature_to_bytes(sig: &secp256k1::ECDSASignature): vector<u8>
+
+
+
+
+
+aborts_if false;
+ensures result == sig.bytes;
+
+
+
+
+
+
+### Function `ecdsa_recover`
+
+
+public fun ecdsa_recover(message: vector<u8>, recovery_id: u8, signature: &secp256k1::ECDSASignature): option::Option<secp256k1::ECDSARawPublicKey>
+
+
+
+
+
+aborts_if ecdsa_recover_internal_abort_condition(message, recovery_id, signature.bytes);
+let pk = spec_ecdsa_recover_internal_result_1(message, recovery_id, signature.bytes);
+let success = spec_ecdsa_recover_internal_result_2(message, recovery_id, signature.bytes);
+ensures success ==> result == std::option::spec_some(ecdsa_raw_public_key_from_64_bytes(pk));
+ensures !success ==> result == std::option::spec_none<ECDSARawPublicKey>();
+
+
+
+
+
+
+### Function `ecdsa_recover_internal`
+
+
+fun ecdsa_recover_internal(message: vector<u8>, recovery_id: u8, signature: vector<u8>): (vector<u8>, bool)
+
+
+
+
+
+pragma opaque;
+aborts_if ecdsa_recover_internal_abort_condition(message, recovery_id, signature);
+ensures result_1 == spec_ecdsa_recover_internal_result_1(message, recovery_id, signature);
+ensures result_2 == spec_ecdsa_recover_internal_result_2(message, recovery_id, signature);
+ensures len(result_1) == if (result_2) { RAW_PUBLIC_KEY_NUM_BYTES } else { 0 };
+
+
+
+
+
+
+
+
+fun ecdsa_recover_internal_abort_condition(message: vector<u8>, recovery_id: u8, signature: vector<u8>): bool;
+
+
+
+
+
+
+
+
+fun spec_ecdsa_recover_internal_result_1(message: vector<u8>, recovery_id: u8, signature: vector<u8>): vector<u8>;
+
+
+
+
+
+
+
+
+fun spec_ecdsa_recover_internal_result_2(message: vector<u8>, recovery_id: u8, signature: vector<u8>): bool;
+
+
+
+[move-book]: https://aptos.dev/move/book/SUMMARY
diff --git a/aptos-move/framework/aptos-stdlib/tests/compiler-v2-doc/simple_map.md b/aptos-move/framework/aptos-stdlib/tests/compiler-v2-doc/simple_map.md
new file mode 100644
index 0000000000000..df818cb386c6f
--- /dev/null
+++ b/aptos-move/framework/aptos-stdlib/tests/compiler-v2-doc/simple_map.md
@@ -0,0 +1,1033 @@
+
+
+
+# Module `0x1::simple_map`
+
+This module provides a solution for unsorted maps, that is it has the properties that
+1) Keys point to Values
+2) Each Key must be unique
+3) A Key can be found within O(N) time
+4) The keys are unsorted.
+5) Adds and removals take O(N) time
+
+
+- [Struct `SimpleMap`](#0x1_simple_map_SimpleMap)
+- [Struct `Element`](#0x1_simple_map_Element)
+- [Constants](#@Constants_0)
+- [Function `length`](#0x1_simple_map_length)
+- [Function `new`](#0x1_simple_map_new)
+- [Function `new_from`](#0x1_simple_map_new_from)
+- [Function `create`](#0x1_simple_map_create)
+- [Function `borrow`](#0x1_simple_map_borrow)
+- [Function `borrow_mut`](#0x1_simple_map_borrow_mut)
+- [Function `contains_key`](#0x1_simple_map_contains_key)
+- [Function `destroy_empty`](#0x1_simple_map_destroy_empty)
+- [Function `add`](#0x1_simple_map_add)
+- [Function `add_all`](#0x1_simple_map_add_all)
+- [Function `upsert`](#0x1_simple_map_upsert)
+- [Function `keys`](#0x1_simple_map_keys)
+- [Function `values`](#0x1_simple_map_values)
+- [Function `to_vec_pair`](#0x1_simple_map_to_vec_pair)
+- [Function `destroy`](#0x1_simple_map_destroy)
+- [Function `remove`](#0x1_simple_map_remove)
+- [Function `find`](#0x1_simple_map_find)
+- [Specification](#@Specification_1)
+ - [Struct `SimpleMap`](#@Specification_1_SimpleMap)
+ - [Function `length`](#@Specification_1_length)
+ - [Function `new`](#@Specification_1_new)
+ - [Function `new_from`](#@Specification_1_new_from)
+ - [Function `create`](#@Specification_1_create)
+ - [Function `borrow`](#@Specification_1_borrow)
+ - [Function `borrow_mut`](#@Specification_1_borrow_mut)
+ - [Function `contains_key`](#@Specification_1_contains_key)
+ - [Function `destroy_empty`](#@Specification_1_destroy_empty)
+ - [Function `add`](#@Specification_1_add)
+ - [Function `add_all`](#@Specification_1_add_all)
+ - [Function `upsert`](#@Specification_1_upsert)
+ - [Function `keys`](#@Specification_1_keys)
+ - [Function `values`](#@Specification_1_values)
+ - [Function `to_vec_pair`](#@Specification_1_to_vec_pair)
+ - [Function `remove`](#@Specification_1_remove)
+ - [Function `find`](#@Specification_1_find)
+
+
+use 0x1::error;
+use 0x1::option;
+use 0x1::vector;
+
+
+
+
+
+
+## Struct `SimpleMap`
+
+
+
+struct SimpleMap<Key, Value> has copy, drop, store
+
+
+
+
+data: vector<simple_map::Element<Key, Value>>
+struct Element<Key, Value> has copy, drop, store
+
+
+
+
+key: Key
+value: Value
+const EKEY_ALREADY_EXISTS: u64 = 1;
+
+
+
+
+
+
+Map key is not found
+
+
+const EKEY_NOT_FOUND: u64 = 2;
+
+
+
+
+
+
+## Function `length`
+
+
+
+public fun length<Key: store, Value: store>(map: &simple_map::SimpleMap<Key, Value>): u64
+
+
+
+
+public fun length<Key: store, Value: store>(map: &SimpleMap<Key, Value>): u64 {
+ vector::length(&map.data)
+}
+
+
+
+
+public fun new<Key: store, Value: store>(): simple_map::SimpleMap<Key, Value>
+
+
+
+
+public fun new<Key: store, Value: store>(): SimpleMap<Key, Value> {
+ SimpleMap {
+ data: vector::empty(),
+ }
+}
+
+
+
+
+public fun new_from<Key: store, Value: store>(keys: vector<Key>, values: vector<Value>): simple_map::SimpleMap<Key, Value>
+
+
+
+
+public fun new_from<Key: store, Value: store>(
+ keys: vector<Key>,
+ values: vector<Value>,
+): SimpleMap<Key, Value> {
+ let map = new();
+ add_all(&mut map, keys, values);
+ map
+}
+
+
+
+
+new
instead.
+
+
+#[deprecated]
+public fun create<Key: store, Value: store>(): simple_map::SimpleMap<Key, Value>
+
+
+
+
+public fun create<Key: store, Value: store>(): SimpleMap<Key, Value> {
+ new()
+}
+
+
+
+
+public fun borrow<Key: store, Value: store>(map: &simple_map::SimpleMap<Key, Value>, key: &Key): &Value
+
+
+
+
+public fun borrow<Key: store, Value: store>(
+ map: &SimpleMap<Key, Value>,
+ key: &Key,
+): &Value {
+ let maybe_idx = find(map, key);
+ assert!(option::is_some(&maybe_idx), error::invalid_argument(EKEY_NOT_FOUND));
+ let idx = option::extract(&mut maybe_idx);
+ &vector::borrow(&map.data, idx).value
+}
+
+
+
+
+public fun borrow_mut<Key: store, Value: store>(map: &mut simple_map::SimpleMap<Key, Value>, key: &Key): &mut Value
+
+
+
+
+public fun borrow_mut<Key: store, Value: store>(
+ map: &mut SimpleMap<Key, Value>,
+ key: &Key,
+): &mut Value {
+ let maybe_idx = find(map, key);
+ assert!(option::is_some(&maybe_idx), error::invalid_argument(EKEY_NOT_FOUND));
+ let idx = option::extract(&mut maybe_idx);
+ &mut vector::borrow_mut(&mut map.data, idx).value
+}
+
+
+
+
+public fun contains_key<Key: store, Value: store>(map: &simple_map::SimpleMap<Key, Value>, key: &Key): bool
+
+
+
+
+public fun contains_key<Key: store, Value: store>(
+ map: &SimpleMap<Key, Value>,
+ key: &Key,
+): bool {
+ let maybe_idx = find(map, key);
+ option::is_some(&maybe_idx)
+}
+
+
+
+
+public fun destroy_empty<Key: store, Value: store>(map: simple_map::SimpleMap<Key, Value>)
+
+
+
+
+public fun destroy_empty<Key: store, Value: store>(map: SimpleMap<Key, Value>) {
+ let SimpleMap { data } = map;
+ vector::destroy_empty(data);
+}
+
+
+
+
+public fun add<Key: store, Value: store>(map: &mut simple_map::SimpleMap<Key, Value>, key: Key, value: Value)
+
+
+
+
+public fun add<Key: store, Value: store>(
+ map: &mut SimpleMap<Key, Value>,
+ key: Key,
+ value: Value,
+) {
+ let maybe_idx = find(map, &key);
+ assert!(option::is_none(&maybe_idx), error::invalid_argument(EKEY_ALREADY_EXISTS));
+
+ vector::push_back(&mut map.data, Element { key, value });
+}
+
+
+
+
+public fun add_all<Key: store, Value: store>(map: &mut simple_map::SimpleMap<Key, Value>, keys: vector<Key>, values: vector<Value>)
+
+
+
+
+public fun add_all<Key: store, Value: store>(
+ map: &mut SimpleMap<Key, Value>,
+ keys: vector<Key>,
+ values: vector<Value>,
+) {
+ vector::zip(keys, values, |key, value| {
+ add(map, key, value);
+ });
+}
+
+
+
+
+public fun upsert<Key: store, Value: store>(map: &mut simple_map::SimpleMap<Key, Value>, key: Key, value: Value): (option::Option<Key>, option::Option<Value>)
+
+
+
+
+public fun upsert<Key: store, Value: store>(
+ map: &mut SimpleMap<Key, Value>,
+ key: Key,
+ value: Value
+): (std::option::Option<Key>, std::option::Option<Value>) {
+ let data = &mut map.data;
+ let len = vector::length(data);
+ let i = 0;
+ while (i < len) {
+ let element = vector::borrow(data, i);
+ if (&element.key == &key) {
+ vector::push_back(data, Element { key, value });
+ vector::swap(data, i, len);
+ let Element { key, value } = vector::pop_back(data);
+ return (std::option::some(key), std::option::some(value))
+ };
+ i = i + 1;
+ };
+ vector::push_back(&mut map.data, Element { key, value });
+ (std::option::none(), std::option::none())
+}
+
+
+
+
+public fun keys<Key: copy, Value>(map: &simple_map::SimpleMap<Key, Value>): vector<Key>
+
+
+
+
+public fun keys<Key: copy, Value>(map: &SimpleMap<Key, Value>): vector<Key> {
+ vector::map_ref(&map.data, |e| {
+ let e: &Element<Key, Value> = e;
+ e.key
+ })
+}
+
+
+
+
+public fun values<Key, Value: copy>(map: &simple_map::SimpleMap<Key, Value>): vector<Value>
+
+
+
+
+public fun values<Key, Value: copy>(map: &SimpleMap<Key, Value>): vector<Value> {
+ vector::map_ref(&map.data, |e| {
+ let e: &Element<Key, Value> = e;
+ e.value
+ })
+}
+
+
+
+
+public fun to_vec_pair<Key: store, Value: store>(map: simple_map::SimpleMap<Key, Value>): (vector<Key>, vector<Value>)
+
+
+
+
+public fun to_vec_pair<Key: store, Value: store>(
+ map: SimpleMap<Key, Value>): (vector<Key>, vector<Value>) {
+ let keys: vector<Key> = vector::empty();
+ let values: vector<Value> = vector::empty();
+ let SimpleMap { data } = map;
+ vector::for_each(data, |e| {
+ let Element { key, value } = e;
+ vector::push_back(&mut keys, key);
+ vector::push_back(&mut values, value);
+ });
+ (keys, values)
+}
+
+
+
+
+public fun destroy<Key: store, Value: store>(map: simple_map::SimpleMap<Key, Value>, dk: |Key|, dv: |Value|)
+
+
+
+
+public inline fun destroy<Key: store, Value: store>(
+ map: SimpleMap<Key, Value>,
+ dk: |Key|,
+ dv: |Value|
+) {
+ let (keys, values) = to_vec_pair(map);
+ vector::destroy(keys, |_k| dk(_k));
+ vector::destroy(values, |_v| dv(_v));
+}
+
+
+
+
+public fun remove<Key: store, Value: store>(map: &mut simple_map::SimpleMap<Key, Value>, key: &Key): (Key, Value)
+
+
+
+
+public fun remove<Key: store, Value: store>(
+ map: &mut SimpleMap<Key, Value>,
+ key: &Key,
+): (Key, Value) {
+ let maybe_idx = find(map, key);
+ assert!(option::is_some(&maybe_idx), error::invalid_argument(EKEY_NOT_FOUND));
+ let placement = option::extract(&mut maybe_idx);
+ let Element { key, value } = vector::swap_remove(&mut map.data, placement);
+ (key, value)
+}
+
+
+
+
+fun find<Key: store, Value: store>(map: &simple_map::SimpleMap<Key, Value>, key: &Key): option::Option<u64>
+
+
+
+
+fun find<Key: store, Value: store>(
+ map: &SimpleMap<Key, Value>,
+ key: &Key,
+): option::Option<u64> {
+ let leng = vector::length(&map.data);
+ let i = 0;
+ while (i < leng) {
+ let element = vector::borrow(&map.data, i);
+ if (&element.key == key) {
+ return option::some(i)
+ };
+ i = i + 1;
+ };
+ option::none<u64>()
+}
+
+
+
+
+struct SimpleMap<Key, Value> has copy, drop, store
+
+
+
+
+data: vector<simple_map::Element<Key, Value>>
+pragma intrinsic = map,
+ map_new = create,
+ map_len = length,
+ map_destroy_empty = destroy_empty,
+ map_has_key = contains_key,
+ map_add_no_override = add,
+ map_del_return_key = remove,
+ map_borrow = borrow,
+ map_borrow_mut = borrow_mut,
+ map_spec_get = spec_get,
+ map_spec_set = spec_set,
+ map_spec_del = spec_remove,
+ map_spec_len = spec_len,
+ map_spec_has_key = spec_contains_key;
+
+
+
+
+
+
+### Function `length`
+
+
+public fun length<Key: store, Value: store>(map: &simple_map::SimpleMap<Key, Value>): u64
+
+
+
+
+
+pragma intrinsic;
+
+
+
+
+
+
+### Function `new`
+
+
+public fun new<Key: store, Value: store>(): simple_map::SimpleMap<Key, Value>
+
+
+
+
+
+pragma intrinsic;
+pragma opaque;
+aborts_if [abstract] false;
+ensures [abstract] spec_len(result) == 0;
+ensures [abstract] forall k: Key: !spec_contains_key(result, k);
+
+
+
+
+
+
+### Function `new_from`
+
+
+public fun new_from<Key: store, Value: store>(keys: vector<Key>, values: vector<Value>): simple_map::SimpleMap<Key, Value>
+
+
+
+
+
+pragma intrinsic;
+pragma opaque;
+aborts_if [abstract] false;
+ensures [abstract] spec_len(result) == len(keys);
+ensures [abstract] forall k: Key: spec_contains_key(result, k) <==> vector::spec_contains(keys, k);
+ensures [abstract] forall i in 0..len(keys):
+ spec_get(result, vector::borrow(keys, i)) == vector::borrow(values, i);
+
+
+
+
+
+
+### Function `create`
+
+
+#[deprecated]
+public fun create<Key: store, Value: store>(): simple_map::SimpleMap<Key, Value>
+
+
+
+
+
+pragma intrinsic;
+
+
+
+
+
+
+### Function `borrow`
+
+
+public fun borrow<Key: store, Value: store>(map: &simple_map::SimpleMap<Key, Value>, key: &Key): &Value
+
+
+
+
+
+pragma intrinsic;
+
+
+
+
+
+
+### Function `borrow_mut`
+
+
+public fun borrow_mut<Key: store, Value: store>(map: &mut simple_map::SimpleMap<Key, Value>, key: &Key): &mut Value
+
+
+
+
+
+pragma intrinsic;
+
+
+
+
+
+
+### Function `contains_key`
+
+
+public fun contains_key<Key: store, Value: store>(map: &simple_map::SimpleMap<Key, Value>, key: &Key): bool
+
+
+
+
+
+pragma intrinsic;
+
+
+
+
+
+
+### Function `destroy_empty`
+
+
+public fun destroy_empty<Key: store, Value: store>(map: simple_map::SimpleMap<Key, Value>)
+
+
+
+
+
+pragma intrinsic;
+
+
+
+
+
+
+### Function `add`
+
+
+public fun add<Key: store, Value: store>(map: &mut simple_map::SimpleMap<Key, Value>, key: Key, value: Value)
+
+
+
+
+
+pragma intrinsic;
+
+
+
+
+
+
+### Function `add_all`
+
+
+public fun add_all<Key: store, Value: store>(map: &mut simple_map::SimpleMap<Key, Value>, keys: vector<Key>, values: vector<Value>)
+
+
+
+
+
+pragma intrinsic;
+
+
+
+
+
+
+### Function `upsert`
+
+
+public fun upsert<Key: store, Value: store>(map: &mut simple_map::SimpleMap<Key, Value>, key: Key, value: Value): (option::Option<Key>, option::Option<Value>)
+
+
+
+
+
+pragma intrinsic;
+pragma opaque;
+aborts_if [abstract] false;
+ensures [abstract] !spec_contains_key(old(map), key) ==> option::is_none(result_1);
+ensures [abstract] !spec_contains_key(old(map), key) ==> option::is_none(result_2);
+ensures [abstract] spec_contains_key(map, key);
+ensures [abstract] spec_get(map, key) == value;
+ensures [abstract] spec_contains_key(old(map), key) ==> ((option::is_some(result_1)) && (option::spec_borrow(result_1) == key));
+ensures [abstract] spec_contains_key(old(map), key) ==> ((option::is_some(result_2)) && (option::spec_borrow(result_2) == spec_get(old(map), key)));
+
+
+
+
+
+
+
+
+native fun spec_len<K, V>(t: SimpleMap<K, V>): num;
+
+
+
+
+
+
+
+
+native fun spec_contains_key<K, V>(t: SimpleMap<K, V>, k: K): bool;
+
+
+
+
+
+
+
+
+native fun spec_set<K, V>(t: SimpleMap<K, V>, k: K, v: V): SimpleMap<K, V>;
+
+
+
+
+
+
+
+
+native fun spec_remove<K, V>(t: SimpleMap<K, V>, k: K): SimpleMap<K, V>;
+
+
+
+
+
+
+
+
+native fun spec_get<K, V>(t: SimpleMap<K, V>, k: K): V;
+
+
+
+
+
+
+### Function `keys`
+
+
+public fun keys<Key: copy, Value>(map: &simple_map::SimpleMap<Key, Value>): vector<Key>
+
+
+
+
+
+pragma verify=false;
+
+
+
+
+
+
+### Function `values`
+
+
+public fun values<Key, Value: copy>(map: &simple_map::SimpleMap<Key, Value>): vector<Value>
+
+
+
+
+
+pragma verify=false;
+
+
+
+
+
+
+### Function `to_vec_pair`
+
+
+public fun to_vec_pair<Key: store, Value: store>(map: simple_map::SimpleMap<Key, Value>): (vector<Key>, vector<Value>)
+
+
+
+
+
+pragma intrinsic;
+pragma opaque;
+ensures [abstract]
+ forall k: Key: vector::spec_contains(result_1, k) <==>
+ spec_contains_key(map, k);
+ensures [abstract] forall i in 0..len(result_1):
+ spec_get(map, vector::borrow(result_1, i)) == vector::borrow(result_2, i);
+
+
+
+
+
+
+### Function `remove`
+
+
+public fun remove<Key: store, Value: store>(map: &mut simple_map::SimpleMap<Key, Value>, key: &Key): (Key, Value)
+
+
+
+
+
+pragma intrinsic;
+
+
+
+
+
+
+### Function `find`
+
+
+fun find<Key: store, Value: store>(map: &simple_map::SimpleMap<Key, Value>, key: &Key): option::Option<u64>
+
+
+
+
+
+pragma verify=false;
+
+
+
+[move-book]: https://aptos.dev/move/book/SUMMARY
diff --git a/aptos-move/framework/aptos-stdlib/tests/compiler-v2-doc/smart_table.md b/aptos-move/framework/aptos-stdlib/tests/compiler-v2-doc/smart_table.md
new file mode 100644
index 0000000000000..b28f1a0800579
--- /dev/null
+++ b/aptos-move/framework/aptos-stdlib/tests/compiler-v2-doc/smart_table.md
@@ -0,0 +1,1786 @@
+
+
+
+# Module `0x1::smart_table`
+
+A smart table implementation based on linear hashing. (https://en.wikipedia.org/wiki/Linear_hashing)
+Compare to Table, it uses less storage slots but has higher chance of collision, a trade-off between space and time.
+Compare to other dynamic hashing implementation, linear hashing splits one bucket a time instead of doubling buckets
+when expanding to avoid unexpected gas cost.
+SmartTable uses faster hash function SipHash instead of cryptographically secure hash functions like sha3-256 since
+it tolerates collisions.
+
+
+- [Struct `Entry`](#0x1_smart_table_Entry)
+- [Struct `SmartTable`](#0x1_smart_table_SmartTable)
+- [Constants](#@Constants_0)
+- [Function `new`](#0x1_smart_table_new)
+- [Function `new_with_config`](#0x1_smart_table_new_with_config)
+- [Function `destroy_empty`](#0x1_smart_table_destroy_empty)
+- [Function `destroy`](#0x1_smart_table_destroy)
+- [Function `clear`](#0x1_smart_table_clear)
+- [Function `add`](#0x1_smart_table_add)
+- [Function `add_all`](#0x1_smart_table_add_all)
+- [Function `unzip_entries`](#0x1_smart_table_unzip_entries)
+- [Function `to_simple_map`](#0x1_smart_table_to_simple_map)
+- [Function `keys`](#0x1_smart_table_keys)
+- [Function `keys_paginated`](#0x1_smart_table_keys_paginated)
+- [Function `split_one_bucket`](#0x1_smart_table_split_one_bucket)
+- [Function `bucket_index`](#0x1_smart_table_bucket_index)
+- [Function `borrow`](#0x1_smart_table_borrow)
+- [Function `borrow_with_default`](#0x1_smart_table_borrow_with_default)
+- [Function `borrow_mut`](#0x1_smart_table_borrow_mut)
+- [Function `borrow_mut_with_default`](#0x1_smart_table_borrow_mut_with_default)
+- [Function `contains`](#0x1_smart_table_contains)
+- [Function `remove`](#0x1_smart_table_remove)
+- [Function `upsert`](#0x1_smart_table_upsert)
+- [Function `length`](#0x1_smart_table_length)
+- [Function `load_factor`](#0x1_smart_table_load_factor)
+- [Function `update_split_load_threshold`](#0x1_smart_table_update_split_load_threshold)
+- [Function `update_target_bucket_size`](#0x1_smart_table_update_target_bucket_size)
+- [Function `for_each_ref`](#0x1_smart_table_for_each_ref)
+- [Function `for_each_mut`](#0x1_smart_table_for_each_mut)
+- [Function `map_ref`](#0x1_smart_table_map_ref)
+- [Function `any`](#0x1_smart_table_any)
+- [Function `borrow_kv`](#0x1_smart_table_borrow_kv)
+- [Function `borrow_kv_mut`](#0x1_smart_table_borrow_kv_mut)
+- [Function `num_buckets`](#0x1_smart_table_num_buckets)
+- [Function `borrow_buckets`](#0x1_smart_table_borrow_buckets)
+- [Function `borrow_buckets_mut`](#0x1_smart_table_borrow_buckets_mut)
+- [Specification](#@Specification_1)
+ - [Struct `SmartTable`](#@Specification_1_SmartTable)
+ - [Function `new_with_config`](#@Specification_1_new_with_config)
+ - [Function `destroy`](#@Specification_1_destroy)
+ - [Function `clear`](#@Specification_1_clear)
+ - [Function `add_all`](#@Specification_1_add_all)
+ - [Function `to_simple_map`](#@Specification_1_to_simple_map)
+ - [Function `keys`](#@Specification_1_keys)
+ - [Function `keys_paginated`](#@Specification_1_keys_paginated)
+ - [Function `split_one_bucket`](#@Specification_1_split_one_bucket)
+ - [Function `bucket_index`](#@Specification_1_bucket_index)
+ - [Function `borrow_with_default`](#@Specification_1_borrow_with_default)
+ - [Function `load_factor`](#@Specification_1_load_factor)
+ - [Function `update_split_load_threshold`](#@Specification_1_update_split_load_threshold)
+ - [Function `update_target_bucket_size`](#@Specification_1_update_target_bucket_size)
+ - [Function `borrow_kv`](#@Specification_1_borrow_kv)
+ - [Function `borrow_kv_mut`](#@Specification_1_borrow_kv_mut)
+ - [Function `num_buckets`](#@Specification_1_num_buckets)
+ - [Function `borrow_buckets`](#@Specification_1_borrow_buckets)
+ - [Function `borrow_buckets_mut`](#@Specification_1_borrow_buckets_mut)
+
+
+use 0x1::aptos_hash;
+use 0x1::error;
+use 0x1::math64;
+use 0x1::option;
+use 0x1::simple_map;
+use 0x1::table_with_length;
+use 0x1::type_info;
+use 0x1::vector;
+
+
+
+
+
+
+## Struct `Entry`
+
+SmartTable entry contains both the key and value.
+
+
+struct Entry<K, V> has copy, drop, store
+
+
+
+
+hash: u64
+key: K
+value: V
+struct SmartTable<K, V> has store
+
+
+
+
+buckets: table_with_length::TableWithLength<u64, vector<smart_table::Entry<K, V>>>
+num_buckets: u64
+level: u8
+size: u64
+split_load_threshold: u8
+target_bucket_size: u64
+const ENOT_EMPTY: u64 = 3;
+
+
+
+
+
+
+Key not found in the smart table
+
+
+const ENOT_FOUND: u64 = 1;
+
+
+
+
+
+
+Key already exists
+
+
+const EALREADY_EXIST: u64 = 4;
+
+
+
+
+
+
+Invalid target bucket size.
+
+
+const EEXCEED_MAX_BUCKET_SIZE: u64 = 7;
+
+
+
+
+
+
+Invalid bucket index.
+
+
+const EINVALID_BUCKET_INDEX: u64 = 8;
+
+
+
+
+
+
+Invalid load threshold percent to trigger split.
+
+
+const EINVALID_LOAD_THRESHOLD_PERCENT: u64 = 5;
+
+
+
+
+
+
+Invalid target bucket size.
+
+
+const EINVALID_TARGET_BUCKET_SIZE: u64 = 6;
+
+
+
+
+
+
+Invalid vector index within a bucket.
+
+
+const EINVALID_VECTOR_INDEX: u64 = 9;
+
+
+
+
+
+
+Smart table capacity must be larger than 0
+
+
+const EZERO_CAPACITY: u64 = 2;
+
+
+
+
+
+
+## Function `new`
+
+Create an empty SmartTable with default configurations.
+
+
+public fun new<K: copy, drop, store, V: store>(): smart_table::SmartTable<K, V>
+
+
+
+
+public fun new<K: copy + drop + store, V: store>(): SmartTable<K, V> {
+ new_with_config<K, V>(0, 0, 0)
+}
+
+
+
+
+num_initial_buckets
: The number of buckets on initialization. 0 means using default value.
+split_load_threshold
: The percent number which once reached, split will be triggered. 0 means using default
+value.
+target_bucket_size
: The target number of entries per bucket, though not guaranteed. 0 means not set and will
+dynamically assgined by the contract code.
+
+
+public fun new_with_config<K: copy, drop, store, V: store>(num_initial_buckets: u64, split_load_threshold: u8, target_bucket_size: u64): smart_table::SmartTable<K, V>
+
+
+
+
+public fun new_with_config<K: copy + drop + store, V: store>(
+ num_initial_buckets: u64,
+ split_load_threshold: u8,
+ target_bucket_size: u64
+): SmartTable<K, V> {
+ assert!(split_load_threshold <= 100, error::invalid_argument(EINVALID_LOAD_THRESHOLD_PERCENT));
+ let buckets = table_with_length::new();
+ table_with_length::add(&mut buckets, 0, vector::empty());
+ let table = SmartTable {
+ buckets,
+ num_buckets: 1,
+ level: 0,
+ size: 0,
+ // The default split load threshold is 75%.
+ split_load_threshold: if (split_load_threshold == 0) { 75 } else { split_load_threshold },
+ target_bucket_size,
+ };
+ // The default number of initial buckets is 2.
+ if (num_initial_buckets == 0) {
+ num_initial_buckets = 2;
+ };
+ while (num_initial_buckets > 1) {
+ num_initial_buckets = num_initial_buckets - 1;
+ split_one_bucket(&mut table);
+ };
+ table
+}
+
+
+
+
+public fun destroy_empty<K, V>(table: smart_table::SmartTable<K, V>)
+
+
+
+
+public fun destroy_empty<K, V>(table: SmartTable<K, V>) {
+ assert!(table.size == 0, error::invalid_argument(ENOT_EMPTY));
+ let i = 0;
+ while (i < table.num_buckets) {
+ vector::destroy_empty(table_with_length::remove(&mut table.buckets, i));
+ i = i + 1;
+ };
+ let SmartTable { buckets, num_buckets: _, level: _, size: _, split_load_threshold: _, target_bucket_size: _ } = table;
+ table_with_length::destroy_empty(buckets);
+}
+
+
+
+
+drop
.
+
+
+public fun destroy<K: drop, V: drop>(table: smart_table::SmartTable<K, V>)
+
+
+
+
+public fun destroy<K: drop, V: drop>(table: SmartTable<K, V>) {
+ clear(&mut table);
+ destroy_empty(table);
+}
+
+
+
+
+drop
.
+
+
+public fun clear<K: drop, V: drop>(table: &mut smart_table::SmartTable<K, V>)
+
+
+
+
+public fun clear<K: drop, V: drop>(table: &mut SmartTable<K, V>) {
+ *table_with_length::borrow_mut(&mut table.buckets, 0) = vector::empty();
+ let i = 1;
+ while (i < table.num_buckets) {
+ table_with_length::remove(&mut table.buckets, i);
+ i = i + 1;
+ };
+ table.num_buckets = 1;
+ table.level = 0;
+ table.size = 0;
+}
+
+
+
+
+num_buckets
and level
.
+For standard linear hash algorithm, it is stored as a variable but num_buckets
here could be leveraged.
+Abort if key
already exists.
+Note: This method may occasionally cost much more gas when triggering bucket split.
+
+
+public fun add<K, V>(table: &mut smart_table::SmartTable<K, V>, key: K, value: V)
+
+
+
+
+public fun add<K, V>(table: &mut SmartTable<K, V>, key: K, value: V) {
+ let hash = sip_hash_from_value(&key);
+ let index = bucket_index(table.level, table.num_buckets, hash);
+ let bucket = table_with_length::borrow_mut(&mut table.buckets, index);
+ // We set a per-bucket limit here with a upper bound (10000) that nobody should normally reach.
+ assert!(vector::length(bucket) <= 10000, error::permission_denied(EEXCEED_MAX_BUCKET_SIZE));
+ assert!(vector::all(bucket, | entry | {
+ let e: &Entry<K, V> = entry;
+ &e.key != &key
+ }), error::invalid_argument(EALREADY_EXIST));
+ let e = Entry { hash, key, value };
+ if (table.target_bucket_size == 0) {
+ let estimated_entry_size = max(size_of_val(&e), 1);
+ table.target_bucket_size = max(1024 /* free_write_quota */ / estimated_entry_size, 1);
+ };
+ vector::push_back(bucket, e);
+ table.size = table.size + 1;
+
+ if (load_factor(table) >= (table.split_load_threshold as u64)) {
+ split_one_bucket(table);
+ }
+}
+
+
+
+
+public fun add_all<K, V>(table: &mut smart_table::SmartTable<K, V>, keys: vector<K>, values: vector<V>)
+
+
+
+
+public fun add_all<K, V>(table: &mut SmartTable<K, V>, keys: vector<K>, values: vector<V>) {
+ vector::zip(keys, values, |key, value| { add(table, key, value); });
+}
+
+
+
+
+fun unzip_entries<K: copy, V: copy>(entries: &vector<smart_table::Entry<K, V>>): (vector<K>, vector<V>)
+
+
+
+
+inline fun unzip_entries<K: copy, V: copy>(entries: &vector<Entry<K, V>>): (vector<K>, vector<V>) {
+ let keys = vector[];
+ let values = vector[];
+ vector::for_each_ref(entries, |e|{
+ let entry: &Entry<K, V> = e;
+ vector::push_back(&mut keys, entry.key);
+ vector::push_back(&mut values, entry.value);
+ });
+ (keys, values)
+}
+
+
+
+
+public fun to_simple_map<K: copy, drop, store, V: copy, store>(table: &smart_table::SmartTable<K, V>): simple_map::SimpleMap<K, V>
+
+
+
+
+public fun to_simple_map<K: store + copy + drop, V: store + copy>(
+ table: &SmartTable<K, V>,
+): SimpleMap<K, V> {
+ let i = 0;
+ let res = simple_map::new<K, V>();
+ while (i < table.num_buckets) {
+ let (keys, values) = unzip_entries(table_with_length::borrow(&table.buckets, i));
+ simple_map::add_all(&mut res, keys, values);
+ i = i + 1;
+ };
+ res
+}
+
+
+
+
+keys_paginated
should be used instead.
+
+
+public fun keys<K: copy, drop, store, V: copy, store>(table_ref: &smart_table::SmartTable<K, V>): vector<K>
+
+
+
+
+public fun keys<K: store + copy + drop, V: store + copy>(
+ table_ref: &SmartTable<K, V>
+): vector<K> {
+ let (keys, _, _) = keys_paginated(table_ref, 0, 0, length(table_ref));
+ keys
+}
+
+
+
+
+num_keys_to_get
before hitting gas
+limits depends on the data types in the smart table.
+
+When starting pagination, pass starting_bucket_index
= starting_vector_index
= 0.
+
+The function will then return a vector of keys, an optional bucket index, and an optional
+vector index. The unpacked return indices can then be used as inputs to another pagination
+call, which will return a vector of more keys. This process can be repeated until the
+returned bucket index and vector index value options are both none, which means that
+pagination is complete. For an example, see test_keys()
.
+
+
+public fun keys_paginated<K: copy, drop, store, V: copy, store>(table_ref: &smart_table::SmartTable<K, V>, starting_bucket_index: u64, starting_vector_index: u64, num_keys_to_get: u64): (vector<K>, option::Option<u64>, option::Option<u64>)
+
+
+
+
+public fun keys_paginated<K: store + copy + drop, V: store + copy>(
+ table_ref: &SmartTable<K, V>,
+ starting_bucket_index: u64,
+ starting_vector_index: u64,
+ num_keys_to_get: u64,
+): (
+ vector<K>,
+ Option<u64>,
+ Option<u64>,
+) {
+ let num_buckets = table_ref.num_buckets;
+ let buckets_ref = &table_ref.buckets;
+ assert!(starting_bucket_index < num_buckets, EINVALID_BUCKET_INDEX);
+ let bucket_ref = table_with_length::borrow(buckets_ref, starting_bucket_index);
+ let bucket_length = vector::length(bucket_ref);
+ assert!(
+ // In the general case, starting vector index should never be equal to bucket length
+ // because then iteration will attempt to borrow a vector element that is out of bounds.
+ // However starting vector index can be equal to bucket length in the special case of
+ // starting iteration at the beginning of an empty bucket since buckets are never
+ // destroyed, only emptied.
+ starting_vector_index < bucket_length || starting_vector_index == 0,
+ EINVALID_VECTOR_INDEX
+ );
+ let keys = vector[];
+ if (num_keys_to_get == 0) return
+ (keys, option::some(starting_bucket_index), option::some(starting_vector_index));
+ for (bucket_index in starting_bucket_index..num_buckets) {
+ bucket_ref = table_with_length::borrow(buckets_ref, bucket_index);
+ bucket_length = vector::length(bucket_ref);
+ for (vector_index in starting_vector_index..bucket_length) {
+ vector::push_back(&mut keys, vector::borrow(bucket_ref, vector_index).key);
+ num_keys_to_get = num_keys_to_get - 1;
+ if (num_keys_to_get == 0) {
+ vector_index = vector_index + 1;
+ return if (vector_index == bucket_length) {
+ bucket_index = bucket_index + 1;
+ if (bucket_index < num_buckets) {
+ (keys, option::some(bucket_index), option::some(0))
+ } else {
+ (keys, option::none(), option::none())
+ }
+ } else {
+ (keys, option::some(bucket_index), option::some(vector_index))
+ }
+ };
+ };
+ starting_vector_index = 0; // Start parsing the next bucket at vector index 0.
+ };
+ (keys, option::none(), option::none())
+}
+
+
+
+
+fun split_one_bucket<K, V>(table: &mut smart_table::SmartTable<K, V>)
+
+
+
+
+fun split_one_bucket<K, V>(table: &mut SmartTable<K, V>) {
+ let new_bucket_index = table.num_buckets;
+ // the next bucket to split is num_bucket without the most significant bit.
+ let to_split = new_bucket_index ^ (1 << table.level);
+ table.num_buckets = new_bucket_index + 1;
+ // if the whole level is splitted once, bump the level.
+ if (to_split + 1 == 1 << table.level) {
+ table.level = table.level + 1;
+ };
+ let old_bucket = table_with_length::borrow_mut(&mut table.buckets, to_split);
+ // partition the bucket, [0..p) stays in old bucket, [p..len) goes to new bucket
+ let p = vector::partition(old_bucket, |e| {
+ let entry: &Entry<K, V> = e; // Explicit type to satisfy compiler
+ bucket_index(table.level, table.num_buckets, entry.hash) != new_bucket_index
+ });
+ let new_bucket = vector::trim_reverse(old_bucket, p);
+ table_with_length::add(&mut table.buckets, new_bucket_index, new_bucket);
+}
+
+
+
+
+1 << level
vs 1 << (level + 1)
in modulo operation based on the target
+bucket index compared to the index of the next bucket to split.
+
+
+fun bucket_index(level: u8, num_buckets: u64, hash: u64): u64
+
+
+
+
+fun bucket_index(level: u8, num_buckets: u64, hash: u64): u64 {
+ let index = hash % (1 << (level + 1));
+ if (index < num_buckets) {
+ // in existing bucket
+ index
+ } else {
+ // in unsplitted bucket
+ index % (1 << level)
+ }
+}
+
+
+
+
+key
maps to.
+Aborts if there is no entry for key
.
+
+
+public fun borrow<K: drop, V>(table: &smart_table::SmartTable<K, V>, key: K): &V
+
+
+
+
+public fun borrow<K: drop, V>(table: &SmartTable<K, V>, key: K): &V {
+ let index = bucket_index(table.level, table.num_buckets, sip_hash_from_value(&key));
+ let bucket = table_with_length::borrow(&table.buckets, index);
+ let i = 0;
+ let len = vector::length(bucket);
+ while (i < len) {
+ let entry = vector::borrow(bucket, i);
+ if (&entry.key == &key) {
+ return &entry.value
+ };
+ i = i + 1;
+ };
+ abort error::invalid_argument(ENOT_FOUND)
+}
+
+
+
+
+key
maps to.
+Returns specified default value if there is no entry for key
.
+
+
+public fun borrow_with_default<K: copy, drop, V>(table: &smart_table::SmartTable<K, V>, key: K, default: &V): &V
+
+
+
+
+public fun borrow_with_default<K: copy + drop, V>(table: &SmartTable<K, V>, key: K, default: &V): &V {
+ if (!contains(table, copy key)) {
+ default
+ } else {
+ borrow(table, copy key)
+ }
+}
+
+
+
+
+key
maps to.
+Aborts if there is no entry for key
.
+
+
+public fun borrow_mut<K: drop, V>(table: &mut smart_table::SmartTable<K, V>, key: K): &mut V
+
+
+
+
+public fun borrow_mut<K: drop, V>(table: &mut SmartTable<K, V>, key: K): &mut V {
+ let index = bucket_index(table.level, table.num_buckets, sip_hash_from_value(&key));
+ let bucket = table_with_length::borrow_mut(&mut table.buckets, index);
+ let i = 0;
+ let len = vector::length(bucket);
+ while (i < len) {
+ let entry = vector::borrow_mut(bucket, i);
+ if (&entry.key == &key) {
+ return &mut entry.value
+ };
+ i = i + 1;
+ };
+ abort error::invalid_argument(ENOT_FOUND)
+}
+
+
+
+
+key
maps to.
+Insert the pair (key
, default
) first if there is no entry for key
.
+
+
+public fun borrow_mut_with_default<K: copy, drop, V: drop>(table: &mut smart_table::SmartTable<K, V>, key: K, default: V): &mut V
+
+
+
+
+public fun borrow_mut_with_default<K: copy + drop, V: drop>(
+ table: &mut SmartTable<K, V>,
+ key: K,
+ default: V
+): &mut V {
+ if (!contains(table, copy key)) {
+ add(table, copy key, default)
+ };
+ borrow_mut(table, key)
+}
+
+
+
+
+table
contains an entry for key
.
+
+
+public fun contains<K: drop, V>(table: &smart_table::SmartTable<K, V>, key: K): bool
+
+
+
+
+public fun contains<K: drop, V>(table: &SmartTable<K, V>, key: K): bool {
+ let hash = sip_hash_from_value(&key);
+ let index = bucket_index(table.level, table.num_buckets, hash);
+ let bucket = table_with_length::borrow(&table.buckets, index);
+ vector::any(bucket, | entry | {
+ let e: &Entry<K, V> = entry;
+ e.hash == hash && &e.key == &key
+ })
+}
+
+
+
+
+table
and return the value which key
maps to.
+Aborts if there is no entry for key
.
+
+
+public fun remove<K: copy, drop, V>(table: &mut smart_table::SmartTable<K, V>, key: K): V
+
+
+
+
+public fun remove<K: copy + drop, V>(table: &mut SmartTable<K, V>, key: K): V {
+ let index = bucket_index(table.level, table.num_buckets, sip_hash_from_value(&key));
+ let bucket = table_with_length::borrow_mut(&mut table.buckets, index);
+ let i = 0;
+ let len = vector::length(bucket);
+ while (i < len) {
+ let entry = vector::borrow(bucket, i);
+ if (&entry.key == &key) {
+ let Entry { hash: _, key: _, value } = vector::swap_remove(bucket, i);
+ table.size = table.size - 1;
+ return value
+ };
+ i = i + 1;
+ };
+ abort error::invalid_argument(ENOT_FOUND)
+}
+
+
+
+
+key
, value
) if there is no entry for key
.
+update the value of the entry for key
to value
otherwise
+
+
+public fun upsert<K: copy, drop, V: drop>(table: &mut smart_table::SmartTable<K, V>, key: K, value: V)
+
+
+
+
+public fun upsert<K: copy + drop, V: drop>(table: &mut SmartTable<K, V>, key: K, value: V) {
+ if (!contains(table, copy key)) {
+ add(table, copy key, value)
+ } else {
+ let ref = borrow_mut(table, key);
+ *ref = value;
+ };
+}
+
+
+
+
+public fun length<K, V>(table: &smart_table::SmartTable<K, V>): u64
+
+
+
+
+public fun length<K, V>(table: &SmartTable<K, V>): u64 {
+ table.size
+}
+
+
+
+
+public fun load_factor<K, V>(table: &smart_table::SmartTable<K, V>): u64
+
+
+
+
+public fun load_factor<K, V>(table: &SmartTable<K, V>): u64 {
+ table.size * 100 / table.num_buckets / table.target_bucket_size
+}
+
+
+
+
+split_load_threshold
.
+
+
+public fun update_split_load_threshold<K, V>(table: &mut smart_table::SmartTable<K, V>, split_load_threshold: u8)
+
+
+
+
+public fun update_split_load_threshold<K, V>(table: &mut SmartTable<K, V>, split_load_threshold: u8) {
+ assert!(
+ split_load_threshold <= 100 && split_load_threshold > 0,
+ error::invalid_argument(EINVALID_LOAD_THRESHOLD_PERCENT)
+ );
+ table.split_load_threshold = split_load_threshold;
+}
+
+
+
+
+target_bucket_size
.
+
+
+public fun update_target_bucket_size<K, V>(table: &mut smart_table::SmartTable<K, V>, target_bucket_size: u64)
+
+
+
+
+public fun update_target_bucket_size<K, V>(table: &mut SmartTable<K, V>, target_bucket_size: u64) {
+ assert!(target_bucket_size > 0, error::invalid_argument(EINVALID_TARGET_BUCKET_SIZE));
+ table.target_bucket_size = target_bucket_size;
+}
+
+
+
+
+public fun for_each_ref<K, V>(table: &smart_table::SmartTable<K, V>, f: |(&K, &V)|)
+
+
+
+
+public inline fun for_each_ref<K, V>(table: &SmartTable<K, V>, f: |&K, &V|) {
+ let i = 0;
+ while (i < aptos_std::smart_table::num_buckets(table)) {
+ vector::for_each_ref(
+ aptos_std::table_with_length::borrow(aptos_std::smart_table::borrow_buckets(table), i),
+ |elem| {
+ let (key, value) = aptos_std::smart_table::borrow_kv(elem);
+ f(key, value)
+ }
+ );
+ i = i + 1;
+ }
+}
+
+
+
+
+public fun for_each_mut<K, V>(table: &mut smart_table::SmartTable<K, V>, f: |(&K, &mut V)|)
+
+
+
+
+public inline fun for_each_mut<K, V>(table: &mut SmartTable<K, V>, f: |&K, &mut V|) {
+ let i = 0;
+ while (i < aptos_std::smart_table::num_buckets(table)) {
+ vector::for_each_mut(
+ table_with_length::borrow_mut(aptos_std::smart_table::borrow_buckets_mut(table), i),
+ |elem| {
+ let (key, value) = aptos_std::smart_table::borrow_kv_mut(elem);
+ f(key, value)
+ }
+ );
+ i = i + 1;
+ };
+}
+
+
+
+
+public fun map_ref<K: copy, drop, store, V1, V2: store>(table: &smart_table::SmartTable<K, V1>, f: |&V1|V2): smart_table::SmartTable<K, V2>
+
+
+
+
+public inline fun map_ref<K: copy + drop + store, V1, V2: store>(
+ table: &SmartTable<K, V1>,
+ f: |&V1|V2
+): SmartTable<K, V2> {
+ let new_table = new<K, V2>();
+ for_each_ref(table, |key, value| add(&mut new_table, *key, f(value)));
+ new_table
+}
+
+
+
+
+public fun any<K, V>(table: &smart_table::SmartTable<K, V>, p: |(&K, &V)|bool): bool
+
+
+
+
+public inline fun any<K, V>(
+ table: &SmartTable<K, V>,
+ p: |&K, &V|bool
+): bool {
+ let found = false;
+ let i = 0;
+ while (i < aptos_std::smart_table::num_buckets(table)) {
+ found = vector::any(table_with_length::borrow(aptos_std::smart_table::borrow_buckets(table), i), |elem| {
+ let (key, value) = aptos_std::smart_table::borrow_kv(elem);
+ p(key, value)
+ });
+ if (found) break;
+ i = i + 1;
+ };
+ found
+}
+
+
+
+
+public fun borrow_kv<K, V>(e: &smart_table::Entry<K, V>): (&K, &V)
+
+
+
+
+public fun borrow_kv<K, V>(e: &Entry<K, V>): (&K, &V) {
+ (&e.key, &e.value)
+}
+
+
+
+
+public fun borrow_kv_mut<K, V>(e: &mut smart_table::Entry<K, V>): (&mut K, &mut V)
+
+
+
+
+public fun borrow_kv_mut<K, V>(e: &mut Entry<K, V>): (&mut K, &mut V) {
+ (&mut e.key, &mut e.value)
+}
+
+
+
+
+public fun num_buckets<K, V>(table: &smart_table::SmartTable<K, V>): u64
+
+
+
+
+public fun num_buckets<K, V>(table: &SmartTable<K, V>): u64 {
+ table.num_buckets
+}
+
+
+
+
+public fun borrow_buckets<K, V>(table: &smart_table::SmartTable<K, V>): &table_with_length::TableWithLength<u64, vector<smart_table::Entry<K, V>>>
+
+
+
+
+public fun borrow_buckets<K, V>(table: &SmartTable<K, V>): &TableWithLength<u64, vector<Entry<K, V>>> {
+ &table.buckets
+}
+
+
+
+
+public fun borrow_buckets_mut<K, V>(table: &mut smart_table::SmartTable<K, V>): &mut table_with_length::TableWithLength<u64, vector<smart_table::Entry<K, V>>>
+
+
+
+
+public fun borrow_buckets_mut<K, V>(table: &mut SmartTable<K, V>): &mut TableWithLength<u64, vector<Entry<K, V>>> {
+ &mut table.buckets
+}
+
+
+
+
+struct SmartTable<K, V> has store
+
+
+
+
+buckets: table_with_length::TableWithLength<u64, vector<smart_table::Entry<K, V>>>
+num_buckets: u64
+level: u8
+size: u64
+split_load_threshold: u8
+target_bucket_size: u64
+pragma intrinsic = map,
+ map_new = new,
+ map_destroy_empty = destroy_empty,
+ map_len = length,
+ map_has_key = contains,
+ map_add_no_override = add,
+ map_add_override_if_exists = upsert,
+ map_del_must_exist = remove,
+ map_borrow = borrow,
+ map_borrow_mut = borrow_mut,
+ map_borrow_mut_with_default = borrow_mut_with_default,
+ map_spec_get = spec_get,
+ map_spec_set = spec_set,
+ map_spec_del = spec_remove,
+ map_spec_len = spec_len,
+map_spec_has_key = spec_contains;
+
+
+
+
+
+
+### Function `new_with_config`
+
+
+public fun new_with_config<K: copy, drop, store, V: store>(num_initial_buckets: u64, split_load_threshold: u8, target_bucket_size: u64): smart_table::SmartTable<K, V>
+
+
+
+
+
+pragma verify = false;
+
+
+
+
+
+
+### Function `destroy`
+
+
+public fun destroy<K: drop, V: drop>(table: smart_table::SmartTable<K, V>)
+
+
+
+
+
+pragma verify = false;
+
+
+
+
+
+
+### Function `clear`
+
+
+public fun clear<K: drop, V: drop>(table: &mut smart_table::SmartTable<K, V>)
+
+
+
+
+
+pragma verify = false;
+
+
+
+
+
+
+### Function `add_all`
+
+
+public fun add_all<K, V>(table: &mut smart_table::SmartTable<K, V>, keys: vector<K>, values: vector<V>)
+
+
+
+
+
+pragma verify = false;
+
+
+
+
+
+
+### Function `to_simple_map`
+
+
+public fun to_simple_map<K: copy, drop, store, V: copy, store>(table: &smart_table::SmartTable<K, V>): simple_map::SimpleMap<K, V>
+
+
+
+
+
+pragma verify = false;
+
+
+
+
+
+
+### Function `keys`
+
+
+public fun keys<K: copy, drop, store, V: copy, store>(table_ref: &smart_table::SmartTable<K, V>): vector<K>
+
+
+
+
+
+pragma verify = false;
+
+
+
+
+
+
+### Function `keys_paginated`
+
+
+public fun keys_paginated<K: copy, drop, store, V: copy, store>(table_ref: &smart_table::SmartTable<K, V>, starting_bucket_index: u64, starting_vector_index: u64, num_keys_to_get: u64): (vector<K>, option::Option<u64>, option::Option<u64>)
+
+
+
+
+
+pragma verify = false;
+
+
+
+
+
+
+### Function `split_one_bucket`
+
+
+fun split_one_bucket<K, V>(table: &mut smart_table::SmartTable<K, V>)
+
+
+
+
+
+pragma verify = false;
+
+
+
+
+
+
+### Function `bucket_index`
+
+
+fun bucket_index(level: u8, num_buckets: u64, hash: u64): u64
+
+
+
+
+
+pragma verify = false;
+
+
+
+
+
+
+### Function `borrow_with_default`
+
+
+public fun borrow_with_default<K: copy, drop, V>(table: &smart_table::SmartTable<K, V>, key: K, default: &V): &V
+
+
+
+
+
+pragma verify = false;
+
+
+
+
+
+
+### Function `load_factor`
+
+
+public fun load_factor<K, V>(table: &smart_table::SmartTable<K, V>): u64
+
+
+
+
+
+pragma verify = false;
+
+
+
+
+
+
+### Function `update_split_load_threshold`
+
+
+public fun update_split_load_threshold<K, V>(table: &mut smart_table::SmartTable<K, V>, split_load_threshold: u8)
+
+
+
+
+
+pragma verify = false;
+
+
+
+
+
+
+### Function `update_target_bucket_size`
+
+
+public fun update_target_bucket_size<K, V>(table: &mut smart_table::SmartTable<K, V>, target_bucket_size: u64)
+
+
+
+
+
+pragma verify = false;
+
+
+
+
+
+
+### Function `borrow_kv`
+
+
+public fun borrow_kv<K, V>(e: &smart_table::Entry<K, V>): (&K, &V)
+
+
+
+
+
+pragma verify = false;
+
+
+
+
+
+
+### Function `borrow_kv_mut`
+
+
+public fun borrow_kv_mut<K, V>(e: &mut smart_table::Entry<K, V>): (&mut K, &mut V)
+
+
+
+
+
+pragma verify = false;
+
+
+
+
+
+
+### Function `num_buckets`
+
+
+public fun num_buckets<K, V>(table: &smart_table::SmartTable<K, V>): u64
+
+
+
+
+
+pragma verify = false;
+
+
+
+
+
+
+### Function `borrow_buckets`
+
+
+public fun borrow_buckets<K, V>(table: &smart_table::SmartTable<K, V>): &table_with_length::TableWithLength<u64, vector<smart_table::Entry<K, V>>>
+
+
+
+
+
+pragma verify = false;
+
+
+
+
+
+
+### Function `borrow_buckets_mut`
+
+
+public fun borrow_buckets_mut<K, V>(table: &mut smart_table::SmartTable<K, V>): &mut table_with_length::TableWithLength<u64, vector<smart_table::Entry<K, V>>>
+
+
+
+
+
+pragma verify = false;
+
+
+
+
+
+
+
+
+native fun spec_len<K, V>(t: SmartTable<K, V>): num;
+
+
+
+
+
+
+
+
+native fun spec_contains<K, V>(t: SmartTable<K, V>, k: K): bool;
+
+
+
+
+
+
+
+
+native fun spec_set<K, V>(t: SmartTable<K, V>, k: K, v: V): SmartTable<K, V>;
+
+
+
+
+
+
+
+
+native fun spec_remove<K, V>(t: SmartTable<K, V>, k: K): SmartTable<K, V>;
+
+
+
+
+
+
+
+
+native fun spec_get<K, V>(t: SmartTable<K, V>, k: K): V;
+
+
+
+[move-book]: https://aptos.dev/move/book/SUMMARY
diff --git a/aptos-move/framework/aptos-stdlib/tests/compiler-v2-doc/smart_vector.md b/aptos-move/framework/aptos-stdlib/tests/compiler-v2-doc/smart_vector.md
new file mode 100644
index 0000000000000..3b82a5c91a65f
--- /dev/null
+++ b/aptos-move/framework/aptos-stdlib/tests/compiler-v2-doc/smart_vector.md
@@ -0,0 +1,1727 @@
+
+
+
+# Module `0x1::smart_vector`
+
+
+
+- [Struct `SmartVector`](#0x1_smart_vector_SmartVector)
+- [Constants](#@Constants_0)
+- [Function `new`](#0x1_smart_vector_new)
+- [Function `empty`](#0x1_smart_vector_empty)
+- [Function `empty_with_config`](#0x1_smart_vector_empty_with_config)
+- [Function `singleton`](#0x1_smart_vector_singleton)
+- [Function `destroy_empty`](#0x1_smart_vector_destroy_empty)
+- [Function `destroy`](#0x1_smart_vector_destroy)
+- [Function `clear`](#0x1_smart_vector_clear)
+- [Function `borrow`](#0x1_smart_vector_borrow)
+- [Function `borrow_mut`](#0x1_smart_vector_borrow_mut)
+- [Function `append`](#0x1_smart_vector_append)
+- [Function `add_all`](#0x1_smart_vector_add_all)
+- [Function `to_vector`](#0x1_smart_vector_to_vector)
+- [Function `push_back`](#0x1_smart_vector_push_back)
+- [Function `pop_back`](#0x1_smart_vector_pop_back)
+- [Function `remove`](#0x1_smart_vector_remove)
+- [Function `swap_remove`](#0x1_smart_vector_swap_remove)
+- [Function `swap`](#0x1_smart_vector_swap)
+- [Function `reverse`](#0x1_smart_vector_reverse)
+- [Function `index_of`](#0x1_smart_vector_index_of)
+- [Function `contains`](#0x1_smart_vector_contains)
+- [Function `length`](#0x1_smart_vector_length)
+- [Function `is_empty`](#0x1_smart_vector_is_empty)
+- [Function `for_each`](#0x1_smart_vector_for_each)
+- [Function `for_each_reverse`](#0x1_smart_vector_for_each_reverse)
+- [Function `for_each_ref`](#0x1_smart_vector_for_each_ref)
+- [Function `for_each_mut`](#0x1_smart_vector_for_each_mut)
+- [Function `enumerate_ref`](#0x1_smart_vector_enumerate_ref)
+- [Function `enumerate_mut`](#0x1_smart_vector_enumerate_mut)
+- [Function `fold`](#0x1_smart_vector_fold)
+- [Function `foldr`](#0x1_smart_vector_foldr)
+- [Function `map_ref`](#0x1_smart_vector_map_ref)
+- [Function `map`](#0x1_smart_vector_map)
+- [Function `filter`](#0x1_smart_vector_filter)
+- [Function `zip`](#0x1_smart_vector_zip)
+- [Function `zip_reverse`](#0x1_smart_vector_zip_reverse)
+- [Function `zip_ref`](#0x1_smart_vector_zip_ref)
+- [Function `zip_mut`](#0x1_smart_vector_zip_mut)
+- [Function `zip_map`](#0x1_smart_vector_zip_map)
+- [Function `zip_map_ref`](#0x1_smart_vector_zip_map_ref)
+- [Specification](#@Specification_1)
+ - [Struct `SmartVector`](#@Specification_1_SmartVector)
+ - [Function `empty`](#@Specification_1_empty)
+ - [Function `empty_with_config`](#@Specification_1_empty_with_config)
+ - [Function `destroy_empty`](#@Specification_1_destroy_empty)
+ - [Function `borrow`](#@Specification_1_borrow)
+ - [Function `append`](#@Specification_1_append)
+ - [Function `push_back`](#@Specification_1_push_back)
+ - [Function `pop_back`](#@Specification_1_pop_back)
+ - [Function `remove`](#@Specification_1_remove)
+ - [Function `swap_remove`](#@Specification_1_swap_remove)
+ - [Function `swap`](#@Specification_1_swap)
+ - [Function `length`](#@Specification_1_length)
+
+
+use 0x1::big_vector;
+use 0x1::error;
+use 0x1::math64;
+use 0x1::option;
+use 0x1::type_info;
+use 0x1::vector;
+
+
+
+
+
+
+## Struct `SmartVector`
+
+A Scalable vector implementation based on tables, Ts are grouped into buckets with bucket_size
.
+The option wrapping BigVector saves space in the metadata associated with BigVector when smart_vector is
+so small that inline_vec vector can hold all the data.
+
+
+struct SmartVector<T> has store
+
+
+
+
+inline_vec: vector<T>
+big_vec: option::Option<big_vector::BigVector<T>>
+inline_capacity: option::Option<u64>
+bucket_size: option::Option<u64>
+const EINDEX_OUT_OF_BOUNDS: u64 = 1;
+
+
+
+
+
+
+Cannot pop back from an empty vector
+
+
+const EVECTOR_EMPTY: u64 = 3;
+
+
+
+
+
+
+Cannot destroy a non-empty vector
+
+
+const EVECTOR_NOT_EMPTY: u64 = 2;
+
+
+
+
+
+
+bucket_size cannot be 0
+
+
+const EZERO_BUCKET_SIZE: u64 = 4;
+
+
+
+
+
+
+The length of the smart vectors are not equal.
+
+
+const ESMART_VECTORS_LENGTH_MISMATCH: u64 = 131077;
+
+
+
+
+
+
+## Function `new`
+
+Regular Vector API
+Create an empty vector using default logic to estimate inline_capacity
and bucket_size
, which may be
+inaccurate.
+This is exactly the same as empty() but is more standardized as all other data structures have new().
+
+
+public fun new<T: store>(): smart_vector::SmartVector<T>
+
+
+
+
+public fun new<T: store>(): SmartVector<T> {
+ empty()
+}
+
+
+
+
+inline_capacity
and bucket_size
, which may be
+inaccurate.
+
+
+#[deprecated]
+public fun empty<T: store>(): smart_vector::SmartVector<T>
+
+
+
+
+public fun empty<T: store>(): SmartVector<T> {
+ SmartVector {
+ inline_vec: vector[],
+ big_vec: option::none(),
+ inline_capacity: option::none(),
+ bucket_size: option::none(),
+ }
+}
+
+
+
+
+public fun empty_with_config<T: store>(inline_capacity: u64, bucket_size: u64): smart_vector::SmartVector<T>
+
+
+
+
+public fun empty_with_config<T: store>(inline_capacity: u64, bucket_size: u64): SmartVector<T> {
+ assert!(bucket_size > 0, error::invalid_argument(EZERO_BUCKET_SIZE));
+ SmartVector {
+ inline_vec: vector[],
+ big_vec: option::none(),
+ inline_capacity: option::some(inline_capacity),
+ bucket_size: option::some(bucket_size),
+ }
+}
+
+
+
+
+public fun singleton<T: store>(element: T): smart_vector::SmartVector<T>
+
+
+
+
+public fun singleton<T: store>(element: T): SmartVector<T> {
+ let v = empty();
+ push_back(&mut v, element);
+ v
+}
+
+
+
+
+v
.
+Aborts if v
is not empty.
+
+
+public fun destroy_empty<T>(v: smart_vector::SmartVector<T>)
+
+
+
+
+public fun destroy_empty<T>(v: SmartVector<T>) {
+ assert!(is_empty(&v), error::invalid_argument(EVECTOR_NOT_EMPTY));
+ let SmartVector { inline_vec, big_vec, inline_capacity: _, bucket_size: _ } = v;
+ vector::destroy_empty(inline_vec);
+ option::destroy_none(big_vec);
+}
+
+
+
+
+drop
.
+
+
+public fun destroy<T: drop>(v: smart_vector::SmartVector<T>)
+
+
+
+
+public fun destroy<T: drop>(v: SmartVector<T>) {
+ clear(&mut v);
+ destroy_empty(v);
+}
+
+
+
+
+drop
.
+
+
+public fun clear<T: drop>(v: &mut smart_vector::SmartVector<T>)
+
+
+
+
+public fun clear<T: drop>(v: &mut SmartVector<T>) {
+ v.inline_vec = vector[];
+ if (option::is_some(&v.big_vec)) {
+ big_vector::destroy(option::extract(&mut v.big_vec));
+ }
+}
+
+
+
+
+i
th T of the vector v
.
+Aborts if i
is out of bounds.
+
+
+public fun borrow<T>(v: &smart_vector::SmartVector<T>, i: u64): &T
+
+
+
+
+public fun borrow<T>(v: &SmartVector<T>, i: u64): &T {
+ assert!(i < length(v), error::invalid_argument(EINDEX_OUT_OF_BOUNDS));
+ let inline_len = vector::length(&v.inline_vec);
+ if (i < inline_len) {
+ vector::borrow(&v.inline_vec, i)
+ } else {
+ big_vector::borrow(option::borrow(&v.big_vec), i - inline_len)
+ }
+}
+
+
+
+
+i
th T in the vector v
.
+Aborts if i
is out of bounds.
+
+
+public fun borrow_mut<T>(v: &mut smart_vector::SmartVector<T>, i: u64): &mut T
+
+
+
+
+public fun borrow_mut<T>(v: &mut SmartVector<T>, i: u64): &mut T {
+ assert!(i < length(v), error::invalid_argument(EINDEX_OUT_OF_BOUNDS));
+ let inline_len = vector::length(&v.inline_vec);
+ if (i < inline_len) {
+ vector::borrow_mut(&mut v.inline_vec, i)
+ } else {
+ big_vector::borrow_mut(option::borrow_mut(&mut v.big_vec), i - inline_len)
+ }
+}
+
+
+
+
+public fun append<T: store>(lhs: &mut smart_vector::SmartVector<T>, other: smart_vector::SmartVector<T>)
+
+
+
+
+public fun append<T: store>(lhs: &mut SmartVector<T>, other: SmartVector<T>) {
+ let other_len = length(&other);
+ let half_other_len = other_len / 2;
+ let i = 0;
+ while (i < half_other_len) {
+ push_back(lhs, swap_remove(&mut other, i));
+ i = i + 1;
+ };
+ while (i < other_len) {
+ push_back(lhs, pop_back(&mut other));
+ i = i + 1;
+ };
+ destroy_empty(other);
+}
+
+
+
+
+public fun add_all<T: store>(v: &mut smart_vector::SmartVector<T>, vals: vector<T>)
+
+
+
+
+public fun add_all<T: store>(v: &mut SmartVector<T>, vals: vector<T>) {
+ vector::for_each(vals, |val| { push_back(v, val); })
+}
+
+
+
+
+public fun to_vector<T: copy, store>(v: &smart_vector::SmartVector<T>): vector<T>
+
+
+
+
+public fun to_vector<T: store + copy>(v: &SmartVector<T>): vector<T> {
+ let res = v.inline_vec;
+ if (option::is_some(&v.big_vec)) {
+ let big_vec = option::borrow(&v.big_vec);
+ vector::append(&mut res, big_vector::to_vector(big_vec));
+ };
+ res
+}
+
+
+
+
+val
to the end of the vector v
. It grows the buckets when the current buckets are full.
+This operation will cost more gas when it adds new bucket.
+
+
+public fun push_back<T: store>(v: &mut smart_vector::SmartVector<T>, val: T)
+
+
+
+
+public fun push_back<T: store>(v: &mut SmartVector<T>, val: T) {
+ let len = length(v);
+ let inline_len = vector::length(&v.inline_vec);
+ if (len == inline_len) {
+ let bucket_size = if (option::is_some(&v.inline_capacity)) {
+ if (len < *option::borrow(&v.inline_capacity)) {
+ vector::push_back(&mut v.inline_vec, val);
+ return
+ };
+ *option::borrow(&v.bucket_size)
+ } else {
+ let val_size = size_of_val(&val);
+ if (val_size * (inline_len + 1) < 150 /* magic number */) {
+ vector::push_back(&mut v.inline_vec, val);
+ return
+ };
+ let estimated_avg_size = max((size_of_val(&v.inline_vec) + val_size) / (inline_len + 1), 1);
+ max(1024 /* free_write_quota */ / estimated_avg_size, 1)
+ };
+ option::fill(&mut v.big_vec, big_vector::empty(bucket_size));
+ };
+ big_vector::push_back(option::borrow_mut(&mut v.big_vec), val);
+}
+
+
+
+
+v
. It does shrink the buckets if they're empty.
+Aborts if v
is empty.
+
+
+public fun pop_back<T>(v: &mut smart_vector::SmartVector<T>): T
+
+
+
+
+public fun pop_back<T>(v: &mut SmartVector<T>): T {
+ assert!(!is_empty(v), error::invalid_state(EVECTOR_EMPTY));
+ let big_vec_wrapper = &mut v.big_vec;
+ if (option::is_some(big_vec_wrapper)) {
+ let big_vec = option::extract(big_vec_wrapper);
+ let val = big_vector::pop_back(&mut big_vec);
+ if (big_vector::is_empty(&big_vec)) {
+ big_vector::destroy_empty(big_vec)
+ } else {
+ option::fill(big_vec_wrapper, big_vec);
+ };
+ val
+ } else {
+ vector::pop_back(&mut v.inline_vec)
+ }
+}
+
+
+
+
+public fun remove<T>(v: &mut smart_vector::SmartVector<T>, i: u64): T
+
+
+
+
+public fun remove<T>(v: &mut SmartVector<T>, i: u64): T {
+ let len = length(v);
+ assert!(i < len, error::invalid_argument(EINDEX_OUT_OF_BOUNDS));
+ let inline_len = vector::length(&v.inline_vec);
+ if (i < inline_len) {
+ vector::remove(&mut v.inline_vec, i)
+ } else {
+ let big_vec_wrapper = &mut v.big_vec;
+ let big_vec = option::extract(big_vec_wrapper);
+ let val = big_vector::remove(&mut big_vec, i - inline_len);
+ if (big_vector::is_empty(&big_vec)) {
+ big_vector::destroy_empty(big_vec)
+ } else {
+ option::fill(big_vec_wrapper, big_vec);
+ };
+ val
+ }
+}
+
+
+
+
+i
th T of the vector v
with the last T and then pop the vector.
+This is O(1), but does not preserve ordering of Ts in the vector.
+Aborts if i
is out of bounds.
+
+
+public fun swap_remove<T>(v: &mut smart_vector::SmartVector<T>, i: u64): T
+
+
+
+
+public fun swap_remove<T>(v: &mut SmartVector<T>, i: u64): T {
+ let len = length(v);
+ assert!(i < len, error::invalid_argument(EINDEX_OUT_OF_BOUNDS));
+ let inline_len = vector::length(&v.inline_vec);
+ let big_vec_wrapper = &mut v.big_vec;
+ let inline_vec = &mut v.inline_vec;
+ if (i >= inline_len) {
+ let big_vec = option::extract(big_vec_wrapper);
+ let val = big_vector::swap_remove(&mut big_vec, i - inline_len);
+ if (big_vector::is_empty(&big_vec)) {
+ big_vector::destroy_empty(big_vec)
+ } else {
+ option::fill(big_vec_wrapper, big_vec);
+ };
+ val
+ } else {
+ if (inline_len < len) {
+ let big_vec = option::extract(big_vec_wrapper);
+ let last_from_big_vec = big_vector::pop_back(&mut big_vec);
+ if (big_vector::is_empty(&big_vec)) {
+ big_vector::destroy_empty(big_vec)
+ } else {
+ option::fill(big_vec_wrapper, big_vec);
+ };
+ vector::push_back(inline_vec, last_from_big_vec);
+ };
+ vector::swap_remove(inline_vec, i)
+ }
+}
+
+
+
+
+public fun swap<T: store>(v: &mut smart_vector::SmartVector<T>, i: u64, j: u64)
+
+
+
+
+public fun swap<T: store>(v: &mut SmartVector<T>, i: u64, j: u64) {
+ if (i > j) {
+ return swap(v, j, i)
+ };
+ let len = length(v);
+ assert!(j < len, error::invalid_argument(EINDEX_OUT_OF_BOUNDS));
+ let inline_len = vector::length(&v.inline_vec);
+ if (i >= inline_len) {
+ big_vector::swap(option::borrow_mut(&mut v.big_vec), i - inline_len, j - inline_len);
+ } else if (j < inline_len) {
+ vector::swap(&mut v.inline_vec, i, j);
+ } else {
+ let big_vec = option::borrow_mut(&mut v.big_vec);
+ let inline_vec = &mut v.inline_vec;
+ let element_i = vector::swap_remove(inline_vec, i);
+ let element_j = big_vector::swap_remove(big_vec, j - inline_len);
+ vector::push_back(inline_vec, element_j);
+ vector::swap(inline_vec, i, inline_len - 1);
+ big_vector::push_back(big_vec, element_i);
+ big_vector::swap(big_vec, j - inline_len, len - inline_len - 1);
+ }
+}
+
+
+
+
+public fun reverse<T: store>(v: &mut smart_vector::SmartVector<T>)
+
+
+
+
+public fun reverse<T: store>(v: &mut SmartVector<T>) {
+ let inline_len = vector::length(&v.inline_vec);
+ let i = 0;
+ let new_inline_vec = vector[];
+ // Push the last `inline_len` Ts into a temp vector.
+ while (i < inline_len) {
+ vector::push_back(&mut new_inline_vec, pop_back(v));
+ i = i + 1;
+ };
+ vector::reverse(&mut new_inline_vec);
+ // Reverse the big_vector left if exists.
+ if (option::is_some(&v.big_vec)) {
+ big_vector::reverse(option::borrow_mut(&mut v.big_vec));
+ };
+ // Mem::swap the two vectors.
+ let temp_vec = vector[];
+ while (!vector::is_empty(&mut v.inline_vec)) {
+ vector::push_back(&mut temp_vec, vector::pop_back(&mut v.inline_vec));
+ };
+ vector::reverse(&mut temp_vec);
+ while (!vector::is_empty(&mut new_inline_vec)) {
+ vector::push_back(&mut v.inline_vec, vector::pop_back(&mut new_inline_vec));
+ };
+ vector::destroy_empty(new_inline_vec);
+ // Push the rest Ts originally left in inline_vector back to the end of the smart vector.
+ while (!vector::is_empty(&mut temp_vec)) {
+ push_back(v, vector::pop_back(&mut temp_vec));
+ };
+ vector::destroy_empty(temp_vec);
+}
+
+
+
+
+(true, i)
if val
is in the vector v
at index i
.
+Otherwise, returns (false, 0)
.
+Disclaimer: This function may be costly. Use it at your own discretion.
+
+
+public fun index_of<T>(v: &smart_vector::SmartVector<T>, val: &T): (bool, u64)
+
+
+
+
+public fun index_of<T>(v: &SmartVector<T>, val: &T): (bool, u64) {
+ let (found, i) = vector::index_of(&v.inline_vec, val);
+ if (found) {
+ (true, i)
+ } else if (option::is_some(&v.big_vec)) {
+ let (found, i) = big_vector::index_of(option::borrow(&v.big_vec), val);
+ (found, i + vector::length(&v.inline_vec))
+ } else {
+ (false, 0)
+ }
+}
+
+
+
+
+val
is in the vector v
.
+Disclaimer: This function may be costly. Use it at your own discretion.
+
+
+public fun contains<T>(v: &smart_vector::SmartVector<T>, val: &T): bool
+
+
+
+
+public fun contains<T>(v: &SmartVector<T>, val: &T): bool {
+ if (is_empty(v)) return false;
+ let (exist, _) = index_of(v, val);
+ exist
+}
+
+
+
+
+public fun length<T>(v: &smart_vector::SmartVector<T>): u64
+
+
+
+
+public fun length<T>(v: &SmartVector<T>): u64 {
+ vector::length(&v.inline_vec) + if (option::is_none(&v.big_vec)) {
+ 0
+ } else {
+ big_vector::length(option::borrow(&v.big_vec))
+ }
+}
+
+
+
+
+true
if the vector v
has no Ts and false
otherwise.
+
+
+public fun is_empty<T>(v: &smart_vector::SmartVector<T>): bool
+
+
+
+
+public fun is_empty<T>(v: &SmartVector<T>): bool {
+ length(v) == 0
+}
+
+
+
+
+public fun for_each<T: store>(v: smart_vector::SmartVector<T>, f: |T|)
+
+
+
+
+public inline fun for_each<T: store>(v: SmartVector<T>, f: |T|) {
+ aptos_std::smart_vector::reverse(&mut v); // We need to reverse the vector to consume it efficiently
+ aptos_std::smart_vector::for_each_reverse(v, |e| f(e));
+}
+
+
+
+
+public fun for_each_reverse<T>(v: smart_vector::SmartVector<T>, f: |T|)
+
+
+
+
+public inline fun for_each_reverse<T>(v: SmartVector<T>, f: |T|) {
+ let len = aptos_std::smart_vector::length(&v);
+ while (len > 0) {
+ f(aptos_std::smart_vector::pop_back(&mut v));
+ len = len - 1;
+ };
+ aptos_std::smart_vector::destroy_empty(v)
+}
+
+
+
+
+public fun for_each_ref<T>(v: &smart_vector::SmartVector<T>, f: |&T|)
+
+
+
+
+public inline fun for_each_ref<T>(v: &SmartVector<T>, f: |&T|) {
+ let i = 0;
+ let len = aptos_std::smart_vector::length(v);
+ while (i < len) {
+ f(aptos_std::smart_vector::borrow(v, i));
+ i = i + 1
+ }
+}
+
+
+
+
+public fun for_each_mut<T>(v: &mut smart_vector::SmartVector<T>, f: |&mut T|)
+
+
+
+
+public inline fun for_each_mut<T>(v: &mut SmartVector<T>, f: |&mut T|) {
+ let i = 0;
+ let len = aptos_std::smart_vector::length(v);
+ while (i < len) {
+ f(aptos_std::smart_vector::borrow_mut(v, i));
+ i = i + 1
+ }
+}
+
+
+
+
+public fun enumerate_ref<T>(v: &smart_vector::SmartVector<T>, f: |(u64, &T)|)
+
+
+
+
+public inline fun enumerate_ref<T>(v: &SmartVector<T>, f: |u64, &T|) {
+ let i = 0;
+ let len = aptos_std::smart_vector::length(v);
+ while (i < len) {
+ f(i, aptos_std::smart_vector::borrow(v, i));
+ i = i + 1;
+ };
+}
+
+
+
+
+public fun enumerate_mut<T>(v: &mut smart_vector::SmartVector<T>, f: |(u64, &mut T)|)
+
+
+
+
+public inline fun enumerate_mut<T>(v: &mut SmartVector<T>, f: |u64, &mut T|) {
+ let i = 0;
+ let len = length(v);
+ while (i < len) {
+ f(i, borrow_mut(v, i));
+ i = i + 1;
+ };
+}
+
+
+
+
+fold(vector[1,2,3], 0, f)
will execute
+f(f(f(0, 1), 2), 3)
+
+
+public fun fold<Accumulator, T: store>(v: smart_vector::SmartVector<T>, init: Accumulator, f: |(Accumulator, T)|Accumulator): Accumulator
+
+
+
+
+public inline fun fold<Accumulator, T: store>(
+ v: SmartVector<T>,
+ init: Accumulator,
+ f: |Accumulator, T|Accumulator
+): Accumulator {
+ let accu = init;
+ aptos_std::smart_vector::for_each(v, |elem| accu = f(accu, elem));
+ accu
+}
+
+
+
+
+fold(vector[1,2,3], 0, f)
will execute
+f(1, f(2, f(3, 0)))
+
+
+public fun foldr<Accumulator, T>(v: smart_vector::SmartVector<T>, init: Accumulator, f: |(T, Accumulator)|Accumulator): Accumulator
+
+
+
+
+public inline fun foldr<Accumulator, T>(
+ v: SmartVector<T>,
+ init: Accumulator,
+ f: |T, Accumulator|Accumulator
+): Accumulator {
+ let accu = init;
+ aptos_std::smart_vector::for_each_reverse(v, |elem| accu = f(elem, accu));
+ accu
+}
+
+
+
+
+public fun map_ref<T1, T2: store>(v: &smart_vector::SmartVector<T1>, f: |&T1|T2): smart_vector::SmartVector<T2>
+
+
+
+
+public inline fun map_ref<T1, T2: store>(
+ v: &SmartVector<T1>,
+ f: |&T1|T2
+): SmartVector<T2> {
+ let result = aptos_std::smart_vector::new<T2>();
+ aptos_std::smart_vector::for_each_ref(v, |elem| aptos_std::smart_vector::push_back(&mut result, f(elem)));
+ result
+}
+
+
+
+
+public fun map<T1: store, T2: store>(v: smart_vector::SmartVector<T1>, f: |T1|T2): smart_vector::SmartVector<T2>
+
+
+
+
+public inline fun map<T1: store, T2: store>(
+ v: SmartVector<T1>,
+ f: |T1|T2
+): SmartVector<T2> {
+ let result = aptos_std::smart_vector::new<T2>();
+ aptos_std::smart_vector::for_each(v, |elem| push_back(&mut result, f(elem)));
+ result
+}
+
+
+
+
+p(e)
is not true.
+
+
+public fun filter<T: drop, store>(v: smart_vector::SmartVector<T>, p: |&T|bool): smart_vector::SmartVector<T>
+
+
+
+
+public inline fun filter<T: store + drop>(
+ v: SmartVector<T>,
+ p: |&T|bool
+): SmartVector<T> {
+ let result = aptos_std::smart_vector::new<T>();
+ aptos_std::smart_vector::for_each(v, |elem| {
+ if (p(&elem)) aptos_std::smart_vector::push_back(&mut result, elem);
+ });
+ result
+}
+
+
+
+
+public fun zip<T1: store, T2: store>(v1: smart_vector::SmartVector<T1>, v2: smart_vector::SmartVector<T2>, f: |(T1, T2)|)
+
+
+
+
+public inline fun zip<T1: store, T2: store>(v1: SmartVector<T1>, v2: SmartVector<T2>, f: |T1, T2|) {
+ // We need to reverse the vectors to consume it efficiently
+ aptos_std::smart_vector::reverse(&mut v1);
+ aptos_std::smart_vector::reverse(&mut v2);
+ aptos_std::smart_vector::zip_reverse(v1, v2, |e1, e2| f(e1, e2));
+}
+
+
+
+
+public fun zip_reverse<T1, T2>(v1: smart_vector::SmartVector<T1>, v2: smart_vector::SmartVector<T2>, f: |(T1, T2)|)
+
+
+
+
+public inline fun zip_reverse<T1, T2>(
+ v1: SmartVector<T1>,
+ v2: SmartVector<T2>,
+ f: |T1, T2|,
+) {
+ let len = aptos_std::smart_vector::length(&v1);
+ // We can't use the constant ESMART_VECTORS_LENGTH_MISMATCH here as all calling code would then need to define it
+ // due to how inline functions work.
+ assert!(len == aptos_std::smart_vector::length(&v2), 0x20005);
+ while (len > 0) {
+ f(aptos_std::smart_vector::pop_back(&mut v1), aptos_std::smart_vector::pop_back(&mut v2));
+ len = len - 1;
+ };
+ aptos_std::smart_vector::destroy_empty(v1);
+ aptos_std::smart_vector::destroy_empty(v2);
+}
+
+
+
+
+public fun zip_ref<T1, T2>(v1: &smart_vector::SmartVector<T1>, v2: &smart_vector::SmartVector<T2>, f: |(&T1, &T2)|)
+
+
+
+
+public inline fun zip_ref<T1, T2>(
+ v1: &SmartVector<T1>,
+ v2: &SmartVector<T2>,
+ f: |&T1, &T2|,
+) {
+ let len = aptos_std::smart_vector::length(v1);
+ // We can't use the constant ESMART_VECTORS_LENGTH_MISMATCH here as all calling code would then need to define it
+ // due to how inline functions work.
+ assert!(len == aptos_std::smart_vector::length(v2), 0x20005);
+ let i = 0;
+ while (i < len) {
+ f(aptos_std::smart_vector::borrow(v1, i), aptos_std::smart_vector::borrow(v2, i));
+ i = i + 1
+ }
+}
+
+
+
+
+public fun zip_mut<T1, T2>(v1: &mut smart_vector::SmartVector<T1>, v2: &mut smart_vector::SmartVector<T2>, f: |(&mut T1, &mut T2)|)
+
+
+
+
+public inline fun zip_mut<T1, T2>(
+ v1: &mut SmartVector<T1>,
+ v2: &mut SmartVector<T2>,
+ f: |&mut T1, &mut T2|,
+) {
+ let i = 0;
+ let len = aptos_std::smart_vector::length(v1);
+ // We can't use the constant ESMART_VECTORS_LENGTH_MISMATCH here as all calling code would then need to define it
+ // due to how inline functions work.
+ assert!(len == aptos_std::smart_vector::length(v2), 0x20005);
+ while (i < len) {
+ f(aptos_std::smart_vector::borrow_mut(v1, i), aptos_std::smart_vector::borrow_mut(v2, i));
+ i = i + 1
+ }
+}
+
+
+
+
+public fun zip_map<T1: store, T2: store, NewT: store>(v1: smart_vector::SmartVector<T1>, v2: smart_vector::SmartVector<T2>, f: |(T1, T2)|NewT): smart_vector::SmartVector<NewT>
+
+
+
+
+public inline fun zip_map<T1: store, T2: store, NewT: store>(
+ v1: SmartVector<T1>,
+ v2: SmartVector<T2>,
+ f: |T1, T2|NewT
+): SmartVector<NewT> {
+ // We can't use the constant ESMART_VECTORS_LENGTH_MISMATCH here as all calling code would then need to define it
+ // due to how inline functions work.
+ assert!(aptos_std::smart_vector::length(&v1) == aptos_std::smart_vector::length(&v2), 0x20005);
+
+ let result = aptos_std::smart_vector::new<NewT>();
+ aptos_std::smart_vector::zip(v1, v2, |e1, e2| push_back(&mut result, f(e1, e2)));
+ result
+}
+
+
+
+
+public fun zip_map_ref<T1, T2, NewT: store>(v1: &smart_vector::SmartVector<T1>, v2: &smart_vector::SmartVector<T2>, f: |(&T1, &T2)|NewT): smart_vector::SmartVector<NewT>
+
+
+
+
+public inline fun zip_map_ref<T1, T2, NewT: store>(
+ v1: &SmartVector<T1>,
+ v2: &SmartVector<T2>,
+ f: |&T1, &T2|NewT
+): SmartVector<NewT> {
+ // We can't use the constant ESMART_VECTORS_LENGTH_MISMATCH here as all calling code would then need to define it
+ // due to how inline functions work.
+ assert!(aptos_std::smart_vector::length(v1) == aptos_std::smart_vector::length(v2), 0x20005);
+
+ let result = aptos_std::smart_vector::new<NewT>();
+ aptos_std::smart_vector::zip_ref(v1, v2, |e1, e2| push_back(&mut result, f(e1, e2)));
+ result
+}
+
+
+
+
+struct SmartVector<T> has store
+
+
+
+
+inline_vec: vector<T>
+big_vec: option::Option<big_vector::BigVector<T>>
+inline_capacity: option::Option<u64>
+bucket_size: option::Option<u64>
+invariant option::is_none(bucket_size)
+ || (option::is_some(bucket_size) && option::borrow(bucket_size) != 0);
+invariant option::is_none(inline_capacity)
+ || (len(inline_vec) <= option::borrow(inline_capacity));
+invariant (option::is_none(inline_capacity) && option::is_none(bucket_size))
+ || (option::is_some(inline_capacity) && option::is_some(bucket_size));
+
+
+
+
+
+
+### Function `empty`
+
+
+#[deprecated]
+public fun empty<T: store>(): smart_vector::SmartVector<T>
+
+
+
+
+
+aborts_if false;
+
+
+
+
+
+
+### Function `empty_with_config`
+
+
+public fun empty_with_config<T: store>(inline_capacity: u64, bucket_size: u64): smart_vector::SmartVector<T>
+
+
+
+
+
+aborts_if bucket_size == 0;
+
+
+
+
+
+
+### Function `destroy_empty`
+
+
+public fun destroy_empty<T>(v: smart_vector::SmartVector<T>)
+
+
+
+
+
+aborts_if !(is_empty(v));
+aborts_if len(v.inline_vec) != 0
+ || option::is_some(v.big_vec);
+
+
+
+
+
+
+### Function `borrow`
+
+
+public fun borrow<T>(v: &smart_vector::SmartVector<T>, i: u64): &T
+
+
+
+
+
+aborts_if i >= length(v);
+aborts_if option::is_some(v.big_vec) && (
+ (len(v.inline_vec) + big_vector::length<T>(option::borrow(v.big_vec))) > MAX_U64
+);
+
+
+
+
+
+
+### Function `append`
+
+
+public fun append<T: store>(lhs: &mut smart_vector::SmartVector<T>, other: smart_vector::SmartVector<T>)
+
+
+
+
+
+pragma verify = false;
+
+
+
+
+
+
+### Function `push_back`
+
+
+public fun push_back<T: store>(v: &mut smart_vector::SmartVector<T>, val: T)
+
+
+
+
+
+pragma verify = false;
+
+
+
+
+
+
+### Function `pop_back`
+
+
+public fun pop_back<T>(v: &mut smart_vector::SmartVector<T>): T
+
+
+
+
+
+pragma verify_duration_estimate = 120;
+aborts_if option::is_some(v.big_vec)
+ &&
+ (table_with_length::spec_len(option::borrow(v.big_vec).buckets) == 0);
+aborts_if is_empty(v);
+aborts_if option::is_some(v.big_vec) && (
+ (len(v.inline_vec) + big_vector::length<T>(option::borrow(v.big_vec))) > MAX_U64
+);
+ensures length(v) == length(old(v)) - 1;
+
+
+
+
+
+
+### Function `remove`
+
+
+public fun remove<T>(v: &mut smart_vector::SmartVector<T>, i: u64): T
+
+
+
+
+
+pragma verify = false;
+
+
+
+
+
+
+### Function `swap_remove`
+
+
+public fun swap_remove<T>(v: &mut smart_vector::SmartVector<T>, i: u64): T
+
+
+
+
+
+pragma verify = false;
+aborts_if i >= length(v);
+aborts_if option::is_some(v.big_vec) && (
+ (len(v.inline_vec) + big_vector::length<T>(option::borrow(v.big_vec))) > MAX_U64
+);
+ensures length(v) == length(old(v)) - 1;
+
+
+
+
+
+
+### Function `swap`
+
+
+public fun swap<T: store>(v: &mut smart_vector::SmartVector<T>, i: u64, j: u64)
+
+
+
+
+
+pragma verify = false;
+
+
+
+
+
+
+### Function `length`
+
+
+public fun length<T>(v: &smart_vector::SmartVector<T>): u64
+
+
+
+
+
+aborts_if option::is_some(v.big_vec) && len(v.inline_vec) + big_vector::length(option::spec_borrow(v.big_vec)) > MAX_U64;
+
+
+
+[move-book]: https://aptos.dev/move/book/SUMMARY
diff --git a/aptos-move/framework/aptos-stdlib/tests/compiler-v2-doc/string_utils.md b/aptos-move/framework/aptos-stdlib/tests/compiler-v2-doc/string_utils.md
new file mode 100644
index 0000000000000..396d1c84fb50c
--- /dev/null
+++ b/aptos-move/framework/aptos-stdlib/tests/compiler-v2-doc/string_utils.md
@@ -0,0 +1,763 @@
+
+
+
+# Module `0x1::string_utils`
+
+A module for formatting move values as strings.
+
+
+- [Struct `Cons`](#0x1_string_utils_Cons)
+- [Struct `NIL`](#0x1_string_utils_NIL)
+- [Struct `FakeCons`](#0x1_string_utils_FakeCons)
+ - [[test_only]](#@[test_only]_0)
+- [Constants](#@Constants_1)
+- [Function `to_string`](#0x1_string_utils_to_string)
+- [Function `to_string_with_canonical_addresses`](#0x1_string_utils_to_string_with_canonical_addresses)
+- [Function `to_string_with_integer_types`](#0x1_string_utils_to_string_with_integer_types)
+- [Function `debug_string`](#0x1_string_utils_debug_string)
+- [Function `format1`](#0x1_string_utils_format1)
+- [Function `format2`](#0x1_string_utils_format2)
+- [Function `format3`](#0x1_string_utils_format3)
+- [Function `format4`](#0x1_string_utils_format4)
+- [Function `cons`](#0x1_string_utils_cons)
+- [Function `nil`](#0x1_string_utils_nil)
+- [Function `list1`](#0x1_string_utils_list1)
+- [Function `list2`](#0x1_string_utils_list2)
+- [Function `list3`](#0x1_string_utils_list3)
+- [Function `list4`](#0x1_string_utils_list4)
+- [Function `native_format`](#0x1_string_utils_native_format)
+- [Function `native_format_list`](#0x1_string_utils_native_format_list)
+- [Specification](#@Specification_2)
+ - [Function `to_string`](#@Specification_2_to_string)
+ - [Function `to_string_with_canonical_addresses`](#@Specification_2_to_string_with_canonical_addresses)
+ - [Function `to_string_with_integer_types`](#@Specification_2_to_string_with_integer_types)
+ - [Function `debug_string`](#@Specification_2_debug_string)
+ - [Function `format1`](#@Specification_2_format1)
+ - [Function `format2`](#@Specification_2_format2)
+ - [Function `format3`](#@Specification_2_format3)
+ - [Function `format4`](#@Specification_2_format4)
+ - [Function `native_format`](#@Specification_2_native_format)
+ - [Function `native_format_list`](#@Specification_2_native_format_list)
+
+
+use 0x1::string;
+
+
+
+
+
+
+## Struct `Cons`
+
+
+
+struct Cons<T, N> has copy, drop, store
+
+
+
+
+car: T
+cdr: N
+struct NIL has copy, drop, store
+
+
+
+
+dummy_field: bool
+struct FakeCons<T, N> has copy, drop, store
+
+
+
+
+car: T
+cdr: N
+const EARGS_MISMATCH: u64 = 1;
+
+
+
+
+
+
+The format string is not valid.
+
+
+const EINVALID_FORMAT: u64 = 2;
+
+
+
+
+
+
+Formatting is not possible because the value contains delayed fields such as aggregators.
+
+
+const EUNABLE_TO_FORMAT_DELAYED_FIELD: u64 = 3;
+
+
+
+
+
+
+## Function `to_string`
+
+Format a move value as a human readable string,
+eg. to_string(&1u64) == "1"
, to_string(&false) == "false"
, to_string(&@0x1) == "@0x1"
.
+For vectors and structs the format is similar to rust, eg.
+to_string(&cons(1,2)) == "Cons { car: 1, cdr: 2 }"
and to_string(&vector[1, 2, 3]) == "[ 1, 2, 3 ]"
+For vectors of u8 the output is hex encoded, eg. to_string(&vector[1u8, 2u8, 3u8]) == "0x010203"
+For std::string::String the output is the string itself including quotes, eg.
+to_string(&std::string::utf8(b"My string")) == "\"My string\""
+
+
+public fun to_string<T>(s: &T): string::String
+
+
+
+
+public fun to_string<T>(s: &T): String {
+ native_format(s, false, false, true, false)
+}
+
+
+
+
+public fun to_string_with_canonical_addresses<T>(s: &T): string::String
+
+
+
+
+public fun to_string_with_canonical_addresses<T>(s: &T): String {
+ native_format(s, false, true, true, false)
+}
+
+
+
+
+public fun to_string_with_integer_types<T>(s: &T): string::String
+
+
+
+
+public fun to_string_with_integer_types<T>(s: &T): String {
+ native_format(s, false, true, true, false)
+}
+
+
+
+
+public fun debug_string<T>(s: &T): string::String
+
+
+
+
+public fun debug_string<T>(s: &T): String {
+ native_format(s, true, false, false, false)
+}
+
+
+
+
+format2(&b"a = {}, b = {}", 1, 2) == "a = 1, b = 2"
.
+
+
+public fun format1<T0: drop>(fmt: &vector<u8>, a: T0): string::String
+
+
+
+
+public fun format1<T0: drop>(fmt: &vector<u8>, a: T0): String {
+ native_format_list(fmt, &list1(a))
+}
+
+
+
+
+public fun format2<T0: drop, T1: drop>(fmt: &vector<u8>, a: T0, b: T1): string::String
+
+
+
+
+public fun format2<T0: drop, T1: drop>(fmt: &vector<u8>, a: T0, b: T1): String {
+ native_format_list(fmt, &list2(a, b))
+}
+
+
+
+
+public fun format3<T0: drop, T1: drop, T2: drop>(fmt: &vector<u8>, a: T0, b: T1, c: T2): string::String
+
+
+
+
+public fun format3<T0: drop, T1: drop, T2: drop>(fmt: &vector<u8>, a: T0, b: T1, c: T2): String {
+ native_format_list(fmt, &list3(a, b, c))
+}
+
+
+
+
+public fun format4<T0: drop, T1: drop, T2: drop, T3: drop>(fmt: &vector<u8>, a: T0, b: T1, c: T2, d: T3): string::String
+
+
+
+
+public fun format4<T0: drop, T1: drop, T2: drop, T3: drop>(fmt: &vector<u8>, a: T0, b: T1, c: T2, d: T3): String {
+ native_format_list(fmt, &list4(a, b, c, d))
+}
+
+
+
+
+fun cons<T, N>(car: T, cdr: N): string_utils::Cons<T, N>
+
+
+
+
+fun cons<T, N>(car: T, cdr: N): Cons<T, N> { Cons { car, cdr } }
+
+
+
+
+fun nil(): string_utils::NIL
+
+
+
+
+fun nil(): NIL { NIL {} }
+
+
+
+
+fun list1<T0>(a: T0): string_utils::Cons<T0, string_utils::NIL>
+
+
+
+
+inline fun list1<T0>(a: T0): Cons<T0, NIL> { cons(a, nil()) }
+
+
+
+
+fun list2<T0, T1>(a: T0, b: T1): string_utils::Cons<T0, string_utils::Cons<T1, string_utils::NIL>>
+
+
+
+
+inline fun list2<T0, T1>(a: T0, b: T1): Cons<T0, Cons<T1, NIL>> { cons(a, list1(b)) }
+
+
+
+
+fun list3<T0, T1, T2>(a: T0, b: T1, c: T2): string_utils::Cons<T0, string_utils::Cons<T1, string_utils::Cons<T2, string_utils::NIL>>>
+
+
+
+
+inline fun list3<T0, T1, T2>(a: T0, b: T1, c: T2): Cons<T0, Cons<T1, Cons<T2, NIL>>> { cons(a, list2(b, c)) }
+
+
+
+
+fun list4<T0, T1, T2, T3>(a: T0, b: T1, c: T2, d: T3): string_utils::Cons<T0, string_utils::Cons<T1, string_utils::Cons<T2, string_utils::Cons<T3, string_utils::NIL>>>>
+
+
+
+
+inline fun list4<T0, T1, T2, T3>(a: T0, b: T1, c: T2, d: T3): Cons<T0, Cons<T1, Cons<T2, Cons<T3, NIL>>>> { cons(a, list3(b, c, d)) }
+
+
+
+
+fun native_format<T>(s: &T, type_tag: bool, canonicalize: bool, single_line: bool, include_int_types: bool): string::String
+
+
+
+
+native fun native_format<T>(s: &T, type_tag: bool, canonicalize: bool, single_line: bool, include_int_types: bool): String;
+
+
+
+
+fun native_format_list<T>(fmt: &vector<u8>, val: &T): string::String
+
+
+
+
+native fun native_format_list<T>(fmt: &vector<u8>, val: &T): String;
+
+
+
+
+public fun to_string<T>(s: &T): string::String
+
+
+
+
+
+aborts_if false;
+ensures result == spec_native_format(s, false, false, true, false);
+
+
+
+
+
+
+### Function `to_string_with_canonical_addresses`
+
+
+public fun to_string_with_canonical_addresses<T>(s: &T): string::String
+
+
+
+
+
+aborts_if false;
+ensures result == spec_native_format(s, false, true, true, false);
+
+
+
+
+
+
+### Function `to_string_with_integer_types`
+
+
+public fun to_string_with_integer_types<T>(s: &T): string::String
+
+
+
+
+
+aborts_if false;
+ensures result == spec_native_format(s, false, true, true, false);
+
+
+
+
+
+
+### Function `debug_string`
+
+
+public fun debug_string<T>(s: &T): string::String
+
+
+
+
+
+aborts_if false;
+ensures result == spec_native_format(s, true, false, false, false);
+
+
+
+
+
+
+### Function `format1`
+
+
+public fun format1<T0: drop>(fmt: &vector<u8>, a: T0): string::String
+
+
+
+
+
+aborts_if args_mismatch_or_invalid_format(fmt, list1(a));
+ensures result == spec_native_format_list(fmt, list1(a));
+
+
+
+
+
+
+### Function `format2`
+
+
+public fun format2<T0: drop, T1: drop>(fmt: &vector<u8>, a: T0, b: T1): string::String
+
+
+
+
+
+aborts_if args_mismatch_or_invalid_format(fmt, list2(a, b));
+ensures result == spec_native_format_list(fmt, list2(a, b));
+
+
+
+
+
+
+### Function `format3`
+
+
+public fun format3<T0: drop, T1: drop, T2: drop>(fmt: &vector<u8>, a: T0, b: T1, c: T2): string::String
+
+
+
+
+
+aborts_if args_mismatch_or_invalid_format(fmt, list3(a, b, c));
+ensures result == spec_native_format_list(fmt, list3(a, b, c));
+
+
+
+
+
+
+### Function `format4`
+
+
+public fun format4<T0: drop, T1: drop, T2: drop, T3: drop>(fmt: &vector<u8>, a: T0, b: T1, c: T2, d: T3): string::String
+
+
+
+
+
+aborts_if args_mismatch_or_invalid_format(fmt, list4(a, b, c, d));
+ensures result == spec_native_format_list(fmt, list4(a, b, c, d));
+
+
+
+
+
+
+### Function `native_format`
+
+
+fun native_format<T>(s: &T, type_tag: bool, canonicalize: bool, single_line: bool, include_int_types: bool): string::String
+
+
+
+
+
+pragma opaque;
+aborts_if false;
+ensures result == spec_native_format(s, type_tag, canonicalize, single_line, include_int_types);
+
+
+
+
+
+
+### Function `native_format_list`
+
+
+fun native_format_list<T>(fmt: &vector<u8>, val: &T): string::String
+
+
+
+
+
+pragma opaque;
+aborts_if args_mismatch_or_invalid_format(fmt, val);
+ensures result == spec_native_format_list(fmt, val);
+
+
+
+
+
+
+
+
+fun spec_native_format<T>(s: T, type_tag: bool, canonicalize: bool, single_line: bool, include_int_types: bool): String;
+
+
+
+
+
+
+
+
+fun spec_native_format_list<T>(fmt: vector<u8>, val: T): String;
+
+
+
+
+
+
+
+
+fun args_mismatch_or_invalid_format<T>(fmt: vector<u8>, val: T): bool;
+
+
+
+[move-book]: https://aptos.dev/move/book/SUMMARY
diff --git a/aptos-move/framework/aptos-stdlib/tests/compiler-v2-doc/table.md b/aptos-move/framework/aptos-stdlib/tests/compiler-v2-doc/table.md
new file mode 100644
index 0000000000000..d05ea82adb590
--- /dev/null
+++ b/aptos-move/framework/aptos-stdlib/tests/compiler-v2-doc/table.md
@@ -0,0 +1,779 @@
+
+
+
+# Module `0x1::table`
+
+Type of large-scale storage tables.
+source: https://github.com/move-language/move/blob/1b6b7513dcc1a5c866f178ca5c1e74beb2ce181e/language/extensions/move-table-extension/sources/Table.move#L1
+
+It implements the Table type which supports individual table items to be represented by
+separate global state items. The number of items and a unique handle are tracked on the table
+struct itself, while the operations are implemented as native functions. No traversal is provided.
+
+
+- [Struct `Table`](#0x1_table_Table)
+- [Resource `Box`](#0x1_table_Box)
+- [Function `new`](#0x1_table_new)
+- [Function `add`](#0x1_table_add)
+- [Function `borrow`](#0x1_table_borrow)
+- [Function `borrow_with_default`](#0x1_table_borrow_with_default)
+- [Function `borrow_mut`](#0x1_table_borrow_mut)
+- [Function `borrow_mut_with_default`](#0x1_table_borrow_mut_with_default)
+- [Function `upsert`](#0x1_table_upsert)
+- [Function `remove`](#0x1_table_remove)
+- [Function `contains`](#0x1_table_contains)
+- [Function `destroy`](#0x1_table_destroy)
+- [Function `new_table_handle`](#0x1_table_new_table_handle)
+- [Function `add_box`](#0x1_table_add_box)
+- [Function `borrow_box`](#0x1_table_borrow_box)
+- [Function `borrow_box_mut`](#0x1_table_borrow_box_mut)
+- [Function `contains_box`](#0x1_table_contains_box)
+- [Function `remove_box`](#0x1_table_remove_box)
+- [Function `destroy_empty_box`](#0x1_table_destroy_empty_box)
+- [Function `drop_unchecked_box`](#0x1_table_drop_unchecked_box)
+- [Specification](#@Specification_0)
+ - [Struct `Table`](#@Specification_0_Table)
+ - [Function `new`](#@Specification_0_new)
+ - [Function `add`](#@Specification_0_add)
+ - [Function `borrow`](#@Specification_0_borrow)
+ - [Function `borrow_mut`](#@Specification_0_borrow_mut)
+ - [Function `borrow_mut_with_default`](#@Specification_0_borrow_mut_with_default)
+ - [Function `upsert`](#@Specification_0_upsert)
+ - [Function `remove`](#@Specification_0_remove)
+ - [Function `contains`](#@Specification_0_contains)
+ - [Function `destroy`](#@Specification_0_destroy)
+
+
+
+
+
+
+
+
+## Struct `Table`
+
+Type of tables
+
+
+struct Table<K: copy, drop, V> has store
+
+
+
+
+handle: address
+struct Box<V> has drop, store, key
+
+
+
+
+val: V
+public fun new<K: copy, drop, V: store>(): table::Table<K, V>
+
+
+
+
+public fun new<K: copy + drop, V: store>(): Table<K, V> {
+ Table {
+ handle: new_table_handle<K, V>(),
+ }
+}
+
+
+
+
+public fun add<K: copy, drop, V>(table: &mut table::Table<K, V>, key: K, val: V)
+
+
+
+
+public fun add<K: copy + drop, V>(table: &mut Table<K, V>, key: K, val: V) {
+ add_box<K, V, Box<V>>(table, key, Box { val })
+}
+
+
+
+
+key
maps to.
+Aborts if there is no entry for key
.
+
+
+public fun borrow<K: copy, drop, V>(table: &table::Table<K, V>, key: K): &V
+
+
+
+
+public fun borrow<K: copy + drop, V>(table: &Table<K, V>, key: K): &V {
+ &borrow_box<K, V, Box<V>>(table, key).val
+}
+
+
+
+
+key
maps to.
+Returns specified default value if there is no entry for key
.
+
+
+public fun borrow_with_default<K: copy, drop, V>(table: &table::Table<K, V>, key: K, default: &V): &V
+
+
+
+
+public fun borrow_with_default<K: copy + drop, V>(table: &Table<K, V>, key: K, default: &V): &V {
+ if (!contains(table, copy key)) {
+ default
+ } else {
+ borrow(table, copy key)
+ }
+}
+
+
+
+
+key
maps to.
+Aborts if there is no entry for key
.
+
+
+public fun borrow_mut<K: copy, drop, V>(table: &mut table::Table<K, V>, key: K): &mut V
+
+
+
+
+public fun borrow_mut<K: copy + drop, V>(table: &mut Table<K, V>, key: K): &mut V {
+ &mut borrow_box_mut<K, V, Box<V>>(table, key).val
+}
+
+
+
+
+key
maps to.
+Insert the pair (key
, default
) first if there is no entry for key
.
+
+
+public fun borrow_mut_with_default<K: copy, drop, V: drop>(table: &mut table::Table<K, V>, key: K, default: V): &mut V
+
+
+
+
+public fun borrow_mut_with_default<K: copy + drop, V: drop>(table: &mut Table<K, V>, key: K, default: V): &mut V {
+ if (!contains(table, copy key)) {
+ add(table, copy key, default)
+ };
+ borrow_mut(table, key)
+}
+
+
+
+
+key
, value
) if there is no entry for key
.
+update the value of the entry for key
to value
otherwise
+
+
+public fun upsert<K: copy, drop, V: drop>(table: &mut table::Table<K, V>, key: K, value: V)
+
+
+
+
+public fun upsert<K: copy + drop, V: drop>(table: &mut Table<K, V>, key: K, value: V) {
+ if (!contains(table, copy key)) {
+ add(table, copy key, value)
+ } else {
+ let ref = borrow_mut(table, key);
+ *ref = value;
+ };
+}
+
+
+
+
+table
and return the value which key
maps to.
+Aborts if there is no entry for key
.
+
+
+public fun remove<K: copy, drop, V>(table: &mut table::Table<K, V>, key: K): V
+
+
+
+
+public fun remove<K: copy + drop, V>(table: &mut Table<K, V>, key: K): V {
+ let Box { val } = remove_box<K, V, Box<V>>(table, key);
+ val
+}
+
+
+
+
+table
contains an entry for key
.
+
+
+public fun contains<K: copy, drop, V>(table: &table::Table<K, V>, key: K): bool
+
+
+
+
+public fun contains<K: copy + drop, V>(table: &Table<K, V>, key: K): bool {
+ contains_box<K, V, Box<V>>(table, key)
+}
+
+
+
+
+public(friend) fun destroy<K: copy, drop, V>(table: table::Table<K, V>)
+
+
+
+
+public(friend) fun destroy<K: copy + drop, V>(table: Table<K, V>) {
+ destroy_empty_box<K, V, Box<V>>(&table);
+ drop_unchecked_box<K, V, Box<V>>(table)
+}
+
+
+
+
+fun new_table_handle<K, V>(): address
+
+
+
+
+native fun new_table_handle<K, V>(): address;
+
+
+
+
+fun add_box<K: copy, drop, V, B>(table: &mut table::Table<K, V>, key: K, val: table::Box<V>)
+
+
+
+
+native fun add_box<K: copy + drop, V, B>(table: &mut Table<K, V>, key: K, val: Box<V>);
+
+
+
+
+fun borrow_box<K: copy, drop, V, B>(table: &table::Table<K, V>, key: K): &table::Box<V>
+
+
+
+
+native fun borrow_box<K: copy + drop, V, B>(table: &Table<K, V>, key: K): &Box<V>;
+
+
+
+
+fun borrow_box_mut<K: copy, drop, V, B>(table: &mut table::Table<K, V>, key: K): &mut table::Box<V>
+
+
+
+
+native fun borrow_box_mut<K: copy + drop, V, B>(table: &mut Table<K, V>, key: K): &mut Box<V>;
+
+
+
+
+fun contains_box<K: copy, drop, V, B>(table: &table::Table<K, V>, key: K): bool
+
+
+
+
+native fun contains_box<K: copy + drop, V, B>(table: &Table<K, V>, key: K): bool;
+
+
+
+
+fun remove_box<K: copy, drop, V, B>(table: &mut table::Table<K, V>, key: K): table::Box<V>
+
+
+
+
+native fun remove_box<K: copy + drop, V, B>(table: &mut Table<K, V>, key: K): Box<V>;
+
+
+
+
+fun destroy_empty_box<K: copy, drop, V, B>(table: &table::Table<K, V>)
+
+
+
+
+native fun destroy_empty_box<K: copy + drop, V, B>(table: &Table<K, V>);
+
+
+
+
+fun drop_unchecked_box<K: copy, drop, V, B>(table: table::Table<K, V>)
+
+
+
+
+native fun drop_unchecked_box<K: copy + drop, V, B>(table: Table<K, V>);
+
+
+
+
+struct Table<K: copy, drop, V> has store
+
+
+
+
+handle: address
+pragma intrinsic = map,
+ map_new = new,
+ map_destroy_empty = destroy,
+ map_has_key = contains,
+ map_add_no_override = add,
+ map_add_override_if_exists = upsert,
+ map_del_must_exist = remove,
+ map_borrow = borrow,
+ map_borrow_mut = borrow_mut,
+ map_borrow_mut_with_default = borrow_mut_with_default,
+ map_spec_get = spec_get,
+ map_spec_set = spec_set,
+ map_spec_del = spec_remove,
+ map_spec_has_key = spec_contains;
+
+
+
+
+
+
+### Function `new`
+
+
+public fun new<K: copy, drop, V: store>(): table::Table<K, V>
+
+
+
+
+
+pragma intrinsic;
+
+
+
+
+
+
+### Function `add`
+
+
+public fun add<K: copy, drop, V>(table: &mut table::Table<K, V>, key: K, val: V)
+
+
+
+
+
+pragma intrinsic;
+
+
+
+
+
+
+### Function `borrow`
+
+
+public fun borrow<K: copy, drop, V>(table: &table::Table<K, V>, key: K): &V
+
+
+
+
+
+pragma intrinsic;
+
+
+
+
+
+
+### Function `borrow_mut`
+
+
+public fun borrow_mut<K: copy, drop, V>(table: &mut table::Table<K, V>, key: K): &mut V
+
+
+
+
+
+pragma intrinsic;
+
+
+
+
+
+
+### Function `borrow_mut_with_default`
+
+
+public fun borrow_mut_with_default<K: copy, drop, V: drop>(table: &mut table::Table<K, V>, key: K, default: V): &mut V
+
+
+
+
+
+pragma intrinsic;
+
+
+
+
+
+
+### Function `upsert`
+
+
+public fun upsert<K: copy, drop, V: drop>(table: &mut table::Table<K, V>, key: K, value: V)
+
+
+
+
+
+pragma intrinsic;
+
+
+
+
+
+
+### Function `remove`
+
+
+public fun remove<K: copy, drop, V>(table: &mut table::Table<K, V>, key: K): V
+
+
+
+
+
+pragma intrinsic;
+
+
+
+
+
+
+### Function `contains`
+
+
+public fun contains<K: copy, drop, V>(table: &table::Table<K, V>, key: K): bool
+
+
+
+
+
+pragma intrinsic;
+
+
+
+
+
+
+
+
+native fun spec_contains<K, V>(t: Table<K, V>, k: K): bool;
+
+
+
+
+
+
+
+
+native fun spec_remove<K, V>(t: Table<K, V>, k: K): Table<K, V>;
+
+
+
+
+
+
+
+
+native fun spec_set<K, V>(t: Table<K, V>, k: K, v: V): Table<K, V>;
+
+
+
+
+
+
+
+
+native fun spec_get<K, V>(t: Table<K, V>, k: K): V;
+
+
+
+
+
+
+### Function `destroy`
+
+
+public(friend) fun destroy<K: copy, drop, V>(table: table::Table<K, V>)
+
+
+
+
+
+pragma intrinsic;
+
+
+
+[move-book]: https://aptos.dev/move/book/SUMMARY
diff --git a/aptos-move/framework/aptos-stdlib/tests/compiler-v2-doc/table_with_length.md b/aptos-move/framework/aptos-stdlib/tests/compiler-v2-doc/table_with_length.md
new file mode 100644
index 0000000000000..733165ad6807f
--- /dev/null
+++ b/aptos-move/framework/aptos-stdlib/tests/compiler-v2-doc/table_with_length.md
@@ -0,0 +1,684 @@
+
+
+
+# Module `0x1::table_with_length`
+
+Extends Table and provides functions such as length and the ability to be destroyed
+
+
+- [Struct `TableWithLength`](#0x1_table_with_length_TableWithLength)
+- [Constants](#@Constants_0)
+- [Function `new`](#0x1_table_with_length_new)
+- [Function `destroy_empty`](#0x1_table_with_length_destroy_empty)
+- [Function `add`](#0x1_table_with_length_add)
+- [Function `borrow`](#0x1_table_with_length_borrow)
+- [Function `borrow_mut`](#0x1_table_with_length_borrow_mut)
+- [Function `length`](#0x1_table_with_length_length)
+- [Function `empty`](#0x1_table_with_length_empty)
+- [Function `borrow_mut_with_default`](#0x1_table_with_length_borrow_mut_with_default)
+- [Function `upsert`](#0x1_table_with_length_upsert)
+- [Function `remove`](#0x1_table_with_length_remove)
+- [Function `contains`](#0x1_table_with_length_contains)
+- [Specification](#@Specification_1)
+ - [Struct `TableWithLength`](#@Specification_1_TableWithLength)
+ - [Function `new`](#@Specification_1_new)
+ - [Function `destroy_empty`](#@Specification_1_destroy_empty)
+ - [Function `add`](#@Specification_1_add)
+ - [Function `borrow`](#@Specification_1_borrow)
+ - [Function `borrow_mut`](#@Specification_1_borrow_mut)
+ - [Function `length`](#@Specification_1_length)
+ - [Function `empty`](#@Specification_1_empty)
+ - [Function `borrow_mut_with_default`](#@Specification_1_borrow_mut_with_default)
+ - [Function `upsert`](#@Specification_1_upsert)
+ - [Function `remove`](#@Specification_1_remove)
+ - [Function `contains`](#@Specification_1_contains)
+
+
+use 0x1::error;
+use 0x1::table;
+
+
+
+
+
+
+## Struct `TableWithLength`
+
+Type of tables
+
+
+struct TableWithLength<K: copy, drop, V> has store
+
+
+
+
+inner: table::Table<K, V>
+length: u64
+const EALREADY_EXISTS: u64 = 100;
+
+
+
+
+
+
+
+
+const ENOT_EMPTY: u64 = 102;
+
+
+
+
+
+
+
+
+const ENOT_FOUND: u64 = 101;
+
+
+
+
+
+
+## Function `new`
+
+Create a new Table.
+
+
+public fun new<K: copy, drop, V: store>(): table_with_length::TableWithLength<K, V>
+
+
+
+
+public fun new<K: copy + drop, V: store>(): TableWithLength<K, V> {
+ TableWithLength {
+ inner: table::new<K, V>(),
+ length: 0,
+ }
+}
+
+
+
+
+public fun destroy_empty<K: copy, drop, V>(table: table_with_length::TableWithLength<K, V>)
+
+
+
+
+public fun destroy_empty<K: copy + drop, V>(table: TableWithLength<K, V>) {
+ assert!(table.length == 0, error::invalid_state(ENOT_EMPTY));
+ let TableWithLength { inner, length: _ } = table;
+ table::destroy(inner)
+}
+
+
+
+
+public fun add<K: copy, drop, V>(table: &mut table_with_length::TableWithLength<K, V>, key: K, val: V)
+
+
+
+
+public fun add<K: copy + drop, V>(table: &mut TableWithLength<K, V>, key: K, val: V) {
+ table::add(&mut table.inner, key, val);
+ table.length = table.length + 1;
+}
+
+
+
+
+key
maps to.
+Aborts if there is no entry for key
.
+
+
+public fun borrow<K: copy, drop, V>(table: &table_with_length::TableWithLength<K, V>, key: K): &V
+
+
+
+
+public fun borrow<K: copy + drop, V>(table: &TableWithLength<K, V>, key: K): &V {
+ table::borrow(&table.inner, key)
+}
+
+
+
+
+key
maps to.
+Aborts if there is no entry for key
.
+
+
+public fun borrow_mut<K: copy, drop, V>(table: &mut table_with_length::TableWithLength<K, V>, key: K): &mut V
+
+
+
+
+public fun borrow_mut<K: copy + drop, V>(table: &mut TableWithLength<K, V>, key: K): &mut V {
+ table::borrow_mut(&mut table.inner, key)
+}
+
+
+
+
+public fun length<K: copy, drop, V>(table: &table_with_length::TableWithLength<K, V>): u64
+
+
+
+
+public fun length<K: copy + drop, V>(table: &TableWithLength<K, V>): u64 {
+ table.length
+}
+
+
+
+
+public fun empty<K: copy, drop, V>(table: &table_with_length::TableWithLength<K, V>): bool
+
+
+
+
+public fun empty<K: copy + drop, V>(table: &TableWithLength<K, V>): bool {
+ table.length == 0
+}
+
+
+
+
+key
maps to.
+Insert the pair (key
, default
) first if there is no entry for key
.
+
+
+public fun borrow_mut_with_default<K: copy, drop, V: drop>(table: &mut table_with_length::TableWithLength<K, V>, key: K, default: V): &mut V
+
+
+
+
+public fun borrow_mut_with_default<K: copy + drop, V: drop>(table: &mut TableWithLength<K, V>, key: K, default: V): &mut V {
+ if (table::contains(&table.inner, key)) {
+ table::borrow_mut(&mut table.inner, key)
+ } else {
+ table::add(&mut table.inner, key, default);
+ table.length = table.length + 1;
+ table::borrow_mut(&mut table.inner, key)
+ }
+}
+
+
+
+
+key
, value
) if there is no entry for key
.
+update the value of the entry for key
to value
otherwise
+
+
+public fun upsert<K: copy, drop, V: drop>(table: &mut table_with_length::TableWithLength<K, V>, key: K, value: V)
+
+
+
+
+public fun upsert<K: copy + drop, V: drop>(table: &mut TableWithLength<K, V>, key: K, value: V) {
+ if (!table::contains(&table.inner, key)) {
+ add(table, copy key, value)
+ } else {
+ let ref = table::borrow_mut(&mut table.inner, key);
+ *ref = value;
+ };
+}
+
+
+
+
+table
and return the value which key
maps to.
+Aborts if there is no entry for key
.
+
+
+public fun remove<K: copy, drop, V>(table: &mut table_with_length::TableWithLength<K, V>, key: K): V
+
+
+
+
+public fun remove<K: copy + drop, V>(table: &mut TableWithLength<K, V>, key: K): V {
+ let val = table::remove(&mut table.inner, key);
+ table.length = table.length - 1;
+ val
+}
+
+
+
+
+table
contains an entry for key
.
+
+
+public fun contains<K: copy, drop, V>(table: &table_with_length::TableWithLength<K, V>, key: K): bool
+
+
+
+
+public fun contains<K: copy + drop, V>(table: &TableWithLength<K, V>, key: K): bool {
+ table::contains(&table.inner, key)
+}
+
+
+
+
+struct TableWithLength<K: copy, drop, V> has store
+
+
+
+
+inner: table::Table<K, V>
+length: u64
+pragma intrinsic = map,
+ map_new = new,
+ map_destroy_empty = destroy_empty,
+ map_len = length,
+ map_is_empty = empty,
+ map_has_key = contains,
+ map_add_no_override = add,
+ map_add_override_if_exists = upsert,
+ map_del_must_exist = remove,
+ map_borrow = borrow,
+ map_borrow_mut = borrow_mut,
+ map_borrow_mut_with_default = borrow_mut_with_default,
+ map_spec_get = spec_get,
+ map_spec_set = spec_set,
+ map_spec_del = spec_remove,
+ map_spec_len = spec_len,
+ map_spec_has_key = spec_contains;
+
+
+
+
+
+
+### Function `new`
+
+
+public fun new<K: copy, drop, V: store>(): table_with_length::TableWithLength<K, V>
+
+
+
+
+
+pragma intrinsic;
+
+
+
+
+
+
+### Function `destroy_empty`
+
+
+public fun destroy_empty<K: copy, drop, V>(table: table_with_length::TableWithLength<K, V>)
+
+
+
+
+
+pragma intrinsic;
+
+
+
+
+
+
+### Function `add`
+
+
+public fun add<K: copy, drop, V>(table: &mut table_with_length::TableWithLength<K, V>, key: K, val: V)
+
+
+
+
+
+pragma intrinsic;
+
+
+
+
+
+
+### Function `borrow`
+
+
+public fun borrow<K: copy, drop, V>(table: &table_with_length::TableWithLength<K, V>, key: K): &V
+
+
+
+
+
+pragma intrinsic;
+
+
+
+
+
+
+### Function `borrow_mut`
+
+
+public fun borrow_mut<K: copy, drop, V>(table: &mut table_with_length::TableWithLength<K, V>, key: K): &mut V
+
+
+
+
+
+pragma intrinsic;
+
+
+
+
+
+
+### Function `length`
+
+
+public fun length<K: copy, drop, V>(table: &table_with_length::TableWithLength<K, V>): u64
+
+
+
+
+
+pragma intrinsic;
+
+
+
+
+
+
+### Function `empty`
+
+
+public fun empty<K: copy, drop, V>(table: &table_with_length::TableWithLength<K, V>): bool
+
+
+
+
+
+pragma intrinsic;
+
+
+
+
+
+
+### Function `borrow_mut_with_default`
+
+
+public fun borrow_mut_with_default<K: copy, drop, V: drop>(table: &mut table_with_length::TableWithLength<K, V>, key: K, default: V): &mut V
+
+
+
+
+
+aborts_if false;
+pragma intrinsic;
+
+
+
+
+
+
+### Function `upsert`
+
+
+public fun upsert<K: copy, drop, V: drop>(table: &mut table_with_length::TableWithLength<K, V>, key: K, value: V)
+
+
+
+
+
+pragma intrinsic;
+
+
+
+
+
+
+### Function `remove`
+
+
+public fun remove<K: copy, drop, V>(table: &mut table_with_length::TableWithLength<K, V>, key: K): V
+
+
+
+
+
+pragma intrinsic;
+
+
+
+
+
+
+### Function `contains`
+
+
+public fun contains<K: copy, drop, V>(table: &table_with_length::TableWithLength<K, V>, key: K): bool
+
+
+
+
+
+pragma intrinsic;
+
+
+
+
+
+
+
+
+native fun spec_len<K, V>(t: TableWithLength<K, V>): num;
+
+
+
+
+
+
+
+
+native fun spec_contains<K, V>(t: TableWithLength<K, V>, k: K): bool;
+
+
+
+
+
+
+
+
+native fun spec_set<K, V>(t: TableWithLength<K, V>, k: K, v: V): TableWithLength<K, V>;
+
+
+
+
+
+
+
+
+native fun spec_remove<K, V>(t: TableWithLength<K, V>, k: K): TableWithLength<K, V>;
+
+
+
+
+
+
+
+
+native fun spec_get<K, V>(t: TableWithLength<K, V>, k: K): V;
+
+
+
+[move-book]: https://aptos.dev/move/book/SUMMARY
diff --git a/aptos-move/framework/aptos-stdlib/tests/compiler-v2-doc/type_info.md b/aptos-move/framework/aptos-stdlib/tests/compiler-v2-doc/type_info.md
new file mode 100644
index 0000000000000..d482395948234
--- /dev/null
+++ b/aptos-move/framework/aptos-stdlib/tests/compiler-v2-doc/type_info.md
@@ -0,0 +1,399 @@
+
+
+
+# Module `0x1::type_info`
+
+
+
+- [Struct `TypeInfo`](#0x1_type_info_TypeInfo)
+- [Constants](#@Constants_0)
+- [Function `account_address`](#0x1_type_info_account_address)
+- [Function `module_name`](#0x1_type_info_module_name)
+- [Function `struct_name`](#0x1_type_info_struct_name)
+- [Function `chain_id`](#0x1_type_info_chain_id)
+- [Function `type_of`](#0x1_type_info_type_of)
+- [Function `type_name`](#0x1_type_info_type_name)
+- [Function `chain_id_internal`](#0x1_type_info_chain_id_internal)
+- [Function `size_of_val`](#0x1_type_info_size_of_val)
+- [Specification](#@Specification_1)
+ - [Function `chain_id`](#@Specification_1_chain_id)
+ - [Function `type_of`](#@Specification_1_type_of)
+ - [Function `type_name`](#@Specification_1_type_name)
+ - [Function `chain_id_internal`](#@Specification_1_chain_id_internal)
+ - [Function `size_of_val`](#@Specification_1_size_of_val)
+
+
+use 0x1::bcs;
+use 0x1::error;
+use 0x1::features;
+use 0x1::string;
+
+
+
+
+
+
+## Struct `TypeInfo`
+
+
+
+struct TypeInfo has copy, drop, store
+
+
+
+
+account_address: address
+module_name: vector<u8>
+struct_name: vector<u8>
+const E_NATIVE_FUN_NOT_AVAILABLE: u64 = 1;
+
+
+
+
+
+
+## Function `account_address`
+
+
+
+public fun account_address(type_info: &type_info::TypeInfo): address
+
+
+
+
+public fun account_address(type_info: &TypeInfo): address {
+ type_info.account_address
+}
+
+
+
+
+public fun module_name(type_info: &type_info::TypeInfo): vector<u8>
+
+
+
+
+public fun module_name(type_info: &TypeInfo): vector<u8> {
+ type_info.module_name
+}
+
+
+
+
+public fun struct_name(type_info: &type_info::TypeInfo): vector<u8>
+
+
+
+
+public fun struct_name(type_info: &TypeInfo): vector<u8> {
+ type_info.struct_name
+}
+
+
+
+
+aptos_framework::chain_id::get()
would return, except in #[test]
+functions, where this will always return 4u8
as the chain ID, whereas aptos_framework::chain_id::get()
will
+return whichever ID was passed to aptos_framework::chain_id::initialize_for_test()
.
+
+
+public fun chain_id(): u8
+
+
+
+
+public fun chain_id(): u8 {
+ if (!features::aptos_stdlib_chain_id_enabled()) {
+ abort(std::error::invalid_state(E_NATIVE_FUN_NOT_AVAILABLE))
+ };
+
+ chain_id_internal()
+}
+
+
+
+
+TypeInfo
struct containing for the type T
.
+
+
+public fun type_of<T>(): type_info::TypeInfo
+
+
+
+
+public native fun type_of<T>(): TypeInfo;
+
+
+
+
+public fun type_name<T>(): string::String
+
+
+
+
+public native fun type_name<T>(): String;
+
+
+
+
+fun chain_id_internal(): u8
+
+
+
+
+native fun chain_id_internal(): u8;
+
+
+
+
+val_ref
.
+
+See the [BCS spec](https://github.com/diem/bcs)
+
+See test_size_of_val()
for an analysis of common types and
+nesting patterns, as well as test_size_of_val_vectors()
for an
+analysis of vector size dynamism.
+
+
+public fun size_of_val<T>(val_ref: &T): u64
+
+
+
+
+public fun size_of_val<T>(val_ref: &T): u64 {
+ // Return vector length of vectorized BCS representation.
+ vector::length(&bcs::to_bytes(val_ref))
+}
+
+
+
+
+native fun spec_is_struct<T>(): bool;
+
+
+
+
+
+
+### Function `chain_id`
+
+
+public fun chain_id(): u8
+
+
+
+
+
+aborts_if !features::spec_is_enabled(features::APTOS_STD_CHAIN_ID_NATIVES);
+ensures result == spec_chain_id_internal();
+
+
+
+
+
+
+### Function `type_of`
+
+
+public fun type_of<T>(): type_info::TypeInfo
+
+
+
+
+
+
+
+### Function `type_name`
+
+
+public fun type_name<T>(): string::String
+
+
+
+
+
+
+
+### Function `chain_id_internal`
+
+
+fun chain_id_internal(): u8
+
+
+
+
+
+pragma opaque;
+aborts_if false;
+ensures result == spec_chain_id_internal();
+
+
+
+
+
+
+
+
+fun spec_chain_id_internal(): u8;
+
+
+
+
+
+
+
+
+fun spec_size_of_val<T>(val_ref: T): u64 {
+ len(std::bcs::serialize(val_ref))
+}
+
+
+
+
+
+
+### Function `size_of_val`
+
+
+public fun size_of_val<T>(val_ref: &T): u64
+
+
+
+
+
+aborts_if false;
+ensures result == spec_size_of_val<T>(val_ref);
+
+
+
+[move-book]: https://aptos.dev/move/book/SUMMARY
diff --git a/aptos-move/framework/aptos-token-objects/tests/compiler-v2-doc/aptos_token.md b/aptos-move/framework/aptos-token-objects/tests/compiler-v2-doc/aptos_token.md
new file mode 100644
index 0000000000000..c7c9f64e1dfca
--- /dev/null
+++ b/aptos-move/framework/aptos-token-objects/tests/compiler-v2-doc/aptos_token.md
@@ -0,0 +1,1703 @@
+
+
+
+# Module `0x4::aptos_token`
+
+This defines a minimally viable token for no-code solutions akin to the original token at
+0x3::token module.
+The key features are:
+* Base token and collection features
+* Creator definable mutability for tokens
+* Creator-based freezing of tokens
+* Standard object-based transfer and events
+* Metadata property type
+
+
+- [Resource `AptosCollection`](#0x4_aptos_token_AptosCollection)
+- [Resource `AptosToken`](#0x4_aptos_token_AptosToken)
+- [Constants](#@Constants_0)
+- [Function `create_collection`](#0x4_aptos_token_create_collection)
+- [Function `create_collection_object`](#0x4_aptos_token_create_collection_object)
+- [Function `mint`](#0x4_aptos_token_mint)
+- [Function `mint_token_object`](#0x4_aptos_token_mint_token_object)
+- [Function `mint_soul_bound`](#0x4_aptos_token_mint_soul_bound)
+- [Function `mint_soul_bound_token_object`](#0x4_aptos_token_mint_soul_bound_token_object)
+- [Function `mint_internal`](#0x4_aptos_token_mint_internal)
+- [Function `borrow`](#0x4_aptos_token_borrow)
+- [Function `are_properties_mutable`](#0x4_aptos_token_are_properties_mutable)
+- [Function `is_burnable`](#0x4_aptos_token_is_burnable)
+- [Function `is_freezable_by_creator`](#0x4_aptos_token_is_freezable_by_creator)
+- [Function `is_mutable_description`](#0x4_aptos_token_is_mutable_description)
+- [Function `is_mutable_name`](#0x4_aptos_token_is_mutable_name)
+- [Function `is_mutable_uri`](#0x4_aptos_token_is_mutable_uri)
+- [Function `authorized_borrow`](#0x4_aptos_token_authorized_borrow)
+- [Function `burn`](#0x4_aptos_token_burn)
+- [Function `freeze_transfer`](#0x4_aptos_token_freeze_transfer)
+- [Function `unfreeze_transfer`](#0x4_aptos_token_unfreeze_transfer)
+- [Function `set_description`](#0x4_aptos_token_set_description)
+- [Function `set_name`](#0x4_aptos_token_set_name)
+- [Function `set_uri`](#0x4_aptos_token_set_uri)
+- [Function `add_property`](#0x4_aptos_token_add_property)
+- [Function `add_typed_property`](#0x4_aptos_token_add_typed_property)
+- [Function `remove_property`](#0x4_aptos_token_remove_property)
+- [Function `update_property`](#0x4_aptos_token_update_property)
+- [Function `update_typed_property`](#0x4_aptos_token_update_typed_property)
+- [Function `collection_object`](#0x4_aptos_token_collection_object)
+- [Function `borrow_collection`](#0x4_aptos_token_borrow_collection)
+- [Function `is_mutable_collection_description`](#0x4_aptos_token_is_mutable_collection_description)
+- [Function `is_mutable_collection_royalty`](#0x4_aptos_token_is_mutable_collection_royalty)
+- [Function `is_mutable_collection_uri`](#0x4_aptos_token_is_mutable_collection_uri)
+- [Function `is_mutable_collection_token_description`](#0x4_aptos_token_is_mutable_collection_token_description)
+- [Function `is_mutable_collection_token_name`](#0x4_aptos_token_is_mutable_collection_token_name)
+- [Function `is_mutable_collection_token_uri`](#0x4_aptos_token_is_mutable_collection_token_uri)
+- [Function `is_mutable_collection_token_properties`](#0x4_aptos_token_is_mutable_collection_token_properties)
+- [Function `are_collection_tokens_burnable`](#0x4_aptos_token_are_collection_tokens_burnable)
+- [Function `are_collection_tokens_freezable`](#0x4_aptos_token_are_collection_tokens_freezable)
+- [Function `authorized_borrow_collection`](#0x4_aptos_token_authorized_borrow_collection)
+- [Function `set_collection_description`](#0x4_aptos_token_set_collection_description)
+- [Function `set_collection_royalties`](#0x4_aptos_token_set_collection_royalties)
+- [Function `set_collection_royalties_call`](#0x4_aptos_token_set_collection_royalties_call)
+- [Function `set_collection_uri`](#0x4_aptos_token_set_collection_uri)
+
+
+use 0x1::error;
+use 0x1::object;
+use 0x1::option;
+use 0x1::signer;
+use 0x1::string;
+use 0x4::collection;
+use 0x4::property_map;
+use 0x4::royalty;
+use 0x4::token;
+
+
+
+
+
+
+## Resource `AptosCollection`
+
+Storage state for managing the no-code Collection.
+
+
+#[resource_group_member(#[group = 0x1::object::ObjectGroup])]
+struct AptosCollection has key
+
+
+
+
+mutator_ref: option::Option<collection::MutatorRef>
+royalty_mutator_ref: option::Option<royalty::MutatorRef>
+mutable_description: bool
+mutable_uri: bool
+mutable_token_description: bool
+mutable_token_name: bool
+mutable_token_properties: bool
+mutable_token_uri: bool
+tokens_burnable_by_creator: bool
+tokens_freezable_by_creator: bool
+#[resource_group_member(#[group = 0x1::object::ObjectGroup])]
+struct AptosToken has key
+
+
+
+
+burn_ref: option::Option<token::BurnRef>
+transfer_ref: option::Option<object::TransferRef>
+mutator_ref: option::Option<token::MutatorRef>
+property_mutator_ref: property_map::MutatorRef
+const ECOLLECTION_DOES_NOT_EXIST: u64 = 1;
+
+
+
+
+
+
+The field being changed is not mutable
+
+
+const EFIELD_NOT_MUTABLE: u64 = 4;
+
+
+
+
+
+
+The provided signer is not the creator
+
+
+const ENOT_CREATOR: u64 = 3;
+
+
+
+
+
+
+The token does not exist
+
+
+const ETOKEN_DOES_NOT_EXIST: u64 = 2;
+
+
+
+
+
+
+The property map being mutated is not mutable
+
+
+const EPROPERTIES_NOT_MUTABLE: u64 = 6;
+
+
+
+
+
+
+The token being burned is not burnable
+
+
+const ETOKEN_NOT_BURNABLE: u64 = 5;
+
+
+
+
+
+
+## Function `create_collection`
+
+Create a new collection
+
+
+public entry fun create_collection(creator: &signer, description: string::String, max_supply: u64, name: string::String, uri: string::String, mutable_description: bool, mutable_royalty: bool, mutable_uri: bool, mutable_token_description: bool, mutable_token_name: bool, mutable_token_properties: bool, mutable_token_uri: bool, tokens_burnable_by_creator: bool, tokens_freezable_by_creator: bool, royalty_numerator: u64, royalty_denominator: u64)
+
+
+
+
+public entry fun create_collection(
+ creator: &signer,
+ description: String,
+ max_supply: u64,
+ name: String,
+ uri: String,
+ mutable_description: bool,
+ mutable_royalty: bool,
+ mutable_uri: bool,
+ mutable_token_description: bool,
+ mutable_token_name: bool,
+ mutable_token_properties: bool,
+ mutable_token_uri: bool,
+ tokens_burnable_by_creator: bool,
+ tokens_freezable_by_creator: bool,
+ royalty_numerator: u64,
+ royalty_denominator: u64,
+) {
+ create_collection_object(
+ creator,
+ description,
+ max_supply,
+ name,
+ uri,
+ mutable_description,
+ mutable_royalty,
+ mutable_uri,
+ mutable_token_description,
+ mutable_token_name,
+ mutable_token_properties,
+ mutable_token_uri,
+ tokens_burnable_by_creator,
+ tokens_freezable_by_creator,
+ royalty_numerator,
+ royalty_denominator
+ );
+}
+
+
+
+
+public fun create_collection_object(creator: &signer, description: string::String, max_supply: u64, name: string::String, uri: string::String, mutable_description: bool, mutable_royalty: bool, mutable_uri: bool, mutable_token_description: bool, mutable_token_name: bool, mutable_token_properties: bool, mutable_token_uri: bool, tokens_burnable_by_creator: bool, tokens_freezable_by_creator: bool, royalty_numerator: u64, royalty_denominator: u64): object::Object<aptos_token::AptosCollection>
+
+
+
+
+public fun create_collection_object(
+ creator: &signer,
+ description: String,
+ max_supply: u64,
+ name: String,
+ uri: String,
+ mutable_description: bool,
+ mutable_royalty: bool,
+ mutable_uri: bool,
+ mutable_token_description: bool,
+ mutable_token_name: bool,
+ mutable_token_properties: bool,
+ mutable_token_uri: bool,
+ tokens_burnable_by_creator: bool,
+ tokens_freezable_by_creator: bool,
+ royalty_numerator: u64,
+ royalty_denominator: u64,
+): Object<AptosCollection> {
+ let creator_addr = signer::address_of(creator);
+ let royalty = royalty::create(royalty_numerator, royalty_denominator, creator_addr);
+ let constructor_ref = collection::create_fixed_collection(
+ creator,
+ description,
+ max_supply,
+ name,
+ option::some(royalty),
+ uri,
+ );
+
+ let object_signer = object::generate_signer(&constructor_ref);
+ let mutator_ref = if (mutable_description || mutable_uri) {
+ option::some(collection::generate_mutator_ref(&constructor_ref))
+ } else {
+ option::none()
+ };
+
+ let royalty_mutator_ref = if (mutable_royalty) {
+ option::some(royalty::generate_mutator_ref(object::generate_extend_ref(&constructor_ref)))
+ } else {
+ option::none()
+ };
+
+ let aptos_collection = AptosCollection {
+ mutator_ref,
+ royalty_mutator_ref,
+ mutable_description,
+ mutable_uri,
+ mutable_token_description,
+ mutable_token_name,
+ mutable_token_properties,
+ mutable_token_uri,
+ tokens_burnable_by_creator,
+ tokens_freezable_by_creator,
+ };
+ move_to(&object_signer, aptos_collection);
+ object::object_from_constructor_ref(&constructor_ref)
+}
+
+
+
+
+public entry fun mint(creator: &signer, collection: string::String, description: string::String, name: string::String, uri: string::String, property_keys: vector<string::String>, property_types: vector<string::String>, property_values: vector<vector<u8>>)
+
+
+
+
+public entry fun mint(
+ creator: &signer,
+ collection: String,
+ description: String,
+ name: String,
+ uri: String,
+ property_keys: vector<String>,
+ property_types: vector<String>,
+ property_values: vector<vector<u8>>,
+) acquires AptosCollection, AptosToken {
+ mint_token_object(creator, collection, description, name, uri, property_keys, property_types, property_values);
+}
+
+
+
+
+public fun mint_token_object(creator: &signer, collection: string::String, description: string::String, name: string::String, uri: string::String, property_keys: vector<string::String>, property_types: vector<string::String>, property_values: vector<vector<u8>>): object::Object<aptos_token::AptosToken>
+
+
+
+
+public fun mint_token_object(
+ creator: &signer,
+ collection: String,
+ description: String,
+ name: String,
+ uri: String,
+ property_keys: vector<String>,
+ property_types: vector<String>,
+ property_values: vector<vector<u8>>,
+): Object<AptosToken> acquires AptosCollection, AptosToken {
+ let constructor_ref = mint_internal(
+ creator,
+ collection,
+ description,
+ name,
+ uri,
+ property_keys,
+ property_types,
+ property_values,
+ );
+
+ let collection = collection_object(creator, &collection);
+
+ // If tokens are freezable, add a transfer ref to be able to freeze transfers
+ let freezable_by_creator = are_collection_tokens_freezable(collection);
+ if (freezable_by_creator) {
+ let aptos_token_addr = object::address_from_constructor_ref(&constructor_ref);
+ let aptos_token = borrow_global_mut<AptosToken>(aptos_token_addr);
+ let transfer_ref = object::generate_transfer_ref(&constructor_ref);
+ option::fill(&mut aptos_token.transfer_ref, transfer_ref);
+ };
+
+ object::object_from_constructor_ref(&constructor_ref)
+}
+
+
+
+
+public entry fun mint_soul_bound(creator: &signer, collection: string::String, description: string::String, name: string::String, uri: string::String, property_keys: vector<string::String>, property_types: vector<string::String>, property_values: vector<vector<u8>>, soul_bound_to: address)
+
+
+
+
+public entry fun mint_soul_bound(
+ creator: &signer,
+ collection: String,
+ description: String,
+ name: String,
+ uri: String,
+ property_keys: vector<String>,
+ property_types: vector<String>,
+ property_values: vector<vector<u8>>,
+ soul_bound_to: address,
+) acquires AptosCollection {
+ mint_soul_bound_token_object(
+ creator,
+ collection,
+ description,
+ name,
+ uri,
+ property_keys,
+ property_types,
+ property_values,
+ soul_bound_to
+ );
+}
+
+
+
+
+public fun mint_soul_bound_token_object(creator: &signer, collection: string::String, description: string::String, name: string::String, uri: string::String, property_keys: vector<string::String>, property_types: vector<string::String>, property_values: vector<vector<u8>>, soul_bound_to: address): object::Object<aptos_token::AptosToken>
+
+
+
+
+public fun mint_soul_bound_token_object(
+ creator: &signer,
+ collection: String,
+ description: String,
+ name: String,
+ uri: String,
+ property_keys: vector<String>,
+ property_types: vector<String>,
+ property_values: vector<vector<u8>>,
+ soul_bound_to: address,
+): Object<AptosToken> acquires AptosCollection {
+ let constructor_ref = mint_internal(
+ creator,
+ collection,
+ description,
+ name,
+ uri,
+ property_keys,
+ property_types,
+ property_values,
+ );
+
+ let transfer_ref = object::generate_transfer_ref(&constructor_ref);
+ let linear_transfer_ref = object::generate_linear_transfer_ref(&transfer_ref);
+ object::transfer_with_ref(linear_transfer_ref, soul_bound_to);
+ object::disable_ungated_transfer(&transfer_ref);
+
+ object::object_from_constructor_ref(&constructor_ref)
+}
+
+
+
+
+fun mint_internal(creator: &signer, collection: string::String, description: string::String, name: string::String, uri: string::String, property_keys: vector<string::String>, property_types: vector<string::String>, property_values: vector<vector<u8>>): object::ConstructorRef
+
+
+
+
+fun mint_internal(
+ creator: &signer,
+ collection: String,
+ description: String,
+ name: String,
+ uri: String,
+ property_keys: vector<String>,
+ property_types: vector<String>,
+ property_values: vector<vector<u8>>,
+): ConstructorRef acquires AptosCollection {
+ let constructor_ref = token::create(creator, collection, description, name, option::none(), uri);
+
+ let object_signer = object::generate_signer(&constructor_ref);
+
+ let collection_obj = collection_object(creator, &collection);
+ let collection = borrow_collection(&collection_obj);
+
+ let mutator_ref = if (
+ collection.mutable_token_description
+ || collection.mutable_token_name
+ || collection.mutable_token_uri
+ ) {
+ option::some(token::generate_mutator_ref(&constructor_ref))
+ } else {
+ option::none()
+ };
+
+ let burn_ref = if (collection.tokens_burnable_by_creator) {
+ option::some(token::generate_burn_ref(&constructor_ref))
+ } else {
+ option::none()
+ };
+
+ let aptos_token = AptosToken {
+ burn_ref,
+ transfer_ref: option::none(),
+ mutator_ref,
+ property_mutator_ref: property_map::generate_mutator_ref(&constructor_ref),
+ };
+ move_to(&object_signer, aptos_token);
+
+ let properties = property_map::prepare_input(property_keys, property_types, property_values);
+ property_map::init(&constructor_ref, properties);
+
+ constructor_ref
+}
+
+
+
+
+fun borrow<T: key>(token: &object::Object<T>): &aptos_token::AptosToken
+
+
+
+
+inline fun borrow<T: key>(token: &Object<T>): &AptosToken {
+ let token_address = object::object_address(token);
+ assert!(
+ exists<AptosToken>(token_address),
+ error::not_found(ETOKEN_DOES_NOT_EXIST),
+ );
+ borrow_global<AptosToken>(token_address)
+}
+
+
+
+
+#[view]
+public fun are_properties_mutable<T: key>(token: object::Object<T>): bool
+
+
+
+
+public fun are_properties_mutable<T: key>(token: Object<T>): bool acquires AptosCollection {
+ let collection = token::collection_object(token);
+ borrow_collection(&collection).mutable_token_properties
+}
+
+
+
+
+#[view]
+public fun is_burnable<T: key>(token: object::Object<T>): bool
+
+
+
+
+public fun is_burnable<T: key>(token: Object<T>): bool acquires AptosToken {
+ option::is_some(&borrow(&token).burn_ref)
+}
+
+
+
+
+#[view]
+public fun is_freezable_by_creator<T: key>(token: object::Object<T>): bool
+
+
+
+
+public fun is_freezable_by_creator<T: key>(token: Object<T>): bool acquires AptosCollection {
+ are_collection_tokens_freezable(token::collection_object(token))
+}
+
+
+
+
+#[view]
+public fun is_mutable_description<T: key>(token: object::Object<T>): bool
+
+
+
+
+public fun is_mutable_description<T: key>(token: Object<T>): bool acquires AptosCollection {
+ is_mutable_collection_token_description(token::collection_object(token))
+}
+
+
+
+
+#[view]
+public fun is_mutable_name<T: key>(token: object::Object<T>): bool
+
+
+
+
+public fun is_mutable_name<T: key>(token: Object<T>): bool acquires AptosCollection {
+ is_mutable_collection_token_name(token::collection_object(token))
+}
+
+
+
+
+#[view]
+public fun is_mutable_uri<T: key>(token: object::Object<T>): bool
+
+
+
+
+public fun is_mutable_uri<T: key>(token: Object<T>): bool acquires AptosCollection {
+ is_mutable_collection_token_uri(token::collection_object(token))
+}
+
+
+
+
+fun authorized_borrow<T: key>(token: &object::Object<T>, creator: &signer): &aptos_token::AptosToken
+
+
+
+
+inline fun authorized_borrow<T: key>(token: &Object<T>, creator: &signer): &AptosToken {
+ let token_address = object::object_address(token);
+ assert!(
+ exists<AptosToken>(token_address),
+ error::not_found(ETOKEN_DOES_NOT_EXIST),
+ );
+
+ assert!(
+ token::creator(*token) == signer::address_of(creator),
+ error::permission_denied(ENOT_CREATOR),
+ );
+ borrow_global<AptosToken>(token_address)
+}
+
+
+
+
+public entry fun burn<T: key>(creator: &signer, token: object::Object<T>)
+
+
+
+
+public entry fun burn<T: key>(creator: &signer, token: Object<T>) acquires AptosToken {
+ let aptos_token = authorized_borrow(&token, creator);
+ assert!(
+ option::is_some(&aptos_token.burn_ref),
+ error::permission_denied(ETOKEN_NOT_BURNABLE),
+ );
+ move aptos_token;
+ let aptos_token = move_from<AptosToken>(object::object_address(&token));
+ let AptosToken {
+ burn_ref,
+ transfer_ref: _,
+ mutator_ref: _,
+ property_mutator_ref,
+ } = aptos_token;
+ property_map::burn(property_mutator_ref);
+ token::burn(option::extract(&mut burn_ref));
+}
+
+
+
+
+public entry fun freeze_transfer<T: key>(creator: &signer, token: object::Object<T>)
+
+
+
+
+public entry fun freeze_transfer<T: key>(creator: &signer, token: Object<T>) acquires AptosCollection, AptosToken {
+ let aptos_token = authorized_borrow(&token, creator);
+ assert!(
+ are_collection_tokens_freezable(token::collection_object(token))
+ && option::is_some(&aptos_token.transfer_ref),
+ error::permission_denied(EFIELD_NOT_MUTABLE),
+ );
+ object::disable_ungated_transfer(option::borrow(&aptos_token.transfer_ref));
+}
+
+
+
+
+public entry fun unfreeze_transfer<T: key>(creator: &signer, token: object::Object<T>)
+
+
+
+
+public entry fun unfreeze_transfer<T: key>(
+ creator: &signer,
+ token: Object<T>
+) acquires AptosCollection, AptosToken {
+ let aptos_token = authorized_borrow(&token, creator);
+ assert!(
+ are_collection_tokens_freezable(token::collection_object(token))
+ && option::is_some(&aptos_token.transfer_ref),
+ error::permission_denied(EFIELD_NOT_MUTABLE),
+ );
+ object::enable_ungated_transfer(option::borrow(&aptos_token.transfer_ref));
+}
+
+
+
+
+public entry fun set_description<T: key>(creator: &signer, token: object::Object<T>, description: string::String)
+
+
+
+
+public entry fun set_description<T: key>(
+ creator: &signer,
+ token: Object<T>,
+ description: String,
+) acquires AptosCollection, AptosToken {
+ assert!(
+ is_mutable_description(token),
+ error::permission_denied(EFIELD_NOT_MUTABLE),
+ );
+ let aptos_token = authorized_borrow(&token, creator);
+ token::set_description(option::borrow(&aptos_token.mutator_ref), description);
+}
+
+
+
+
+public entry fun set_name<T: key>(creator: &signer, token: object::Object<T>, name: string::String)
+
+
+
+
+public entry fun set_name<T: key>(
+ creator: &signer,
+ token: Object<T>,
+ name: String,
+) acquires AptosCollection, AptosToken {
+ assert!(
+ is_mutable_name(token),
+ error::permission_denied(EFIELD_NOT_MUTABLE),
+ );
+ let aptos_token = authorized_borrow(&token, creator);
+ token::set_name(option::borrow(&aptos_token.mutator_ref), name);
+}
+
+
+
+
+public entry fun set_uri<T: key>(creator: &signer, token: object::Object<T>, uri: string::String)
+
+
+
+
+public entry fun set_uri<T: key>(
+ creator: &signer,
+ token: Object<T>,
+ uri: String,
+) acquires AptosCollection, AptosToken {
+ assert!(
+ is_mutable_uri(token),
+ error::permission_denied(EFIELD_NOT_MUTABLE),
+ );
+ let aptos_token = authorized_borrow(&token, creator);
+ token::set_uri(option::borrow(&aptos_token.mutator_ref), uri);
+}
+
+
+
+
+public entry fun add_property<T: key>(creator: &signer, token: object::Object<T>, key: string::String, type: string::String, value: vector<u8>)
+
+
+
+
+public entry fun add_property<T: key>(
+ creator: &signer,
+ token: Object<T>,
+ key: String,
+ type: String,
+ value: vector<u8>,
+) acquires AptosCollection, AptosToken {
+ let aptos_token = authorized_borrow(&token, creator);
+ assert!(
+ are_properties_mutable(token),
+ error::permission_denied(EPROPERTIES_NOT_MUTABLE),
+ );
+
+ property_map::add(&aptos_token.property_mutator_ref, key, type, value);
+}
+
+
+
+
+public entry fun add_typed_property<T: key, V: drop>(creator: &signer, token: object::Object<T>, key: string::String, value: V)
+
+
+
+
+public entry fun add_typed_property<T: key, V: drop>(
+ creator: &signer,
+ token: Object<T>,
+ key: String,
+ value: V,
+) acquires AptosCollection, AptosToken {
+ let aptos_token = authorized_borrow(&token, creator);
+ assert!(
+ are_properties_mutable(token),
+ error::permission_denied(EPROPERTIES_NOT_MUTABLE),
+ );
+
+ property_map::add_typed(&aptos_token.property_mutator_ref, key, value);
+}
+
+
+
+
+public entry fun remove_property<T: key>(creator: &signer, token: object::Object<T>, key: string::String)
+
+
+
+
+public entry fun remove_property<T: key>(
+ creator: &signer,
+ token: Object<T>,
+ key: String,
+) acquires AptosCollection, AptosToken {
+ let aptos_token = authorized_borrow(&token, creator);
+ assert!(
+ are_properties_mutable(token),
+ error::permission_denied(EPROPERTIES_NOT_MUTABLE),
+ );
+
+ property_map::remove(&aptos_token.property_mutator_ref, &key);
+}
+
+
+
+
+public entry fun update_property<T: key>(creator: &signer, token: object::Object<T>, key: string::String, type: string::String, value: vector<u8>)
+
+
+
+
+public entry fun update_property<T: key>(
+ creator: &signer,
+ token: Object<T>,
+ key: String,
+ type: String,
+ value: vector<u8>,
+) acquires AptosCollection, AptosToken {
+ let aptos_token = authorized_borrow(&token, creator);
+ assert!(
+ are_properties_mutable(token),
+ error::permission_denied(EPROPERTIES_NOT_MUTABLE),
+ );
+
+ property_map::update(&aptos_token.property_mutator_ref, &key, type, value);
+}
+
+
+
+
+public entry fun update_typed_property<T: key, V: drop>(creator: &signer, token: object::Object<T>, key: string::String, value: V)
+
+
+
+
+public entry fun update_typed_property<T: key, V: drop>(
+ creator: &signer,
+ token: Object<T>,
+ key: String,
+ value: V,
+) acquires AptosCollection, AptosToken {
+ let aptos_token = authorized_borrow(&token, creator);
+ assert!(
+ are_properties_mutable(token),
+ error::permission_denied(EPROPERTIES_NOT_MUTABLE),
+ );
+
+ property_map::update_typed(&aptos_token.property_mutator_ref, &key, value);
+}
+
+
+
+
+fun collection_object(creator: &signer, name: &string::String): object::Object<aptos_token::AptosCollection>
+
+
+
+
+inline fun collection_object(creator: &signer, name: &String): Object<AptosCollection> {
+ let collection_addr = collection::create_collection_address(&signer::address_of(creator), name);
+ object::address_to_object<AptosCollection>(collection_addr)
+}
+
+
+
+
+fun borrow_collection<T: key>(token: &object::Object<T>): &aptos_token::AptosCollection
+
+
+
+
+inline fun borrow_collection<T: key>(token: &Object<T>): &AptosCollection {
+ let collection_address = object::object_address(token);
+ assert!(
+ exists<AptosCollection>(collection_address),
+ error::not_found(ECOLLECTION_DOES_NOT_EXIST),
+ );
+ borrow_global<AptosCollection>(collection_address)
+}
+
+
+
+
+public fun is_mutable_collection_description<T: key>(collection: object::Object<T>): bool
+
+
+
+
+public fun is_mutable_collection_description<T: key>(
+ collection: Object<T>,
+): bool acquires AptosCollection {
+ borrow_collection(&collection).mutable_description
+}
+
+
+
+
+public fun is_mutable_collection_royalty<T: key>(collection: object::Object<T>): bool
+
+
+
+
+public fun is_mutable_collection_royalty<T: key>(
+ collection: Object<T>,
+): bool acquires AptosCollection {
+ option::is_some(&borrow_collection(&collection).royalty_mutator_ref)
+}
+
+
+
+
+public fun is_mutable_collection_uri<T: key>(collection: object::Object<T>): bool
+
+
+
+
+public fun is_mutable_collection_uri<T: key>(
+ collection: Object<T>,
+): bool acquires AptosCollection {
+ borrow_collection(&collection).mutable_uri
+}
+
+
+
+
+public fun is_mutable_collection_token_description<T: key>(collection: object::Object<T>): bool
+
+
+
+
+public fun is_mutable_collection_token_description<T: key>(
+ collection: Object<T>,
+): bool acquires AptosCollection {
+ borrow_collection(&collection).mutable_token_description
+}
+
+
+
+
+public fun is_mutable_collection_token_name<T: key>(collection: object::Object<T>): bool
+
+
+
+
+public fun is_mutable_collection_token_name<T: key>(
+ collection: Object<T>,
+): bool acquires AptosCollection {
+ borrow_collection(&collection).mutable_token_name
+}
+
+
+
+
+public fun is_mutable_collection_token_uri<T: key>(collection: object::Object<T>): bool
+
+
+
+
+public fun is_mutable_collection_token_uri<T: key>(
+ collection: Object<T>,
+): bool acquires AptosCollection {
+ borrow_collection(&collection).mutable_token_uri
+}
+
+
+
+
+public fun is_mutable_collection_token_properties<T: key>(collection: object::Object<T>): bool
+
+
+
+
+public fun is_mutable_collection_token_properties<T: key>(
+ collection: Object<T>,
+): bool acquires AptosCollection {
+ borrow_collection(&collection).mutable_token_properties
+}
+
+
+
+
+public fun are_collection_tokens_burnable<T: key>(collection: object::Object<T>): bool
+
+
+
+
+public fun are_collection_tokens_burnable<T: key>(
+ collection: Object<T>,
+): bool acquires AptosCollection {
+ borrow_collection(&collection).tokens_burnable_by_creator
+}
+
+
+
+
+public fun are_collection_tokens_freezable<T: key>(collection: object::Object<T>): bool
+
+
+
+
+public fun are_collection_tokens_freezable<T: key>(
+ collection: Object<T>,
+): bool acquires AptosCollection {
+ borrow_collection(&collection).tokens_freezable_by_creator
+}
+
+
+
+
+fun authorized_borrow_collection<T: key>(collection: &object::Object<T>, creator: &signer): &aptos_token::AptosCollection
+
+
+
+
+inline fun authorized_borrow_collection<T: key>(collection: &Object<T>, creator: &signer): &AptosCollection {
+ let collection_address = object::object_address(collection);
+ assert!(
+ exists<AptosCollection>(collection_address),
+ error::not_found(ECOLLECTION_DOES_NOT_EXIST),
+ );
+ assert!(
+ collection::creator(*collection) == signer::address_of(creator),
+ error::permission_denied(ENOT_CREATOR),
+ );
+ borrow_global<AptosCollection>(collection_address)
+}
+
+
+
+
+public entry fun set_collection_description<T: key>(creator: &signer, collection: object::Object<T>, description: string::String)
+
+
+
+
+public entry fun set_collection_description<T: key>(
+ creator: &signer,
+ collection: Object<T>,
+ description: String,
+) acquires AptosCollection {
+ let aptos_collection = authorized_borrow_collection(&collection, creator);
+ assert!(
+ aptos_collection.mutable_description,
+ error::permission_denied(EFIELD_NOT_MUTABLE),
+ );
+ collection::set_description(option::borrow(&aptos_collection.mutator_ref), description);
+}
+
+
+
+
+public fun set_collection_royalties<T: key>(creator: &signer, collection: object::Object<T>, royalty: royalty::Royalty)
+
+
+
+
+public fun set_collection_royalties<T: key>(
+ creator: &signer,
+ collection: Object<T>,
+ royalty: royalty::Royalty,
+) acquires AptosCollection {
+ let aptos_collection = authorized_borrow_collection(&collection, creator);
+ assert!(
+ option::is_some(&aptos_collection.royalty_mutator_ref),
+ error::permission_denied(EFIELD_NOT_MUTABLE),
+ );
+ royalty::update(option::borrow(&aptos_collection.royalty_mutator_ref), royalty);
+}
+
+
+
+
+entry fun set_collection_royalties_call<T: key>(creator: &signer, collection: object::Object<T>, royalty_numerator: u64, royalty_denominator: u64, payee_address: address)
+
+
+
+
+entry fun set_collection_royalties_call<T: key>(
+ creator: &signer,
+ collection: Object<T>,
+ royalty_numerator: u64,
+ royalty_denominator: u64,
+ payee_address: address,
+) acquires AptosCollection {
+ let royalty = royalty::create(royalty_numerator, royalty_denominator, payee_address);
+ set_collection_royalties(creator, collection, royalty);
+}
+
+
+
+
+public entry fun set_collection_uri<T: key>(creator: &signer, collection: object::Object<T>, uri: string::String)
+
+
+
+
+public entry fun set_collection_uri<T: key>(
+ creator: &signer,
+ collection: Object<T>,
+ uri: String,
+) acquires AptosCollection {
+ let aptos_collection = authorized_borrow_collection(&collection, creator);
+ assert!(
+ aptos_collection.mutable_uri,
+ error::permission_denied(EFIELD_NOT_MUTABLE),
+ );
+ collection::set_uri(option::borrow(&aptos_collection.mutator_ref), uri);
+}
+
+
+
+
+use 0x1::aggregator_v2;
+use 0x1::error;
+use 0x1::event;
+use 0x1::features;
+use 0x1::object;
+use 0x1::option;
+use 0x1::signer;
+use 0x1::string;
+use 0x4::royalty;
+
+
+
+
+
+
+## Resource `Collection`
+
+Represents the common fields for a collection.
+
+
+#[resource_group_member(#[group = 0x1::object::ObjectGroup])]
+struct Collection has key
+
+
+
+
+creator: address
+description: string::String
+name: string::String
+uri: string::String
+mutation_events: event::EventHandle<collection::MutationEvent>
+struct MutatorRef has drop, store
+
+
+
+
+self: address
+struct MutationEvent has drop, store
+
+
+
+
+mutated_field_name: string::String
+#[event]
+struct Mutation has drop, store
+
+
+
+
+mutated_field_name: string::String
+collection: object::Object<collection::Collection>
+old_value: string::String
+new_value: string::String
+#[resource_group_member(#[group = 0x1::object::ObjectGroup])]
+struct FixedSupply has key
+
+
+
+
+current_supply: u64
+max_supply: u64
+total_minted: u64
+burn_events: event::EventHandle<collection::BurnEvent>
+mint_events: event::EventHandle<collection::MintEvent>
+#[resource_group_member(#[group = 0x1::object::ObjectGroup])]
+struct UnlimitedSupply has key
+
+
+
+
+current_supply: u64
+total_minted: u64
+burn_events: event::EventHandle<collection::BurnEvent>
+mint_events: event::EventHandle<collection::MintEvent>
+#[resource_group_member(#[group = 0x1::object::ObjectGroup])]
+struct ConcurrentSupply has key
+
+
+
+
+current_supply: aggregator_v2::Aggregator<u64>
+total_minted: aggregator_v2::Aggregator<u64>
+struct BurnEvent has drop, store
+
+
+
+
+index: u64
+token: address
+struct MintEvent has drop, store
+
+
+
+
+index: u64
+token: address
+#[event]
+struct Burn has drop, store
+
+
+
+
+collection: address
+index: u64
+token: address
+previous_owner: address
+#[event]
+struct Mint has drop, store
+
+
+
+
+collection: address
+index: aggregator_v2::AggregatorSnapshot<u64>
+token: address
+#[event]
+#[deprecated]
+struct ConcurrentBurnEvent has drop, store
+
+
+
+
+collection_addr: address
+index: u64
+token: address
+#[event]
+#[deprecated]
+struct ConcurrentMintEvent has drop, store
+
+
+
+
+collection_addr: address
+index: aggregator_v2::AggregatorSnapshot<u64>
+token: address
+#[event]
+struct SetMaxSupply has drop, store
+
+
+
+
+collection: object::Object<collection::Collection>
+old_max_supply: u64
+new_max_supply: u64
+const MAX_U64: u64 = 18446744073709551615;
+
+
+
+
+
+
+The URI is over the maximum length
+
+
+const EURI_TOO_LONG: u64 = 4;
+
+
+
+
+
+
+
+
+const MAX_URI_LENGTH: u64 = 512;
+
+
+
+
+
+
+Tried upgrading collection to concurrent, but collection is already concurrent
+
+
+const EALREADY_CONCURRENT: u64 = 8;
+
+
+
+
+
+
+The collection does not exist
+
+
+const ECOLLECTION_DOES_NOT_EXIST: u64 = 1;
+
+
+
+
+
+
+The collection name is over the maximum length
+
+
+const ECOLLECTION_NAME_TOO_LONG: u64 = 3;
+
+
+
+
+
+
+The collection has reached its supply and no more tokens can be minted, unless some are burned
+
+
+const ECOLLECTION_SUPPLY_EXCEEDED: u64 = 2;
+
+
+
+
+
+
+Concurrent feature flag is not yet enabled, so the function cannot be performed
+
+
+const ECONCURRENT_NOT_ENABLED: u64 = 7;
+
+
+
+
+
+
+The description is over the maximum length
+
+
+const EDESCRIPTION_TOO_LONG: u64 = 5;
+
+
+
+
+
+
+The new max supply cannot be less than the current supply
+
+
+const EINVALID_MAX_SUPPLY: u64 = 9;
+
+
+
+
+
+
+The max supply must be positive
+
+
+const EMAX_SUPPLY_CANNOT_BE_ZERO: u64 = 6;
+
+
+
+
+
+
+The collection does not have a max supply
+
+
+const ENO_MAX_SUPPLY_IN_COLLECTION: u64 = 10;
+
+
+
+
+
+
+
+
+const MAX_COLLECTION_NAME_LENGTH: u64 = 128;
+
+
+
+
+
+
+
+
+const MAX_DESCRIPTION_LENGTH: u64 = 2048;
+
+
+
+
+
+
+## Function `create_fixed_collection`
+
+Creates a fixed-sized collection, or a collection that supports a fixed amount of tokens.
+This is useful to create a guaranteed, limited supply on-chain digital asset. For example,
+a collection 1111 vicious vipers. Note, creating restrictions such as upward limits results
+in data structures that prevent Aptos from parallelizing mints of this collection type.
+Beyond that, it adds supply tracking with events.
+
+
+public fun create_fixed_collection(creator: &signer, description: string::String, max_supply: u64, name: string::String, royalty: option::Option<royalty::Royalty>, uri: string::String): object::ConstructorRef
+
+
+
+
+public fun create_fixed_collection(
+ creator: &signer,
+ description: String,
+ max_supply: u64,
+ name: String,
+ royalty: Option<Royalty>,
+ uri: String,
+): ConstructorRef {
+ assert!(max_supply != 0, error::invalid_argument(EMAX_SUPPLY_CANNOT_BE_ZERO));
+ let collection_seed = create_collection_seed(&name);
+ let constructor_ref = object::create_named_object(creator, collection_seed);
+
+ let supply = ConcurrentSupply {
+ current_supply: aggregator_v2::create_aggregator(max_supply),
+ total_minted: aggregator_v2::create_unbounded_aggregator(),
+ };
+
+ create_collection_internal(
+ creator,
+ constructor_ref,
+ description,
+ name,
+ royalty,
+ uri,
+ option::some(supply),
+ )
+}
+
+
+
+
+public fun create_unlimited_collection(creator: &signer, description: string::String, name: string::String, royalty: option::Option<royalty::Royalty>, uri: string::String): object::ConstructorRef
+
+
+
+
+public fun create_unlimited_collection(
+ creator: &signer,
+ description: String,
+ name: String,
+ royalty: Option<Royalty>,
+ uri: String,
+): ConstructorRef {
+ let collection_seed = create_collection_seed(&name);
+ let constructor_ref = object::create_named_object(creator, collection_seed);
+
+ let supply = ConcurrentSupply {
+ current_supply: aggregator_v2::create_unbounded_aggregator(),
+ total_minted: aggregator_v2::create_unbounded_aggregator(),
+ };
+
+ create_collection_internal(
+ creator,
+ constructor_ref,
+ description,
+ name,
+ royalty,
+ uri,
+ option::some(supply),
+ )
+}
+
+
+
+
+fun create_untracked_collection(creator: &signer, description: string::String, name: string::String, royalty: option::Option<royalty::Royalty>, uri: string::String): object::ConstructorRef
+
+
+
+
+fun create_untracked_collection(
+ creator: &signer,
+ description: String,
+ name: String,
+ royalty: Option<Royalty>,
+ uri: String,
+): ConstructorRef {
+ let collection_seed = create_collection_seed(&name);
+ let constructor_ref = object::create_named_object(creator, collection_seed);
+
+ create_collection_internal<FixedSupply>(
+ creator,
+ constructor_ref,
+ description,
+ name,
+ royalty,
+ uri,
+ option::none(),
+ )
+}
+
+
+
+
+fun create_collection_internal<Supply: key>(creator: &signer, constructor_ref: object::ConstructorRef, description: string::String, name: string::String, royalty: option::Option<royalty::Royalty>, uri: string::String, supply: option::Option<Supply>): object::ConstructorRef
+
+
+
+
+inline fun create_collection_internal<Supply: key>(
+ creator: &signer,
+ constructor_ref: ConstructorRef,
+ description: String,
+ name: String,
+ royalty: Option<Royalty>,
+ uri: String,
+ supply: Option<Supply>,
+): ConstructorRef {
+ assert!(string::length(&name) <= MAX_COLLECTION_NAME_LENGTH, error::out_of_range(ECOLLECTION_NAME_TOO_LONG));
+ assert!(string::length(&uri) <= MAX_URI_LENGTH, error::out_of_range(EURI_TOO_LONG));
+ assert!(string::length(&description) <= MAX_DESCRIPTION_LENGTH, error::out_of_range(EDESCRIPTION_TOO_LONG));
+
+ let object_signer = object::generate_signer(&constructor_ref);
+
+ let collection = Collection {
+ creator: signer::address_of(creator),
+ description,
+ name,
+ uri,
+ mutation_events: object::new_event_handle(&object_signer),
+ };
+ move_to(&object_signer, collection);
+
+ if (option::is_some(&supply)) {
+ move_to(&object_signer, option::destroy_some(supply))
+ } else {
+ option::destroy_none(supply)
+ };
+
+ if (option::is_some(&royalty)) {
+ royalty::init(&constructor_ref, option::extract(&mut royalty))
+ };
+
+ let transfer_ref = object::generate_transfer_ref(&constructor_ref);
+ object::disable_ungated_transfer(&transfer_ref);
+
+ constructor_ref
+}
+
+
+
+
+public fun create_collection_address(creator: &address, name: &string::String): address
+
+
+
+
+public fun create_collection_address(creator: &address, name: &String): address {
+ object::create_object_address(creator, create_collection_seed(name))
+}
+
+
+
+
+public fun create_collection_seed(name: &string::String): vector<u8>
+
+
+
+
+public fun create_collection_seed(name: &String): vector<u8> {
+ assert!(string::length(name) <= MAX_COLLECTION_NAME_LENGTH, error::out_of_range(ECOLLECTION_NAME_TOO_LONG));
+ *string::bytes(name)
+}
+
+
+
+
+public(friend) fun increment_supply(collection: &object::Object<collection::Collection>, token: address): option::Option<aggregator_v2::AggregatorSnapshot<u64>>
+
+
+
+
+public(friend) fun increment_supply(
+ collection: &Object<Collection>,
+ token: address,
+): Option<AggregatorSnapshot<u64>> acquires FixedSupply, UnlimitedSupply, ConcurrentSupply {
+ let collection_addr = object::object_address(collection);
+ if (exists<ConcurrentSupply>(collection_addr)) {
+ let supply = borrow_global_mut<ConcurrentSupply>(collection_addr);
+ assert!(
+ aggregator_v2::try_add(&mut supply.current_supply, 1),
+ error::out_of_range(ECOLLECTION_SUPPLY_EXCEEDED),
+ );
+ aggregator_v2::add(&mut supply.total_minted, 1);
+ event::emit(
+ Mint {
+ collection: collection_addr,
+ index: aggregator_v2::snapshot(&supply.total_minted),
+ token,
+ },
+ );
+ option::some(aggregator_v2::snapshot(&supply.total_minted))
+ } else if (exists<FixedSupply>(collection_addr)) {
+ let supply = borrow_global_mut<FixedSupply>(collection_addr);
+ supply.current_supply = supply.current_supply + 1;
+ supply.total_minted = supply.total_minted + 1;
+ assert!(
+ supply.current_supply <= supply.max_supply,
+ error::out_of_range(ECOLLECTION_SUPPLY_EXCEEDED),
+ );
+ if (std::features::module_event_migration_enabled()) {
+ event::emit(
+ Mint {
+ collection: collection_addr,
+ index: aggregator_v2::create_snapshot(supply.total_minted),
+ token,
+ },
+ );
+ };
+ event::emit_event(&mut supply.mint_events,
+ MintEvent {
+ index: supply.total_minted,
+ token,
+ },
+ );
+ option::some(aggregator_v2::create_snapshot<u64>(supply.total_minted))
+ } else if (exists<UnlimitedSupply>(collection_addr)) {
+ let supply = borrow_global_mut<UnlimitedSupply>(collection_addr);
+ supply.current_supply = supply.current_supply + 1;
+ supply.total_minted = supply.total_minted + 1;
+ if (std::features::module_event_migration_enabled()) {
+ event::emit(
+ Mint {
+ collection: collection_addr,
+ index: aggregator_v2::create_snapshot(supply.total_minted),
+ token,
+ },
+ );
+ };
+ event::emit_event(
+ &mut supply.mint_events,
+ MintEvent {
+ index: supply.total_minted,
+ token,
+ },
+ );
+ option::some(aggregator_v2::create_snapshot<u64>(supply.total_minted))
+ } else {
+ option::none()
+ }
+}
+
+
+
+
+public(friend) fun decrement_supply(collection: &object::Object<collection::Collection>, token: address, index: option::Option<u64>, previous_owner: address)
+
+
+
+
+public(friend) fun decrement_supply(
+ collection: &Object<Collection>,
+ token: address,
+ index: Option<u64>,
+ previous_owner: address,
+) acquires FixedSupply, UnlimitedSupply, ConcurrentSupply {
+ let collection_addr = object::object_address(collection);
+ if (exists<ConcurrentSupply>(collection_addr)) {
+ let supply = borrow_global_mut<ConcurrentSupply>(collection_addr);
+ aggregator_v2::sub(&mut supply.current_supply, 1);
+
+ event::emit(
+ Burn {
+ collection: collection_addr,
+ index: *option::borrow(&index),
+ token,
+ previous_owner,
+ },
+ );
+ } else if (exists<FixedSupply>(collection_addr)) {
+ let supply = borrow_global_mut<FixedSupply>(collection_addr);
+ supply.current_supply = supply.current_supply - 1;
+ if (std::features::module_event_migration_enabled()) {
+ event::emit(
+ Burn {
+ collection: collection_addr,
+ index: *option::borrow(&index),
+ token,
+ previous_owner,
+ },
+ );
+ };
+ event::emit_event(
+ &mut supply.burn_events,
+ BurnEvent {
+ index: *option::borrow(&index),
+ token,
+ },
+ );
+ } else if (exists<UnlimitedSupply>(collection_addr)) {
+ let supply = borrow_global_mut<UnlimitedSupply>(collection_addr);
+ supply.current_supply = supply.current_supply - 1;
+ if (std::features::module_event_migration_enabled()) {
+ event::emit(
+ Burn {
+ collection: collection_addr,
+ index: *option::borrow(&index),
+ token,
+ previous_owner,
+ },
+ );
+ };
+ event::emit_event(
+ &mut supply.burn_events,
+ BurnEvent {
+ index: *option::borrow(&index),
+ token,
+ },
+ );
+ }
+}
+
+
+
+
+public fun generate_mutator_ref(ref: &object::ConstructorRef): collection::MutatorRef
+
+
+
+
+public fun generate_mutator_ref(ref: &ConstructorRef): MutatorRef {
+ let object = object::object_from_constructor_ref<Collection>(ref);
+ MutatorRef { self: object::object_address(&object) }
+}
+
+
+
+
+public fun upgrade_to_concurrent(ref: &object::ExtendRef)
+
+
+
+
+public fun upgrade_to_concurrent(
+ ref: &ExtendRef,
+) acquires FixedSupply, UnlimitedSupply {
+ let metadata_object_address = object::address_from_extend_ref(ref);
+ let metadata_object_signer = object::generate_signer_for_extending(ref);
+
+ let (supply, current_supply, total_minted, burn_events, mint_events) = if (exists<FixedSupply>(
+ metadata_object_address
+ )) {
+ let FixedSupply {
+ current_supply,
+ max_supply,
+ total_minted,
+ burn_events,
+ mint_events,
+ } = move_from<FixedSupply>(metadata_object_address);
+
+ let supply = ConcurrentSupply {
+ current_supply: aggregator_v2::create_aggregator(max_supply),
+ total_minted: aggregator_v2::create_unbounded_aggregator(),
+ };
+ (supply, current_supply, total_minted, burn_events, mint_events)
+ } else if (exists<UnlimitedSupply>(metadata_object_address)) {
+ let UnlimitedSupply {
+ current_supply,
+ total_minted,
+ burn_events,
+ mint_events,
+ } = move_from<UnlimitedSupply>(metadata_object_address);
+
+ let supply = ConcurrentSupply {
+ current_supply: aggregator_v2::create_unbounded_aggregator(),
+ total_minted: aggregator_v2::create_unbounded_aggregator(),
+ };
+ (supply, current_supply, total_minted, burn_events, mint_events)
+ } else {
+ // untracked collection is already concurrent, and other variants too.
+ abort error::invalid_argument(EALREADY_CONCURRENT)
+ };
+
+ // update current state:
+ aggregator_v2::add(&mut supply.current_supply, current_supply);
+ aggregator_v2::add(&mut supply.total_minted, total_minted);
+ move_to(&metadata_object_signer, supply);
+
+ event::destroy_handle(burn_events);
+ event::destroy_handle(mint_events);
+}
+
+
+
+
+fun check_collection_exists(addr: address)
+
+
+
+
+inline fun check_collection_exists(addr: address) {
+ assert!(
+ exists<Collection>(addr),
+ error::not_found(ECOLLECTION_DOES_NOT_EXIST),
+ );
+}
+
+
+
+
+fun borrow<T: key>(collection: &object::Object<T>): &collection::Collection
+
+
+
+
+inline fun borrow<T: key>(collection: &Object<T>): &Collection {
+ let collection_address = object::object_address(collection);
+ check_collection_exists(collection_address);
+ borrow_global<Collection>(collection_address)
+}
+
+
+
+
+#[view]
+public fun count<T: key>(collection: object::Object<T>): option::Option<u64>
+
+
+
+
+public fun count<T: key>(
+ collection: Object<T>
+): Option<u64> acquires FixedSupply, UnlimitedSupply, ConcurrentSupply {
+ let collection_address = object::object_address(&collection);
+ check_collection_exists(collection_address);
+
+ if (exists<ConcurrentSupply>(collection_address)) {
+ let supply = borrow_global_mut<ConcurrentSupply>(collection_address);
+ option::some(aggregator_v2::read(&supply.current_supply))
+ } else if (exists<FixedSupply>(collection_address)) {
+ let supply = borrow_global_mut<FixedSupply>(collection_address);
+ option::some(supply.current_supply)
+ } else if (exists<UnlimitedSupply>(collection_address)) {
+ let supply = borrow_global_mut<UnlimitedSupply>(collection_address);
+ option::some(supply.current_supply)
+ } else {
+ option::none()
+ }
+}
+
+
+
+
+#[view]
+public fun creator<T: key>(collection: object::Object<T>): address
+
+
+
+
+public fun creator<T: key>(collection: Object<T>): address acquires Collection {
+ borrow(&collection).creator
+}
+
+
+
+
+#[view]
+public fun description<T: key>(collection: object::Object<T>): string::String
+
+
+
+
+public fun description<T: key>(collection: Object<T>): String acquires Collection {
+ borrow(&collection).description
+}
+
+
+
+
+#[view]
+public fun name<T: key>(collection: object::Object<T>): string::String
+
+
+
+
+public fun name<T: key>(collection: Object<T>): String acquires Collection {
+ borrow(&collection).name
+}
+
+
+
+
+#[view]
+public fun uri<T: key>(collection: object::Object<T>): string::String
+
+
+
+
+public fun uri<T: key>(collection: Object<T>): String acquires Collection {
+ borrow(&collection).uri
+}
+
+
+
+
+fun borrow_mut(mutator_ref: &collection::MutatorRef): &mut collection::Collection
+
+
+
+
+inline fun borrow_mut(mutator_ref: &MutatorRef): &mut Collection {
+ check_collection_exists(mutator_ref.self);
+ borrow_global_mut<Collection>(mutator_ref.self)
+}
+
+
+
+
+create_collection_address
.
+Once the collection has been created, the collection address should be saved for reference and
+create_collection_address
should not be used to derive the collection's address.
+
+After changing the collection's name, to create tokens - only call functions that accept the collection object as an argument.
+
+
+public fun set_name(mutator_ref: &collection::MutatorRef, name: string::String)
+
+
+
+
+public fun set_name(mutator_ref: &MutatorRef, name: String) acquires Collection {
+ assert!(string::length(&name) <= MAX_COLLECTION_NAME_LENGTH, error::out_of_range(ECOLLECTION_NAME_TOO_LONG));
+ let collection = borrow_mut(mutator_ref);
+ event::emit(Mutation {
+ mutated_field_name: string::utf8(b"name") ,
+ collection: object::address_to_object(mutator_ref.self),
+ old_value: collection.name,
+ new_value: name,
+ });
+ collection.name = name;
+}
+
+
+
+
+public fun set_description(mutator_ref: &collection::MutatorRef, description: string::String)
+
+
+
+
+public fun set_description(mutator_ref: &MutatorRef, description: String) acquires Collection {
+ assert!(string::length(&description) <= MAX_DESCRIPTION_LENGTH, error::out_of_range(EDESCRIPTION_TOO_LONG));
+ let collection = borrow_mut(mutator_ref);
+ if (std::features::module_event_migration_enabled()) {
+ event::emit(Mutation {
+ mutated_field_name: string::utf8(b"description"),
+ collection: object::address_to_object(mutator_ref.self),
+ old_value: collection.description,
+ new_value: description,
+ });
+ };
+ collection.description = description;
+ event::emit_event(
+ &mut collection.mutation_events,
+ MutationEvent { mutated_field_name: string::utf8(b"description") },
+ );
+}
+
+
+
+
+public fun set_uri(mutator_ref: &collection::MutatorRef, uri: string::String)
+
+
+
+
+public fun set_uri(mutator_ref: &MutatorRef, uri: String) acquires Collection {
+ assert!(string::length(&uri) <= MAX_URI_LENGTH, error::out_of_range(EURI_TOO_LONG));
+ let collection = borrow_mut(mutator_ref);
+ if (std::features::module_event_migration_enabled()) {
+ event::emit(Mutation {
+ mutated_field_name: string::utf8(b"uri"),
+ collection: object::address_to_object(mutator_ref.self),
+ old_value: collection.uri,
+ new_value: uri,
+ });
+ };
+ collection.uri = uri;
+ event::emit_event(
+ &mut collection.mutation_events,
+ MutationEvent { mutated_field_name: string::utf8(b"uri") },
+ );
+}
+
+
+
+
+public fun set_max_supply(mutator_ref: &collection::MutatorRef, max_supply: u64)
+
+
+
+
+public fun set_max_supply(mutator_ref: &MutatorRef, max_supply: u64) acquires ConcurrentSupply, FixedSupply {
+ let collection = object::address_to_object<Collection>(mutator_ref.self);
+ let collection_address = object::object_address(&collection);
+ let old_max_supply;
+
+ if (exists<ConcurrentSupply>(collection_address)) {
+ let supply = borrow_global_mut<ConcurrentSupply>(collection_address);
+ let current_supply = aggregator_v2::read(&supply.current_supply);
+ assert!(
+ max_supply >= current_supply,
+ error::out_of_range(EINVALID_MAX_SUPPLY),
+ );
+ old_max_supply = aggregator_v2::max_value(&supply.current_supply);
+ supply.current_supply = aggregator_v2::create_aggregator(max_supply);
+ aggregator_v2::add(&mut supply.current_supply, current_supply);
+ } else if (exists<FixedSupply>(collection_address)) {
+ let supply = borrow_global_mut<FixedSupply>(collection_address);
+ assert!(
+ max_supply >= supply.current_supply,
+ error::out_of_range(EINVALID_MAX_SUPPLY),
+ );
+ old_max_supply = supply.max_supply;
+ supply.max_supply = max_supply;
+ } else {
+ abort error::invalid_argument(ENO_MAX_SUPPLY_IN_COLLECTION)
+ };
+
+ event::emit(SetMaxSupply { collection, old_max_supply, new_max_supply: max_supply });
+}
+
+
+
+
+PropertyMap
provides generic metadata support for AptosToken
. It is a specialization of
+SimpleMap
that enforces strict typing with minimal storage use by using constant u64 to
+represent types and storing values in bcs format.
+
+
+- [Resource `PropertyMap`](#0x4_property_map_PropertyMap)
+- [Struct `PropertyValue`](#0x4_property_map_PropertyValue)
+- [Struct `MutatorRef`](#0x4_property_map_MutatorRef)
+- [Constants](#@Constants_0)
+- [Function `init`](#0x4_property_map_init)
+- [Function `extend`](#0x4_property_map_extend)
+- [Function `burn`](#0x4_property_map_burn)
+- [Function `prepare_input`](#0x4_property_map_prepare_input)
+- [Function `to_external_type`](#0x4_property_map_to_external_type)
+- [Function `to_internal_type`](#0x4_property_map_to_internal_type)
+- [Function `type_info_to_internal_type`](#0x4_property_map_type_info_to_internal_type)
+- [Function `validate_type`](#0x4_property_map_validate_type)
+- [Function `generate_mutator_ref`](#0x4_property_map_generate_mutator_ref)
+- [Function `contains_key`](#0x4_property_map_contains_key)
+- [Function `length`](#0x4_property_map_length)
+- [Function `read`](#0x4_property_map_read)
+- [Function `assert_exists`](#0x4_property_map_assert_exists)
+- [Function `read_typed`](#0x4_property_map_read_typed)
+- [Function `read_bool`](#0x4_property_map_read_bool)
+- [Function `read_u8`](#0x4_property_map_read_u8)
+- [Function `read_u16`](#0x4_property_map_read_u16)
+- [Function `read_u32`](#0x4_property_map_read_u32)
+- [Function `read_u64`](#0x4_property_map_read_u64)
+- [Function `read_u128`](#0x4_property_map_read_u128)
+- [Function `read_u256`](#0x4_property_map_read_u256)
+- [Function `read_address`](#0x4_property_map_read_address)
+- [Function `read_bytes`](#0x4_property_map_read_bytes)
+- [Function `read_string`](#0x4_property_map_read_string)
+- [Function `add`](#0x4_property_map_add)
+- [Function `add_typed`](#0x4_property_map_add_typed)
+- [Function `add_internal`](#0x4_property_map_add_internal)
+- [Function `update`](#0x4_property_map_update)
+- [Function `update_typed`](#0x4_property_map_update_typed)
+- [Function `update_internal`](#0x4_property_map_update_internal)
+- [Function `remove`](#0x4_property_map_remove)
+- [Function `assert_end_to_end_input`](#0x4_property_map_assert_end_to_end_input)
+
+
+use 0x1::bcs;
+use 0x1::error;
+use 0x1::from_bcs;
+use 0x1::object;
+use 0x1::simple_map;
+use 0x1::string;
+use 0x1::type_info;
+use 0x1::vector;
+
+
+
+
+
+
+## Resource `PropertyMap`
+
+A Map for typed key to value mapping, the contract using it
+should keep track of what keys are what types, and parse them accordingly.
+
+
+#[resource_group_member(#[group = 0x1::object::ObjectGroup])]
+struct PropertyMap has drop, key
+
+
+
+
+inner: simple_map::SimpleMap<string::String, property_map::PropertyValue>
+PropertyMap
to ensure that typing is always consistent
+
+
+struct PropertyValue has drop, store
+
+
+
+
+type: u8
+value: vector<u8>
+struct MutatorRef has drop, store
+
+
+
+
+self: address
+const ETYPE_MISMATCH: u64 = 6;
+
+
+
+
+
+
+
+
+const ADDRESS: u8 = 7;
+
+
+
+
+
+
+
+
+const BOOL: u8 = 0;
+
+
+
+
+
+
+
+
+const BYTE_VECTOR: u8 = 8;
+
+
+
+
+
+
+The property key already exists
+
+
+const EKEY_ALREADY_EXISTS_IN_PROPERTY_MAP: u64 = 2;
+
+
+
+
+
+
+Property key and type counts do not match
+
+
+const EKEY_TYPE_COUNT_MISMATCH: u64 = 5;
+
+
+
+
+
+
+Property key and value counts do not match
+
+
+const EKEY_VALUE_COUNT_MISMATCH: u64 = 4;
+
+
+
+
+
+
+The property map does not exist
+
+
+const EPROPERTY_MAP_DOES_NOT_EXIST: u64 = 1;
+
+
+
+
+
+
+The key of the property is too long
+
+
+const EPROPERTY_MAP_KEY_TOO_LONG: u64 = 8;
+
+
+
+
+
+
+The number of properties exceeds the maximum
+
+
+const ETOO_MANY_PROPERTIES: u64 = 3;
+
+
+
+
+
+
+Invalid value type specified
+
+
+const ETYPE_INVALID: u64 = 7;
+
+
+
+
+
+
+Maximum number of items in a PropertyMap
+
+
+const MAX_PROPERTY_MAP_SIZE: u64 = 1000;
+
+
+
+
+
+
+Maximum number of characters in a property name
+
+
+const MAX_PROPERTY_NAME_LENGTH: u64 = 128;
+
+
+
+
+
+
+
+
+const STRING: u8 = 9;
+
+
+
+
+
+
+
+
+const U128: u8 = 5;
+
+
+
+
+
+
+
+
+const U16: u8 = 2;
+
+
+
+
+
+
+
+
+const U256: u8 = 6;
+
+
+
+
+
+
+
+
+const U32: u8 = 3;
+
+
+
+
+
+
+
+
+const U64: u8 = 4;
+
+
+
+
+
+
+
+
+const U8: u8 = 1;
+
+
+
+
+
+
+## Function `init`
+
+
+
+public fun init(ref: &object::ConstructorRef, container: property_map::PropertyMap)
+
+
+
+
+public fun init(ref: &ConstructorRef, container: PropertyMap) {
+ let signer = object::generate_signer(ref);
+ move_to(&signer, container);
+}
+
+
+
+
+public fun extend(ref: &object::ExtendRef, container: property_map::PropertyMap)
+
+
+
+
+public fun extend(ref: &ExtendRef, container: PropertyMap) {
+ let signer = object::generate_signer_for_extending(ref);
+ move_to(&signer, container);
+}
+
+
+
+
+public fun burn(ref: property_map::MutatorRef)
+
+
+
+
+public fun burn(ref: MutatorRef) acquires PropertyMap {
+ move_from<PropertyMap>(ref.self);
+}
+
+
+
+
+public fun prepare_input(keys: vector<string::String>, types: vector<string::String>, values: vector<vector<u8>>): property_map::PropertyMap
+
+
+
+
+public fun prepare_input(
+ keys: vector<String>,
+ types: vector<String>,
+ values: vector<vector<u8>>,
+): PropertyMap {
+ let length = vector::length(&keys);
+ assert!(length <= MAX_PROPERTY_MAP_SIZE, error::invalid_argument(ETOO_MANY_PROPERTIES));
+ assert!(length == vector::length(&values), error::invalid_argument(EKEY_VALUE_COUNT_MISMATCH));
+ assert!(length == vector::length(&types), error::invalid_argument(EKEY_TYPE_COUNT_MISMATCH));
+
+ let container = simple_map::create<String, PropertyValue>();
+ while (!vector::is_empty(&keys)) {
+ let key = vector::pop_back(&mut keys);
+ assert!(
+ string::length(&key) <= MAX_PROPERTY_NAME_LENGTH,
+ error::invalid_argument(EPROPERTY_MAP_KEY_TOO_LONG),
+ );
+
+ let value = vector::pop_back(&mut values);
+ let type = vector::pop_back(&mut types);
+
+ let new_type = to_internal_type(type);
+ validate_type(new_type, value);
+
+ simple_map::add(&mut container, key, PropertyValue { value, type: new_type });
+ };
+
+ PropertyMap { inner: container }
+}
+
+
+
+
+String
representation of types from their u8
representation
+
+
+fun to_external_type(type: u8): string::String
+
+
+
+
+inline fun to_external_type(type: u8): String {
+ if (type == BOOL) {
+ string::utf8(b"bool")
+ } else if (type == U8) {
+ string::utf8(b"u8")
+ } else if (type == U16) {
+ string::utf8(b"u16")
+ } else if (type == U32) {
+ string::utf8(b"u32")
+ } else if (type == U64) {
+ string::utf8(b"u64")
+ } else if (type == U128) {
+ string::utf8(b"u128")
+ } else if (type == U256) {
+ string::utf8(b"u256")
+ } else if (type == ADDRESS) {
+ string::utf8(b"address")
+ } else if (type == BYTE_VECTOR) {
+ string::utf8(b"vector<u8>")
+ } else if (type == STRING) {
+ string::utf8(b"0x1::string::String")
+ } else {
+ abort (error::invalid_argument(ETYPE_INVALID))
+ }
+}
+
+
+
+
+String
representation of types to u8
+
+
+fun to_internal_type(type: string::String): u8
+
+
+
+
+inline fun to_internal_type(type: String): u8 {
+ if (type == string::utf8(b"bool")) {
+ BOOL
+ } else if (type == string::utf8(b"u8")) {
+ U8
+ } else if (type == string::utf8(b"u16")) {
+ U16
+ } else if (type == string::utf8(b"u32")) {
+ U32
+ } else if (type == string::utf8(b"u64")) {
+ U64
+ } else if (type == string::utf8(b"u128")) {
+ U128
+ } else if (type == string::utf8(b"u256")) {
+ U256
+ } else if (type == string::utf8(b"address")) {
+ ADDRESS
+ } else if (type == string::utf8(b"vector<u8>")) {
+ BYTE_VECTOR
+ } else if (type == string::utf8(b"0x1::string::String")) {
+ STRING
+ } else {
+ abort (error::invalid_argument(ETYPE_INVALID))
+ }
+}
+
+
+
+
+u8
representation
+
+
+fun type_info_to_internal_type<T>(): u8
+
+
+
+
+inline fun type_info_to_internal_type<T>(): u8 {
+ let type = type_info::type_name<T>();
+ to_internal_type(type)
+}
+
+
+
+
+fun validate_type(type: u8, value: vector<u8>)
+
+
+
+
+inline fun validate_type(type: u8, value: vector<u8>) {
+ if (type == BOOL) {
+ from_bcs::to_bool(value);
+ } else if (type == U8) {
+ from_bcs::to_u8(value);
+ } else if (type == U16) {
+ from_bcs::to_u16(value);
+ } else if (type == U32) {
+ from_bcs::to_u32(value);
+ } else if (type == U64) {
+ from_bcs::to_u64(value);
+ } else if (type == U128) {
+ from_bcs::to_u128(value);
+ } else if (type == U256) {
+ from_bcs::to_u256(value);
+ } else if (type == ADDRESS) {
+ from_bcs::to_address(value);
+ } else if (type == BYTE_VECTOR) {
+ // nothing to validate...
+ } else if (type == STRING) {
+ from_bcs::to_string(value);
+ } else {
+ abort (error::invalid_argument(ETYPE_MISMATCH))
+ };
+}
+
+
+
+
+public fun generate_mutator_ref(ref: &object::ConstructorRef): property_map::MutatorRef
+
+
+
+
+public fun generate_mutator_ref(ref: &ConstructorRef): MutatorRef {
+ MutatorRef { self: object::address_from_constructor_ref(ref) }
+}
+
+
+
+
+public fun contains_key<T: key>(object: &object::Object<T>, key: &string::String): bool
+
+
+
+
+public fun contains_key<T: key>(object: &Object<T>, key: &String): bool acquires PropertyMap {
+ assert_exists(object::object_address(object));
+ let property_map = borrow_global<PropertyMap>(object::object_address(object));
+ simple_map::contains_key(&property_map.inner, key)
+}
+
+
+
+
+public fun length<T: key>(object: &object::Object<T>): u64
+
+
+
+
+public fun length<T: key>(object: &Object<T>): u64 acquires PropertyMap {
+ assert_exists(object::object_address(object));
+ let property_map = borrow_global<PropertyMap>(object::object_address(object));
+ simple_map::length(&property_map.inner)
+}
+
+
+
+
+read_<type>
where the type is already known.
+
+
+public fun read<T: key>(object: &object::Object<T>, key: &string::String): (string::String, vector<u8>)
+
+
+
+
+public fun read<T: key>(object: &Object<T>, key: &String): (String, vector<u8>) acquires PropertyMap {
+ assert_exists(object::object_address(object));
+ let property_map = borrow_global<PropertyMap>(object::object_address(object));
+ let property_value = simple_map::borrow(&property_map.inner, key);
+ let new_type = to_external_type(property_value.type);
+ (new_type, property_value.value)
+}
+
+
+
+
+fun assert_exists(object: address)
+
+
+
+
+inline fun assert_exists(object: address) {
+ assert!(
+ exists<PropertyMap>(object),
+ error::not_found(EPROPERTY_MAP_DOES_NOT_EXIST),
+ );
+}
+
+
+
+
+fun read_typed<T: key, V>(object: &object::Object<T>, key: &string::String): vector<u8>
+
+
+
+
+inline fun read_typed<T: key, V>(object: &Object<T>, key: &String): vector<u8> acquires PropertyMap {
+ let (type, value) = read(object, key);
+ assert!(
+ type == type_info::type_name<V>(),
+ error::invalid_argument(ETYPE_MISMATCH),
+ );
+ value
+}
+
+
+
+
+public fun read_bool<T: key>(object: &object::Object<T>, key: &string::String): bool
+
+
+
+
+public fun read_bool<T: key>(object: &Object<T>, key: &String): bool acquires PropertyMap {
+ let value = read_typed<T, bool>(object, key);
+ from_bcs::to_bool(value)
+}
+
+
+
+
+public fun read_u8<T: key>(object: &object::Object<T>, key: &string::String): u8
+
+
+
+
+public fun read_u8<T: key>(object: &Object<T>, key: &String): u8 acquires PropertyMap {
+ let value = read_typed<T, u8>(object, key);
+ from_bcs::to_u8(value)
+}
+
+
+
+
+public fun read_u16<T: key>(object: &object::Object<T>, key: &string::String): u16
+
+
+
+
+public fun read_u16<T: key>(object: &Object<T>, key: &String): u16 acquires PropertyMap {
+ let value = read_typed<T, u16>(object, key);
+ from_bcs::to_u16(value)
+}
+
+
+
+
+public fun read_u32<T: key>(object: &object::Object<T>, key: &string::String): u32
+
+
+
+
+public fun read_u32<T: key>(object: &Object<T>, key: &String): u32 acquires PropertyMap {
+ let value = read_typed<T, u32>(object, key);
+ from_bcs::to_u32(value)
+}
+
+
+
+
+public fun read_u64<T: key>(object: &object::Object<T>, key: &string::String): u64
+
+
+
+
+public fun read_u64<T: key>(object: &Object<T>, key: &String): u64 acquires PropertyMap {
+ let value = read_typed<T, u64>(object, key);
+ from_bcs::to_u64(value)
+}
+
+
+
+
+public fun read_u128<T: key>(object: &object::Object<T>, key: &string::String): u128
+
+
+
+
+public fun read_u128<T: key>(object: &Object<T>, key: &String): u128 acquires PropertyMap {
+ let value = read_typed<T, u128>(object, key);
+ from_bcs::to_u128(value)
+}
+
+
+
+
+public fun read_u256<T: key>(object: &object::Object<T>, key: &string::String): u256
+
+
+
+
+public fun read_u256<T: key>(object: &Object<T>, key: &String): u256 acquires PropertyMap {
+ let value = read_typed<T, u256>(object, key);
+ from_bcs::to_u256(value)
+}
+
+
+
+
+public fun read_address<T: key>(object: &object::Object<T>, key: &string::String): address
+
+
+
+
+public fun read_address<T: key>(object: &Object<T>, key: &String): address acquires PropertyMap {
+ let value = read_typed<T, address>(object, key);
+ from_bcs::to_address(value)
+}
+
+
+
+
+public fun read_bytes<T: key>(object: &object::Object<T>, key: &string::String): vector<u8>
+
+
+
+
+public fun read_bytes<T: key>(object: &Object<T>, key: &String): vector<u8> acquires PropertyMap {
+ let value = read_typed<T, vector<u8>>(object, key);
+ from_bcs::to_bytes(value)
+}
+
+
+
+
+public fun read_string<T: key>(object: &object::Object<T>, key: &string::String): string::String
+
+
+
+
+public fun read_string<T: key>(object: &Object<T>, key: &String): String acquires PropertyMap {
+ let value = read_typed<T, String>(object, key);
+ from_bcs::to_string(value)
+}
+
+
+
+
+vector<u8>
+
+
+public fun add(ref: &property_map::MutatorRef, key: string::String, type: string::String, value: vector<u8>)
+
+
+
+
+public fun add(ref: &MutatorRef, key: String, type: String, value: vector<u8>) acquires PropertyMap {
+ let new_type = to_internal_type(type);
+ validate_type(new_type, value);
+ add_internal(ref, key, new_type, value);
+}
+
+
+
+
+vector<u8>
+
+
+public fun add_typed<T: drop>(ref: &property_map::MutatorRef, key: string::String, value: T)
+
+
+
+
+public fun add_typed<T: drop>(ref: &MutatorRef, key: String, value: T) acquires PropertyMap {
+ let type = type_info_to_internal_type<T>();
+ add_internal(ref, key, type, bcs::to_bytes(&value));
+}
+
+
+
+
+fun add_internal(ref: &property_map::MutatorRef, key: string::String, type: u8, value: vector<u8>)
+
+
+
+
+inline fun add_internal(ref: &MutatorRef, key: String, type: u8, value: vector<u8>) acquires PropertyMap {
+ assert_exists(ref.self);
+ let property_map = borrow_global_mut<PropertyMap>(ref.self);
+ simple_map::add(&mut property_map.inner, key, PropertyValue { type, value });
+}
+
+
+
+
+public fun update(ref: &property_map::MutatorRef, key: &string::String, type: string::String, value: vector<u8>)
+
+
+
+
+public fun update(ref: &MutatorRef, key: &String, type: String, value: vector<u8>) acquires PropertyMap {
+ let new_type = to_internal_type(type);
+ validate_type(new_type, value);
+ update_internal(ref, key, new_type, value);
+}
+
+
+
+
+public fun update_typed<T: drop>(ref: &property_map::MutatorRef, key: &string::String, value: T)
+
+
+
+
+public fun update_typed<T: drop>(ref: &MutatorRef, key: &String, value: T) acquires PropertyMap {
+ let type = type_info_to_internal_type<T>();
+ update_internal(ref, key, type, bcs::to_bytes(&value));
+}
+
+
+
+
+fun update_internal(ref: &property_map::MutatorRef, key: &string::String, type: u8, value: vector<u8>)
+
+
+
+
+inline fun update_internal(ref: &MutatorRef, key: &String, type: u8, value: vector<u8>) acquires PropertyMap {
+ assert_exists(ref.self);
+ let property_map = borrow_global_mut<PropertyMap>(ref.self);
+ let old_value = simple_map::borrow_mut(&mut property_map.inner, key);
+ *old_value = PropertyValue { type, value };
+}
+
+
+
+
+public fun remove(ref: &property_map::MutatorRef, key: &string::String)
+
+
+
+
+public fun remove(ref: &MutatorRef, key: &String) acquires PropertyMap {
+ assert_exists(ref.self);
+ let property_map = borrow_global_mut<PropertyMap>(ref.self);
+ simple_map::remove(&mut property_map.inner, key);
+}
+
+
+
+
+fun assert_end_to_end_input(object: object::Object<object::ObjectCore>)
+
+
+
+
+fun assert_end_to_end_input(object: Object<ObjectCore>) acquires PropertyMap {
+ assert!(read_bool(&object, &string::utf8(b"bool")), 0);
+ assert!(read_u8(&object, &string::utf8(b"u8")) == 0x12, 1);
+ assert!(read_u16(&object, &string::utf8(b"u16")) == 0x1234, 2);
+ assert!(read_u32(&object, &string::utf8(b"u32")) == 0x12345678, 3);
+ assert!(read_u64(&object, &string::utf8(b"u64")) == 0x1234567812345678, 4);
+ assert!(read_u128(&object, &string::utf8(b"u128")) == 0x12345678123456781234567812345678, 5);
+ assert!(
+ read_u256(
+ &object,
+ &string::utf8(b"u256")
+ ) == 0x1234567812345678123456781234567812345678123456781234567812345678,
+ 6
+ );
+ assert!(read_bytes(&object, &string::utf8(b"vector<u8>")) == vector[0x01], 7);
+ assert!(read_string(&object, &string::utf8(b"0x1::string::String")) == string::utf8(b"a"), 8);
+
+ assert!(length(&object) == 9, 9);
+}
+
+
+
+
+use 0x1::error;
+use 0x1::object;
+use 0x1::option;
+
+
+
+
+
+
+## Resource `Royalty`
+
+The royalty of a token within this collection
+
+Royalties are optional for a collection. Royalty percentage is calculated
+by (numerator / denominator) * 100%
+
+
+#[resource_group_member(#[group = 0x1::object::ObjectGroup])]
+struct Royalty has copy, drop, key
+
+
+
+
+numerator: u64
+denominator: u64
+payee_address: address
+shared_account
for how to handle multiple
+ creators.
+MutatorRef
.
+
+
+struct MutatorRef has drop, store
+
+
+
+
+inner: object::ExtendRef
+const EROYALTY_DENOMINATOR_IS_ZERO: u64 = 3;
+
+
+
+
+
+
+Royalty does not exist
+
+
+const EROYALTY_DOES_NOT_EXIST: u64 = 1;
+
+
+
+
+
+
+The royalty cannot be greater than 100%
+
+
+const EROYALTY_EXCEEDS_MAXIMUM: u64 = 2;
+
+
+
+
+
+
+## Function `init`
+
+Add a royalty, given a ConstructorRef.
+
+
+public fun init(ref: &object::ConstructorRef, royalty: royalty::Royalty)
+
+
+
+
+public fun init(ref: &ConstructorRef, royalty: Royalty) {
+ let signer = object::generate_signer(ref);
+ move_to(&signer, royalty);
+}
+
+
+
+
+public fun update(mutator_ref: &royalty::MutatorRef, royalty: royalty::Royalty)
+
+
+
+
+public fun update(mutator_ref: &MutatorRef, royalty: Royalty) acquires Royalty {
+ let addr = object::address_from_extend_ref(&mutator_ref.inner);
+ if (exists<Royalty>(addr)) {
+ move_from<Royalty>(addr);
+ };
+
+ let signer = object::generate_signer_for_extending(&mutator_ref.inner);
+ move_to(&signer, royalty);
+}
+
+
+
+
+public fun create(numerator: u64, denominator: u64, payee_address: address): royalty::Royalty
+
+
+
+
+public fun create(numerator: u64, denominator: u64, payee_address: address): Royalty {
+ assert!(denominator != 0, error::out_of_range(EROYALTY_DENOMINATOR_IS_ZERO));
+ assert!(numerator <= denominator, error::out_of_range(EROYALTY_EXCEEDS_MAXIMUM));
+
+ Royalty { numerator, denominator, payee_address }
+}
+
+
+
+
+public fun generate_mutator_ref(ref: object::ExtendRef): royalty::MutatorRef
+
+
+
+
+public fun generate_mutator_ref(ref: ExtendRef): MutatorRef {
+ MutatorRef { inner: ref }
+}
+
+
+
+
+public fun exists_at(addr: address): bool
+
+
+
+
+public fun exists_at(addr: address): bool {
+ exists<Royalty>(addr)
+}
+
+
+
+
+public(friend) fun delete(addr: address)
+
+
+
+
+public(friend) fun delete(addr: address) acquires Royalty {
+ assert!(exists<Royalty>(addr), error::not_found(EROYALTY_DOES_NOT_EXIST));
+ move_from<Royalty>(addr);
+}
+
+
+
+
+public fun get<T: key>(maybe_royalty: object::Object<T>): option::Option<royalty::Royalty>
+
+
+
+
+public fun get<T: key>(maybe_royalty: Object<T>): Option<Royalty> acquires Royalty {
+ let obj_addr = object::object_address(&maybe_royalty);
+ if (exists<Royalty>(obj_addr)) {
+ option::some(*borrow_global<Royalty>(obj_addr))
+ } else {
+ option::none()
+ }
+}
+
+
+
+
+public fun denominator(royalty: &royalty::Royalty): u64
+
+
+
+
+public fun denominator(royalty: &Royalty): u64 {
+ royalty.denominator
+}
+
+
+
+
+public fun numerator(royalty: &royalty::Royalty): u64
+
+
+
+
+public fun numerator(royalty: &Royalty): u64 {
+ royalty.numerator
+}
+
+
+
+
+public fun payee_address(royalty: &royalty::Royalty): address
+
+
+
+
+public fun payee_address(royalty: &Royalty): address {
+ royalty.payee_address
+}
+
+
+
+
+use 0x1::aggregator_v2;
+use 0x1::error;
+use 0x1::event;
+use 0x1::features;
+use 0x1::object;
+use 0x1::option;
+use 0x1::signer;
+use 0x1::string;
+use 0x1::vector;
+use 0x4::collection;
+use 0x4::royalty;
+
+
+
+
+
+
+## Resource `Token`
+
+Represents the common fields to all tokens.
+
+
+#[resource_group_member(#[group = 0x1::object::ObjectGroup])]
+struct Token has key
+
+
+
+
+collection: object::Object<collection::Collection>
+index: u64
+index
inside TokenIdentifiers.
+ Was populated until concurrent_token_v2_enabled feature flag was enabled.
+
+ Unique identifier within the collection, optional, 0 means unassigned
+description: string::String
+name: string::String
+name
inside TokenIdentifiers.
+ Was populated until concurrent_token_v2_enabled feature flag was enabled.
+
+ The name of the token, which should be unique within the collection; the length of name
+ should be smaller than 128, characters, eg: "Aptos Animal #1234"
+uri: string::String
+mutation_events: event::EventHandle<token::MutationEvent>
+#[resource_group_member(#[group = 0x1::object::ObjectGroup])]
+struct TokenIdentifiers has key
+
+
+
+
+index: aggregator_v2::AggregatorSnapshot<u64>
+name: aggregator_v2::DerivedStringSnapshot
+#[resource_group_member(#[group = 0x1::object::ObjectGroup])]
+#[deprecated]
+struct ConcurrentTokenIdentifiers has key
+
+
+
+
+index: aggregator_v2::AggregatorSnapshot<u64>
+name: aggregator_v2::AggregatorSnapshot<string::String>
+struct BurnRef has drop, store
+
+
+
+
+inner: option::Option<object::DeleteRef>
+self: option::Option<address>
+struct MutatorRef has drop, store
+
+
+
+
+self: address
+struct MutationEvent has drop, store
+
+
+
+
+mutated_field_name: string::String
+old_value: string::String
+new_value: string::String
+#[event]
+struct Mutation has drop, store
+
+
+
+
+token_address: address
+mutated_field_name: string::String
+old_value: string::String
+new_value: string::String
+const EURI_TOO_LONG: u64 = 5;
+
+
+
+
+
+
+
+
+const MAX_URI_LENGTH: u64 = 512;
+
+
+
+
+
+
+The description is over the maximum length
+
+
+const EDESCRIPTION_TOO_LONG: u64 = 6;
+
+
+
+
+
+
+
+
+const MAX_DESCRIPTION_LENGTH: u64 = 2048;
+
+
+
+
+
+
+The field being changed is not mutable
+
+
+const EFIELD_NOT_MUTABLE: u64 = 3;
+
+
+
+
+
+
+The provided signer is not the creator
+
+
+const ENOT_CREATOR: u64 = 2;
+
+
+
+
+
+
+The seed is over the maximum length
+
+
+const ESEED_TOO_LONG: u64 = 7;
+
+
+
+
+
+
+The token does not exist
+
+
+const ETOKEN_DOES_NOT_EXIST: u64 = 1;
+
+
+
+
+
+
+The token name is over the maximum length
+
+
+const ETOKEN_NAME_TOO_LONG: u64 = 4;
+
+
+
+
+
+
+
+
+const MAX_TOKEN_NAME_LENGTH: u64 = 128;
+
+
+
+
+
+
+
+
+const MAX_TOKEN_SEED_LENGTH: u64 = 128;
+
+
+
+
+
+
+## Function `create_common`
+
+
+
+fun create_common(creator: &signer, constructor_ref: &object::ConstructorRef, collection_name: string::String, description: string::String, name_prefix: string::String, name_with_index_suffix: option::Option<string::String>, royalty: option::Option<royalty::Royalty>, uri: string::String)
+
+
+
+
+inline fun create_common(
+ creator: &signer,
+ constructor_ref: &ConstructorRef,
+ collection_name: String,
+ description: String,
+ name_prefix: String,
+ // If option::some, numbered token is created - i.e. index is appended to the name.
+ // If option::none, name_prefix is the full name of the token.
+ name_with_index_suffix: Option<String>,
+ royalty: Option<Royalty>,
+ uri: String,
+) {
+ let creator_address = signer::address_of(creator);
+ let collection_addr = collection::create_collection_address(&creator_address, &collection_name);
+ let collection = object::address_to_object<Collection>(collection_addr);
+
+ create_common_with_collection(
+ creator,
+ constructor_ref,
+ collection,
+ description,
+ name_prefix,
+ name_with_index_suffix,
+ royalty,
+ uri
+ )
+}
+
+
+
+
+fun create_common_with_collection(creator: &signer, constructor_ref: &object::ConstructorRef, collection: object::Object<collection::Collection>, description: string::String, name_prefix: string::String, name_with_index_suffix: option::Option<string::String>, royalty: option::Option<royalty::Royalty>, uri: string::String)
+
+
+
+
+inline fun create_common_with_collection(
+ creator: &signer,
+ constructor_ref: &ConstructorRef,
+ collection: Object<Collection>,
+ description: String,
+ name_prefix: String,
+ // If option::some, numbered token is created - i.e. index is appended to the name.
+ // If option::none, name_prefix is the full name of the token.
+ name_with_index_suffix: Option<String>,
+ royalty: Option<Royalty>,
+ uri: String,
+) {
+ assert!(collection::creator(collection) == signer::address_of(creator), error::unauthenticated(ENOT_CREATOR));
+
+ if (option::is_some(&name_with_index_suffix)) {
+ // Be conservative, as we don't know what length the index will be, and assume worst case (20 chars in MAX_U64)
+ assert!(
+ string::length(&name_prefix) + 20 + string::length(
+ option::borrow(&name_with_index_suffix)
+ ) <= MAX_TOKEN_NAME_LENGTH,
+ error::out_of_range(ETOKEN_NAME_TOO_LONG)
+ );
+ } else {
+ assert!(string::length(&name_prefix) <= MAX_TOKEN_NAME_LENGTH, error::out_of_range(ETOKEN_NAME_TOO_LONG));
+ };
+ assert!(string::length(&description) <= MAX_DESCRIPTION_LENGTH, error::out_of_range(EDESCRIPTION_TOO_LONG));
+ assert!(string::length(&uri) <= MAX_URI_LENGTH, error::out_of_range(EURI_TOO_LONG));
+
+ let object_signer = object::generate_signer(constructor_ref);
+
+ let index = option::destroy_with_default(
+ collection::increment_supply(&collection, signer::address_of(&object_signer)),
+ aggregator_v2::create_snapshot<u64>(0)
+ );
+
+ // If create_numbered_token called us, add index to the name.
+ let name = if (option::is_some(&name_with_index_suffix)) {
+ aggregator_v2::derive_string_concat(name_prefix, &index, option::extract(&mut name_with_index_suffix))
+ } else {
+ aggregator_v2::create_derived_string(name_prefix)
+ };
+
+ let deprecated_index = 0;
+ let deprecated_name = string::utf8(b"");
+
+ let token_concurrent = TokenIdentifiers {
+ index,
+ name,
+ };
+ move_to(&object_signer, token_concurrent);
+
+ let token = Token {
+ collection,
+ index: deprecated_index,
+ description,
+ name: deprecated_name,
+ uri,
+ mutation_events: object::new_event_handle(&object_signer),
+ };
+ move_to(&object_signer, token);
+
+ if (option::is_some(&royalty)) {
+ royalty::init(constructor_ref, option::extract(&mut royalty))
+ };
+}
+
+
+
+
+public fun create_token(creator: &signer, collection: object::Object<collection::Collection>, description: string::String, name: string::String, royalty: option::Option<royalty::Royalty>, uri: string::String): object::ConstructorRef
+
+
+
+
+public fun create_token(
+ creator: &signer,
+ collection: Object<Collection>,
+ description: String,
+ name: String,
+ royalty: Option<Royalty>,
+ uri: String,
+): ConstructorRef {
+ let creator_address = signer::address_of(creator);
+ let constructor_ref = object::create_object(creator_address);
+ create_common_with_collection(
+ creator,
+ &constructor_ref,
+ collection,
+ description,
+ name,
+ option::none(),
+ royalty,
+ uri
+ );
+ constructor_ref
+}
+
+
+
+
+public fun create(creator: &signer, collection_name: string::String, description: string::String, name: string::String, royalty: option::Option<royalty::Royalty>, uri: string::String): object::ConstructorRef
+
+
+
+
+public fun create(
+ creator: &signer,
+ collection_name: String,
+ description: String,
+ name: String,
+ royalty: Option<Royalty>,
+ uri: String,
+): ConstructorRef {
+ let creator_address = signer::address_of(creator);
+ let constructor_ref = object::create_object(creator_address);
+ create_common(
+ creator,
+ &constructor_ref,
+ collection_name,
+ description,
+ name,
+ option::none(),
+ royalty,
+ uri
+ );
+ constructor_ref
+}
+
+
+
+
+public fun create_numbered_token_object(creator: &signer, collection: object::Object<collection::Collection>, description: string::String, name_with_index_prefix: string::String, name_with_index_suffix: string::String, royalty: option::Option<royalty::Royalty>, uri: string::String): object::ConstructorRef
+
+
+
+
+public fun create_numbered_token_object(
+ creator: &signer,
+ collection: Object<Collection>,
+ description: String,
+ name_with_index_prefix: String,
+ name_with_index_suffix: String,
+ royalty: Option<Royalty>,
+ uri: String,
+): ConstructorRef {
+ let creator_address = signer::address_of(creator);
+ let constructor_ref = object::create_object(creator_address);
+ create_common_with_collection(
+ creator,
+ &constructor_ref,
+ collection,
+ description,
+ name_with_index_prefix,
+ option::some(name_with_index_suffix),
+ royalty,
+ uri
+ );
+ constructor_ref
+}
+
+
+
+
+public fun create_numbered_token(creator: &signer, collection_name: string::String, description: string::String, name_with_index_prefix: string::String, name_with_index_suffix: string::String, royalty: option::Option<royalty::Royalty>, uri: string::String): object::ConstructorRef
+
+
+
+
+public fun create_numbered_token(
+ creator: &signer,
+ collection_name: String,
+ description: String,
+ name_with_index_prefix: String,
+ name_with_index_suffix: String,
+ royalty: Option<Royalty>,
+ uri: String,
+): ConstructorRef {
+ let creator_address = signer::address_of(creator);
+ let constructor_ref = object::create_object(creator_address);
+ create_common(
+ creator,
+ &constructor_ref,
+ collection_name,
+ description,
+ name_with_index_prefix,
+ option::some(name_with_index_suffix),
+ royalty,
+ uri
+ );
+ constructor_ref
+}
+
+
+
+
+public fun create_named_token_object(creator: &signer, collection: object::Object<collection::Collection>, description: string::String, name: string::String, royalty: option::Option<royalty::Royalty>, uri: string::String): object::ConstructorRef
+
+
+
+
+public fun create_named_token_object(
+ creator: &signer,
+ collection: Object<Collection>,
+ description: String,
+ name: String,
+ royalty: Option<Royalty>,
+ uri: String,
+): ConstructorRef {
+ let seed = create_token_seed(&collection::name(collection), &name);
+ let constructor_ref = object::create_named_object(creator, seed);
+ create_common_with_collection(
+ creator,
+ &constructor_ref,
+ collection,
+ description,
+ name,
+ option::none(),
+ royalty,
+ uri
+ );
+ constructor_ref
+}
+
+
+
+
+public fun create_named_token(creator: &signer, collection_name: string::String, description: string::String, name: string::String, royalty: option::Option<royalty::Royalty>, uri: string::String): object::ConstructorRef
+
+
+
+
+public fun create_named_token(
+ creator: &signer,
+ collection_name: String,
+ description: String,
+ name: String,
+ royalty: Option<Royalty>,
+ uri: String,
+): ConstructorRef {
+ let seed = create_token_seed(&collection_name, &name);
+
+ let constructor_ref = object::create_named_object(creator, seed);
+ create_common(
+ creator,
+ &constructor_ref,
+ collection_name,
+ description,
+ name,
+ option::none(),
+ royalty,
+ uri
+ );
+ constructor_ref
+}
+
+
+
+
+public fun create_named_token_from_seed(creator: &signer, collection: object::Object<collection::Collection>, description: string::String, name: string::String, seed: string::String, royalty: option::Option<royalty::Royalty>, uri: string::String): object::ConstructorRef
+
+
+
+
+public fun create_named_token_from_seed(
+ creator: &signer,
+ collection: Object<Collection>,
+ description: String,
+ name: String,
+ seed: String,
+ royalty: Option<Royalty>,
+ uri: String,
+): ConstructorRef {
+ let seed = create_token_name_with_seed(&collection::name(collection), &name, &seed);
+ let constructor_ref = object::create_named_object(creator, seed);
+ create_common_with_collection(creator, &constructor_ref, collection, description, name, option::none(), royalty, uri);
+ constructor_ref
+}
+
+
+
+
+create
instead for identical behavior.
+
+Creates a new token object from an account GUID and returns the ConstructorRef for
+additional specialization.
+
+
+#[deprecated]
+public fun create_from_account(creator: &signer, collection_name: string::String, description: string::String, name: string::String, royalty: option::Option<royalty::Royalty>, uri: string::String): object::ConstructorRef
+
+
+
+
+public fun create_from_account(
+ creator: &signer,
+ collection_name: String,
+ description: String,
+ name: String,
+ royalty: Option<Royalty>,
+ uri: String,
+): ConstructorRef {
+ let constructor_ref = object::create_object_from_account(creator);
+ create_common(
+ creator,
+ &constructor_ref,
+ collection_name,
+ description,
+ name,
+ option::none(),
+ royalty,
+ uri
+ );
+ constructor_ref
+}
+
+
+
+
+public fun create_token_address(creator: &address, collection: &string::String, name: &string::String): address
+
+
+
+
+public fun create_token_address(creator: &address, collection: &String, name: &String): address {
+ object::create_object_address(creator, create_token_seed(collection, name))
+}
+
+
+
+
+#[view]
+public fun create_token_address_with_seed(creator: address, collection: string::String, name: string::String, seed: string::String): address
+
+
+
+
+public fun create_token_address_with_seed(creator: address, collection: String, name: String, seed: String): address {
+ let seed = create_token_name_with_seed(&collection, &name, &seed);
+ object::create_object_address(&creator, seed)
+}
+
+
+
+
+public fun create_token_seed(collection: &string::String, name: &string::String): vector<u8>
+
+
+
+
+public fun create_token_seed(collection: &String, name: &String): vector<u8> {
+ assert!(string::length(name) <= MAX_TOKEN_NAME_LENGTH, error::out_of_range(ETOKEN_NAME_TOO_LONG));
+ let seed = *string::bytes(collection);
+ vector::append(&mut seed, b"::");
+ vector::append(&mut seed, *string::bytes(name));
+ seed
+}
+
+
+
+
+public fun create_token_name_with_seed(collection: &string::String, name: &string::String, seed: &string::String): vector<u8>
+
+
+
+
+public fun create_token_name_with_seed(collection: &String, name: &String, seed: &String): vector<u8> {
+ assert!(string::length(seed) <= MAX_TOKEN_SEED_LENGTH, error::out_of_range(ESEED_TOO_LONG));
+ let seeds = create_token_seed(collection, name);
+ vector::append(&mut seeds, *string::bytes(seed));
+ seeds
+}
+
+
+
+
+public fun generate_mutator_ref(ref: &object::ConstructorRef): token::MutatorRef
+
+
+
+
+public fun generate_mutator_ref(ref: &ConstructorRef): MutatorRef {
+ let object = object::object_from_constructor_ref<Token>(ref);
+ MutatorRef { self: object::object_address(&object) }
+}
+
+
+
+
+public fun generate_burn_ref(ref: &object::ConstructorRef): token::BurnRef
+
+
+
+
+public fun generate_burn_ref(ref: &ConstructorRef): BurnRef {
+ let (inner, self) = if (object::can_generate_delete_ref(ref)) {
+ let delete_ref = object::generate_delete_ref(ref);
+ (option::some(delete_ref), option::none())
+ } else {
+ let addr = object::address_from_constructor_ref(ref);
+ (option::none(), option::some(addr))
+ };
+ BurnRef { self, inner }
+}
+
+
+
+
+public fun address_from_burn_ref(ref: &token::BurnRef): address
+
+
+
+
+public fun address_from_burn_ref(ref: &BurnRef): address {
+ if (option::is_some(&ref.inner)) {
+ object::address_from_delete_ref(option::borrow(&ref.inner))
+ } else {
+ *option::borrow(&ref.self)
+ }
+}
+
+
+
+
+fun borrow<T: key>(token: &object::Object<T>): &token::Token
+
+
+
+
+inline fun borrow<T: key>(token: &Object<T>): &Token acquires Token {
+ let token_address = object::object_address(token);
+ assert!(
+ exists<Token>(token_address),
+ error::not_found(ETOKEN_DOES_NOT_EXIST),
+ );
+ borrow_global<Token>(token_address)
+}
+
+
+
+
+#[view]
+public fun creator<T: key>(token: object::Object<T>): address
+
+
+
+
+public fun creator<T: key>(token: Object<T>): address acquires Token {
+ collection::creator(borrow(&token).collection)
+}
+
+
+
+
+#[view]
+public fun collection_name<T: key>(token: object::Object<T>): string::String
+
+
+
+
+public fun collection_name<T: key>(token: Object<T>): String acquires Token {
+ collection::name(borrow(&token).collection)
+}
+
+
+
+
+#[view]
+public fun collection_object<T: key>(token: object::Object<T>): object::Object<collection::Collection>
+
+
+
+
+public fun collection_object<T: key>(token: Object<T>): Object<Collection> acquires Token {
+ borrow(&token).collection
+}
+
+
+
+
+#[view]
+public fun description<T: key>(token: object::Object<T>): string::String
+
+
+
+
+public fun description<T: key>(token: Object<T>): String acquires Token {
+ borrow(&token).description
+}
+
+
+
+
+#[view]
+public fun name<T: key>(token: object::Object<T>): string::String
+
+
+
+
+public fun name<T: key>(token: Object<T>): String acquires Token, TokenIdentifiers {
+ let token_address = object::object_address(&token);
+ if (exists<TokenIdentifiers>(token_address)) {
+ aggregator_v2::read_derived_string(&borrow_global<TokenIdentifiers>(token_address).name)
+ } else {
+ borrow(&token).name
+ }
+}
+
+
+
+
+#[view]
+public fun uri<T: key>(token: object::Object<T>): string::String
+
+
+
+
+public fun uri<T: key>(token: Object<T>): String acquires Token {
+ borrow(&token).uri
+}
+
+
+
+
+#[view]
+public fun royalty<T: key>(token: object::Object<T>): option::Option<royalty::Royalty>
+
+
+
+
+public fun royalty<T: key>(token: Object<T>): Option<Royalty> acquires Token {
+ borrow(&token);
+ let royalty = royalty::get(token);
+ if (option::is_some(&royalty)) {
+ royalty
+ } else {
+ let creator = creator(token);
+ let collection_name = collection_name(token);
+ let collection_address = collection::create_collection_address(&creator, &collection_name);
+ let collection = object::address_to_object<collection::Collection>(collection_address);
+ royalty::get(collection)
+ }
+}
+
+
+
+
+#[view]
+public fun index<T: key>(token: object::Object<T>): u64
+
+
+
+
+public fun index<T: key>(token: Object<T>): u64 acquires Token, TokenIdentifiers {
+ let token_address = object::object_address(&token);
+ if (exists<TokenIdentifiers>(token_address)) {
+ aggregator_v2::read_snapshot(&borrow_global<TokenIdentifiers>(token_address).index)
+ } else {
+ borrow(&token).index
+ }
+}
+
+
+
+
+fun borrow_mut(mutator_ref: &token::MutatorRef): &mut token::Token
+
+
+
+
+inline fun borrow_mut(mutator_ref: &MutatorRef): &mut Token acquires Token {
+ assert!(
+ exists<Token>(mutator_ref.self),
+ error::not_found(ETOKEN_DOES_NOT_EXIST),
+ );
+ borrow_global_mut<Token>(mutator_ref.self)
+}
+
+
+
+
+public fun burn(burn_ref: token::BurnRef)
+
+
+
+
+public fun burn(burn_ref: BurnRef) acquires Token, TokenIdentifiers {
+ let (addr, previous_owner) = if (option::is_some(&burn_ref.inner)) {
+ let delete_ref = option::extract(&mut burn_ref.inner);
+ let addr = object::address_from_delete_ref(&delete_ref);
+ let previous_owner = object::owner(object::address_to_object<Token>(addr));
+ object::delete(delete_ref);
+ (addr, previous_owner)
+ } else {
+ let addr = option::extract(&mut burn_ref.self);
+ let previous_owner = object::owner(object::address_to_object<Token>(addr));
+ (addr, previous_owner)
+ };
+
+ if (royalty::exists_at(addr)) {
+ royalty::delete(addr)
+ };
+
+ let Token {
+ collection,
+ index: deprecated_index,
+ description: _,
+ name: _,
+ uri: _,
+ mutation_events,
+ } = move_from<Token>(addr);
+
+ let index = if (exists<TokenIdentifiers>(addr)) {
+ let TokenIdentifiers {
+ index,
+ name: _,
+ } = move_from<TokenIdentifiers>(addr);
+ aggregator_v2::read_snapshot(&index)
+ } else {
+ deprecated_index
+ };
+
+ event::destroy_handle(mutation_events);
+ collection::decrement_supply(&collection, addr, option::some(index), previous_owner);
+}
+
+
+
+
+public fun set_description(mutator_ref: &token::MutatorRef, description: string::String)
+
+
+
+
+public fun set_description(mutator_ref: &MutatorRef, description: String) acquires Token {
+ assert!(string::length(&description) <= MAX_DESCRIPTION_LENGTH, error::out_of_range(EDESCRIPTION_TOO_LONG));
+ let token = borrow_mut(mutator_ref);
+ if (std::features::module_event_migration_enabled()) {
+ event::emit(Mutation {
+ token_address: mutator_ref.self,
+ mutated_field_name: string::utf8(b"description"),
+ old_value: token.description,
+ new_value: description
+ })
+ };
+ event::emit_event(
+ &mut token.mutation_events,
+ MutationEvent {
+ mutated_field_name: string::utf8(b"description"),
+ old_value: token.description,
+ new_value: description
+ },
+ );
+ token.description = description;
+}
+
+
+
+
+public fun set_name(mutator_ref: &token::MutatorRef, name: string::String)
+
+
+
+
+public fun set_name(mutator_ref: &MutatorRef, name: String) acquires Token, TokenIdentifiers {
+ assert!(string::length(&name) <= MAX_TOKEN_NAME_LENGTH, error::out_of_range(ETOKEN_NAME_TOO_LONG));
+
+ let token = borrow_mut(mutator_ref);
+
+ let old_name = if (exists<TokenIdentifiers>(mutator_ref.self)) {
+ let token_concurrent = borrow_global_mut<TokenIdentifiers>(mutator_ref.self);
+ let old_name = aggregator_v2::read_derived_string(&token_concurrent.name);
+ token_concurrent.name = aggregator_v2::create_derived_string(name);
+ old_name
+ } else {
+ let old_name = token.name;
+ token.name = name;
+ old_name
+ };
+
+ if (std::features::module_event_migration_enabled()) {
+ event::emit(Mutation {
+ token_address: mutator_ref.self,
+ mutated_field_name: string::utf8(b"name"),
+ old_value: old_name,
+ new_value: name
+ })
+ };
+ event::emit_event(
+ &mut token.mutation_events,
+ MutationEvent {
+ mutated_field_name: string::utf8(b"name"),
+ old_value: old_name,
+ new_value: name
+ },
+ );
+}
+
+
+
+
+public fun set_uri(mutator_ref: &token::MutatorRef, uri: string::String)
+
+
+
+
+public fun set_uri(mutator_ref: &MutatorRef, uri: String) acquires Token {
+ assert!(string::length(&uri) <= MAX_URI_LENGTH, error::out_of_range(EURI_TOO_LONG));
+ let token = borrow_mut(mutator_ref);
+ if (std::features::module_event_migration_enabled()) {
+ event::emit(Mutation {
+ token_address: mutator_ref.self,
+ mutated_field_name: string::utf8(b"uri"),
+ old_value: token.uri,
+ new_value: uri,
+ })
+ };
+ event::emit_event(
+ &mut token.mutation_events,
+ MutationEvent {
+ mutated_field_name: string::utf8(b"uri"),
+ old_value: token.uri,
+ new_value: uri,
+ },
+ );
+ token.uri = uri;
+}
+
+
+
+
+use 0x1::bcs;
+use 0x1::error;
+use 0x1::from_bcs;
+use 0x1::simple_map;
+use 0x1::string;
+use 0x1::type_info;
+
+
+
+
+
+
+## Struct `PropertyMap`
+
+
+
+struct PropertyMap has copy, drop, store
+
+
+
+
+map: simple_map::SimpleMap<string::String, property_map::PropertyValue>
+struct PropertyValue has copy, drop, store
+
+
+
+
+value: vector<u8>
+type: string::String
+const EKEY_AREADY_EXIST_IN_PROPERTY_MAP: u64 = 1;
+
+
+
+
+
+
+Property key and type count don't match
+
+
+const EKEY_COUNT_NOT_MATCH_TYPE_COUNT: u64 = 5;
+
+
+
+
+
+
+Property key and value count don't match
+
+
+const EKEY_COUNT_NOT_MATCH_VALUE_COUNT: u64 = 4;
+
+
+
+
+
+
+The name (key) of the property is too long
+
+
+const EPROPERTY_MAP_NAME_TOO_LONG: u64 = 7;
+
+
+
+
+
+
+The property doesn't exist
+
+
+const EPROPERTY_NOT_EXIST: u64 = 3;
+
+
+
+
+
+
+The number of property exceeds the limit
+
+
+const EPROPERTY_NUMBER_EXCEED_LIMIT: u64 = 2;
+
+
+
+
+
+
+Property type doesn't match
+
+
+const ETYPE_NOT_MATCH: u64 = 6;
+
+
+
+
+
+
+The maximal number of property that can be stored in property map
+
+
+const MAX_PROPERTY_MAP_SIZE: u64 = 1000;
+
+
+
+
+
+
+
+
+const MAX_PROPERTY_NAME_LENGTH: u64 = 128;
+
+
+
+
+
+
+## Function `new`
+
+
+
+public fun new(keys: vector<string::String>, values: vector<vector<u8>>, types: vector<string::String>): property_map::PropertyMap
+
+
+
+
+public fun new(
+ keys: vector<String>,
+ values: vector<vector<u8>>,
+ types: vector<String>
+): PropertyMap {
+ let length = vector::length(&keys);
+ assert!(length <= MAX_PROPERTY_MAP_SIZE, error::invalid_argument(EPROPERTY_NUMBER_EXCEED_LIMIT));
+ assert!(length == vector::length(&values), error::invalid_argument(EKEY_COUNT_NOT_MATCH_VALUE_COUNT));
+ assert!(length == vector::length(&types), error::invalid_argument(EKEY_COUNT_NOT_MATCH_TYPE_COUNT));
+
+ let properties = empty();
+
+ let i = 0;
+ while (i < length) {
+ let key = *vector::borrow(&keys, i);
+ assert!(string::length(&key) <= MAX_PROPERTY_NAME_LENGTH, error::invalid_argument(EPROPERTY_MAP_NAME_TOO_LONG));
+ simple_map::add(
+ &mut properties.map,
+ key,
+ PropertyValue { value: *vector::borrow(&values, i), type: *vector::borrow(&types, i) }
+ );
+ i = i + 1;
+ };
+ properties
+}
+
+
+
+
+public fun new_with_key_and_property_value(keys: vector<string::String>, values: vector<property_map::PropertyValue>): property_map::PropertyMap
+
+
+
+
+public fun new_with_key_and_property_value(
+ keys: vector<String>,
+ values: vector<PropertyValue>
+): PropertyMap {
+ let length = vector::length(&keys);
+ assert!(length <= MAX_PROPERTY_MAP_SIZE, error::invalid_argument(EPROPERTY_NUMBER_EXCEED_LIMIT));
+ assert!(length == vector::length(&values), error::invalid_argument(EKEY_COUNT_NOT_MATCH_VALUE_COUNT));
+
+ let properties = empty();
+
+ let i = 0;
+ while (i < length) {
+ let key = *vector::borrow(&keys, i);
+ let val = *vector::borrow(&values, i);
+ assert!(string::length(&key) <= MAX_PROPERTY_NAME_LENGTH, error::invalid_argument(EPROPERTY_MAP_NAME_TOO_LONG));
+ add(&mut properties, key, val);
+ i = i + 1;
+ };
+ properties
+}
+
+
+
+
+public fun empty(): property_map::PropertyMap
+
+
+
+
+public fun empty(): PropertyMap {
+ PropertyMap {
+ map: simple_map::create<String, PropertyValue>(),
+ }
+}
+
+
+
+
+public fun contains_key(map: &property_map::PropertyMap, key: &string::String): bool
+
+
+
+
+public fun contains_key(map: &PropertyMap, key: &String): bool {
+ simple_map::contains_key(&map.map, key)
+}
+
+
+
+
+public fun add(map: &mut property_map::PropertyMap, key: string::String, value: property_map::PropertyValue)
+
+
+
+
+public fun add(map: &mut PropertyMap, key: String, value: PropertyValue) {
+ assert!(string::length(&key) <= MAX_PROPERTY_NAME_LENGTH, error::invalid_argument(EPROPERTY_MAP_NAME_TOO_LONG));
+ assert!(simple_map::length(&map.map) < MAX_PROPERTY_MAP_SIZE, error::invalid_state(EPROPERTY_NUMBER_EXCEED_LIMIT));
+ simple_map::add(&mut map.map, key, value);
+}
+
+
+
+
+public fun length(map: &property_map::PropertyMap): u64
+
+
+
+
+public fun length(map: &PropertyMap): u64 {
+ simple_map::length(&map.map)
+}
+
+
+
+
+public fun borrow(map: &property_map::PropertyMap, key: &string::String): &property_map::PropertyValue
+
+
+
+
+public fun borrow(map: &PropertyMap, key: &String): &PropertyValue {
+ let found = contains_key(map, key);
+ assert!(found, EPROPERTY_NOT_EXIST);
+ simple_map::borrow(&map.map, key)
+}
+
+
+
+
+public fun keys(map: &property_map::PropertyMap): vector<string::String>
+
+
+
+
+public fun keys(map: &PropertyMap): vector<String> {
+ simple_map::keys(&map.map)
+}
+
+
+
+
+public fun types(map: &property_map::PropertyMap): vector<string::String>
+
+
+
+
+public fun types(map: &PropertyMap): vector<String> {
+ vector::map_ref(&simple_map::values(&map.map), |v| {
+ let v: &PropertyValue = v;
+ v.type
+ })
+}
+
+
+
+
+public fun values(map: &property_map::PropertyMap): vector<vector<u8>>
+
+
+
+
+public fun values(map: &PropertyMap): vector<vector<u8>> {
+ vector::map_ref(&simple_map::values(&map.map), |v| {
+ let v: &PropertyValue = v;
+ v.value
+ })
+}
+
+
+
+
+public fun read_string(map: &property_map::PropertyMap, key: &string::String): string::String
+
+
+
+
+public fun read_string(map: &PropertyMap, key: &String): String {
+ let prop = borrow(map, key);
+ assert!(prop.type == string::utf8(b"0x1::string::String"), error::invalid_state(ETYPE_NOT_MATCH));
+ from_bcs::to_string(prop.value)
+}
+
+
+
+
+public fun read_u8(map: &property_map::PropertyMap, key: &string::String): u8
+
+
+
+
+public fun read_u8(map: &PropertyMap, key: &String): u8 {
+ let prop = borrow(map, key);
+ assert!(prop.type == string::utf8(b"u8"), error::invalid_state(ETYPE_NOT_MATCH));
+ from_bcs::to_u8(prop.value)
+}
+
+
+
+
+public fun read_u64(map: &property_map::PropertyMap, key: &string::String): u64
+
+
+
+
+public fun read_u64(map: &PropertyMap, key: &String): u64 {
+ let prop = borrow(map, key);
+ assert!(prop.type == string::utf8(b"u64"), error::invalid_state(ETYPE_NOT_MATCH));
+ from_bcs::to_u64(prop.value)
+}
+
+
+
+
+public fun read_address(map: &property_map::PropertyMap, key: &string::String): address
+
+
+
+
+public fun read_address(map: &PropertyMap, key: &String): address {
+ let prop = borrow(map, key);
+ assert!(prop.type == string::utf8(b"address"), error::invalid_state(ETYPE_NOT_MATCH));
+ from_bcs::to_address(prop.value)
+}
+
+
+
+
+public fun read_u128(map: &property_map::PropertyMap, key: &string::String): u128
+
+
+
+
+public fun read_u128(map: &PropertyMap, key: &String): u128 {
+ let prop = borrow(map, key);
+ assert!(prop.type == string::utf8(b"u128"), error::invalid_state(ETYPE_NOT_MATCH));
+ from_bcs::to_u128(prop.value)
+}
+
+
+
+
+public fun read_bool(map: &property_map::PropertyMap, key: &string::String): bool
+
+
+
+
+public fun read_bool(map: &PropertyMap, key: &String): bool {
+ let prop = borrow(map, key);
+ assert!(prop.type == string::utf8(b"bool"), error::invalid_state(ETYPE_NOT_MATCH));
+ from_bcs::to_bool(prop.value)
+}
+
+
+
+
+public fun borrow_value(property: &property_map::PropertyValue): vector<u8>
+
+
+
+
+public fun borrow_value(property: &PropertyValue): vector<u8> {
+ property.value
+}
+
+
+
+
+public fun borrow_type(property: &property_map::PropertyValue): string::String
+
+
+
+
+public fun borrow_type(property: &PropertyValue): String {
+ property.type
+}
+
+
+
+
+public fun remove(map: &mut property_map::PropertyMap, key: &string::String): (string::String, property_map::PropertyValue)
+
+
+
+
+public fun remove(
+ map: &mut PropertyMap,
+ key: &String
+): (String, PropertyValue) {
+ let found = contains_key(map, key);
+ assert!(found, error::not_found(EPROPERTY_NOT_EXIST));
+ simple_map::remove(&mut map.map, key)
+}
+
+
+
+
+public fun update_property_map(map: &mut property_map::PropertyMap, keys: vector<string::String>, values: vector<vector<u8>>, types: vector<string::String>)
+
+
+
+
+public fun update_property_map(
+ map: &mut PropertyMap,
+ keys: vector<String>,
+ values: vector<vector<u8>>,
+ types: vector<String>,
+) {
+ let key_len = vector::length(&keys);
+ let val_len = vector::length(&values);
+ let typ_len = vector::length(&types);
+ assert!(key_len == val_len, error::invalid_state(EKEY_COUNT_NOT_MATCH_VALUE_COUNT));
+ assert!(key_len == typ_len, error::invalid_state(EKEY_COUNT_NOT_MATCH_TYPE_COUNT));
+
+ let i = 0;
+ while (i < key_len) {
+ let key = vector::borrow(&keys, i);
+ let prop_val = PropertyValue {
+ value: *vector::borrow(&values, i),
+ type: *vector::borrow(&types, i),
+ };
+ if (contains_key(map, key)) {
+ update_property_value(map, key, prop_val);
+ } else {
+ add(map, *key, prop_val);
+ };
+ i = i + 1;
+ }
+}
+
+
+
+
+public fun update_property_value(map: &mut property_map::PropertyMap, key: &string::String, value: property_map::PropertyValue)
+
+
+
+
+public fun update_property_value(
+ map: &mut PropertyMap,
+ key: &String,
+ value: PropertyValue
+) {
+ let property_val = simple_map::borrow_mut(&mut map.map, key);
+ *property_val = value;
+}
+
+
+
+
+public fun create_property_value_raw(value: vector<u8>, type: string::String): property_map::PropertyValue
+
+
+
+
+public fun create_property_value_raw(
+ value: vector<u8>,
+ type: String
+): PropertyValue {
+ PropertyValue {
+ value,
+ type,
+ }
+}
+
+
+
+
+public fun create_property_value<T: copy>(data: &T): property_map::PropertyValue
+
+
+
+
+public fun create_property_value<T: copy>(data: &T): PropertyValue {
+ let name = type_name<T>();
+ if (
+ name == string::utf8(b"bool") ||
+ name == string::utf8(b"u8") ||
+ name == string::utf8(b"u64") ||
+ name == string::utf8(b"u128") ||
+ name == string::utf8(b"address") ||
+ name == string::utf8(b"0x1::string::String")
+ ) {
+ create_property_value_raw(bcs::to_bytes<T>(data), name)
+ } else {
+ create_property_value_raw(bcs::to_bytes<T>(data), string::utf8(b"vector<u8>"))
+ }
+}
+
+
+
+
+pragma verify = true;
+pragma aborts_if_is_strict;
+let MAX_PROPERTY_MAP_SIZE = 1000;
+let MAX_PROPERTY_NAME_LENGTH = 128;
+
+
+
+
+
+
+### Function `new`
+
+
+public fun new(keys: vector<string::String>, values: vector<vector<u8>>, types: vector<string::String>): property_map::PropertyMap
+
+
+
+
+
+pragma aborts_if_is_partial;
+let length = len(keys);
+aborts_if !(length <= MAX_PROPERTY_MAP_SIZE);
+aborts_if !(length == vector::length(values));
+aborts_if !(length == vector::length(types));
+
+
+
+
+
+
+### Function `new_with_key_and_property_value`
+
+
+public fun new_with_key_and_property_value(keys: vector<string::String>, values: vector<property_map::PropertyValue>): property_map::PropertyMap
+
+
+
+
+
+pragma aborts_if_is_partial;
+let length = vector::length(keys);
+aborts_if !(length <= MAX_PROPERTY_MAP_SIZE);
+aborts_if !(length == len(values));
+
+
+
+
+
+
+### Function `empty`
+
+
+public fun empty(): property_map::PropertyMap
+
+
+
+
+
+aborts_if false;
+
+
+
+
+
+
+### Function `contains_key`
+
+
+public fun contains_key(map: &property_map::PropertyMap, key: &string::String): bool
+
+
+
+
+
+aborts_if false;
+
+
+
+
+
+
+### Function `add`
+
+
+public fun add(map: &mut property_map::PropertyMap, key: string::String, value: property_map::PropertyValue)
+
+
+
+
+
+aborts_if !(string::length(key) <= MAX_PROPERTY_NAME_LENGTH);
+aborts_if !(!simple_map::spec_contains_key(map.map, key));
+aborts_if !(simple_map::spec_len(map.map) < MAX_PROPERTY_MAP_SIZE);
+
+
+
+
+
+
+### Function `length`
+
+
+public fun length(map: &property_map::PropertyMap): u64
+
+
+
+
+
+aborts_if false;
+
+
+
+
+
+
+### Function `borrow`
+
+
+public fun borrow(map: &property_map::PropertyMap, key: &string::String): &property_map::PropertyValue
+
+
+
+
+
+aborts_if !simple_map::spec_contains_key(map.map, key);
+
+
+
+
+
+
+### Function `keys`
+
+
+public fun keys(map: &property_map::PropertyMap): vector<string::String>
+
+
+
+
+
+pragma verify = false;
+
+
+
+
+
+
+### Function `types`
+
+
+public fun types(map: &property_map::PropertyMap): vector<string::String>
+
+
+
+
+
+pragma verify = false;
+
+
+
+
+
+
+### Function `values`
+
+
+public fun values(map: &property_map::PropertyMap): vector<vector<u8>>
+
+
+
+
+
+pragma verify = false;
+
+
+
+
+
+
+### Function `read_string`
+
+
+public fun read_string(map: &property_map::PropertyMap, key: &string::String): string::String
+
+
+
+Check utf8 for correctness and whether equal
+to prop.type
+
+
+pragma aborts_if_is_partial;
+aborts_if !simple_map::spec_contains_key(map.map, key);
+aborts_if !string::spec_internal_check_utf8(b"0x1::string::String");
+let prop = simple_map::spec_get(map.map, key);
+aborts_if prop.type != spec_utf8(b"0x1::string::String");
+aborts_if !aptos_std::from_bcs::deserializable<String>(prop.value);
+
+
+
+
+
+
+
+
+fun spec_utf8(bytes: vector<u8>): String {
+ String{bytes}
+}
+
+
+
+
+
+
+### Function `read_u8`
+
+
+public fun read_u8(map: &property_map::PropertyMap, key: &string::String): u8
+
+
+
+
+
+let str = b"u8";
+aborts_if !simple_map::spec_contains_key(map.map, key);
+aborts_if !string::spec_internal_check_utf8(str);
+let prop = simple_map::spec_get(map.map, key);
+aborts_if prop.type != spec_utf8(str);
+aborts_if !aptos_std::from_bcs::deserializable<u8>(prop.value);
+
+
+
+
+
+
+### Function `read_u64`
+
+
+public fun read_u64(map: &property_map::PropertyMap, key: &string::String): u64
+
+
+
+
+
+let str = b"u64";
+aborts_if !simple_map::spec_contains_key(map.map, key);
+aborts_if !string::spec_internal_check_utf8(str);
+let prop = simple_map::spec_get(map.map, key);
+aborts_if prop.type != spec_utf8(str);
+aborts_if !aptos_std::from_bcs::deserializable<u64>(prop.value);
+
+
+
+
+
+
+### Function `read_address`
+
+
+public fun read_address(map: &property_map::PropertyMap, key: &string::String): address
+
+
+
+
+
+let str = b"address";
+aborts_if !simple_map::spec_contains_key(map.map, key);
+aborts_if !string::spec_internal_check_utf8(str);
+let prop = simple_map::spec_get(map.map, key);
+aborts_if prop.type != spec_utf8(str);
+aborts_if !aptos_std::from_bcs::deserializable<address>(prop.value);
+
+
+
+
+
+
+### Function `read_u128`
+
+
+public fun read_u128(map: &property_map::PropertyMap, key: &string::String): u128
+
+
+
+
+
+let str = b"u128";
+aborts_if !simple_map::spec_contains_key(map.map, key);
+aborts_if !string::spec_internal_check_utf8(str);
+let prop = simple_map::spec_get(map.map, key);
+aborts_if prop.type != spec_utf8(str);
+aborts_if !aptos_std::from_bcs::deserializable<u128>(prop.value);
+
+
+
+
+
+
+### Function `read_bool`
+
+
+public fun read_bool(map: &property_map::PropertyMap, key: &string::String): bool
+
+
+
+
+
+let str = b"bool";
+aborts_if !simple_map::spec_contains_key(map.map, key);
+aborts_if !string::spec_internal_check_utf8(str);
+let prop = simple_map::spec_get(map.map, key);
+aborts_if prop.type != spec_utf8(str);
+aborts_if !aptos_std::from_bcs::deserializable<bool>(prop.value);
+
+
+
+
+
+
+### Function `borrow_value`
+
+
+public fun borrow_value(property: &property_map::PropertyValue): vector<u8>
+
+
+
+
+
+aborts_if false;
+
+
+
+
+
+
+### Function `borrow_type`
+
+
+public fun borrow_type(property: &property_map::PropertyValue): string::String
+
+
+
+
+
+aborts_if false;
+
+
+
+
+
+
+### Function `remove`
+
+
+public fun remove(map: &mut property_map::PropertyMap, key: &string::String): (string::String, property_map::PropertyValue)
+
+
+
+
+
+aborts_if !simple_map::spec_contains_key(map.map, key);
+
+
+
+
+
+
+### Function `update_property_map`
+
+
+public fun update_property_map(map: &mut property_map::PropertyMap, keys: vector<string::String>, values: vector<vector<u8>>, types: vector<string::String>)
+
+
+
+
+
+pragma aborts_if_is_partial;
+let key_len = len(keys);
+let val_len = len(values);
+let typ_len = len(types);
+aborts_if !(key_len == val_len);
+aborts_if !(key_len == typ_len);
+
+
+
+
+
+
+### Function `update_property_value`
+
+
+public fun update_property_value(map: &mut property_map::PropertyMap, key: &string::String, value: property_map::PropertyValue)
+
+
+
+
+
+aborts_if !simple_map::spec_contains_key(map.map, key);
+
+
+
+
+
+
+### Function `create_property_value_raw`
+
+
+public fun create_property_value_raw(value: vector<u8>, type: string::String): property_map::PropertyValue
+
+
+
+
+
+aborts_if false;
+
+
+
+
+
+
+### Function `create_property_value`
+
+
+public fun create_property_value<T: copy>(data: &T): property_map::PropertyValue
+
+
+
+Abort according to the code
+
+
+let name = type_name<T>();
+aborts_if !string::spec_internal_check_utf8(b"bool");
+aborts_if name != spec_utf8(b"bool") &&
+ !string::spec_internal_check_utf8(b"u8");
+aborts_if name != spec_utf8(b"bool") &&
+ name != spec_utf8(b"u8") &&
+ !string::spec_internal_check_utf8(b"u64");
+aborts_if name != spec_utf8(b"bool") &&
+ name != spec_utf8(b"u8") &&
+ name != spec_utf8(b"u64") &&
+ !string::spec_internal_check_utf8(b"u128");
+aborts_if name != spec_utf8(b"bool") &&
+ name != spec_utf8(b"u8") &&
+ name != spec_utf8(b"u64") &&
+ name != spec_utf8(b"u128") &&
+ !string::spec_internal_check_utf8(b"address");
+aborts_if name != spec_utf8(b"bool") &&
+ name != spec_utf8(b"u8") &&
+ name != spec_utf8(b"u64") &&
+ name != spec_utf8(b"u128") &&
+ name != spec_utf8(b"address") &&
+ !string::spec_internal_check_utf8(b"0x1::string::String");
+aborts_if name != spec_utf8(b"bool") &&
+ name != spec_utf8(b"u8") &&
+ name != spec_utf8(b"u64") &&
+ name != spec_utf8(b"u128") &&
+ name != spec_utf8(b"address") &&
+ name != spec_utf8(b"0x1::string::String") &&
+ !string::spec_internal_check_utf8(b"vector<u8>");
+
+
+
+[move-book]: https://aptos.dev/move/book/SUMMARY
diff --git a/aptos-move/framework/aptos-token/tests/compiler-v2-doc/token.md b/aptos-move/framework/aptos-token/tests/compiler-v2-doc/token.md
new file mode 100644
index 0000000000000..818be7527c2f8
--- /dev/null
+++ b/aptos-move/framework/aptos-token/tests/compiler-v2-doc/token.md
@@ -0,0 +1,6594 @@
+
+
+
+# Module `0x3::token`
+
+This module provides the foundation for Tokens.
+Checkout our developer doc on our token standard https://aptos.dev/standards
+
+
+- [Struct `Token`](#0x3_token_Token)
+- [Struct `TokenId`](#0x3_token_TokenId)
+- [Struct `TokenDataId`](#0x3_token_TokenDataId)
+- [Struct `TokenData`](#0x3_token_TokenData)
+- [Struct `Royalty`](#0x3_token_Royalty)
+- [Struct `TokenMutabilityConfig`](#0x3_token_TokenMutabilityConfig)
+- [Resource `TokenStore`](#0x3_token_TokenStore)
+- [Struct `CollectionMutabilityConfig`](#0x3_token_CollectionMutabilityConfig)
+- [Resource `Collections`](#0x3_token_Collections)
+- [Struct `CollectionData`](#0x3_token_CollectionData)
+- [Struct `WithdrawCapability`](#0x3_token_WithdrawCapability)
+- [Struct `DepositEvent`](#0x3_token_DepositEvent)
+- [Struct `Deposit`](#0x3_token_Deposit)
+- [Struct `WithdrawEvent`](#0x3_token_WithdrawEvent)
+- [Struct `Withdraw`](#0x3_token_Withdraw)
+- [Struct `CreateTokenDataEvent`](#0x3_token_CreateTokenDataEvent)
+- [Struct `CreateTokenData`](#0x3_token_CreateTokenData)
+- [Struct `MintTokenEvent`](#0x3_token_MintTokenEvent)
+- [Struct `MintToken`](#0x3_token_MintToken)
+- [Struct `BurnTokenEvent`](#0x3_token_BurnTokenEvent)
+- [Struct `BurnToken`](#0x3_token_BurnToken)
+- [Struct `MutateTokenPropertyMapEvent`](#0x3_token_MutateTokenPropertyMapEvent)
+- [Struct `MutateTokenPropertyMap`](#0x3_token_MutateTokenPropertyMap)
+- [Struct `CreateCollectionEvent`](#0x3_token_CreateCollectionEvent)
+- [Struct `CreateCollection`](#0x3_token_CreateCollection)
+- [Constants](#@Constants_0)
+- [Function `create_collection_script`](#0x3_token_create_collection_script)
+- [Function `create_token_script`](#0x3_token_create_token_script)
+- [Function `mint_script`](#0x3_token_mint_script)
+- [Function `mutate_token_properties`](#0x3_token_mutate_token_properties)
+- [Function `direct_transfer_script`](#0x3_token_direct_transfer_script)
+- [Function `opt_in_direct_transfer`](#0x3_token_opt_in_direct_transfer)
+- [Function `transfer_with_opt_in`](#0x3_token_transfer_with_opt_in)
+- [Function `burn_by_creator`](#0x3_token_burn_by_creator)
+- [Function `burn`](#0x3_token_burn)
+- [Function `mutate_collection_description`](#0x3_token_mutate_collection_description)
+- [Function `mutate_collection_uri`](#0x3_token_mutate_collection_uri)
+- [Function `mutate_collection_maximum`](#0x3_token_mutate_collection_maximum)
+- [Function `mutate_tokendata_maximum`](#0x3_token_mutate_tokendata_maximum)
+- [Function `mutate_tokendata_uri`](#0x3_token_mutate_tokendata_uri)
+- [Function `mutate_tokendata_royalty`](#0x3_token_mutate_tokendata_royalty)
+- [Function `mutate_tokendata_description`](#0x3_token_mutate_tokendata_description)
+- [Function `mutate_tokendata_property`](#0x3_token_mutate_tokendata_property)
+- [Function `mutate_one_token`](#0x3_token_mutate_one_token)
+- [Function `create_royalty`](#0x3_token_create_royalty)
+- [Function `deposit_token`](#0x3_token_deposit_token)
+- [Function `direct_deposit_with_opt_in`](#0x3_token_direct_deposit_with_opt_in)
+- [Function `direct_transfer`](#0x3_token_direct_transfer)
+- [Function `initialize_token_store`](#0x3_token_initialize_token_store)
+- [Function `merge`](#0x3_token_merge)
+- [Function `split`](#0x3_token_split)
+- [Function `token_id`](#0x3_token_token_id)
+- [Function `transfer`](#0x3_token_transfer)
+- [Function `create_withdraw_capability`](#0x3_token_create_withdraw_capability)
+- [Function `withdraw_with_capability`](#0x3_token_withdraw_with_capability)
+- [Function `partial_withdraw_with_capability`](#0x3_token_partial_withdraw_with_capability)
+- [Function `withdraw_token`](#0x3_token_withdraw_token)
+- [Function `create_collection`](#0x3_token_create_collection)
+- [Function `check_collection_exists`](#0x3_token_check_collection_exists)
+- [Function `check_tokendata_exists`](#0x3_token_check_tokendata_exists)
+- [Function `create_tokendata`](#0x3_token_create_tokendata)
+- [Function `get_collection_supply`](#0x3_token_get_collection_supply)
+- [Function `get_collection_description`](#0x3_token_get_collection_description)
+- [Function `get_collection_uri`](#0x3_token_get_collection_uri)
+- [Function `get_collection_maximum`](#0x3_token_get_collection_maximum)
+- [Function `get_token_supply`](#0x3_token_get_token_supply)
+- [Function `get_tokendata_largest_property_version`](#0x3_token_get_tokendata_largest_property_version)
+- [Function `get_token_id`](#0x3_token_get_token_id)
+- [Function `get_direct_transfer`](#0x3_token_get_direct_transfer)
+- [Function `create_token_mutability_config`](#0x3_token_create_token_mutability_config)
+- [Function `create_collection_mutability_config`](#0x3_token_create_collection_mutability_config)
+- [Function `mint_token`](#0x3_token_mint_token)
+- [Function `mint_token_to`](#0x3_token_mint_token_to)
+- [Function `create_token_id`](#0x3_token_create_token_id)
+- [Function `create_token_data_id`](#0x3_token_create_token_data_id)
+- [Function `create_token_id_raw`](#0x3_token_create_token_id_raw)
+- [Function `balance_of`](#0x3_token_balance_of)
+- [Function `has_token_store`](#0x3_token_has_token_store)
+- [Function `get_royalty`](#0x3_token_get_royalty)
+- [Function `get_royalty_numerator`](#0x3_token_get_royalty_numerator)
+- [Function `get_royalty_denominator`](#0x3_token_get_royalty_denominator)
+- [Function `get_royalty_payee`](#0x3_token_get_royalty_payee)
+- [Function `get_token_amount`](#0x3_token_get_token_amount)
+- [Function `get_token_id_fields`](#0x3_token_get_token_id_fields)
+- [Function `get_token_data_id_fields`](#0x3_token_get_token_data_id_fields)
+- [Function `get_property_map`](#0x3_token_get_property_map)
+- [Function `get_tokendata_maximum`](#0x3_token_get_tokendata_maximum)
+- [Function `get_tokendata_uri`](#0x3_token_get_tokendata_uri)
+- [Function `get_tokendata_description`](#0x3_token_get_tokendata_description)
+- [Function `get_tokendata_royalty`](#0x3_token_get_tokendata_royalty)
+- [Function `get_tokendata_id`](#0x3_token_get_tokendata_id)
+- [Function `get_tokendata_mutability_config`](#0x3_token_get_tokendata_mutability_config)
+- [Function `get_token_mutability_maximum`](#0x3_token_get_token_mutability_maximum)
+- [Function `get_token_mutability_royalty`](#0x3_token_get_token_mutability_royalty)
+- [Function `get_token_mutability_uri`](#0x3_token_get_token_mutability_uri)
+- [Function `get_token_mutability_description`](#0x3_token_get_token_mutability_description)
+- [Function `get_token_mutability_default_properties`](#0x3_token_get_token_mutability_default_properties)
+- [Function `get_collection_mutability_config`](#0x3_token_get_collection_mutability_config)
+- [Function `get_collection_mutability_description`](#0x3_token_get_collection_mutability_description)
+- [Function `get_collection_mutability_uri`](#0x3_token_get_collection_mutability_uri)
+- [Function `get_collection_mutability_maximum`](#0x3_token_get_collection_mutability_maximum)
+- [Function `destroy_token_data`](#0x3_token_destroy_token_data)
+- [Function `destroy_collection_data`](#0x3_token_destroy_collection_data)
+- [Function `withdraw_with_event_internal`](#0x3_token_withdraw_with_event_internal)
+- [Function `update_token_property_internal`](#0x3_token_update_token_property_internal)
+- [Function `direct_deposit`](#0x3_token_direct_deposit)
+- [Function `assert_collection_exists`](#0x3_token_assert_collection_exists)
+- [Function `assert_tokendata_exists`](#0x3_token_assert_tokendata_exists)
+- [Function `assert_non_standard_reserved_property`](#0x3_token_assert_non_standard_reserved_property)
+- [Function `initialize_token_script`](#0x3_token_initialize_token_script)
+- [Function `initialize_token`](#0x3_token_initialize_token)
+- [Specification](#@Specification_1)
+ - [Function `create_collection_script`](#@Specification_1_create_collection_script)
+ - [Function `create_token_script`](#@Specification_1_create_token_script)
+ - [Function `mint_script`](#@Specification_1_mint_script)
+ - [Function `mutate_token_properties`](#@Specification_1_mutate_token_properties)
+ - [Function `direct_transfer_script`](#@Specification_1_direct_transfer_script)
+ - [Function `opt_in_direct_transfer`](#@Specification_1_opt_in_direct_transfer)
+ - [Function `transfer_with_opt_in`](#@Specification_1_transfer_with_opt_in)
+ - [Function `burn_by_creator`](#@Specification_1_burn_by_creator)
+ - [Function `burn`](#@Specification_1_burn)
+ - [Function `mutate_collection_description`](#@Specification_1_mutate_collection_description)
+ - [Function `mutate_collection_uri`](#@Specification_1_mutate_collection_uri)
+ - [Function `mutate_collection_maximum`](#@Specification_1_mutate_collection_maximum)
+ - [Function `mutate_tokendata_maximum`](#@Specification_1_mutate_tokendata_maximum)
+ - [Function `mutate_tokendata_uri`](#@Specification_1_mutate_tokendata_uri)
+ - [Function `mutate_tokendata_royalty`](#@Specification_1_mutate_tokendata_royalty)
+ - [Function `mutate_tokendata_description`](#@Specification_1_mutate_tokendata_description)
+ - [Function `mutate_tokendata_property`](#@Specification_1_mutate_tokendata_property)
+ - [Function `mutate_one_token`](#@Specification_1_mutate_one_token)
+ - [Function `create_royalty`](#@Specification_1_create_royalty)
+ - [Function `deposit_token`](#@Specification_1_deposit_token)
+ - [Function `direct_deposit_with_opt_in`](#@Specification_1_direct_deposit_with_opt_in)
+ - [Function `direct_transfer`](#@Specification_1_direct_transfer)
+ - [Function `initialize_token_store`](#@Specification_1_initialize_token_store)
+ - [Function `merge`](#@Specification_1_merge)
+ - [Function `split`](#@Specification_1_split)
+ - [Function `transfer`](#@Specification_1_transfer)
+ - [Function `withdraw_with_capability`](#@Specification_1_withdraw_with_capability)
+ - [Function `partial_withdraw_with_capability`](#@Specification_1_partial_withdraw_with_capability)
+ - [Function `withdraw_token`](#@Specification_1_withdraw_token)
+ - [Function `create_collection`](#@Specification_1_create_collection)
+ - [Function `check_collection_exists`](#@Specification_1_check_collection_exists)
+ - [Function `check_tokendata_exists`](#@Specification_1_check_tokendata_exists)
+ - [Function `create_tokendata`](#@Specification_1_create_tokendata)
+ - [Function `get_collection_supply`](#@Specification_1_get_collection_supply)
+ - [Function `get_collection_description`](#@Specification_1_get_collection_description)
+ - [Function `get_collection_uri`](#@Specification_1_get_collection_uri)
+ - [Function `get_collection_maximum`](#@Specification_1_get_collection_maximum)
+ - [Function `get_token_supply`](#@Specification_1_get_token_supply)
+ - [Function `get_tokendata_largest_property_version`](#@Specification_1_get_tokendata_largest_property_version)
+ - [Function `create_token_mutability_config`](#@Specification_1_create_token_mutability_config)
+ - [Function `create_collection_mutability_config`](#@Specification_1_create_collection_mutability_config)
+ - [Function `mint_token`](#@Specification_1_mint_token)
+ - [Function `mint_token_to`](#@Specification_1_mint_token_to)
+ - [Function `create_token_data_id`](#@Specification_1_create_token_data_id)
+ - [Function `create_token_id_raw`](#@Specification_1_create_token_id_raw)
+ - [Function `get_royalty`](#@Specification_1_get_royalty)
+ - [Function `get_property_map`](#@Specification_1_get_property_map)
+ - [Function `get_tokendata_maximum`](#@Specification_1_get_tokendata_maximum)
+ - [Function `get_tokendata_uri`](#@Specification_1_get_tokendata_uri)
+ - [Function `get_tokendata_description`](#@Specification_1_get_tokendata_description)
+ - [Function `get_tokendata_royalty`](#@Specification_1_get_tokendata_royalty)
+ - [Function `get_tokendata_mutability_config`](#@Specification_1_get_tokendata_mutability_config)
+ - [Function `get_collection_mutability_config`](#@Specification_1_get_collection_mutability_config)
+ - [Function `withdraw_with_event_internal`](#@Specification_1_withdraw_with_event_internal)
+ - [Function `update_token_property_internal`](#@Specification_1_update_token_property_internal)
+ - [Function `direct_deposit`](#@Specification_1_direct_deposit)
+ - [Function `assert_collection_exists`](#@Specification_1_assert_collection_exists)
+ - [Function `assert_tokendata_exists`](#@Specification_1_assert_tokendata_exists)
+ - [Function `assert_non_standard_reserved_property`](#@Specification_1_assert_non_standard_reserved_property)
+ - [Function `initialize_token_script`](#@Specification_1_initialize_token_script)
+ - [Function `initialize_token`](#@Specification_1_initialize_token)
+
+
+use 0x1::account;
+use 0x1::error;
+use 0x1::event;
+use 0x1::features;
+use 0x1::option;
+use 0x1::signer;
+use 0x1::string;
+use 0x1::table;
+use 0x1::timestamp;
+use 0x3::property_map;
+use 0x3::token_event_store;
+
+
+
+
+
+
+## Struct `Token`
+
+
+
+struct Token has store
+
+
+
+
+id: token::TokenId
+amount: u64
+token_properties: property_map::PropertyMap
+struct TokenId has copy, drop, store
+
+
+
+
+token_data_id: token::TokenDataId
+property_version: u64
+struct TokenDataId has copy, drop, store
+
+
+
+
+creator: address
+collection: string::String
+name: string::String
+struct TokenData has store
+
+
+
+
+maximum: u64
+largest_property_version: u64
+supply: u64
+uri: string::String
+royalty: token::Royalty
+name: string::String
+description: string::String
+default_properties: property_map::PropertyMap
+mutability_config: token::TokenMutabilityConfig
+struct Royalty has copy, drop, store
+
+
+
+
+royalty_points_numerator: u64
+royalty_points_denominator: u64
+payee_address: address
+struct TokenMutabilityConfig has copy, drop, store
+
+
+
+
+maximum: bool
+uri: bool
+royalty: bool
+description: bool
+properties: bool
+struct TokenStore has key
+
+
+
+
+tokens: table::Table<token::TokenId, token::Token>
+direct_transfer: bool
+deposit_events: event::EventHandle<token::DepositEvent>
+withdraw_events: event::EventHandle<token::WithdrawEvent>
+burn_events: event::EventHandle<token::BurnTokenEvent>
+mutate_token_property_events: event::EventHandle<token::MutateTokenPropertyMapEvent>
+struct CollectionMutabilityConfig has copy, drop, store
+
+
+
+
+description: bool
+uri: bool
+maximum: bool
+struct Collections has key
+
+
+
+
+collection_data: table::Table<string::String, token::CollectionData>
+token_data: table::Table<token::TokenDataId, token::TokenData>
+create_collection_events: event::EventHandle<token::CreateCollectionEvent>
+create_token_data_events: event::EventHandle<token::CreateTokenDataEvent>
+mint_token_events: event::EventHandle<token::MintTokenEvent>
+struct CollectionData has store
+
+
+
+
+description: string::String
+name: string::String
+uri: string::String
+supply: u64
+maximum: u64
+mutability_config: token::CollectionMutabilityConfig
+struct WithdrawCapability has drop, store
+
+
+
+
+token_owner: address
+token_id: token::TokenId
+amount: u64
+expiration_sec: u64
+struct DepositEvent has drop, store
+
+
+
+
+id: token::TokenId
+amount: u64
+#[event]
+struct Deposit has drop, store
+
+
+
+
+id: token::TokenId
+amount: u64
+struct WithdrawEvent has drop, store
+
+
+
+
+id: token::TokenId
+amount: u64
+#[event]
+struct Withdraw has drop, store
+
+
+
+
+id: token::TokenId
+amount: u64
+struct CreateTokenDataEvent has drop, store
+
+
+
+
+id: token::TokenDataId
+description: string::String
+maximum: u64
+uri: string::String
+royalty_payee_address: address
+royalty_points_denominator: u64
+royalty_points_numerator: u64
+name: string::String
+mutability_config: token::TokenMutabilityConfig
+property_keys: vector<string::String>
+property_values: vector<vector<u8>>
+property_types: vector<string::String>
+#[event]
+struct CreateTokenData has drop, store
+
+
+
+
+id: token::TokenDataId
+description: string::String
+maximum: u64
+uri: string::String
+royalty_payee_address: address
+royalty_points_denominator: u64
+royalty_points_numerator: u64
+name: string::String
+mutability_config: token::TokenMutabilityConfig
+property_keys: vector<string::String>
+property_values: vector<vector<u8>>
+property_types: vector<string::String>
+struct MintTokenEvent has drop, store
+
+
+
+
+id: token::TokenDataId
+amount: u64
+#[event]
+struct MintToken has drop, store
+
+
+
+
+id: token::TokenDataId
+amount: u64
+struct BurnTokenEvent has drop, store
+
+
+
+
+id: token::TokenId
+amount: u64
+#[event]
+struct BurnToken has drop, store
+
+
+
+
+id: token::TokenId
+amount: u64
+struct MutateTokenPropertyMapEvent has drop, store
+
+
+
+
+old_id: token::TokenId
+new_id: token::TokenId
+keys: vector<string::String>
+values: vector<vector<u8>>
+types: vector<string::String>
+#[event]
+struct MutateTokenPropertyMap has drop, store
+
+
+
+
+old_id: token::TokenId
+new_id: token::TokenId
+keys: vector<string::String>
+values: vector<vector<u8>>
+types: vector<string::String>
+struct CreateCollectionEvent has drop, store
+
+
+
+
+creator: address
+collection_name: string::String
+uri: string::String
+description: string::String
+maximum: u64
+#[event]
+struct CreateCollection has drop, store
+
+
+
+
+creator: address
+collection_name: string::String
+uri: string::String
+description: string::String
+maximum: u64
+const EINSUFFICIENT_BALANCE: u64 = 5;
+
+
+
+
+
+
+The URI is too long
+
+
+const EURI_TOO_LONG: u64 = 27;
+
+
+
+
+
+
+
+
+const MAX_URI_LENGTH: u64 = 512;
+
+
+
+
+
+
+
+
+const BURNABLE_BY_CREATOR: vector<u8> = [84, 79, 75, 69, 78, 95, 66, 85, 82, 78, 65, 66, 76, 69, 95, 66, 89, 95, 67, 82, 69, 65, 84, 79, 82];
+
+
+
+
+
+
+
+
+const BURNABLE_BY_OWNER: vector<u8> = [84, 79, 75, 69, 78, 95, 66, 85, 82, 78, 65, 66, 76, 69, 95, 66, 89, 95, 79, 87, 78, 69, 82];
+
+
+
+
+
+
+
+
+const COLLECTION_DESCRIPTION_MUTABLE_IND: u64 = 0;
+
+
+
+
+
+
+
+
+const COLLECTION_MAX_MUTABLE_IND: u64 = 2;
+
+
+
+
+
+
+
+
+const COLLECTION_URI_MUTABLE_IND: u64 = 1;
+
+
+
+
+
+
+The token has balance and cannot be initialized
+
+
+const EALREADY_HAS_BALANCE: u64 = 0;
+
+
+
+
+
+
+Reserved fields for token contract
+Cannot be updated by user
+
+
+const ECANNOT_UPDATE_RESERVED_PROPERTY: u64 = 32;
+
+
+
+
+
+
+There isn't any collection under this account
+
+
+const ECOLLECTIONS_NOT_PUBLISHED: u64 = 1;
+
+
+
+
+
+
+The collection already exists
+
+
+const ECOLLECTION_ALREADY_EXISTS: u64 = 3;
+
+
+
+
+
+
+The collection name is too long
+
+
+const ECOLLECTION_NAME_TOO_LONG: u64 = 25;
+
+
+
+
+
+
+Cannot find collection in creator's account
+
+
+const ECOLLECTION_NOT_PUBLISHED: u64 = 2;
+
+
+
+
+
+
+Exceeds the collection's maximal number of token_data
+
+
+const ECREATE_WOULD_EXCEED_COLLECTION_MAXIMUM: u64 = 4;
+
+
+
+
+
+
+Token is not burnable by creator
+
+
+const ECREATOR_CANNOT_BURN_TOKEN: u64 = 31;
+
+
+
+
+
+
+The field is not mutable
+
+
+const EFIELD_NOT_MUTABLE: u64 = 13;
+
+
+
+
+
+
+Withdraw capability doesn't have sufficient amount
+
+
+const EINSUFFICIENT_WITHDRAW_CAPABILITY_AMOUNT: u64 = 38;
+
+
+
+
+
+
+Collection or tokendata maximum must be larger than supply
+
+
+const EINVALID_MAXIMUM: u64 = 36;
+
+
+
+
+
+
+Royalty invalid if the numerator is larger than the denominator
+
+
+const EINVALID_ROYALTY_NUMERATOR_DENOMINATOR: u64 = 34;
+
+
+
+
+
+
+Cannot merge the two tokens with different token id
+
+
+const EINVALID_TOKEN_MERGE: u64 = 6;
+
+
+
+
+
+
+Exceed the token data maximal allowed
+
+
+const EMINT_WOULD_EXCEED_TOKEN_MAXIMUM: u64 = 7;
+
+
+
+
+
+
+The NFT name is too long
+
+
+const ENFT_NAME_TOO_LONG: u64 = 26;
+
+
+
+
+
+
+Cannot split a token that only has 1 amount
+
+
+const ENFT_NOT_SPLITABLE: u64 = 18;
+
+
+
+
+
+
+No burn capability
+
+
+const ENO_BURN_CAPABILITY: u64 = 8;
+
+
+
+
+
+
+Cannot burn 0 Token
+
+
+const ENO_BURN_TOKEN_WITH_ZERO_AMOUNT: u64 = 29;
+
+
+
+
+
+
+Cannot deposit a Token with 0 amount
+
+
+const ENO_DEPOSIT_TOKEN_WITH_ZERO_AMOUNT: u64 = 28;
+
+
+
+
+
+
+No mint capability
+
+
+const ENO_MINT_CAPABILITY: u64 = 19;
+
+
+
+
+
+
+Not authorized to mutate
+
+
+const ENO_MUTATE_CAPABILITY: u64 = 14;
+
+
+
+
+
+
+Token not in the token store
+
+
+const ENO_TOKEN_IN_TOKEN_STORE: u64 = 15;
+
+
+
+
+
+
+Token is not burnable by owner
+
+
+const EOWNER_CANNOT_BURN_TOKEN: u64 = 30;
+
+
+
+
+
+
+The property is reserved by token standard
+
+
+const EPROPERTY_RESERVED_BY_STANDARD: u64 = 40;
+
+
+
+
+
+
+Royalty payee account does not exist
+
+
+const EROYALTY_PAYEE_ACCOUNT_DOES_NOT_EXIST: u64 = 35;
+
+
+
+
+
+
+TOKEN with 0 amount is not allowed
+
+
+const ETOKEN_CANNOT_HAVE_ZERO_AMOUNT: u64 = 33;
+
+
+
+
+
+
+TokenData already exists
+
+
+const ETOKEN_DATA_ALREADY_EXISTS: u64 = 9;
+
+
+
+
+
+
+TokenData not published
+
+
+const ETOKEN_DATA_NOT_PUBLISHED: u64 = 10;
+
+
+
+
+
+
+Token Properties count doesn't match
+
+
+const ETOKEN_PROPERTIES_COUNT_NOT_MATCH: u64 = 37;
+
+
+
+
+
+
+Cannot split token to an amount larger than its amount
+
+
+const ETOKEN_SPLIT_AMOUNT_LARGER_OR_EQUAL_TO_TOKEN_AMOUNT: u64 = 12;
+
+
+
+
+
+
+TokenStore doesn't exist
+
+
+const ETOKEN_STORE_NOT_PUBLISHED: u64 = 11;
+
+
+
+
+
+
+User didn't opt-in direct transfer
+
+
+const EUSER_NOT_OPT_IN_DIRECT_TRANSFER: u64 = 16;
+
+
+
+
+
+
+Withdraw proof expires
+
+
+const EWITHDRAW_PROOF_EXPIRES: u64 = 39;
+
+
+
+
+
+
+Cannot withdraw 0 token
+
+
+const EWITHDRAW_ZERO: u64 = 17;
+
+
+
+
+
+
+
+
+const MAX_COLLECTION_NAME_LENGTH: u64 = 128;
+
+
+
+
+
+
+
+
+const MAX_NFT_NAME_LENGTH: u64 = 128;
+
+
+
+
+
+
+
+
+const TOKEN_DESCRIPTION_MUTABLE_IND: u64 = 3;
+
+
+
+
+
+
+
+
+const TOKEN_MAX_MUTABLE_IND: u64 = 0;
+
+
+
+
+
+
+
+
+const TOKEN_PROPERTY_MUTABLE: vector<u8> = [84, 79, 75, 69, 78, 95, 80, 82, 79, 80, 69, 82, 84, 89, 95, 77, 85, 84, 65, 84, 66, 76, 69];
+
+
+
+
+
+
+
+
+const TOKEN_PROPERTY_MUTABLE_IND: u64 = 4;
+
+
+
+
+
+
+
+
+const TOKEN_PROPERTY_VALUE_MUTABLE_IND: u64 = 5;
+
+
+
+
+
+
+
+
+const TOKEN_ROYALTY_MUTABLE_IND: u64 = 2;
+
+
+
+
+
+
+
+
+const TOKEN_URI_MUTABLE_IND: u64 = 1;
+
+
+
+
+
+
+## Function `create_collection_script`
+
+create a empty token collection with parameters
+
+
+public entry fun create_collection_script(creator: &signer, name: string::String, description: string::String, uri: string::String, maximum: u64, mutate_setting: vector<bool>)
+
+
+
+
+public entry fun create_collection_script(
+ creator: &signer,
+ name: String,
+ description: String,
+ uri: String,
+ maximum: u64,
+ mutate_setting: vector<bool>,
+) acquires Collections {
+ create_collection(
+ creator,
+ name,
+ description,
+ uri,
+ maximum,
+ mutate_setting
+ );
+}
+
+
+
+
+public entry fun create_token_script(account: &signer, collection: string::String, name: string::String, description: string::String, balance: u64, maximum: u64, uri: string::String, royalty_payee_address: address, royalty_points_denominator: u64, royalty_points_numerator: u64, mutate_setting: vector<bool>, property_keys: vector<string::String>, property_values: vector<vector<u8>>, property_types: vector<string::String>)
+
+
+
+
+public entry fun create_token_script(
+ account: &signer,
+ collection: String,
+ name: String,
+ description: String,
+ balance: u64,
+ maximum: u64,
+ uri: String,
+ royalty_payee_address: address,
+ royalty_points_denominator: u64,
+ royalty_points_numerator: u64,
+ mutate_setting: vector<bool>,
+ property_keys: vector<String>,
+ property_values: vector<vector<u8>>,
+ property_types: vector<String>
+) acquires Collections, TokenStore {
+ let token_mut_config = create_token_mutability_config(&mutate_setting);
+ let tokendata_id = create_tokendata(
+ account,
+ collection,
+ name,
+ description,
+ maximum,
+ uri,
+ royalty_payee_address,
+ royalty_points_denominator,
+ royalty_points_numerator,
+ token_mut_config,
+ property_keys,
+ property_values,
+ property_types
+ );
+
+ mint_token(
+ account,
+ tokendata_id,
+ balance,
+ );
+}
+
+
+
+
+public entry fun mint_script(account: &signer, token_data_address: address, collection: string::String, name: string::String, amount: u64)
+
+
+
+
+public entry fun mint_script(
+ account: &signer,
+ token_data_address: address,
+ collection: String,
+ name: String,
+ amount: u64,
+) acquires Collections, TokenStore {
+ let token_data_id = create_token_data_id(
+ token_data_address,
+ collection,
+ name,
+ );
+ // only creator of the tokendata can mint more tokens for now
+ assert!(token_data_id.creator == signer::address_of(account), error::permission_denied(ENO_MINT_CAPABILITY));
+ mint_token(
+ account,
+ token_data_id,
+ amount,
+ );
+}
+
+
+
+
+public entry fun mutate_token_properties(account: &signer, token_owner: address, creator: address, collection_name: string::String, token_name: string::String, token_property_version: u64, amount: u64, keys: vector<string::String>, values: vector<vector<u8>>, types: vector<string::String>)
+
+
+
+
+public entry fun mutate_token_properties(
+ account: &signer,
+ token_owner: address,
+ creator: address,
+ collection_name: String,
+ token_name: String,
+ token_property_version: u64,
+ amount: u64,
+ keys: vector<String>,
+ values: vector<vector<u8>>,
+ types: vector<String>,
+) acquires Collections, TokenStore {
+ assert!(signer::address_of(account) == creator, error::not_found(ENO_MUTATE_CAPABILITY));
+ let i = 0;
+ let token_id = create_token_id_raw(
+ creator,
+ collection_name,
+ token_name,
+ token_property_version,
+ );
+ // give a new property_version for each token
+ while (i < amount) {
+ mutate_one_token(account, token_owner, token_id, keys, values, types);
+ i = i + 1;
+ };
+}
+
+
+
+
+public entry fun direct_transfer_script(sender: &signer, receiver: &signer, creators_address: address, collection: string::String, name: string::String, property_version: u64, amount: u64)
+
+
+
+
+public entry fun direct_transfer_script(
+ sender: &signer,
+ receiver: &signer,
+ creators_address: address,
+ collection: String,
+ name: String,
+ property_version: u64,
+ amount: u64,
+) acquires TokenStore {
+ let token_id = create_token_id_raw(creators_address, collection, name, property_version);
+ direct_transfer(sender, receiver, token_id, amount);
+}
+
+
+
+
+public entry fun opt_in_direct_transfer(account: &signer, opt_in: bool)
+
+
+
+
+public entry fun opt_in_direct_transfer(account: &signer, opt_in: bool) acquires TokenStore {
+ let addr = signer::address_of(account);
+ initialize_token_store(account);
+ let opt_in_flag = &mut borrow_global_mut<TokenStore>(addr).direct_transfer;
+ *opt_in_flag = opt_in;
+ token_event_store::emit_token_opt_in_event(account, opt_in);
+}
+
+
+
+
+amount
of tokens from from
to to
.
+The receiver to
has to opt-in direct transfer first
+
+
+public entry fun transfer_with_opt_in(from: &signer, creator: address, collection_name: string::String, token_name: string::String, token_property_version: u64, to: address, amount: u64)
+
+
+
+
+public entry fun transfer_with_opt_in(
+ from: &signer,
+ creator: address,
+ collection_name: String,
+ token_name: String,
+ token_property_version: u64,
+ to: address,
+ amount: u64,
+) acquires TokenStore {
+ let token_id = create_token_id_raw(creator, collection_name, token_name, token_property_version);
+ transfer(from, token_id, to, amount);
+}
+
+
+
+
+public entry fun burn_by_creator(creator: &signer, owner: address, collection: string::String, name: string::String, property_version: u64, amount: u64)
+
+
+
+
+public entry fun burn_by_creator(
+ creator: &signer,
+ owner: address,
+ collection: String,
+ name: String,
+ property_version: u64,
+ amount: u64,
+) acquires Collections, TokenStore {
+ let creator_address = signer::address_of(creator);
+ assert!(amount > 0, error::invalid_argument(ENO_BURN_TOKEN_WITH_ZERO_AMOUNT));
+ let token_id = create_token_id_raw(creator_address, collection, name, property_version);
+ let creator_addr = token_id.token_data_id.creator;
+ assert!(
+ exists<Collections>(creator_addr),
+ error::not_found(ECOLLECTIONS_NOT_PUBLISHED),
+ );
+
+ let collections = borrow_global_mut<Collections>(creator_address);
+ assert!(
+ table::contains(&collections.token_data, token_id.token_data_id),
+ error::not_found(ETOKEN_DATA_NOT_PUBLISHED),
+ );
+
+ let token_data = table::borrow_mut(
+ &mut collections.token_data,
+ token_id.token_data_id,
+ );
+
+ // The property should be explicitly set in the property_map for creator to burn the token
+ assert!(
+ property_map::contains_key(&token_data.default_properties, &string::utf8(BURNABLE_BY_CREATOR)),
+ error::permission_denied(ECREATOR_CANNOT_BURN_TOKEN)
+ );
+
+ let burn_by_creator_flag = property_map::read_bool(&token_data.default_properties, &string::utf8(BURNABLE_BY_CREATOR));
+ assert!(burn_by_creator_flag, error::permission_denied(ECREATOR_CANNOT_BURN_TOKEN));
+
+ // Burn the tokens.
+ let Token { id: _, amount: burned_amount, token_properties: _ } = withdraw_with_event_internal(owner, token_id, amount);
+ let token_store = borrow_global_mut<TokenStore>(owner);
+ if (std::features::module_event_migration_enabled()) {
+ event::emit(BurnToken { id: token_id, amount: burned_amount });
+ };
+ event::emit_event<BurnTokenEvent>(
+ &mut token_store.burn_events,
+ BurnTokenEvent { id: token_id, amount: burned_amount }
+ );
+
+ if (token_data.maximum > 0) {
+ token_data.supply = token_data.supply - burned_amount;
+
+ // Delete the token_data if supply drops to 0.
+ if (token_data.supply == 0) {
+ destroy_token_data(table::remove(&mut collections.token_data, token_id.token_data_id));
+
+ // update the collection supply
+ let collection_data = table::borrow_mut(
+ &mut collections.collection_data,
+ token_id.token_data_id.collection
+ );
+ if (collection_data.maximum > 0) {
+ collection_data.supply = collection_data.supply - 1;
+ // delete the collection data if the collection supply equals 0
+ if (collection_data.supply == 0) {
+ destroy_collection_data(table::remove(&mut collections.collection_data, collection_data.name));
+ };
+ };
+ };
+ };
+}
+
+
+
+
+public entry fun burn(owner: &signer, creators_address: address, collection: string::String, name: string::String, property_version: u64, amount: u64)
+
+
+
+
+public entry fun burn(
+ owner: &signer,
+ creators_address: address,
+ collection: String,
+ name: String,
+ property_version: u64,
+ amount: u64
+) acquires Collections, TokenStore {
+ assert!(amount > 0, error::invalid_argument(ENO_BURN_TOKEN_WITH_ZERO_AMOUNT));
+ let token_id = create_token_id_raw(creators_address, collection, name, property_version);
+ let creator_addr = token_id.token_data_id.creator;
+ assert!(
+ exists<Collections>(creator_addr),
+ error::not_found(ECOLLECTIONS_NOT_PUBLISHED),
+ );
+
+ let collections = borrow_global_mut<Collections>(creator_addr);
+ assert!(
+ table::contains(&collections.token_data, token_id.token_data_id),
+ error::not_found(ETOKEN_DATA_NOT_PUBLISHED),
+ );
+
+ let token_data = table::borrow_mut(
+ &mut collections.token_data,
+ token_id.token_data_id,
+ );
+
+ assert!(
+ property_map::contains_key(&token_data.default_properties, &string::utf8(BURNABLE_BY_OWNER)),
+ error::permission_denied(EOWNER_CANNOT_BURN_TOKEN)
+ );
+ let burn_by_owner_flag = property_map::read_bool(&token_data.default_properties, &string::utf8(BURNABLE_BY_OWNER));
+ assert!(burn_by_owner_flag, error::permission_denied(EOWNER_CANNOT_BURN_TOKEN));
+
+ // Burn the tokens.
+ let Token { id: _, amount: burned_amount, token_properties: _ } = withdraw_token(owner, token_id, amount);
+ let token_store = borrow_global_mut<TokenStore>(signer::address_of(owner));
+ if (std::features::module_event_migration_enabled()) {
+ event::emit(BurnToken { id: token_id, amount: burned_amount });
+ };
+ event::emit_event<BurnTokenEvent>(
+ &mut token_store.burn_events,
+ BurnTokenEvent { id: token_id, amount: burned_amount }
+ );
+
+ // Decrease the supply correspondingly by the amount of tokens burned.
+ let token_data = table::borrow_mut(
+ &mut collections.token_data,
+ token_id.token_data_id,
+ );
+
+ // only update the supply if we tracking the supply and maximal
+ // maximal == 0 is reserved for unlimited token and collection with no tracking info.
+ if (token_data.maximum > 0) {
+ token_data.supply = token_data.supply - burned_amount;
+
+ // Delete the token_data if supply drops to 0.
+ if (token_data.supply == 0) {
+ destroy_token_data(table::remove(&mut collections.token_data, token_id.token_data_id));
+
+ // update the collection supply
+ let collection_data = table::borrow_mut(
+ &mut collections.collection_data,
+ token_id.token_data_id.collection
+ );
+
+ // only update and check the supply for unlimited collection
+ if (collection_data.maximum > 0){
+ collection_data.supply = collection_data.supply - 1;
+ // delete the collection data if the collection supply equals 0
+ if (collection_data.supply == 0) {
+ destroy_collection_data(table::remove(&mut collections.collection_data, collection_data.name));
+ };
+ };
+ };
+ };
+}
+
+
+
+
+public fun mutate_collection_description(creator: &signer, collection_name: string::String, description: string::String)
+
+
+
+
+public fun mutate_collection_description(creator: &signer, collection_name: String, description: String) acquires Collections {
+ let creator_address = signer::address_of(creator);
+ assert_collection_exists(creator_address, collection_name);
+ let collection_data = table::borrow_mut(&mut borrow_global_mut<Collections>(creator_address).collection_data, collection_name);
+ assert!(collection_data.mutability_config.description, error::permission_denied(EFIELD_NOT_MUTABLE));
+ token_event_store::emit_collection_description_mutate_event(creator, collection_name, collection_data.description, description);
+ collection_data.description = description;
+}
+
+
+
+
+public fun mutate_collection_uri(creator: &signer, collection_name: string::String, uri: string::String)
+
+
+
+
+public fun mutate_collection_uri(creator: &signer, collection_name: String, uri: String) acquires Collections {
+ assert!(string::length(&uri) <= MAX_URI_LENGTH, error::invalid_argument(EURI_TOO_LONG));
+ let creator_address = signer::address_of(creator);
+ assert_collection_exists(creator_address, collection_name);
+ let collection_data = table::borrow_mut(&mut borrow_global_mut<Collections>(creator_address).collection_data, collection_name);
+ assert!(collection_data.mutability_config.uri, error::permission_denied(EFIELD_NOT_MUTABLE));
+ token_event_store::emit_collection_uri_mutate_event(creator, collection_name, collection_data.uri , uri);
+ collection_data.uri = uri;
+}
+
+
+
+
+public fun mutate_collection_maximum(creator: &signer, collection_name: string::String, maximum: u64)
+
+
+
+
+public fun mutate_collection_maximum(creator: &signer, collection_name: String, maximum: u64) acquires Collections {
+ let creator_address = signer::address_of(creator);
+ assert_collection_exists(creator_address, collection_name);
+ let collection_data = table::borrow_mut(&mut borrow_global_mut<Collections>(creator_address).collection_data, collection_name);
+ // cannot change maximum from 0 and cannot change maximum to 0
+ assert!(collection_data.maximum != 0 && maximum != 0, error::invalid_argument(EINVALID_MAXIMUM));
+ assert!(maximum >= collection_data.supply, error::invalid_argument(EINVALID_MAXIMUM));
+ assert!(collection_data.mutability_config.maximum, error::permission_denied(EFIELD_NOT_MUTABLE));
+ token_event_store::emit_collection_maximum_mutate_event(creator, collection_name, collection_data.maximum, maximum);
+ collection_data.maximum = maximum;
+}
+
+
+
+
+public fun mutate_tokendata_maximum(creator: &signer, token_data_id: token::TokenDataId, maximum: u64)
+
+
+
+
+public fun mutate_tokendata_maximum(creator: &signer, token_data_id: TokenDataId, maximum: u64) acquires Collections {
+ assert_tokendata_exists(creator, token_data_id);
+ let all_token_data = &mut borrow_global_mut<Collections>(token_data_id.creator).token_data;
+ let token_data = table::borrow_mut(all_token_data, token_data_id);
+ // cannot change maximum from 0 and cannot change maximum to 0
+ assert!(token_data.maximum != 0 && maximum != 0, error::invalid_argument(EINVALID_MAXIMUM));
+ assert!(maximum >= token_data.supply, error::invalid_argument(EINVALID_MAXIMUM));
+ assert!(token_data.mutability_config.maximum, error::permission_denied(EFIELD_NOT_MUTABLE));
+ token_event_store::emit_token_maximum_mutate_event(creator, token_data_id.collection, token_data_id.name, token_data.maximum, maximum);
+ token_data.maximum = maximum;
+}
+
+
+
+
+public fun mutate_tokendata_uri(creator: &signer, token_data_id: token::TokenDataId, uri: string::String)
+
+
+
+
+public fun mutate_tokendata_uri(
+ creator: &signer,
+ token_data_id: TokenDataId,
+ uri: String
+) acquires Collections {
+ assert!(string::length(&uri) <= MAX_URI_LENGTH, error::invalid_argument(EURI_TOO_LONG));
+ assert_tokendata_exists(creator, token_data_id);
+
+ let all_token_data = &mut borrow_global_mut<Collections>(token_data_id.creator).token_data;
+ let token_data = table::borrow_mut(all_token_data, token_data_id);
+ assert!(token_data.mutability_config.uri, error::permission_denied(EFIELD_NOT_MUTABLE));
+ token_event_store::emit_token_uri_mutate_event(creator, token_data_id.collection, token_data_id.name, token_data.uri ,uri);
+ token_data.uri = uri;
+}
+
+
+
+
+public fun mutate_tokendata_royalty(creator: &signer, token_data_id: token::TokenDataId, royalty: token::Royalty)
+
+
+
+
+public fun mutate_tokendata_royalty(creator: &signer, token_data_id: TokenDataId, royalty: Royalty) acquires Collections {
+ assert_tokendata_exists(creator, token_data_id);
+
+ let all_token_data = &mut borrow_global_mut<Collections>(token_data_id.creator).token_data;
+ let token_data = table::borrow_mut(all_token_data, token_data_id);
+ assert!(token_data.mutability_config.royalty, error::permission_denied(EFIELD_NOT_MUTABLE));
+
+ token_event_store::emit_token_royalty_mutate_event(
+ creator,
+ token_data_id.collection,
+ token_data_id.name,
+ token_data.royalty.royalty_points_numerator,
+ token_data.royalty.royalty_points_denominator,
+ token_data.royalty.payee_address,
+ royalty.royalty_points_numerator,
+ royalty.royalty_points_denominator,
+ royalty.payee_address
+ );
+ token_data.royalty = royalty;
+}
+
+
+
+
+public fun mutate_tokendata_description(creator: &signer, token_data_id: token::TokenDataId, description: string::String)
+
+
+
+
+public fun mutate_tokendata_description(creator: &signer, token_data_id: TokenDataId, description: String) acquires Collections {
+ assert_tokendata_exists(creator, token_data_id);
+
+ let all_token_data = &mut borrow_global_mut<Collections>(token_data_id.creator).token_data;
+ let token_data = table::borrow_mut(all_token_data, token_data_id);
+ assert!(token_data.mutability_config.description, error::permission_denied(EFIELD_NOT_MUTABLE));
+ token_event_store::emit_token_descrition_mutate_event(creator, token_data_id.collection, token_data_id.name, token_data.description, description);
+ token_data.description = description;
+}
+
+
+
+
+public fun mutate_tokendata_property(creator: &signer, token_data_id: token::TokenDataId, keys: vector<string::String>, values: vector<vector<u8>>, types: vector<string::String>)
+
+
+
+
+public fun mutate_tokendata_property(
+ creator: &signer,
+ token_data_id: TokenDataId,
+ keys: vector<String>,
+ values: vector<vector<u8>>,
+ types: vector<String>,
+) acquires Collections {
+ assert_tokendata_exists(creator, token_data_id);
+ let key_len = vector::length(&keys);
+ let val_len = vector::length(&values);
+ let typ_len = vector::length(&types);
+ assert!(key_len == val_len, error::invalid_state(ETOKEN_PROPERTIES_COUNT_NOT_MATCH));
+ assert!(key_len == typ_len, error::invalid_state(ETOKEN_PROPERTIES_COUNT_NOT_MATCH));
+
+ let all_token_data = &mut borrow_global_mut<Collections>(token_data_id.creator).token_data;
+ let token_data = table::borrow_mut(all_token_data, token_data_id);
+ assert!(token_data.mutability_config.properties, error::permission_denied(EFIELD_NOT_MUTABLE));
+ let i: u64 = 0;
+ let old_values: vector<Option<PropertyValue>> = vector::empty();
+ let new_values: vector<PropertyValue> = vector::empty();
+ assert_non_standard_reserved_property(&keys);
+ while (i < vector::length(&keys)){
+ let key = vector::borrow(&keys, i);
+ let old_pv = if (property_map::contains_key(&token_data.default_properties, key)) {
+ option::some(*property_map::borrow(&token_data.default_properties, key))
+ } else {
+ option::none<PropertyValue>()
+ };
+ vector::push_back(&mut old_values, old_pv);
+ let new_pv = property_map::create_property_value_raw(*vector::borrow(&values, i), *vector::borrow(&types, i));
+ vector::push_back(&mut new_values, new_pv);
+ if (option::is_some(&old_pv)) {
+ property_map::update_property_value(&mut token_data.default_properties, key, new_pv);
+ } else {
+ property_map::add(&mut token_data.default_properties, *key, new_pv);
+ };
+ i = i + 1;
+ };
+ token_event_store::emit_default_property_mutate_event(creator, token_data_id.collection, token_data_id.name, keys, old_values, new_values);
+}
+
+
+
+
+public fun mutate_one_token(account: &signer, token_owner: address, token_id: token::TokenId, keys: vector<string::String>, values: vector<vector<u8>>, types: vector<string::String>): token::TokenId
+
+
+
+
+public fun mutate_one_token(
+ account: &signer,
+ token_owner: address,
+ token_id: TokenId,
+ keys: vector<String>,
+ values: vector<vector<u8>>,
+ types: vector<String>,
+): TokenId acquires Collections, TokenStore {
+ let creator = token_id.token_data_id.creator;
+ assert!(signer::address_of(account) == creator, error::permission_denied(ENO_MUTATE_CAPABILITY));
+ // validate if the properties is mutable
+ assert!(exists<Collections>(creator), error::not_found(ECOLLECTIONS_NOT_PUBLISHED));
+ let all_token_data = &mut borrow_global_mut<Collections>(
+ creator
+ ).token_data;
+
+ assert!(table::contains(all_token_data, token_id.token_data_id), error::not_found(ETOKEN_DATA_NOT_PUBLISHED));
+ let token_data = table::borrow_mut(all_token_data, token_id.token_data_id);
+
+ // if default property is mutatable, token property is alwasy mutable
+ // we only need to check TOKEN_PROPERTY_MUTABLE when default property is immutable
+ if (!token_data.mutability_config.properties) {
+ assert!(
+ property_map::contains_key(&token_data.default_properties, &string::utf8(TOKEN_PROPERTY_MUTABLE)),
+ error::permission_denied(EFIELD_NOT_MUTABLE)
+ );
+
+ let token_prop_mutable = property_map::read_bool(&token_data.default_properties, &string::utf8(TOKEN_PROPERTY_MUTABLE));
+ assert!(token_prop_mutable, error::permission_denied(EFIELD_NOT_MUTABLE));
+ };
+
+ // check if the property_version is 0 to determine if we need to update the property_version
+ if (token_id.property_version == 0) {
+ let token = withdraw_with_event_internal(token_owner, token_id, 1);
+ // give a new property_version for each token
+ let cur_property_version = token_data.largest_property_version + 1;
+ let new_token_id = create_token_id(token_id.token_data_id, cur_property_version);
+ let new_token = Token {
+ id: new_token_id,
+ amount: 1,
+ token_properties: token_data.default_properties,
+ };
+ direct_deposit(token_owner, new_token);
+ update_token_property_internal(token_owner, new_token_id, keys, values, types);
+ if (std::features::module_event_migration_enabled()) {
+ event::emit(MutateTokenPropertyMap {
+ old_id: token_id,
+ new_id: new_token_id,
+ keys,
+ values,
+ types
+ });
+ };
+ event::emit_event<MutateTokenPropertyMapEvent>(
+ &mut borrow_global_mut<TokenStore>(token_owner).mutate_token_property_events,
+ MutateTokenPropertyMapEvent {
+ old_id: token_id,
+ new_id: new_token_id,
+ keys,
+ values,
+ types
+ },
+ );
+
+ token_data.largest_property_version = cur_property_version;
+ // burn the orignial property_version 0 token after mutation
+ let Token { id: _, amount: _, token_properties: _ } = token;
+ new_token_id
+ } else {
+ // only 1 copy for the token with property verion bigger than 0
+ update_token_property_internal(token_owner, token_id, keys, values, types);
+ if (std::features::module_event_migration_enabled()) {
+ event::emit(MutateTokenPropertyMap {
+ old_id: token_id,
+ new_id: token_id,
+ keys,
+ values,
+ types
+ });
+ };
+ event::emit_event<MutateTokenPropertyMapEvent>(
+ &mut borrow_global_mut<TokenStore>(token_owner).mutate_token_property_events,
+ MutateTokenPropertyMapEvent {
+ old_id: token_id,
+ new_id: token_id,
+ keys,
+ values,
+ types
+ },
+ );
+ token_id
+ }
+}
+
+
+
+
+public fun create_royalty(royalty_points_numerator: u64, royalty_points_denominator: u64, payee_address: address): token::Royalty
+
+
+
+
+public fun create_royalty(royalty_points_numerator: u64, royalty_points_denominator: u64, payee_address: address): Royalty {
+ assert!(royalty_points_numerator <= royalty_points_denominator, error::invalid_argument(EINVALID_ROYALTY_NUMERATOR_DENOMINATOR));
+ assert!(account::exists_at(payee_address), error::invalid_argument(EROYALTY_PAYEE_ACCOUNT_DOES_NOT_EXIST));
+ Royalty {
+ royalty_points_numerator,
+ royalty_points_denominator,
+ payee_address
+ }
+}
+
+
+
+
+public fun deposit_token(account: &signer, token: token::Token)
+
+
+
+
+public fun deposit_token(account: &signer, token: Token) acquires TokenStore {
+ let account_addr = signer::address_of(account);
+ initialize_token_store(account);
+ direct_deposit(account_addr, token)
+}
+
+
+
+
+public fun direct_deposit_with_opt_in(account_addr: address, token: token::Token)
+
+
+
+
+public fun direct_deposit_with_opt_in(account_addr: address, token: Token) acquires TokenStore {
+ let opt_in_transfer = borrow_global<TokenStore>(account_addr).direct_transfer;
+ assert!(opt_in_transfer, error::permission_denied(EUSER_NOT_OPT_IN_DIRECT_TRANSFER));
+ direct_deposit(account_addr, token);
+}
+
+
+
+
+public fun direct_transfer(sender: &signer, receiver: &signer, token_id: token::TokenId, amount: u64)
+
+
+
+
+public fun direct_transfer(
+ sender: &signer,
+ receiver: &signer,
+ token_id: TokenId,
+ amount: u64,
+) acquires TokenStore {
+ let token = withdraw_token(sender, token_id, amount);
+ deposit_token(receiver, token);
+}
+
+
+
+
+public fun initialize_token_store(account: &signer)
+
+
+
+
+public fun initialize_token_store(account: &signer) {
+ if (!exists<TokenStore>(signer::address_of(account))) {
+ move_to(
+ account,
+ TokenStore {
+ tokens: table::new(),
+ direct_transfer: false,
+ deposit_events: account::new_event_handle<DepositEvent>(account),
+ withdraw_events: account::new_event_handle<WithdrawEvent>(account),
+ burn_events: account::new_event_handle<BurnTokenEvent>(account),
+ mutate_token_property_events: account::new_event_handle<MutateTokenPropertyMapEvent>(account),
+ },
+ );
+ }
+}
+
+
+
+
+public fun merge(dst_token: &mut token::Token, source_token: token::Token)
+
+
+
+
+public fun merge(dst_token: &mut Token, source_token: Token) {
+ assert!(&dst_token.id == &source_token.id, error::invalid_argument(EINVALID_TOKEN_MERGE));
+ dst_token.amount = dst_token.amount + source_token.amount;
+ let Token { id: _, amount: _, token_properties: _ } = source_token;
+}
+
+
+
+
+public fun split(dst_token: &mut token::Token, amount: u64): token::Token
+
+
+
+
+public fun split(dst_token: &mut Token, amount: u64): Token {
+ assert!(dst_token.id.property_version == 0, error::invalid_state(ENFT_NOT_SPLITABLE));
+ assert!(dst_token.amount > amount, error::invalid_argument(ETOKEN_SPLIT_AMOUNT_LARGER_OR_EQUAL_TO_TOKEN_AMOUNT));
+ assert!(amount > 0, error::invalid_argument(ETOKEN_CANNOT_HAVE_ZERO_AMOUNT));
+ dst_token.amount = dst_token.amount - amount;
+ Token {
+ id: dst_token.id,
+ amount,
+ token_properties: property_map::empty(),
+ }
+}
+
+
+
+
+public fun token_id(token: &token::Token): &token::TokenId
+
+
+
+
+public fun token_id(token: &Token): &TokenId {
+ &token.id
+}
+
+
+
+
+amount
of tokens from from
to to
.
+
+
+public fun transfer(from: &signer, id: token::TokenId, to: address, amount: u64)
+
+
+
+
+public fun transfer(
+ from: &signer,
+ id: TokenId,
+ to: address,
+ amount: u64,
+) acquires TokenStore {
+ let opt_in_transfer = borrow_global<TokenStore>(to).direct_transfer;
+ assert!(opt_in_transfer, error::permission_denied(EUSER_NOT_OPT_IN_DIRECT_TRANSFER));
+ let token = withdraw_token(from, id, amount);
+ direct_deposit(to, token);
+}
+
+
+
+
+public fun create_withdraw_capability(owner: &signer, token_id: token::TokenId, amount: u64, expiration_sec: u64): token::WithdrawCapability
+
+
+
+
+public fun create_withdraw_capability(
+ owner: &signer,
+ token_id: TokenId,
+ amount: u64,
+ expiration_sec: u64,
+): WithdrawCapability {
+ WithdrawCapability {
+ token_owner: signer::address_of(owner),
+ token_id,
+ amount,
+ expiration_sec,
+ }
+}
+
+
+
+
+public fun withdraw_with_capability(withdraw_proof: token::WithdrawCapability): token::Token
+
+
+
+
+public fun withdraw_with_capability(
+ withdraw_proof: WithdrawCapability,
+): Token acquires TokenStore {
+ // verify the delegation hasn't expired yet
+ assert!(timestamp::now_seconds() <= withdraw_proof.expiration_sec, error::invalid_argument(EWITHDRAW_PROOF_EXPIRES));
+
+ withdraw_with_event_internal(
+ withdraw_proof.token_owner,
+ withdraw_proof.token_id,
+ withdraw_proof.amount,
+ )
+}
+
+
+
+
+public fun partial_withdraw_with_capability(withdraw_proof: token::WithdrawCapability, withdraw_amount: u64): (token::Token, option::Option<token::WithdrawCapability>)
+
+
+
+
+public fun partial_withdraw_with_capability(
+ withdraw_proof: WithdrawCapability,
+ withdraw_amount: u64,
+): (Token, Option<WithdrawCapability>) acquires TokenStore {
+ // verify the delegation hasn't expired yet
+ assert!(timestamp::now_seconds() <= withdraw_proof.expiration_sec, error::invalid_argument(EWITHDRAW_PROOF_EXPIRES));
+
+ assert!(withdraw_amount <= withdraw_proof.amount, error::invalid_argument(EINSUFFICIENT_WITHDRAW_CAPABILITY_AMOUNT));
+
+ let res: Option<WithdrawCapability> = if (withdraw_amount == withdraw_proof.amount) {
+ option::none<WithdrawCapability>()
+ } else {
+ option::some(
+ WithdrawCapability {
+ token_owner: withdraw_proof.token_owner,
+ token_id: withdraw_proof.token_id,
+ amount: withdraw_proof.amount - withdraw_amount,
+ expiration_sec: withdraw_proof.expiration_sec,
+ }
+ )
+ };
+
+ (
+ withdraw_with_event_internal(
+ withdraw_proof.token_owner,
+ withdraw_proof.token_id,
+ withdraw_amount,
+ ),
+ res
+ )
+
+}
+
+
+
+
+public fun withdraw_token(account: &signer, id: token::TokenId, amount: u64): token::Token
+
+
+
+
+public fun withdraw_token(
+ account: &signer,
+ id: TokenId,
+ amount: u64,
+): Token acquires TokenStore {
+ let account_addr = signer::address_of(account);
+ withdraw_with_event_internal(account_addr, id, amount)
+}
+
+
+
+
+public fun create_collection(creator: &signer, name: string::String, description: string::String, uri: string::String, maximum: u64, mutate_setting: vector<bool>)
+
+
+
+
+public fun create_collection(
+ creator: &signer,
+ name: String,
+ description: String,
+ uri: String,
+ maximum: u64,
+ mutate_setting: vector<bool>
+) acquires Collections {
+ assert!(string::length(&name) <= MAX_COLLECTION_NAME_LENGTH, error::invalid_argument(ECOLLECTION_NAME_TOO_LONG));
+ assert!(string::length(&uri) <= MAX_URI_LENGTH, error::invalid_argument(EURI_TOO_LONG));
+ let account_addr = signer::address_of(creator);
+ if (!exists<Collections>(account_addr)) {
+ move_to(
+ creator,
+ Collections {
+ collection_data: table::new(),
+ token_data: table::new(),
+ create_collection_events: account::new_event_handle<CreateCollectionEvent>(creator),
+ create_token_data_events: account::new_event_handle<CreateTokenDataEvent>(creator),
+ mint_token_events: account::new_event_handle<MintTokenEvent>(creator),
+ },
+ )
+ };
+
+ let collection_data = &mut borrow_global_mut<Collections>(account_addr).collection_data;
+
+ assert!(
+ !table::contains(collection_data, name),
+ error::already_exists(ECOLLECTION_ALREADY_EXISTS),
+ );
+
+ let mutability_config = create_collection_mutability_config(&mutate_setting);
+ let collection = CollectionData {
+ description,
+ name: name,
+ uri,
+ supply: 0,
+ maximum,
+ mutability_config
+ };
+
+ table::add(collection_data, name, collection);
+ let collection_handle = borrow_global_mut<Collections>(account_addr);
+ if (std::features::module_event_migration_enabled()) {
+ event::emit(
+ CreateCollection {
+ creator: account_addr,
+ collection_name: name,
+ uri,
+ description,
+ maximum,
+ }
+ );
+ };
+ event::emit_event<CreateCollectionEvent>(
+ &mut collection_handle.create_collection_events,
+ CreateCollectionEvent {
+ creator: account_addr,
+ collection_name: name,
+ uri,
+ description,
+ maximum,
+ }
+ );
+}
+
+
+
+
+public fun check_collection_exists(creator: address, name: string::String): bool
+
+
+
+
+public fun check_collection_exists(creator: address, name: String): bool acquires Collections {
+ assert!(
+ exists<Collections>(creator),
+ error::not_found(ECOLLECTIONS_NOT_PUBLISHED),
+ );
+
+ let collection_data = &borrow_global<Collections>(creator).collection_data;
+ table::contains(collection_data, name)
+}
+
+
+
+
+public fun check_tokendata_exists(creator: address, collection_name: string::String, token_name: string::String): bool
+
+
+
+
+public fun check_tokendata_exists(creator: address, collection_name: String, token_name: String): bool acquires Collections {
+ assert!(
+ exists<Collections>(creator),
+ error::not_found(ECOLLECTIONS_NOT_PUBLISHED),
+ );
+
+ let token_data = &borrow_global<Collections>(creator).token_data;
+ let token_data_id = create_token_data_id(creator, collection_name, token_name);
+ table::contains(token_data, token_data_id)
+}
+
+
+
+
+public fun create_tokendata(account: &signer, collection: string::String, name: string::String, description: string::String, maximum: u64, uri: string::String, royalty_payee_address: address, royalty_points_denominator: u64, royalty_points_numerator: u64, token_mutate_config: token::TokenMutabilityConfig, property_keys: vector<string::String>, property_values: vector<vector<u8>>, property_types: vector<string::String>): token::TokenDataId
+
+
+
+
+public fun create_tokendata(
+ account: &signer,
+ collection: String,
+ name: String,
+ description: String,
+ maximum: u64,
+ uri: String,
+ royalty_payee_address: address,
+ royalty_points_denominator: u64,
+ royalty_points_numerator: u64,
+ token_mutate_config: TokenMutabilityConfig,
+ property_keys: vector<String>,
+ property_values: vector<vector<u8>>,
+ property_types: vector<String>
+): TokenDataId acquires Collections {
+ assert!(string::length(&name) <= MAX_NFT_NAME_LENGTH, error::invalid_argument(ENFT_NAME_TOO_LONG));
+ assert!(string::length(&collection) <= MAX_COLLECTION_NAME_LENGTH, error::invalid_argument(ECOLLECTION_NAME_TOO_LONG));
+ assert!(string::length(&uri) <= MAX_URI_LENGTH, error::invalid_argument(EURI_TOO_LONG));
+ assert!(royalty_points_numerator <= royalty_points_denominator, error::invalid_argument(EINVALID_ROYALTY_NUMERATOR_DENOMINATOR));
+
+ let account_addr = signer::address_of(account);
+ assert!(
+ exists<Collections>(account_addr),
+ error::not_found(ECOLLECTIONS_NOT_PUBLISHED),
+ );
+ let collections = borrow_global_mut<Collections>(account_addr);
+
+ let token_data_id = create_token_data_id(account_addr, collection, name);
+
+ assert!(
+ table::contains(&collections.collection_data, token_data_id.collection),
+ error::not_found(ECOLLECTION_NOT_PUBLISHED),
+ );
+ assert!(
+ !table::contains(&collections.token_data, token_data_id),
+ error::already_exists(ETOKEN_DATA_ALREADY_EXISTS),
+ );
+
+ let collection = table::borrow_mut(&mut collections.collection_data, token_data_id.collection);
+
+ // if collection maximum == 0, user don't want to enforce supply constraint.
+ // we don't track supply to make token creation parallelizable
+ if (collection.maximum > 0) {
+ collection.supply = collection.supply + 1;
+ assert!(
+ collection.maximum >= collection.supply,
+ error::invalid_argument(ECREATE_WOULD_EXCEED_COLLECTION_MAXIMUM),
+ );
+ };
+
+ let token_data = TokenData {
+ maximum,
+ largest_property_version: 0,
+ supply: 0,
+ uri,
+ royalty: create_royalty(royalty_points_numerator, royalty_points_denominator, royalty_payee_address),
+ name,
+ description,
+ default_properties: property_map::new(property_keys, property_values, property_types),
+ mutability_config: token_mutate_config,
+ };
+
+ table::add(&mut collections.token_data, token_data_id, token_data);
+ if (std::features::module_event_migration_enabled()) {
+ event::emit(
+ CreateTokenData {
+ id: token_data_id,
+ description,
+ maximum,
+ uri,
+ royalty_payee_address,
+ royalty_points_denominator,
+ royalty_points_numerator,
+ name,
+ mutability_config: token_mutate_config,
+ property_keys,
+ property_values,
+ property_types,
+ }
+ );
+ };
+
+ event::emit_event<CreateTokenDataEvent>(
+ &mut collections.create_token_data_events,
+ CreateTokenDataEvent {
+ id: token_data_id,
+ description,
+ maximum,
+ uri,
+ royalty_payee_address,
+ royalty_points_denominator,
+ royalty_points_numerator,
+ name,
+ mutability_config: token_mutate_config,
+ property_keys,
+ property_values,
+ property_types,
+ },
+ );
+ token_data_id
+}
+
+
+
+
+public fun get_collection_supply(creator_address: address, collection_name: string::String): option::Option<u64>
+
+
+
+
+public fun get_collection_supply(creator_address: address, collection_name: String): Option<u64> acquires Collections {
+ assert_collection_exists(creator_address, collection_name);
+ let collection_data = table::borrow_mut(&mut borrow_global_mut<Collections>(creator_address).collection_data, collection_name);
+
+ if (collection_data.maximum > 0) {
+ option::some(collection_data.supply)
+ } else {
+ option::none()
+ }
+}
+
+
+
+
+public fun get_collection_description(creator_address: address, collection_name: string::String): string::String
+
+
+
+
+public fun get_collection_description(creator_address: address, collection_name: String): String acquires Collections {
+ assert_collection_exists(creator_address, collection_name);
+ let collection_data = table::borrow_mut(&mut borrow_global_mut<Collections>(creator_address).collection_data, collection_name);
+ collection_data.description
+}
+
+
+
+
+public fun get_collection_uri(creator_address: address, collection_name: string::String): string::String
+
+
+
+
+public fun get_collection_uri(creator_address: address, collection_name: String): String acquires Collections {
+ assert_collection_exists(creator_address, collection_name);
+ let collection_data = table::borrow_mut(&mut borrow_global_mut<Collections>(creator_address).collection_data, collection_name);
+ collection_data.uri
+}
+
+
+
+
+public fun get_collection_maximum(creator_address: address, collection_name: string::String): u64
+
+
+
+
+public fun get_collection_maximum(creator_address: address, collection_name: String): u64 acquires Collections {
+ assert_collection_exists(creator_address, collection_name);
+ let collection_data = table::borrow_mut(&mut borrow_global_mut<Collections>(creator_address).collection_data, collection_name);
+ collection_data.maximum
+}
+
+
+
+
+public fun get_token_supply(creator_address: address, token_data_id: token::TokenDataId): option::Option<u64>
+
+
+
+
+public fun get_token_supply(creator_address: address, token_data_id: TokenDataId): Option<u64> acquires Collections {
+ assert!(exists<Collections>(creator_address), error::not_found(ECOLLECTIONS_NOT_PUBLISHED));
+ let all_token_data = &borrow_global<Collections>(creator_address).token_data;
+ assert!(table::contains(all_token_data, token_data_id), error::not_found(ETOKEN_DATA_NOT_PUBLISHED));
+ let token_data = table::borrow(all_token_data, token_data_id);
+
+ if (token_data.maximum > 0) {
+ option::some(token_data.supply)
+ } else {
+ option::none<u64>()
+ }
+}
+
+
+
+
+public fun get_tokendata_largest_property_version(creator_address: address, token_data_id: token::TokenDataId): u64
+
+
+
+
+public fun get_tokendata_largest_property_version(creator_address: address, token_data_id: TokenDataId): u64 acquires Collections {
+ assert!(exists<Collections>(creator_address), error::not_found(ECOLLECTIONS_NOT_PUBLISHED));
+ let all_token_data = &borrow_global<Collections>(creator_address).token_data;
+ assert!(table::contains(all_token_data, token_data_id), error::not_found(ETOKEN_DATA_NOT_PUBLISHED));
+ table::borrow(all_token_data, token_data_id).largest_property_version
+}
+
+
+
+
+public fun get_token_id(token: &token::Token): token::TokenId
+
+
+
+
+public fun get_token_id(token: &Token): TokenId {
+ token.id
+}
+
+
+
+
+public fun get_direct_transfer(receiver: address): bool
+
+
+
+
+public fun get_direct_transfer(receiver: address): bool acquires TokenStore {
+ if (!exists<TokenStore>(receiver)) {
+ return false
+ };
+
+ borrow_global<TokenStore>(receiver).direct_transfer
+}
+
+
+
+
+public fun create_token_mutability_config(mutate_setting: &vector<bool>): token::TokenMutabilityConfig
+
+
+
+
+public fun create_token_mutability_config(mutate_setting: &vector<bool>): TokenMutabilityConfig {
+ TokenMutabilityConfig {
+ maximum: *vector::borrow(mutate_setting, TOKEN_MAX_MUTABLE_IND),
+ uri: *vector::borrow(mutate_setting, TOKEN_URI_MUTABLE_IND),
+ royalty: *vector::borrow(mutate_setting, TOKEN_ROYALTY_MUTABLE_IND),
+ description: *vector::borrow(mutate_setting, TOKEN_DESCRIPTION_MUTABLE_IND),
+ properties: *vector::borrow(mutate_setting, TOKEN_PROPERTY_MUTABLE_IND),
+ }
+}
+
+
+
+
+public fun create_collection_mutability_config(mutate_setting: &vector<bool>): token::CollectionMutabilityConfig
+
+
+
+
+public fun create_collection_mutability_config(mutate_setting: &vector<bool>): CollectionMutabilityConfig {
+ CollectionMutabilityConfig {
+ description: *vector::borrow(mutate_setting, COLLECTION_DESCRIPTION_MUTABLE_IND),
+ uri: *vector::borrow(mutate_setting, COLLECTION_URI_MUTABLE_IND),
+ maximum: *vector::borrow(mutate_setting, COLLECTION_MAX_MUTABLE_IND),
+ }
+}
+
+
+
+
+public fun mint_token(account: &signer, token_data_id: token::TokenDataId, amount: u64): token::TokenId
+
+
+
+
+public fun mint_token(
+ account: &signer,
+ token_data_id: TokenDataId,
+ amount: u64,
+): TokenId acquires Collections, TokenStore {
+ assert!(token_data_id.creator == signer::address_of(account), error::permission_denied(ENO_MINT_CAPABILITY));
+ let creator_addr = token_data_id.creator;
+ let all_token_data = &mut borrow_global_mut<Collections>(creator_addr).token_data;
+ assert!(table::contains(all_token_data, token_data_id), error::not_found(ETOKEN_DATA_NOT_PUBLISHED));
+ let token_data = table::borrow_mut(all_token_data, token_data_id);
+
+ if (token_data.maximum > 0) {
+ assert!(token_data.supply + amount <= token_data.maximum, error::invalid_argument(EMINT_WOULD_EXCEED_TOKEN_MAXIMUM));
+ token_data.supply = token_data.supply + amount;
+ };
+
+ // we add more tokens with property_version 0
+ let token_id = create_token_id(token_data_id, 0);
+ if (std::features::module_event_migration_enabled()) {
+ event::emit(MintToken { id: token_data_id, amount })
+ };
+ event::emit_event<MintTokenEvent>(
+ &mut borrow_global_mut<Collections>(creator_addr).mint_token_events,
+ MintTokenEvent {
+ id: token_data_id,
+ amount,
+ }
+ );
+
+ deposit_token(account,
+ Token {
+ id: token_id,
+ amount,
+ token_properties: property_map::empty(), // same as default properties no need to store
+ }
+ );
+
+ token_id
+}
+
+
+
+
+public fun mint_token_to(account: &signer, receiver: address, token_data_id: token::TokenDataId, amount: u64)
+
+
+
+
+public fun mint_token_to(
+ account: &signer,
+ receiver: address,
+ token_data_id: TokenDataId,
+ amount: u64,
+) acquires Collections, TokenStore {
+ assert!(exists<TokenStore>(receiver), error::not_found(ETOKEN_STORE_NOT_PUBLISHED));
+ let opt_in_transfer = borrow_global<TokenStore>(receiver).direct_transfer;
+ assert!(opt_in_transfer, error::permission_denied(EUSER_NOT_OPT_IN_DIRECT_TRANSFER));
+
+ assert!(token_data_id.creator == signer::address_of(account), error::permission_denied(ENO_MINT_CAPABILITY));
+ let creator_addr = token_data_id.creator;
+ let all_token_data = &mut borrow_global_mut<Collections>(creator_addr).token_data;
+ assert!(table::contains(all_token_data, token_data_id), error::not_found(ETOKEN_DATA_NOT_PUBLISHED));
+ let token_data = table::borrow_mut(all_token_data, token_data_id);
+
+ if (token_data.maximum > 0) {
+ assert!(token_data.supply + amount <= token_data.maximum, error::invalid_argument(EMINT_WOULD_EXCEED_TOKEN_MAXIMUM));
+ token_data.supply = token_data.supply + amount;
+ };
+
+ // we add more tokens with property_version 0
+ let token_id = create_token_id(token_data_id, 0);
+
+ if (std::features::module_event_migration_enabled()) {
+ event::emit(MintToken { id: token_data_id, amount })
+ };
+ event::emit_event<MintTokenEvent>(
+ &mut borrow_global_mut<Collections>(creator_addr).mint_token_events,
+ MintTokenEvent {
+ id: token_data_id,
+ amount,
+ }
+ );
+
+ direct_deposit(receiver,
+ Token {
+ id: token_id,
+ amount,
+ token_properties: property_map::empty(), // same as default properties no need to store
+ }
+ );
+}
+
+
+
+
+public fun create_token_id(token_data_id: token::TokenDataId, property_version: u64): token::TokenId
+
+
+
+
+public fun create_token_id(token_data_id: TokenDataId, property_version: u64): TokenId {
+ TokenId {
+ token_data_id,
+ property_version,
+ }
+}
+
+
+
+
+public fun create_token_data_id(creator: address, collection: string::String, name: string::String): token::TokenDataId
+
+
+
+
+public fun create_token_data_id(
+ creator: address,
+ collection: String,
+ name: String,
+): TokenDataId {
+ assert!(string::length(&collection) <= MAX_COLLECTION_NAME_LENGTH, error::invalid_argument(ECOLLECTION_NAME_TOO_LONG));
+ assert!(string::length(&name) <= MAX_NFT_NAME_LENGTH, error::invalid_argument(ENFT_NAME_TOO_LONG));
+ TokenDataId { creator, collection, name }
+}
+
+
+
+
+public fun create_token_id_raw(creator: address, collection: string::String, name: string::String, property_version: u64): token::TokenId
+
+
+
+
+public fun create_token_id_raw(
+ creator: address,
+ collection: String,
+ name: String,
+ property_version: u64,
+): TokenId {
+ TokenId {
+ token_data_id: create_token_data_id(creator, collection, name),
+ property_version,
+ }
+}
+
+
+
+
+public fun balance_of(owner: address, id: token::TokenId): u64
+
+
+
+
+public fun balance_of(owner: address, id: TokenId): u64 acquires TokenStore {
+ if (!exists<TokenStore>(owner)) {
+ return 0
+ };
+ let token_store = borrow_global<TokenStore>(owner);
+ if (table::contains(&token_store.tokens, id)) {
+ table::borrow(&token_store.tokens, id).amount
+ } else {
+ 0
+ }
+}
+
+
+
+
+public fun has_token_store(owner: address): bool
+
+
+
+
+public fun has_token_store(owner: address): bool {
+ exists<TokenStore>(owner)
+}
+
+
+
+
+public fun get_royalty(token_id: token::TokenId): token::Royalty
+
+
+
+
+public fun get_royalty(token_id: TokenId): Royalty acquires Collections {
+ let token_data_id = token_id.token_data_id;
+ get_tokendata_royalty(token_data_id)
+}
+
+
+
+
+public fun get_royalty_numerator(royalty: &token::Royalty): u64
+
+
+
+
+public fun get_royalty_numerator(royalty: &Royalty): u64 {
+ royalty.royalty_points_numerator
+}
+
+
+
+
+public fun get_royalty_denominator(royalty: &token::Royalty): u64
+
+
+
+
+public fun get_royalty_denominator(royalty: &Royalty): u64 {
+ royalty.royalty_points_denominator
+}
+
+
+
+
+public fun get_royalty_payee(royalty: &token::Royalty): address
+
+
+
+
+public fun get_royalty_payee(royalty: &Royalty): address {
+ royalty.payee_address
+}
+
+
+
+
+public fun get_token_amount(token: &token::Token): u64
+
+
+
+
+public fun get_token_amount(token: &Token): u64 {
+ token.amount
+}
+
+
+
+
+public fun get_token_id_fields(token_id: &token::TokenId): (address, string::String, string::String, u64)
+
+
+
+
+public fun get_token_id_fields(token_id: &TokenId): (address, String, String, u64) {
+ (
+ token_id.token_data_id.creator,
+ token_id.token_data_id.collection,
+ token_id.token_data_id.name,
+ token_id.property_version,
+ )
+}
+
+
+
+
+public fun get_token_data_id_fields(token_data_id: &token::TokenDataId): (address, string::String, string::String)
+
+
+
+
+public fun get_token_data_id_fields(token_data_id: &TokenDataId): (address, String, String) {
+ (
+ token_data_id.creator,
+ token_data_id.collection,
+ token_data_id.name,
+ )
+}
+
+
+
+
+public fun get_property_map(owner: address, token_id: token::TokenId): property_map::PropertyMap
+
+
+
+
+public fun get_property_map(owner: address, token_id: TokenId): PropertyMap acquires Collections, TokenStore {
+ assert!(balance_of(owner, token_id) > 0, error::not_found(EINSUFFICIENT_BALANCE));
+ // if property_version = 0, return default property map
+ if (token_id.property_version == 0) {
+ let creator_addr = token_id.token_data_id.creator;
+ let all_token_data = &borrow_global<Collections>(creator_addr).token_data;
+ assert!(table::contains(all_token_data, token_id.token_data_id), error::not_found(ETOKEN_DATA_NOT_PUBLISHED));
+ let token_data = table::borrow(all_token_data, token_id.token_data_id);
+ token_data.default_properties
+ } else {
+ let tokens = &borrow_global<TokenStore>(owner).tokens;
+ table::borrow(tokens, token_id).token_properties
+ }
+}
+
+
+
+
+public fun get_tokendata_maximum(token_data_id: token::TokenDataId): u64
+
+
+
+
+public fun get_tokendata_maximum(token_data_id: TokenDataId): u64 acquires Collections {
+ let creator_address = token_data_id.creator;
+ assert!(exists<Collections>(creator_address), error::not_found(ECOLLECTIONS_NOT_PUBLISHED));
+ let all_token_data = &borrow_global<Collections>(creator_address).token_data;
+ assert!(table::contains(all_token_data, token_data_id), error::not_found(ETOKEN_DATA_NOT_PUBLISHED));
+
+ let token_data = table::borrow(all_token_data, token_data_id);
+ token_data.maximum
+}
+
+
+
+
+public fun get_tokendata_uri(creator: address, token_data_id: token::TokenDataId): string::String
+
+
+
+
+public fun get_tokendata_uri(creator: address, token_data_id: TokenDataId): String acquires Collections {
+ assert!(exists<Collections>(creator), error::not_found(ECOLLECTIONS_NOT_PUBLISHED));
+ let all_token_data = &borrow_global<Collections>(creator).token_data;
+ assert!(table::contains(all_token_data, token_data_id), error::not_found(ETOKEN_DATA_NOT_PUBLISHED));
+
+ let token_data = table::borrow(all_token_data, token_data_id);
+ token_data.uri
+}
+
+
+
+
+public fun get_tokendata_description(token_data_id: token::TokenDataId): string::String
+
+
+
+
+public fun get_tokendata_description(token_data_id: TokenDataId): String acquires Collections {
+ let creator_address = token_data_id.creator;
+ assert!(exists<Collections>(creator_address), error::not_found(ECOLLECTIONS_NOT_PUBLISHED));
+ let all_token_data = &borrow_global<Collections>(creator_address).token_data;
+ assert!(table::contains(all_token_data, token_data_id), error::not_found(ETOKEN_DATA_NOT_PUBLISHED));
+
+ let token_data = table::borrow(all_token_data, token_data_id);
+ token_data.description
+}
+
+
+
+
+public fun get_tokendata_royalty(token_data_id: token::TokenDataId): token::Royalty
+
+
+
+
+public fun get_tokendata_royalty(token_data_id: TokenDataId): Royalty acquires Collections {
+ let creator_address = token_data_id.creator;
+ assert!(exists<Collections>(creator_address), error::not_found(ECOLLECTIONS_NOT_PUBLISHED));
+ let all_token_data = &borrow_global<Collections>(creator_address).token_data;
+ assert!(table::contains(all_token_data, token_data_id), error::not_found(ETOKEN_DATA_NOT_PUBLISHED));
+
+ let token_data = table::borrow(all_token_data, token_data_id);
+ token_data.royalty
+}
+
+
+
+
+public fun get_tokendata_id(token_id: token::TokenId): token::TokenDataId
+
+
+
+
+public fun get_tokendata_id(token_id: TokenId): TokenDataId {
+ token_id.token_data_id
+}
+
+
+
+
+public fun get_tokendata_mutability_config(token_data_id: token::TokenDataId): token::TokenMutabilityConfig
+
+
+
+
+public fun get_tokendata_mutability_config(token_data_id: TokenDataId): TokenMutabilityConfig acquires Collections {
+ let creator_addr = token_data_id.creator;
+ assert!(exists<Collections>(creator_addr), error::not_found(ECOLLECTIONS_NOT_PUBLISHED));
+ let all_token_data = &borrow_global<Collections>(creator_addr).token_data;
+ assert!(table::contains(all_token_data, token_data_id), error::not_found(ETOKEN_DATA_NOT_PUBLISHED));
+ table::borrow(all_token_data, token_data_id).mutability_config
+}
+
+
+
+
+public fun get_token_mutability_maximum(config: &token::TokenMutabilityConfig): bool
+
+
+
+
+public fun get_token_mutability_maximum(config: &TokenMutabilityConfig): bool {
+ config.maximum
+}
+
+
+
+
+public fun get_token_mutability_royalty(config: &token::TokenMutabilityConfig): bool
+
+
+
+
+public fun get_token_mutability_royalty(config: &TokenMutabilityConfig): bool {
+ config.royalty
+}
+
+
+
+
+public fun get_token_mutability_uri(config: &token::TokenMutabilityConfig): bool
+
+
+
+
+public fun get_token_mutability_uri(config: &TokenMutabilityConfig): bool {
+ config.uri
+}
+
+
+
+
+public fun get_token_mutability_description(config: &token::TokenMutabilityConfig): bool
+
+
+
+
+public fun get_token_mutability_description(config: &TokenMutabilityConfig): bool {
+ config.description
+}
+
+
+
+
+public fun get_token_mutability_default_properties(config: &token::TokenMutabilityConfig): bool
+
+
+
+
+public fun get_token_mutability_default_properties(config: &TokenMutabilityConfig): bool {
+ config.properties
+}
+
+
+
+
+#[view]
+public fun get_collection_mutability_config(creator: address, collection_name: string::String): token::CollectionMutabilityConfig
+
+
+
+
+public fun get_collection_mutability_config(
+ creator: address,
+ collection_name: String
+): CollectionMutabilityConfig acquires Collections {
+ assert!(exists<Collections>(creator), error::not_found(ECOLLECTIONS_NOT_PUBLISHED));
+ let all_collection_data = &borrow_global<Collections>(creator).collection_data;
+ assert!(table::contains(all_collection_data, collection_name), error::not_found(ECOLLECTION_NOT_PUBLISHED));
+ table::borrow(all_collection_data, collection_name).mutability_config
+}
+
+
+
+
+public fun get_collection_mutability_description(config: &token::CollectionMutabilityConfig): bool
+
+
+
+
+public fun get_collection_mutability_description(config: &CollectionMutabilityConfig): bool {
+ config.description
+}
+
+
+
+
+public fun get_collection_mutability_uri(config: &token::CollectionMutabilityConfig): bool
+
+
+
+
+public fun get_collection_mutability_uri(config: &CollectionMutabilityConfig): bool {
+ config.uri
+}
+
+
+
+
+public fun get_collection_mutability_maximum(config: &token::CollectionMutabilityConfig): bool
+
+
+
+
+public fun get_collection_mutability_maximum(config: &CollectionMutabilityConfig): bool {
+ config.maximum
+}
+
+
+
+
+fun destroy_token_data(token_data: token::TokenData)
+
+
+
+
+fun destroy_token_data(token_data: TokenData) {
+ let TokenData {
+ maximum: _,
+ largest_property_version: _,
+ supply: _,
+ uri: _,
+ royalty: _,
+ name: _,
+ description: _,
+ default_properties: _,
+ mutability_config: _,
+ } = token_data;
+}
+
+
+
+
+fun destroy_collection_data(collection_data: token::CollectionData)
+
+
+
+
+fun destroy_collection_data(collection_data: CollectionData) {
+ let CollectionData {
+ description: _,
+ name: _,
+ uri: _,
+ supply: _,
+ maximum: _,
+ mutability_config: _,
+ } = collection_data;
+}
+
+
+
+
+fun withdraw_with_event_internal(account_addr: address, id: token::TokenId, amount: u64): token::Token
+
+
+
+
+fun withdraw_with_event_internal(
+ account_addr: address,
+ id: TokenId,
+ amount: u64,
+): Token acquires TokenStore {
+ // It does not make sense to withdraw 0 tokens.
+ assert!(amount > 0, error::invalid_argument(EWITHDRAW_ZERO));
+ // Make sure the account has sufficient tokens to withdraw.
+ assert!(balance_of(account_addr, id) >= amount, error::invalid_argument(EINSUFFICIENT_BALANCE));
+
+ assert!(
+ exists<TokenStore>(account_addr),
+ error::not_found(ETOKEN_STORE_NOT_PUBLISHED),
+ );
+
+ let token_store = borrow_global_mut<TokenStore>(account_addr);
+ if (std::features::module_event_migration_enabled()) {
+ event::emit(Withdraw { id, amount })
+ };
+ event::emit_event<WithdrawEvent>(
+ &mut token_store.withdraw_events,
+ WithdrawEvent { id, amount }
+ );
+ let tokens = &mut borrow_global_mut<TokenStore>(account_addr).tokens;
+ assert!(
+ table::contains(tokens, id),
+ error::not_found(ENO_TOKEN_IN_TOKEN_STORE),
+ );
+ // balance > amount and amount > 0 indirectly asserted that balance > 0.
+ let balance = &mut table::borrow_mut(tokens, id).amount;
+ if (*balance > amount) {
+ *balance = *balance - amount;
+ Token { id, amount, token_properties: property_map::empty() }
+ } else {
+ table::remove(tokens, id)
+ }
+}
+
+
+
+
+fun update_token_property_internal(token_owner: address, token_id: token::TokenId, keys: vector<string::String>, values: vector<vector<u8>>, types: vector<string::String>)
+
+
+
+
+fun update_token_property_internal(
+ token_owner: address,
+ token_id: TokenId,
+ keys: vector<String>,
+ values: vector<vector<u8>>,
+ types: vector<String>,
+) acquires TokenStore {
+ let tokens = &mut borrow_global_mut<TokenStore>(token_owner).tokens;
+ assert!(table::contains(tokens, token_id), error::not_found(ENO_TOKEN_IN_TOKEN_STORE));
+
+ let value = &mut table::borrow_mut(tokens, token_id).token_properties;
+ assert_non_standard_reserved_property(&keys);
+ property_map::update_property_map(value, keys, values, types);
+}
+
+
+
+
+fun direct_deposit(account_addr: address, token: token::Token)
+
+
+
+
+fun direct_deposit(account_addr: address, token: Token) acquires TokenStore {
+ assert!(token.amount > 0, error::invalid_argument(ETOKEN_CANNOT_HAVE_ZERO_AMOUNT));
+ let token_store = borrow_global_mut<TokenStore>(account_addr);
+
+ if (std::features::module_event_migration_enabled()) {
+ event::emit(Deposit { id: token.id, amount: token.amount });
+ };
+ event::emit_event<DepositEvent>(
+ &mut token_store.deposit_events,
+ DepositEvent { id: token.id, amount: token.amount },
+ );
+
+ assert!(
+ exists<TokenStore>(account_addr),
+ error::not_found(ETOKEN_STORE_NOT_PUBLISHED),
+ );
+
+ if (!table::contains(&token_store.tokens, token.id)) {
+ table::add(&mut token_store.tokens, token.id, token);
+ } else {
+ let recipient_token = table::borrow_mut(&mut token_store.tokens, token.id);
+ merge(recipient_token, token);
+ };
+}
+
+
+
+
+fun assert_collection_exists(creator_address: address, collection_name: string::String)
+
+
+
+
+fun assert_collection_exists(creator_address: address, collection_name: String) acquires Collections {
+ assert!(exists<Collections>(creator_address), error::not_found(ECOLLECTIONS_NOT_PUBLISHED));
+ let all_collection_data = &borrow_global<Collections>(creator_address).collection_data;
+ assert!(table::contains(all_collection_data, collection_name), error::not_found(ECOLLECTION_NOT_PUBLISHED));
+}
+
+
+
+
+fun assert_tokendata_exists(creator: &signer, token_data_id: token::TokenDataId)
+
+
+
+
+fun assert_tokendata_exists(creator: &signer, token_data_id: TokenDataId) acquires Collections {
+ let creator_addr = token_data_id.creator;
+ assert!(signer::address_of(creator) == creator_addr, error::permission_denied(ENO_MUTATE_CAPABILITY));
+ assert!(exists<Collections>(creator_addr), error::not_found(ECOLLECTIONS_NOT_PUBLISHED));
+ let all_token_data = &mut borrow_global_mut<Collections>(creator_addr).token_data;
+ assert!(table::contains(all_token_data, token_data_id), error::not_found(ETOKEN_DATA_NOT_PUBLISHED));
+}
+
+
+
+
+fun assert_non_standard_reserved_property(keys: &vector<string::String>)
+
+
+
+
+fun assert_non_standard_reserved_property(keys: &vector<String>) {
+ vector::for_each_ref(keys, |key| {
+ let key: &String = key;
+ let length = string::length(key);
+ if (length >= 6) {
+ let prefix = string::sub_string(&*key, 0, 6);
+ assert!(prefix != string::utf8(b"TOKEN_"), error::permission_denied(EPROPERTY_RESERVED_BY_STANDARD));
+ };
+ });
+}
+
+
+
+
+public entry fun initialize_token_script(_account: &signer)
+
+
+
+
+public entry fun initialize_token_script(_account: &signer) {
+ abort 0
+}
+
+
+
+
+public fun initialize_token(_account: &signer, _token_id: token::TokenId)
+
+
+
+
+public fun initialize_token(_account: &signer, _token_id: TokenId) {
+ abort 0
+}
+
+
+
+
+pragma verify = true;
+pragma aborts_if_is_strict;
+
+
+
+
+
+
+### Function `create_collection_script`
+
+
+public entry fun create_collection_script(creator: &signer, name: string::String, description: string::String, uri: string::String, maximum: u64, mutate_setting: vector<bool>)
+
+
+
+The length of the name is up to MAX_COLLECTION_NAME_LENGTH;
+The length of the uri is up to MAX_URI_LENGTH;
+
+
+pragma aborts_if_is_partial;
+include CreateCollectionAbortsIf;
+
+
+
+
+
+
+### Function `create_token_script`
+
+
+public entry fun create_token_script(account: &signer, collection: string::String, name: string::String, description: string::String, balance: u64, maximum: u64, uri: string::String, royalty_payee_address: address, royalty_points_denominator: u64, royalty_points_numerator: u64, mutate_setting: vector<bool>, property_keys: vector<string::String>, property_values: vector<vector<u8>>, property_types: vector<string::String>)
+
+
+
+the length of 'mutate_setting' should maore than five.
+The creator of the TokenDataId is signer.
+The token_data_id should exist in the creator's collections..
+The sum of supply and mint Token is less than maximum.
+
+
+pragma aborts_if_is_partial;
+let addr = signer::address_of(account);
+let token_data_id = spec_create_tokendata(addr, collection, name);
+let creator_addr = token_data_id.creator;
+let all_token_data = global<Collections>(creator_addr).token_data;
+let token_data = table::spec_get(all_token_data, token_data_id);
+aborts_if token_data_id.creator != addr;
+aborts_if !exists<Collections>(creator_addr);
+aborts_if balance <= 0;
+include CreateTokenMutabilityConfigAbortsIf;
+include CreateTokenMutabilityConfigAbortsIf;
+
+
+
+
+
+
+
+
+fun spec_create_tokendata(
+ creator: address,
+ collection: String,
+ name: String): TokenDataId {
+ TokenDataId { creator, collection, name }
+}
+
+
+
+
+
+
+### Function `mint_script`
+
+
+public entry fun mint_script(account: &signer, token_data_address: address, collection: string::String, name: string::String, amount: u64)
+
+
+
+only creator of the tokendata can mint tokens
+
+
+pragma aborts_if_is_partial;
+let token_data_id = spec_create_token_data_id(
+ token_data_address,
+ collection,
+ name,
+);
+let addr = signer::address_of(account);
+let creator_addr = token_data_id.creator;
+let all_token_data = global<Collections>(creator_addr).token_data;
+let token_data = table::spec_get(all_token_data, token_data_id);
+aborts_if token_data_id.creator != signer::address_of(account);
+include CreateTokenDataIdAbortsIf{
+creator: token_data_address,
+collection: collection,
+name: name
+};
+include MintTokenAbortsIf {
+token_data_id: token_data_id
+};
+
+
+
+
+
+
+### Function `mutate_token_properties`
+
+
+public entry fun mutate_token_properties(account: &signer, token_owner: address, creator: address, collection_name: string::String, token_name: string::String, token_property_version: u64, amount: u64, keys: vector<string::String>, values: vector<vector<u8>>, types: vector<string::String>)
+
+
+
+The signer is creator.
+
+
+pragma aborts_if_is_partial;
+let addr = signer::address_of(account);
+aborts_if addr != creator;
+include CreateTokenDataIdAbortsIf {
+ creator: creator,
+ collection: collection_name,
+ name: token_name
+};
+
+
+
+
+
+
+### Function `direct_transfer_script`
+
+
+public entry fun direct_transfer_script(sender: &signer, receiver: &signer, creators_address: address, collection: string::String, name: string::String, property_version: u64, amount: u64)
+
+
+
+
+
+pragma aborts_if_is_partial;
+include CreateTokenDataIdAbortsIf{
+ creator: creators_address,
+ collection: collection,
+ name: name
+};
+
+
+
+
+
+
+### Function `opt_in_direct_transfer`
+
+
+public entry fun opt_in_direct_transfer(account: &signer, opt_in: bool)
+
+
+
+
+
+pragma aborts_if_is_partial;
+let addr = signer::address_of(account);
+let account_addr = global<account::Account>(addr);
+aborts_if !exists<TokenStore>(addr) && !exists<account::Account>(addr);
+aborts_if !exists<TokenStore>(addr) && account_addr.guid_creation_num + 4 >= account::MAX_GUID_CREATION_NUM;
+aborts_if !exists<TokenStore>(addr) && account_addr.guid_creation_num + 4 > MAX_U64;
+aborts_if !exists<token_event_store::TokenEventStoreV1>(addr) && account_addr.guid_creation_num + 9 > account::MAX_GUID_CREATION_NUM;
+aborts_if !exists<token_event_store::TokenEventStoreV1>(addr) && account_addr.guid_creation_num + 9 > MAX_U64;
+aborts_if !exists<token_event_store::TokenEventStoreV1>(addr) && !exists<account::Account>(addr);
+
+
+
+
+
+
+### Function `transfer_with_opt_in`
+
+
+public entry fun transfer_with_opt_in(from: &signer, creator: address, collection_name: string::String, token_name: string::String, token_property_version: u64, to: address, amount: u64)
+
+
+
+
+
+pragma aborts_if_is_partial;
+include CreateTokenDataIdAbortsIf{
+ creator: creator,
+ collection: collection_name,
+ name: token_name
+};
+
+
+
+
+
+
+### Function `burn_by_creator`
+
+
+public entry fun burn_by_creator(creator: &signer, owner: address, collection: string::String, name: string::String, property_version: u64, amount: u64)
+
+
+
+
+
+pragma aborts_if_is_partial;
+let creator_address = signer::address_of(creator);
+let token_id = spec_create_token_id_raw(creator_address, collection, name, property_version);
+let creator_addr = token_id.token_data_id.creator;
+let collections = borrow_global_mut<Collections>(creator_address);
+let token_data = table::spec_get(
+ collections.token_data,
+ token_id.token_data_id,
+);
+aborts_if amount <= 0;
+aborts_if !exists<Collections>(creator_addr);
+aborts_if !table::spec_contains(collections.token_data, token_id.token_data_id);
+aborts_if !simple_map::spec_contains_key(token_data.default_properties.map, std::string::spec_utf8(BURNABLE_BY_CREATOR));
+
+
+
+
+
+
+### Function `burn`
+
+
+public entry fun burn(owner: &signer, creators_address: address, collection: string::String, name: string::String, property_version: u64, amount: u64)
+
+
+
+The token_data_id should exist in token_data.
+
+
+pragma aborts_if_is_partial;
+let token_id = spec_create_token_id_raw(creators_address, collection, name, property_version);
+let creator_addr = token_id.token_data_id.creator;
+let collections = borrow_global_mut<Collections>(creator_addr);
+let token_data = table::spec_get(
+ collections.token_data,
+ token_id.token_data_id,
+);
+include CreateTokenDataIdAbortsIf {
+creator: creators_address
+};
+aborts_if amount <= 0;
+aborts_if !exists<Collections>(creator_addr);
+aborts_if !table::spec_contains(collections.token_data, token_id.token_data_id);
+aborts_if !simple_map::spec_contains_key(token_data.default_properties.map, std::string::spec_utf8(BURNABLE_BY_OWNER));
+aborts_if !string::spec_internal_check_utf8(BURNABLE_BY_OWNER);
+
+
+
+
+
+
+
+
+fun spec_create_token_id_raw(
+ creator: address,
+ collection: String,
+ name: String,
+ property_version: u64,
+): TokenId {
+ let token_data_id = TokenDataId { creator, collection, name };
+ TokenId {
+ token_data_id,
+ property_version
+ }
+}
+
+
+
+
+
+
+### Function `mutate_collection_description`
+
+
+public fun mutate_collection_description(creator: &signer, collection_name: string::String, description: string::String)
+
+
+
+The description of Collection is mutable.
+
+
+let addr = signer::address_of(creator);
+let account = global<account::Account>(addr);
+let collection_data = table::spec_get(global<Collections>(addr).collection_data, collection_name);
+include AssertCollectionExistsAbortsIf {
+ creator_address: addr,
+ collection_name: collection_name
+};
+aborts_if !collection_data.mutability_config.description;
+aborts_if !exists<token_event_store::TokenEventStoreV1>(addr) && !exists<account::Account>(addr);
+aborts_if !exists<token_event_store::TokenEventStoreV1>(addr) && account.guid_creation_num + 9 >= account::MAX_GUID_CREATION_NUM;
+aborts_if !exists<token_event_store::TokenEventStoreV1>(addr) && account.guid_creation_num + 9 > MAX_U64;
+
+
+
+
+
+
+### Function `mutate_collection_uri`
+
+
+public fun mutate_collection_uri(creator: &signer, collection_name: string::String, uri: string::String)
+
+
+
+The uri of Collection is mutable.
+
+
+let addr = signer::address_of(creator);
+let account = global<account::Account>(addr);
+let collection_data = table::spec_get(global<Collections>(addr).collection_data, collection_name);
+aborts_if len(uri.bytes) > MAX_URI_LENGTH;
+include AssertCollectionExistsAbortsIf {
+ creator_address: addr,
+ collection_name: collection_name
+};
+aborts_if !collection_data.mutability_config.uri;
+aborts_if !exists<token_event_store::TokenEventStoreV1>(addr) && !exists<account::Account>(addr);
+aborts_if !exists<token_event_store::TokenEventStoreV1>(addr) && account.guid_creation_num + 9 >= account::MAX_GUID_CREATION_NUM;
+aborts_if !exists<token_event_store::TokenEventStoreV1>(addr) && account.guid_creation_num + 9 > MAX_U64;
+
+
+
+
+
+
+### Function `mutate_collection_maximum`
+
+
+public fun mutate_collection_maximum(creator: &signer, collection_name: string::String, maximum: u64)
+
+
+
+Cannot change maximum from 0 and cannot change maximum to 0.
+The maximum should more than suply.
+The maxium of Collection is mutable.
+
+
+let addr = signer::address_of(creator);
+let account = global<account::Account>(addr);
+let collection_data = table::spec_get(global<Collections>(addr).collection_data, collection_name);
+include AssertCollectionExistsAbortsIf {
+ creator_address: addr,
+ collection_name: collection_name
+};
+aborts_if collection_data.maximum == 0 || maximum == 0;
+aborts_if maximum < collection_data.supply;
+aborts_if !collection_data.mutability_config.maximum;
+aborts_if !exists<token_event_store::TokenEventStoreV1>(addr) && !exists<account::Account>(addr);
+aborts_if !exists<token_event_store::TokenEventStoreV1>(addr) && account.guid_creation_num + 9 >= account::MAX_GUID_CREATION_NUM;
+aborts_if !exists<token_event_store::TokenEventStoreV1>(addr) && account.guid_creation_num + 9 > MAX_U64;
+
+
+
+
+
+
+### Function `mutate_tokendata_maximum`
+
+
+public fun mutate_tokendata_maximum(creator: &signer, token_data_id: token::TokenDataId, maximum: u64)
+
+
+
+Cannot change maximum from 0 and cannot change maximum to 0.
+The maximum should more than suply.
+The token maximum is mutable
+
+
+let addr = signer::address_of(creator);
+let account = global<account::Account>(addr);
+let all_token_data = global<Collections>(token_data_id.creator).token_data;
+let token_data = table::spec_get(all_token_data, token_data_id);
+include AssertTokendataExistsAbortsIf;
+aborts_if token_data.maximum == 0 || maximum == 0;
+aborts_if maximum < token_data.supply;
+aborts_if !token_data.mutability_config.maximum;
+aborts_if !exists<token_event_store::TokenEventStoreV1>(addr) && !exists<account::Account>(addr);
+aborts_if !exists<token_event_store::TokenEventStoreV1>(addr) && account.guid_creation_num + 9 >= account::MAX_GUID_CREATION_NUM;
+aborts_if !exists<token_event_store::TokenEventStoreV1>(addr) && account.guid_creation_num + 9 > MAX_U64;
+
+
+
+
+
+
+### Function `mutate_tokendata_uri`
+
+
+public fun mutate_tokendata_uri(creator: &signer, token_data_id: token::TokenDataId, uri: string::String)
+
+
+
+The length of uri should less than MAX_URI_LENGTH.
+The creator of token_data_id should exist in Collections.
+The token uri is mutable
+
+
+let addr = signer::address_of(creator);
+let account = global<account::Account>(addr);
+let all_token_data = global<Collections>(token_data_id.creator).token_data;
+let token_data = table::spec_get(all_token_data, token_data_id);
+include AssertTokendataExistsAbortsIf;
+aborts_if len(uri.bytes) > MAX_URI_LENGTH;
+aborts_if !token_data.mutability_config.uri;
+aborts_if !exists<token_event_store::TokenEventStoreV1>(addr) && !exists<account::Account>(addr);
+aborts_if !exists<token_event_store::TokenEventStoreV1>(addr) && account.guid_creation_num + 9 >= account::MAX_GUID_CREATION_NUM;
+aborts_if !exists<token_event_store::TokenEventStoreV1>(addr) && account.guid_creation_num + 9 > MAX_U64;
+
+
+
+
+
+
+### Function `mutate_tokendata_royalty`
+
+
+public fun mutate_tokendata_royalty(creator: &signer, token_data_id: token::TokenDataId, royalty: token::Royalty)
+
+
+
+The token royalty is mutable
+
+
+include AssertTokendataExistsAbortsIf;
+let addr = signer::address_of(creator);
+let account = global<account::Account>(addr);
+let all_token_data = global<Collections>(token_data_id.creator).token_data;
+let token_data = table::spec_get(all_token_data, token_data_id);
+aborts_if !token_data.mutability_config.royalty;
+aborts_if !exists<token_event_store::TokenEventStoreV1>(addr) && !exists<account::Account>(addr);
+aborts_if !exists<token_event_store::TokenEventStoreV1>(addr) && account.guid_creation_num + 9 >= account::MAX_GUID_CREATION_NUM;
+aborts_if !exists<token_event_store::TokenEventStoreV1>(addr) && account.guid_creation_num + 9 > MAX_U64;
+
+
+
+
+
+
+### Function `mutate_tokendata_description`
+
+
+public fun mutate_tokendata_description(creator: &signer, token_data_id: token::TokenDataId, description: string::String)
+
+
+
+The token description is mutable
+
+
+include AssertTokendataExistsAbortsIf;
+let addr = signer::address_of(creator);
+let account = global<account::Account>(addr);
+let all_token_data = global<Collections>(token_data_id.creator).token_data;
+let token_data = table::spec_get(all_token_data, token_data_id);
+aborts_if !token_data.mutability_config.description;
+aborts_if !exists<token_event_store::TokenEventStoreV1>(addr) && !exists<account::Account>(addr);
+aborts_if !exists<token_event_store::TokenEventStoreV1>(addr) && account.guid_creation_num + 9 >= account::MAX_GUID_CREATION_NUM;
+aborts_if !exists<token_event_store::TokenEventStoreV1>(addr) && account.guid_creation_num + 9 > MAX_U64;
+
+
+
+
+
+
+### Function `mutate_tokendata_property`
+
+
+public fun mutate_tokendata_property(creator: &signer, token_data_id: token::TokenDataId, keys: vector<string::String>, values: vector<vector<u8>>, types: vector<string::String>)
+
+
+
+The property map is mutable
+
+
+pragma aborts_if_is_partial;
+let all_token_data = global<Collections>(token_data_id.creator).token_data;
+let token_data = table::spec_get(all_token_data, token_data_id);
+include AssertTokendataExistsAbortsIf;
+aborts_if len(keys) != len(values);
+aborts_if len(keys) != len(types);
+aborts_if !token_data.mutability_config.properties;
+
+
+
+
+
+
+### Function `mutate_one_token`
+
+
+public fun mutate_one_token(account: &signer, token_owner: address, token_id: token::TokenId, keys: vector<string::String>, values: vector<vector<u8>>, types: vector<string::String>): token::TokenId
+
+
+
+The signer is creator.
+The token_data_id should exist in token_data.
+The property map is mutable.
+
+
+pragma aborts_if_is_partial;
+let creator = token_id.token_data_id.creator;
+let addr = signer::address_of(account);
+let all_token_data = global<Collections>(creator).token_data;
+let token_data = table::spec_get(all_token_data, token_id.token_data_id);
+aborts_if addr != creator;
+aborts_if !exists<Collections>(creator);
+aborts_if !table::spec_contains(all_token_data, token_id.token_data_id);
+aborts_if !token_data.mutability_config.properties && !simple_map::spec_contains_key(token_data.default_properties.map, std::string::spec_utf8(TOKEN_PROPERTY_MUTABLE));
+
+
+
+
+
+
+### Function `create_royalty`
+
+
+public fun create_royalty(royalty_points_numerator: u64, royalty_points_denominator: u64, payee_address: address): token::Royalty
+
+
+
+
+
+include CreateRoyaltyAbortsIf;
+
+
+
+The royalty_points_numerator should less than royalty_points_denominator.
+
+
+
+
+
+schema CreateRoyaltyAbortsIf {
+ royalty_points_numerator: u64;
+ royalty_points_denominator: u64;
+ payee_address: address;
+ aborts_if royalty_points_numerator > royalty_points_denominator;
+ aborts_if !exists<account::Account>(payee_address);
+}
+
+
+
+
+
+
+### Function `deposit_token`
+
+
+public fun deposit_token(account: &signer, token: token::Token)
+
+
+
+
+
+pragma verify = false;
+pragma aborts_if_is_partial;
+let account_addr = signer::address_of(account);
+include !exists<TokenStore>(account_addr) ==> InitializeTokenStore;
+let token_id = token.id;
+let token_amount = token.amount;
+include DirectDepositAbortsIf;
+
+
+
+
+
+
+### Function `direct_deposit_with_opt_in`
+
+
+public fun direct_deposit_with_opt_in(account_addr: address, token: token::Token)
+
+
+
+The token can direct_transfer.
+
+
+let opt_in_transfer = global<TokenStore>(account_addr).direct_transfer;
+aborts_if !exists<TokenStore>(account_addr);
+aborts_if !opt_in_transfer;
+let token_id = token.id;
+let token_amount = token.amount;
+include DirectDepositAbortsIf;
+
+
+
+
+
+
+### Function `direct_transfer`
+
+
+public fun direct_transfer(sender: &signer, receiver: &signer, token_id: token::TokenId, amount: u64)
+
+
+
+Cannot withdraw 0 tokens.
+Make sure the account has sufficient tokens to withdraw.
+
+
+pragma verify = false;
+
+
+
+
+
+
+### Function `initialize_token_store`
+
+
+public fun initialize_token_store(account: &signer)
+
+
+
+
+
+include InitializeTokenStore;
+
+
+
+
+
+
+
+
+schema InitializeTokenStore {
+ account: signer;
+ let addr = signer::address_of(account);
+ let account_addr = global<account::Account>(addr);
+ aborts_if !exists<TokenStore>(addr) && !exists<account::Account>(addr);
+ aborts_if !exists<TokenStore>(addr) && account_addr.guid_creation_num + 4 >= account::MAX_GUID_CREATION_NUM;
+ aborts_if !exists<TokenStore>(addr) && account_addr.guid_creation_num + 4 > MAX_U64;
+}
+
+
+
+
+
+
+### Function `merge`
+
+
+public fun merge(dst_token: &mut token::Token, source_token: token::Token)
+
+
+
+
+
+aborts_if dst_token.id != source_token.id;
+aborts_if dst_token.amount + source_token.amount > MAX_U64;
+
+
+
+
+
+
+### Function `split`
+
+
+public fun split(dst_token: &mut token::Token, amount: u64): token::Token
+
+
+
+
+
+aborts_if dst_token.id.property_version != 0;
+aborts_if dst_token.amount <= amount;
+aborts_if amount <= 0;
+
+
+
+
+
+
+### Function `transfer`
+
+
+public fun transfer(from: &signer, id: token::TokenId, to: address, amount: u64)
+
+
+
+
+
+let opt_in_transfer = global<TokenStore>(to).direct_transfer;
+let account_addr = signer::address_of(from);
+aborts_if !opt_in_transfer;
+pragma aborts_if_is_partial;
+include WithdrawWithEventInternalAbortsIf;
+
+
+
+
+
+
+### Function `withdraw_with_capability`
+
+
+public fun withdraw_with_capability(withdraw_proof: token::WithdrawCapability): token::Token
+
+
+
+
+
+let now_seconds = global<timestamp::CurrentTimeMicroseconds>(@aptos_framework).microseconds;
+aborts_if !exists<timestamp::CurrentTimeMicroseconds>(@aptos_framework);
+aborts_if now_seconds / timestamp::MICRO_CONVERSION_FACTOR > withdraw_proof.expiration_sec;
+include WithdrawWithEventInternalAbortsIf{
+account_addr: withdraw_proof.token_owner,
+id: withdraw_proof.token_id,
+amount: withdraw_proof.amount};
+
+
+
+
+
+
+### Function `partial_withdraw_with_capability`
+
+
+public fun partial_withdraw_with_capability(withdraw_proof: token::WithdrawCapability, withdraw_amount: u64): (token::Token, option::Option<token::WithdrawCapability>)
+
+
+
+
+
+let now_seconds = global<timestamp::CurrentTimeMicroseconds>(@aptos_framework).microseconds;
+aborts_if !exists<timestamp::CurrentTimeMicroseconds>(@aptos_framework);
+aborts_if now_seconds / timestamp::MICRO_CONVERSION_FACTOR > withdraw_proof.expiration_sec;
+aborts_if withdraw_amount > withdraw_proof.amount;
+include WithdrawWithEventInternalAbortsIf{
+ account_addr: withdraw_proof.token_owner,
+ id: withdraw_proof.token_id,
+ amount: withdraw_amount
+};
+
+
+
+
+
+
+### Function `withdraw_token`
+
+
+public fun withdraw_token(account: &signer, id: token::TokenId, amount: u64): token::Token
+
+
+
+Cannot withdraw 0 tokens.
+Make sure the account has sufficient tokens to withdraw.
+
+
+let account_addr = signer::address_of(account);
+include WithdrawWithEventInternalAbortsIf;
+
+
+
+
+
+
+### Function `create_collection`
+
+
+public fun create_collection(creator: &signer, name: string::String, description: string::String, uri: string::String, maximum: u64, mutate_setting: vector<bool>)
+
+
+
+The length of the name is up to MAX_COLLECTION_NAME_LENGTH;
+The length of the uri is up to MAX_URI_LENGTH;
+The collection_data should not exist before you create it.
+
+
+pragma aborts_if_is_partial;
+let account_addr = signer::address_of(creator);
+aborts_if len(name.bytes) > 128;
+aborts_if len(uri.bytes) > 512;
+include CreateCollectionAbortsIf;
+
+
+
+
+
+
+
+
+schema CreateCollectionAbortsIf {
+ creator: signer;
+ name: String;
+ description: String;
+ uri: String;
+ maximum: u64;
+ mutate_setting: vector<bool>;
+ let addr = signer::address_of(creator);
+ let account = global<account::Account>(addr);
+ let collection = global<Collections>(addr);
+ let b = !exists<Collections>(addr);
+ let collection_data = global<Collections>(addr).collection_data;
+ aborts_if b && !exists<account::Account>(addr);
+ aborts_if len(name.bytes) > MAX_COLLECTION_NAME_LENGTH;
+ aborts_if len(uri.bytes) > MAX_URI_LENGTH;
+ aborts_if b && account.guid_creation_num + 3 >= account::MAX_GUID_CREATION_NUM;
+ aborts_if b && account.guid_creation_num + 3 > MAX_U64;
+ include CreateCollectionMutabilityConfigAbortsIf;
+}
+
+
+
+
+
+
+### Function `check_collection_exists`
+
+
+public fun check_collection_exists(creator: address, name: string::String): bool
+
+
+
+
+
+aborts_if !exists<Collections>(creator);
+
+
+
+
+
+
+### Function `check_tokendata_exists`
+
+
+public fun check_tokendata_exists(creator: address, collection_name: string::String, token_name: string::String): bool
+
+
+
+The length of collection should less than MAX_COLLECTION_NAME_LENGTH
+The length of name should less than MAX_NFT_NAME_LENGTH
+
+
+aborts_if !exists<Collections>(creator);
+include CreateTokenDataIdAbortsIf {
+ creator: creator,
+ collection: collection_name,
+ name: token_name
+};
+
+
+
+
+
+
+### Function `create_tokendata`
+
+
+public fun create_tokendata(account: &signer, collection: string::String, name: string::String, description: string::String, maximum: u64, uri: string::String, royalty_payee_address: address, royalty_points_denominator: u64, royalty_points_numerator: u64, token_mutate_config: token::TokenMutabilityConfig, property_keys: vector<string::String>, property_values: vector<vector<u8>>, property_types: vector<string::String>): token::TokenDataId
+
+
+
+The length of collection should less than MAX_COLLECTION_NAME_LENGTH
+The length of name should less than MAX_NFT_NAME_LENGTH
+
+
+pragma verify = false;
+pragma aborts_if_is_partial;
+let account_addr = signer::address_of(account);
+let collections = global<Collections>(account_addr);
+let token_data_id = spec_create_token_data_id(account_addr, collection, name);
+let Collection = table::spec_get(collections.collection_data, token_data_id.collection);
+let length = len(property_keys);
+aborts_if len(name.bytes) > MAX_NFT_NAME_LENGTH;
+aborts_if len(collection.bytes) > MAX_COLLECTION_NAME_LENGTH;
+aborts_if len(uri.bytes) > MAX_URI_LENGTH;
+aborts_if royalty_points_numerator > royalty_points_denominator;
+aborts_if !exists<Collections>(account_addr);
+include CreateTokenDataIdAbortsIf {
+ creator: account_addr,
+ collection: collection,
+ name: name
+};
+aborts_if !table::spec_contains(collections.collection_data, collection);
+aborts_if table::spec_contains(collections.token_data, token_data_id);
+aborts_if Collection.maximum > 0 && Collection.supply + 1 > MAX_U64;
+aborts_if Collection.maximum > 0 && Collection.maximum < Collection.supply + 1;
+include CreateRoyaltyAbortsIf {
+ payee_address: royalty_payee_address
+};
+aborts_if length > property_map::MAX_PROPERTY_MAP_SIZE;
+aborts_if length != len(property_values);
+aborts_if length != len(property_types);
+
+
+
+
+
+
+
+
+fun spec_create_token_data_id(
+ creator: address,
+ collection: String,
+ name: String,
+): TokenDataId {
+ TokenDataId { creator, collection, name }
+}
+
+
+
+
+
+
+### Function `get_collection_supply`
+
+
+public fun get_collection_supply(creator_address: address, collection_name: string::String): option::Option<u64>
+
+
+
+
+
+include AssertCollectionExistsAbortsIf;
+
+
+
+
+
+
+### Function `get_collection_description`
+
+
+public fun get_collection_description(creator_address: address, collection_name: string::String): string::String
+
+
+
+
+
+include AssertCollectionExistsAbortsIf;
+
+
+
+
+
+
+### Function `get_collection_uri`
+
+
+public fun get_collection_uri(creator_address: address, collection_name: string::String): string::String
+
+
+
+
+
+include AssertCollectionExistsAbortsIf;
+
+
+
+
+
+
+### Function `get_collection_maximum`
+
+
+public fun get_collection_maximum(creator_address: address, collection_name: string::String): u64
+
+
+
+
+
+include AssertCollectionExistsAbortsIf;
+
+
+
+
+
+
+### Function `get_token_supply`
+
+
+public fun get_token_supply(creator_address: address, token_data_id: token::TokenDataId): option::Option<u64>
+
+
+
+
+
+aborts_if !exists<Collections>(creator_address);
+let all_token_data = global<Collections>(creator_address).token_data;
+aborts_if !table::spec_contains(all_token_data, token_data_id);
+
+
+
+
+
+
+### Function `get_tokendata_largest_property_version`
+
+
+public fun get_tokendata_largest_property_version(creator_address: address, token_data_id: token::TokenDataId): u64
+
+
+
+
+
+aborts_if !exists<Collections>(creator_address);
+let all_token_data = global<Collections>(creator_address).token_data;
+aborts_if !table::spec_contains(all_token_data, token_data_id);
+
+
+
+
+
+
+### Function `create_token_mutability_config`
+
+
+public fun create_token_mutability_config(mutate_setting: &vector<bool>): token::TokenMutabilityConfig
+
+
+
+The length of 'mutate_setting' should more than five.
+The mutate_setting shuold have a value.
+
+
+include CreateTokenMutabilityConfigAbortsIf;
+
+
+
+
+
+
+
+
+schema CreateTokenMutabilityConfigAbortsIf {
+ mutate_setting: vector<bool>;
+ aborts_if len(mutate_setting) < 5;
+ aborts_if !vector::spec_contains(mutate_setting, mutate_setting[TOKEN_MAX_MUTABLE_IND]);
+ aborts_if !vector::spec_contains(mutate_setting, mutate_setting[TOKEN_URI_MUTABLE_IND]);
+ aborts_if !vector::spec_contains(mutate_setting, mutate_setting[TOKEN_ROYALTY_MUTABLE_IND]);
+ aborts_if !vector::spec_contains(mutate_setting, mutate_setting[TOKEN_DESCRIPTION_MUTABLE_IND]);
+ aborts_if !vector::spec_contains(mutate_setting, mutate_setting[TOKEN_PROPERTY_MUTABLE_IND]);
+}
+
+
+
+
+
+
+### Function `create_collection_mutability_config`
+
+
+public fun create_collection_mutability_config(mutate_setting: &vector<bool>): token::CollectionMutabilityConfig
+
+
+
+
+
+include CreateCollectionMutabilityConfigAbortsIf;
+
+
+
+
+
+
+
+
+schema CreateCollectionMutabilityConfigAbortsIf {
+ mutate_setting: vector<bool>;
+ aborts_if len(mutate_setting) < 3;
+ aborts_if !vector::spec_contains(mutate_setting, mutate_setting[COLLECTION_DESCRIPTION_MUTABLE_IND]);
+ aborts_if !vector::spec_contains(mutate_setting, mutate_setting[COLLECTION_URI_MUTABLE_IND]);
+ aborts_if !vector::spec_contains(mutate_setting, mutate_setting[COLLECTION_MAX_MUTABLE_IND]);
+}
+
+
+
+
+
+
+### Function `mint_token`
+
+
+public fun mint_token(account: &signer, token_data_id: token::TokenDataId, amount: u64): token::TokenId
+
+
+
+The creator of the TokenDataId is signer.
+The token_data_id should exist in the creator's collections..
+The sum of supply and the amount of mint Token is less than maximum.
+
+
+pragma verify = false;
+
+
+
+
+
+
+
+
+schema MintTokenAbortsIf {
+ account: signer;
+ token_data_id: TokenDataId;
+ amount: u64;
+ let addr = signer::address_of(account);
+ let creator_addr = token_data_id.creator;
+ let all_token_data = global<Collections>(creator_addr).token_data;
+ let token_data = table::spec_get(all_token_data, token_data_id);
+ aborts_if token_data_id.creator != addr;
+ aborts_if !table::spec_contains(all_token_data, token_data_id);
+ aborts_if token_data.maximum > 0 && token_data.supply + amount > token_data.maximum;
+ aborts_if !exists<Collections>(creator_addr);
+ aborts_if amount <= 0;
+ include InitializeTokenStore;
+ let token_id = create_token_id(token_data_id, 0);
+}
+
+
+
+
+
+
+### Function `mint_token_to`
+
+
+public fun mint_token_to(account: &signer, receiver: address, token_data_id: token::TokenDataId, amount: u64)
+
+
+
+
+
+let addr = signer::address_of(account);
+let opt_in_transfer = global<TokenStore>(receiver).direct_transfer;
+let creator_addr = token_data_id.creator;
+let all_token_data = global<Collections>(creator_addr).token_data;
+let token_data = table::spec_get(all_token_data, token_data_id);
+aborts_if !exists<TokenStore>(receiver);
+aborts_if !opt_in_transfer;
+aborts_if token_data_id.creator != addr;
+aborts_if !table::spec_contains(all_token_data, token_data_id);
+aborts_if token_data.maximum > 0 && token_data.supply + amount > token_data.maximum;
+aborts_if amount <= 0;
+aborts_if !exists<Collections>(creator_addr);
+let token_id = create_token_id(token_data_id, 0);
+include DirectDepositAbortsIf {
+ account_addr: receiver,
+ token_id: token_id,
+ token_amount: amount,
+};
+
+
+
+
+
+
+### Function `create_token_data_id`
+
+
+public fun create_token_data_id(creator: address, collection: string::String, name: string::String): token::TokenDataId
+
+
+
+The length of collection should less than MAX_COLLECTION_NAME_LENGTH
+The length of name should less than MAX_NFT_NAME_LENGTH
+
+
+include CreateTokenDataIdAbortsIf;
+
+
+
+
+
+
+
+
+schema CreateTokenDataIdAbortsIf {
+ creator: address;
+ collection: String;
+ name: String;
+ aborts_if len(collection.bytes) > MAX_COLLECTION_NAME_LENGTH;
+ aborts_if len(name.bytes) > MAX_NFT_NAME_LENGTH;
+}
+
+
+
+
+
+
+### Function `create_token_id_raw`
+
+
+public fun create_token_id_raw(creator: address, collection: string::String, name: string::String, property_version: u64): token::TokenId
+
+
+
+The length of collection should less than MAX_COLLECTION_NAME_LENGTH
+The length of name should less than MAX_NFT_NAME_LENGTH
+
+
+include CreateTokenDataIdAbortsIf;
+
+
+
+
+
+
+
+
+fun spec_balance_of(owner: address, id: TokenId): u64 {
+ let token_store = borrow_global<TokenStore>(owner);
+ if (!exists<TokenStore>(owner)) {
+ 0
+ }
+ else if (table::spec_contains(token_store.tokens, id)) {
+ table::spec_get(token_store.tokens, id).amount
+ } else {
+ 0
+ }
+}
+
+
+
+
+
+
+### Function `get_royalty`
+
+
+public fun get_royalty(token_id: token::TokenId): token::Royalty
+
+
+
+
+
+include GetTokendataRoyaltyAbortsIf {
+ token_data_id: token_id.token_data_id
+};
+
+
+
+
+
+
+### Function `get_property_map`
+
+
+public fun get_property_map(owner: address, token_id: token::TokenId): property_map::PropertyMap
+
+
+
+
+
+let creator_addr = token_id.token_data_id.creator;
+let all_token_data = global<Collections>(creator_addr).token_data;
+aborts_if spec_balance_of(owner, token_id) <= 0;
+aborts_if token_id.property_version == 0 && !table::spec_contains(all_token_data, token_id.token_data_id);
+aborts_if token_id.property_version == 0 && !exists<Collections>(creator_addr);
+
+
+
+
+
+
+### Function `get_tokendata_maximum`
+
+
+public fun get_tokendata_maximum(token_data_id: token::TokenDataId): u64
+
+
+
+
+
+let creator_address = token_data_id.creator;
+aborts_if !exists<Collections>(creator_address);
+let all_token_data = global<Collections>(creator_address).token_data;
+aborts_if !table::spec_contains(all_token_data, token_data_id);
+
+
+
+
+
+
+### Function `get_tokendata_uri`
+
+
+public fun get_tokendata_uri(creator: address, token_data_id: token::TokenDataId): string::String
+
+
+
+
+
+aborts_if !exists<Collections>(creator);
+let all_token_data = global<Collections>(creator).token_data;
+aborts_if !table::spec_contains(all_token_data, token_data_id);
+
+
+
+
+
+
+### Function `get_tokendata_description`
+
+
+public fun get_tokendata_description(token_data_id: token::TokenDataId): string::String
+
+
+
+
+
+let creator_address = token_data_id.creator;
+aborts_if !exists<Collections>(creator_address);
+let all_token_data = global<Collections>(creator_address).token_data;
+aborts_if !table::spec_contains(all_token_data, token_data_id);
+
+
+
+
+
+
+### Function `get_tokendata_royalty`
+
+
+public fun get_tokendata_royalty(token_data_id: token::TokenDataId): token::Royalty
+
+
+
+
+
+include GetTokendataRoyaltyAbortsIf;
+
+
+
+
+
+
+
+
+schema GetTokendataRoyaltyAbortsIf {
+ token_data_id: TokenDataId;
+ let creator_address = token_data_id.creator;
+ let all_token_data = global<Collections>(creator_address).token_data;
+ aborts_if !exists<Collections>(creator_address);
+ aborts_if !table::spec_contains(all_token_data, token_data_id);
+}
+
+
+
+
+
+
+### Function `get_tokendata_mutability_config`
+
+
+public fun get_tokendata_mutability_config(token_data_id: token::TokenDataId): token::TokenMutabilityConfig
+
+
+
+
+
+let creator_addr = token_data_id.creator;
+let all_token_data = global<Collections>(creator_addr).token_data;
+aborts_if !exists<Collections>(creator_addr);
+aborts_if !table::spec_contains(all_token_data, token_data_id);
+
+
+
+
+
+
+### Function `get_collection_mutability_config`
+
+
+#[view]
+public fun get_collection_mutability_config(creator: address, collection_name: string::String): token::CollectionMutabilityConfig
+
+
+
+
+
+let all_collection_data = global<Collections>(creator).collection_data;
+aborts_if !exists<Collections>(creator);
+aborts_if !table::spec_contains(all_collection_data, collection_name);
+
+
+
+
+
+
+### Function `withdraw_with_event_internal`
+
+
+fun withdraw_with_event_internal(account_addr: address, id: token::TokenId, amount: u64): token::Token
+
+
+
+
+
+include WithdrawWithEventInternalAbortsIf;
+
+
+
+
+
+
+
+
+schema WithdrawWithEventInternalAbortsIf {
+ account_addr: address;
+ id: TokenId;
+ amount: u64;
+ let tokens = global<TokenStore>(account_addr).tokens;
+ aborts_if amount <= 0;
+ aborts_if spec_balance_of(account_addr, id) < amount;
+ aborts_if !exists<TokenStore>(account_addr);
+ aborts_if !table::spec_contains(tokens, id);
+}
+
+
+
+
+
+
+### Function `update_token_property_internal`
+
+
+fun update_token_property_internal(token_owner: address, token_id: token::TokenId, keys: vector<string::String>, values: vector<vector<u8>>, types: vector<string::String>)
+
+
+
+
+
+pragma aborts_if_is_partial;
+let tokens = global<TokenStore>(token_owner).tokens;
+aborts_if !exists<TokenStore>(token_owner);
+aborts_if !table::spec_contains(tokens, token_id);
+
+
+
+
+
+
+### Function `direct_deposit`
+
+
+fun direct_deposit(account_addr: address, token: token::Token)
+
+
+
+
+
+let token_id = token.id;
+let token_amount = token.amount;
+include DirectDepositAbortsIf;
+
+
+
+
+
+
+
+
+schema DirectDepositAbortsIf {
+ account_addr: address;
+ token_id: TokenId;
+ token_amount: u64;
+ let token_store = global<TokenStore>(account_addr);
+ let recipient_token = table::spec_get(token_store.tokens, token_id);
+ let b = table::spec_contains(token_store.tokens, token_id);
+ aborts_if token_amount <= 0;
+ aborts_if !exists<TokenStore>(account_addr);
+ aborts_if b && recipient_token.id != token_id;
+ aborts_if b && recipient_token.amount + token_amount > MAX_U64;
+}
+
+
+
+
+
+
+### Function `assert_collection_exists`
+
+
+fun assert_collection_exists(creator_address: address, collection_name: string::String)
+
+
+
+The collection_name should exist in collection_data of the creator_address's Collections.
+
+
+include AssertCollectionExistsAbortsIf;
+
+
+
+
+
+
+
+
+schema AssertCollectionExistsAbortsIf {
+ creator_address: address;
+ collection_name: String;
+ let all_collection_data = global<Collections>(creator_address).collection_data;
+ aborts_if !exists<Collections>(creator_address);
+ aborts_if !table::spec_contains(all_collection_data, collection_name);
+}
+
+
+
+
+
+
+### Function `assert_tokendata_exists`
+
+
+fun assert_tokendata_exists(creator: &signer, token_data_id: token::TokenDataId)
+
+
+
+The creator of token_data_id should be signer.
+The creator of token_data_id exists in Collections.
+The token_data_id is in the all_token_data.
+
+
+include AssertTokendataExistsAbortsIf;
+
+
+
+
+
+
+
+
+schema AssertTokendataExistsAbortsIf {
+ creator: signer;
+ token_data_id: TokenDataId;
+ let creator_addr = token_data_id.creator;
+ let addr = signer::address_of(creator);
+ aborts_if addr != creator_addr;
+ aborts_if !exists<Collections>(creator_addr);
+ let all_token_data = global<Collections>(creator_addr).token_data;
+ aborts_if !table::spec_contains(all_token_data, token_data_id);
+}
+
+
+
+
+
+
+### Function `assert_non_standard_reserved_property`
+
+
+fun assert_non_standard_reserved_property(keys: &vector<string::String>)
+
+
+
+
+
+pragma verify = false;
+
+
+
+
+
+
+### Function `initialize_token_script`
+
+
+public entry fun initialize_token_script(_account: &signer)
+
+
+
+Deprecated function
+
+
+pragma verify = false;
+
+
+
+
+
+
+### Function `initialize_token`
+
+
+public fun initialize_token(_account: &signer, _token_id: token::TokenId)
+
+
+
+Deprecated function
+
+
+pragma verify = false;
+
+
+
+[move-book]: https://aptos.dev/move/book/SUMMARY
diff --git a/aptos-move/framework/aptos-token/tests/compiler-v2-doc/token_coin_swap.md b/aptos-move/framework/aptos-token/tests/compiler-v2-doc/token_coin_swap.md
new file mode 100644
index 0000000000000..5d66a8ffd04c8
--- /dev/null
+++ b/aptos-move/framework/aptos-token/tests/compiler-v2-doc/token_coin_swap.md
@@ -0,0 +1,633 @@
+
+
+
+# Module `0x3::token_coin_swap`
+
+Deprecated module
+
+
+- [Struct `TokenCoinSwap`](#0x3_token_coin_swap_TokenCoinSwap)
+- [Resource `TokenListings`](#0x3_token_coin_swap_TokenListings)
+- [Struct `TokenEscrow`](#0x3_token_coin_swap_TokenEscrow)
+- [Resource `TokenStoreEscrow`](#0x3_token_coin_swap_TokenStoreEscrow)
+- [Struct `TokenListingEvent`](#0x3_token_coin_swap_TokenListingEvent)
+- [Struct `TokenSwapEvent`](#0x3_token_coin_swap_TokenSwapEvent)
+- [Constants](#@Constants_0)
+- [Function `does_listing_exist`](#0x3_token_coin_swap_does_listing_exist)
+- [Function `exchange_coin_for_token`](#0x3_token_coin_swap_exchange_coin_for_token)
+- [Function `list_token_for_swap`](#0x3_token_coin_swap_list_token_for_swap)
+- [Function `initialize_token_listing`](#0x3_token_coin_swap_initialize_token_listing)
+- [Function `initialize_token_store_escrow`](#0x3_token_coin_swap_initialize_token_store_escrow)
+- [Function `deposit_token_to_escrow`](#0x3_token_coin_swap_deposit_token_to_escrow)
+- [Function `withdraw_token_from_escrow_internal`](#0x3_token_coin_swap_withdraw_token_from_escrow_internal)
+- [Function `withdraw_token_from_escrow`](#0x3_token_coin_swap_withdraw_token_from_escrow)
+- [Function `cancel_token_listing`](#0x3_token_coin_swap_cancel_token_listing)
+- [Specification](#@Specification_1)
+
+
+use 0x1::error;
+use 0x1::event;
+use 0x1::string;
+use 0x1::table;
+use 0x1::type_info;
+use 0x3::token;
+
+
+
+
+
+
+## Struct `TokenCoinSwap`
+
+TokenCoinSwap records a swap ask for swapping token_amount with CoinType with a minimal price per token
+
+
+struct TokenCoinSwap<CoinType> has drop, store
+
+
+
+
+token_amount: u64
+min_price_per_token: u64
+struct TokenListings<CoinType> has key
+
+
+
+
+listings: table::Table<token::TokenId, token_coin_swap::TokenCoinSwap<CoinType>>
+listing_events: event::EventHandle<token_coin_swap::TokenListingEvent>
+swap_events: event::EventHandle<token_coin_swap::TokenSwapEvent>
+struct TokenEscrow has store
+
+
+
+
+token: token::Token
+locked_until_secs: u64
+struct TokenStoreEscrow has key
+
+
+
+
+token_escrows: table::Table<token::TokenId, token_coin_swap::TokenEscrow>
+struct TokenListingEvent has drop, store
+
+
+
+
+token_id: token::TokenId
+amount: u64
+min_price: u64
+locked_until_secs: u64
+coin_type_info: type_info::TypeInfo
+struct TokenSwapEvent has drop, store
+
+
+
+
+token_id: token::TokenId
+token_buyer: address
+token_amount: u64
+coin_amount: u64
+coin_type_info: type_info::TypeInfo
+const EDEPRECATED_MODULE: u64 = 8;
+
+
+
+
+
+
+Not enough coin to buy token
+
+
+const ENOT_ENOUGH_COIN: u64 = 7;
+
+
+
+
+
+
+Token already listed
+
+
+const ETOKEN_ALREADY_LISTED: u64 = 1;
+
+
+
+
+
+
+Token buy amount doesn't match listing amount
+
+
+const ETOKEN_AMOUNT_NOT_MATCH: u64 = 6;
+
+
+
+
+
+
+Token cannot be moved out of escrow before the lockup time
+
+
+const ETOKEN_CANNOT_MOVE_OUT_OF_ESCROW_BEFORE_LOCKUP_TIME: u64 = 4;
+
+
+
+
+
+
+Token listing no longer exists
+
+
+const ETOKEN_LISTING_NOT_EXIST: u64 = 2;
+
+
+
+
+
+
+Token buy price doesn't match listing price
+
+
+const ETOKEN_MIN_PRICE_NOT_MATCH: u64 = 5;
+
+
+
+
+
+
+Token is not in escrow
+
+
+const ETOKEN_NOT_IN_ESCROW: u64 = 3;
+
+
+
+
+
+
+## Function `does_listing_exist`
+
+
+
+public fun does_listing_exist<CoinType>(_token_owner: address, _token_id: token::TokenId): bool
+
+
+
+
+public fun does_listing_exist<CoinType>(
+ _token_owner: address,
+ _token_id: TokenId
+): bool {
+ abort error::invalid_argument(EDEPRECATED_MODULE)
+}
+
+
+
+
+public fun exchange_coin_for_token<CoinType>(_coin_owner: &signer, _coin_amount: u64, _token_owner: address, _creators_address: address, _collection: string::String, _name: string::String, _property_version: u64, _token_amount: u64)
+
+
+
+
+public fun exchange_coin_for_token<CoinType>(
+ _coin_owner: &signer,
+ _coin_amount: u64,
+ _token_owner: address,
+ _creators_address: address,
+ _collection: String,
+ _name: String,
+ _property_version: u64,
+ _token_amount: u64,
+) {
+ abort error::invalid_argument(EDEPRECATED_MODULE)
+}
+
+
+
+
+public entry fun list_token_for_swap<CoinType>(_token_owner: &signer, _creators_address: address, _collection: string::String, _name: string::String, _property_version: u64, _token_amount: u64, _min_coin_per_token: u64, _locked_until_secs: u64)
+
+
+
+
+public entry fun list_token_for_swap<CoinType>(
+ _token_owner: &signer,
+ _creators_address: address,
+ _collection: String,
+ _name: String,
+ _property_version: u64,
+ _token_amount: u64,
+ _min_coin_per_token: u64,
+ _locked_until_secs: u64
+) {
+ abort error::invalid_argument(EDEPRECATED_MODULE)
+}
+
+
+
+
+fun initialize_token_listing<CoinType>(_token_owner: &signer)
+
+
+
+
+fun initialize_token_listing<CoinType>(_token_owner: &signer) {
+ abort error::invalid_argument(EDEPRECATED_MODULE)
+}
+
+
+
+
+fun initialize_token_store_escrow(_token_owner: &signer)
+
+
+
+
+fun initialize_token_store_escrow(_token_owner: &signer) {
+ abort error::invalid_argument(EDEPRECATED_MODULE)
+}
+
+
+
+
+public fun deposit_token_to_escrow(_token_owner: &signer, _token_id: token::TokenId, _tokens: token::Token, _locked_until_secs: u64)
+
+
+
+
+public fun deposit_token_to_escrow(
+ _token_owner: &signer,
+ _token_id: TokenId,
+ _tokens: Token,
+ _locked_until_secs: u64
+) {
+ abort error::invalid_argument(EDEPRECATED_MODULE)
+}
+
+
+
+
+fun withdraw_token_from_escrow_internal(_token_owner_addr: address, _token_id: token::TokenId, _amount: u64): token::Token
+
+
+
+
+fun withdraw_token_from_escrow_internal(
+ _token_owner_addr: address,
+ _token_id: TokenId,
+ _amount: u64
+): Token {
+ abort error::invalid_argument(EDEPRECATED_MODULE)
+}
+
+
+
+
+public fun withdraw_token_from_escrow(_token_owner: &signer, _token_id: token::TokenId, _amount: u64): token::Token
+
+
+
+
+public fun withdraw_token_from_escrow(
+ _token_owner: &signer,
+ _token_id: TokenId,
+ _amount: u64
+): Token {
+ abort error::invalid_argument(EDEPRECATED_MODULE)
+}
+
+
+
+
+public fun cancel_token_listing<CoinType>(_token_owner: &signer, _token_id: token::TokenId, _token_amount: u64)
+
+
+
+
+public fun cancel_token_listing<CoinType>(
+ _token_owner: &signer,
+ _token_id: TokenId,
+ _token_amount: u64,
+) {
+ abort error::invalid_argument(EDEPRECATED_MODULE)
+}
+
+
+
+
+pragma verify = false;
+
+
+
+[move-book]: https://aptos.dev/move/book/SUMMARY
diff --git a/aptos-move/framework/aptos-token/tests/compiler-v2-doc/token_event_store.md b/aptos-move/framework/aptos-token/tests/compiler-v2-doc/token_event_store.md
new file mode 100644
index 0000000000000..031fefa26208c
--- /dev/null
+++ b/aptos-move/framework/aptos-token/tests/compiler-v2-doc/token_event_store.md
@@ -0,0 +1,1787 @@
+
+
+
+# Module `0x3::token_event_store`
+
+This module provides utils to add and emit new token events that are not in token.move
+
+
+- [Struct `CollectionDescriptionMutateEvent`](#0x3_token_event_store_CollectionDescriptionMutateEvent)
+- [Struct `CollectionDescriptionMutate`](#0x3_token_event_store_CollectionDescriptionMutate)
+- [Struct `CollectionUriMutateEvent`](#0x3_token_event_store_CollectionUriMutateEvent)
+- [Struct `CollectionUriMutate`](#0x3_token_event_store_CollectionUriMutate)
+- [Struct `CollectionMaxiumMutateEvent`](#0x3_token_event_store_CollectionMaxiumMutateEvent)
+- [Struct `CollectionMaxiumMutate`](#0x3_token_event_store_CollectionMaxiumMutate)
+- [Struct `OptInTransferEvent`](#0x3_token_event_store_OptInTransferEvent)
+- [Struct `OptInTransfer`](#0x3_token_event_store_OptInTransfer)
+- [Struct `UriMutationEvent`](#0x3_token_event_store_UriMutationEvent)
+- [Struct `UriMutation`](#0x3_token_event_store_UriMutation)
+- [Struct `DefaultPropertyMutateEvent`](#0x3_token_event_store_DefaultPropertyMutateEvent)
+- [Struct `DefaultPropertyMutate`](#0x3_token_event_store_DefaultPropertyMutate)
+- [Struct `DescriptionMutateEvent`](#0x3_token_event_store_DescriptionMutateEvent)
+- [Struct `DescriptionMutate`](#0x3_token_event_store_DescriptionMutate)
+- [Struct `RoyaltyMutateEvent`](#0x3_token_event_store_RoyaltyMutateEvent)
+- [Struct `RoyaltyMutate`](#0x3_token_event_store_RoyaltyMutate)
+- [Struct `MaxiumMutateEvent`](#0x3_token_event_store_MaxiumMutateEvent)
+- [Struct `MaximumMutate`](#0x3_token_event_store_MaximumMutate)
+- [Resource `TokenEventStoreV1`](#0x3_token_event_store_TokenEventStoreV1)
+- [Function `initialize_token_event_store`](#0x3_token_event_store_initialize_token_event_store)
+- [Function `emit_collection_uri_mutate_event`](#0x3_token_event_store_emit_collection_uri_mutate_event)
+- [Function `emit_collection_description_mutate_event`](#0x3_token_event_store_emit_collection_description_mutate_event)
+- [Function `emit_collection_maximum_mutate_event`](#0x3_token_event_store_emit_collection_maximum_mutate_event)
+- [Function `emit_token_opt_in_event`](#0x3_token_event_store_emit_token_opt_in_event)
+- [Function `emit_token_uri_mutate_event`](#0x3_token_event_store_emit_token_uri_mutate_event)
+- [Function `emit_default_property_mutate_event`](#0x3_token_event_store_emit_default_property_mutate_event)
+- [Function `emit_token_descrition_mutate_event`](#0x3_token_event_store_emit_token_descrition_mutate_event)
+- [Function `emit_token_royalty_mutate_event`](#0x3_token_event_store_emit_token_royalty_mutate_event)
+- [Function `emit_token_maximum_mutate_event`](#0x3_token_event_store_emit_token_maximum_mutate_event)
+- [Specification](#@Specification_0)
+ - [Function `initialize_token_event_store`](#@Specification_0_initialize_token_event_store)
+ - [Function `emit_collection_uri_mutate_event`](#@Specification_0_emit_collection_uri_mutate_event)
+ - [Function `emit_collection_description_mutate_event`](#@Specification_0_emit_collection_description_mutate_event)
+ - [Function `emit_collection_maximum_mutate_event`](#@Specification_0_emit_collection_maximum_mutate_event)
+ - [Function `emit_token_opt_in_event`](#@Specification_0_emit_token_opt_in_event)
+ - [Function `emit_token_uri_mutate_event`](#@Specification_0_emit_token_uri_mutate_event)
+ - [Function `emit_default_property_mutate_event`](#@Specification_0_emit_default_property_mutate_event)
+ - [Function `emit_token_descrition_mutate_event`](#@Specification_0_emit_token_descrition_mutate_event)
+ - [Function `emit_token_royalty_mutate_event`](#@Specification_0_emit_token_royalty_mutate_event)
+ - [Function `emit_token_maximum_mutate_event`](#@Specification_0_emit_token_maximum_mutate_event)
+
+
+use 0x1::account;
+use 0x1::any;
+use 0x1::event;
+use 0x1::features;
+use 0x1::option;
+use 0x1::signer;
+use 0x1::string;
+use 0x3::property_map;
+
+
+
+
+
+
+## Struct `CollectionDescriptionMutateEvent`
+
+Event emitted when collection description is mutated
+
+
+struct CollectionDescriptionMutateEvent has drop, store
+
+
+
+
+creator_addr: address
+collection_name: string::String
+old_description: string::String
+new_description: string::String
+#[event]
+struct CollectionDescriptionMutate has drop, store
+
+
+
+
+creator_addr: address
+collection_name: string::String
+old_description: string::String
+new_description: string::String
+struct CollectionUriMutateEvent has drop, store
+
+
+
+
+creator_addr: address
+collection_name: string::String
+old_uri: string::String
+new_uri: string::String
+#[event]
+struct CollectionUriMutate has drop, store
+
+
+
+
+creator_addr: address
+collection_name: string::String
+old_uri: string::String
+new_uri: string::String
+struct CollectionMaxiumMutateEvent has drop, store
+
+
+
+
+creator_addr: address
+collection_name: string::String
+old_maximum: u64
+new_maximum: u64
+#[event]
+struct CollectionMaxiumMutate has drop, store
+
+
+
+
+creator_addr: address
+collection_name: string::String
+old_maximum: u64
+new_maximum: u64
+struct OptInTransferEvent has drop, store
+
+
+
+
+opt_in: bool
+#[event]
+struct OptInTransfer has drop, store
+
+
+
+
+account_address: address
+opt_in: bool
+struct UriMutationEvent has drop, store
+
+
+
+
+creator: address
+collection: string::String
+token: string::String
+old_uri: string::String
+new_uri: string::String
+#[event]
+struct UriMutation has drop, store
+
+
+
+
+creator: address
+collection: string::String
+token: string::String
+old_uri: string::String
+new_uri: string::String
+struct DefaultPropertyMutateEvent has drop, store
+
+
+
+
+creator: address
+collection: string::String
+token: string::String
+keys: vector<string::String>
+old_values: vector<option::Option<property_map::PropertyValue>>
+new_values: vector<property_map::PropertyValue>
+#[event]
+struct DefaultPropertyMutate has drop, store
+
+
+
+
+creator: address
+collection: string::String
+token: string::String
+keys: vector<string::String>
+old_values: vector<option::Option<property_map::PropertyValue>>
+new_values: vector<property_map::PropertyValue>
+struct DescriptionMutateEvent has drop, store
+
+
+
+
+creator: address
+collection: string::String
+token: string::String
+old_description: string::String
+new_description: string::String
+#[event]
+struct DescriptionMutate has drop, store
+
+
+
+
+creator: address
+collection: string::String
+token: string::String
+old_description: string::String
+new_description: string::String
+struct RoyaltyMutateEvent has drop, store
+
+
+
+
+creator: address
+collection: string::String
+token: string::String
+old_royalty_numerator: u64
+old_royalty_denominator: u64
+old_royalty_payee_addr: address
+new_royalty_numerator: u64
+new_royalty_denominator: u64
+new_royalty_payee_addr: address
+#[event]
+struct RoyaltyMutate has drop, store
+
+
+
+
+creator: address
+collection: string::String
+token: string::String
+old_royalty_numerator: u64
+old_royalty_denominator: u64
+old_royalty_payee_addr: address
+new_royalty_numerator: u64
+new_royalty_denominator: u64
+new_royalty_payee_addr: address
+struct MaxiumMutateEvent has drop, store
+
+
+
+
+creator: address
+collection: string::String
+token: string::String
+old_maximum: u64
+new_maximum: u64
+#[event]
+struct MaximumMutate has drop, store
+
+
+
+
+creator: address
+collection: string::String
+token: string::String
+old_maximum: u64
+new_maximum: u64
+struct TokenEventStoreV1 has key
+
+
+
+
+collection_uri_mutate_events: event::EventHandle<token_event_store::CollectionUriMutateEvent>
+collection_maximum_mutate_events: event::EventHandle<token_event_store::CollectionMaxiumMutateEvent>
+collection_description_mutate_events: event::EventHandle<token_event_store::CollectionDescriptionMutateEvent>
+opt_in_events: event::EventHandle<token_event_store::OptInTransferEvent>
+uri_mutate_events: event::EventHandle<token_event_store::UriMutationEvent>
+default_property_mutate_events: event::EventHandle<token_event_store::DefaultPropertyMutateEvent>
+description_mutate_events: event::EventHandle<token_event_store::DescriptionMutateEvent>
+royalty_mutate_events: event::EventHandle<token_event_store::RoyaltyMutateEvent>
+maximum_mutate_events: event::EventHandle<token_event_store::MaxiumMutateEvent>
+extension: option::Option<any::Any>
+fun initialize_token_event_store(acct: &signer)
+
+
+
+
+fun initialize_token_event_store(acct: &signer){
+ if (!exists<TokenEventStoreV1>(signer::address_of(acct))) {
+ move_to(acct, TokenEventStoreV1 {
+ collection_uri_mutate_events: account::new_event_handle<CollectionUriMutateEvent>(acct),
+ collection_maximum_mutate_events: account::new_event_handle<CollectionMaxiumMutateEvent>(acct),
+ collection_description_mutate_events: account::new_event_handle<CollectionDescriptionMutateEvent>(acct),
+ opt_in_events: account::new_event_handle<OptInTransferEvent>(acct),
+ uri_mutate_events: account::new_event_handle<UriMutationEvent>(acct),
+ default_property_mutate_events: account::new_event_handle<DefaultPropertyMutateEvent>(acct),
+ description_mutate_events: account::new_event_handle<DescriptionMutateEvent>(acct),
+ royalty_mutate_events: account::new_event_handle<RoyaltyMutateEvent>(acct),
+ maximum_mutate_events: account::new_event_handle<MaxiumMutateEvent>(acct),
+ extension: option::none<Any>(),
+ });
+ };
+}
+
+
+
+
+public(friend) fun emit_collection_uri_mutate_event(creator: &signer, collection: string::String, old_uri: string::String, new_uri: string::String)
+
+
+
+
+public(friend) fun emit_collection_uri_mutate_event(creator: &signer, collection: String, old_uri: String, new_uri: String) acquires TokenEventStoreV1 {
+ let event = CollectionUriMutateEvent {
+ creator_addr: signer::address_of(creator),
+ collection_name: collection,
+ old_uri,
+ new_uri,
+ };
+ initialize_token_event_store(creator);
+ let token_event_store = borrow_global_mut<TokenEventStoreV1>(signer::address_of(creator));
+ if (std::features::module_event_migration_enabled()) {
+ event::emit(
+ CollectionUriMutate {
+ creator_addr: signer::address_of(creator),
+ collection_name: collection,
+ old_uri,
+ new_uri,
+ }
+ );
+ };
+ event::emit_event<CollectionUriMutateEvent>(
+ &mut token_event_store.collection_uri_mutate_events,
+ event,
+ );
+}
+
+
+
+
+public(friend) fun emit_collection_description_mutate_event(creator: &signer, collection: string::String, old_description: string::String, new_description: string::String)
+
+
+
+
+public(friend) fun emit_collection_description_mutate_event(creator: &signer, collection: String, old_description: String, new_description: String) acquires TokenEventStoreV1 {
+ let event = CollectionDescriptionMutateEvent {
+ creator_addr: signer::address_of(creator),
+ collection_name: collection,
+ old_description,
+ new_description,
+ };
+ initialize_token_event_store(creator);
+ let token_event_store = borrow_global_mut<TokenEventStoreV1>(signer::address_of(creator));
+ if (std::features::module_event_migration_enabled()) {
+ event::emit(
+ CollectionDescriptionMutate {
+ creator_addr: signer::address_of(creator),
+ collection_name: collection,
+ old_description,
+ new_description,
+ }
+ );
+ };
+ event::emit_event<CollectionDescriptionMutateEvent>(
+ &mut token_event_store.collection_description_mutate_events,
+ event,
+ );
+}
+
+
+
+
+public(friend) fun emit_collection_maximum_mutate_event(creator: &signer, collection: string::String, old_maximum: u64, new_maximum: u64)
+
+
+
+
+public(friend) fun emit_collection_maximum_mutate_event(creator: &signer, collection: String, old_maximum: u64, new_maximum: u64) acquires TokenEventStoreV1 {
+ let event = CollectionMaxiumMutateEvent {
+ creator_addr: signer::address_of(creator),
+ collection_name: collection,
+ old_maximum,
+ new_maximum,
+ };
+ initialize_token_event_store(creator);
+ let token_event_store = borrow_global_mut<TokenEventStoreV1>(signer::address_of(creator));
+ if (std::features::module_event_migration_enabled()) {
+ event::emit(
+ CollectionMaxiumMutate {
+ creator_addr: signer::address_of(creator),
+ collection_name: collection,
+ old_maximum,
+ new_maximum,
+ }
+ );
+ };
+ event::emit_event<CollectionMaxiumMutateEvent>(
+ &mut token_event_store.collection_maximum_mutate_events,
+ event,
+ );
+}
+
+
+
+
+public(friend) fun emit_token_opt_in_event(account: &signer, opt_in: bool)
+
+
+
+
+public(friend) fun emit_token_opt_in_event(account: &signer, opt_in: bool) acquires TokenEventStoreV1 {
+ let opt_in_event = OptInTransferEvent {
+ opt_in,
+ };
+ initialize_token_event_store(account);
+ let token_event_store = borrow_global_mut<TokenEventStoreV1>(signer::address_of(account));
+ if (std::features::module_event_migration_enabled()) {
+ event::emit(
+ OptInTransfer {
+ account_address: signer::address_of(account),
+ opt_in,
+ });
+ };
+ event::emit_event<OptInTransferEvent>(
+ &mut token_event_store.opt_in_events,
+ opt_in_event,
+ );
+}
+
+
+
+
+public(friend) fun emit_token_uri_mutate_event(creator: &signer, collection: string::String, token: string::String, old_uri: string::String, new_uri: string::String)
+
+
+
+
+public(friend) fun emit_token_uri_mutate_event(
+ creator: &signer,
+ collection: String,
+ token: String,
+ old_uri: String,
+ new_uri: String,
+) acquires TokenEventStoreV1 {
+ let creator_addr = signer::address_of(creator);
+
+ let event = UriMutationEvent {
+ creator: creator_addr,
+ collection,
+ token,
+ old_uri,
+ new_uri,
+ };
+
+ initialize_token_event_store(creator);
+ let token_event_store = borrow_global_mut<TokenEventStoreV1>(creator_addr);
+ if (std::features::module_event_migration_enabled()) {
+ event::emit(
+ UriMutation {
+ creator: creator_addr,
+ collection,
+ token,
+ old_uri,
+ new_uri,
+ });
+ };
+ event::emit_event<UriMutationEvent>(
+ &mut token_event_store.uri_mutate_events,
+ event,
+ );
+}
+
+
+
+
+public(friend) fun emit_default_property_mutate_event(creator: &signer, collection: string::String, token: string::String, keys: vector<string::String>, old_values: vector<option::Option<property_map::PropertyValue>>, new_values: vector<property_map::PropertyValue>)
+
+
+
+
+public(friend) fun emit_default_property_mutate_event(
+ creator: &signer,
+ collection: String,
+ token: String,
+ keys: vector<String>,
+ old_values: vector<Option<PropertyValue>>,
+ new_values: vector<PropertyValue>,
+) acquires TokenEventStoreV1 {
+ let creator_addr = signer::address_of(creator);
+
+ let event = DefaultPropertyMutateEvent {
+ creator: creator_addr,
+ collection,
+ token,
+ keys,
+ old_values,
+ new_values,
+ };
+
+ initialize_token_event_store(creator);
+ let token_event_store = borrow_global_mut<TokenEventStoreV1>(creator_addr);
+ if (std::features::module_event_migration_enabled()) {
+ event::emit(
+ DefaultPropertyMutate {
+ creator: creator_addr,
+ collection,
+ token,
+ keys,
+ old_values,
+ new_values,
+ });
+ };
+ event::emit_event<DefaultPropertyMutateEvent>(
+ &mut token_event_store.default_property_mutate_events,
+ event,
+ );
+}
+
+
+
+
+public(friend) fun emit_token_descrition_mutate_event(creator: &signer, collection: string::String, token: string::String, old_description: string::String, new_description: string::String)
+
+
+
+
+public(friend) fun emit_token_descrition_mutate_event(
+ creator: &signer,
+ collection: String,
+ token: String,
+ old_description: String,
+ new_description: String,
+) acquires TokenEventStoreV1 {
+ let creator_addr = signer::address_of(creator);
+
+ let event = DescriptionMutateEvent {
+ creator: creator_addr,
+ collection,
+ token,
+ old_description,
+ new_description,
+ };
+
+ initialize_token_event_store(creator);
+ let token_event_store = borrow_global_mut<TokenEventStoreV1>(creator_addr);
+ if (std::features::module_event_migration_enabled()) {
+ event::emit(
+ DescriptionMutate {
+ creator: creator_addr,
+ collection,
+ token,
+ old_description,
+ new_description,
+ });
+ };
+ event::emit_event<DescriptionMutateEvent>(
+ &mut token_event_store.description_mutate_events,
+ event,
+ );
+}
+
+
+
+
+public(friend) fun emit_token_royalty_mutate_event(creator: &signer, collection: string::String, token: string::String, old_royalty_numerator: u64, old_royalty_denominator: u64, old_royalty_payee_addr: address, new_royalty_numerator: u64, new_royalty_denominator: u64, new_royalty_payee_addr: address)
+
+
+
+
+public(friend) fun emit_token_royalty_mutate_event(
+ creator: &signer,
+ collection: String,
+ token: String,
+ old_royalty_numerator: u64,
+ old_royalty_denominator: u64,
+ old_royalty_payee_addr: address,
+ new_royalty_numerator: u64,
+ new_royalty_denominator: u64,
+ new_royalty_payee_addr: address,
+) acquires TokenEventStoreV1 {
+ let creator_addr = signer::address_of(creator);
+ let event = RoyaltyMutateEvent {
+ creator: creator_addr,
+ collection,
+ token,
+ old_royalty_numerator,
+ old_royalty_denominator,
+ old_royalty_payee_addr,
+ new_royalty_numerator,
+ new_royalty_denominator,
+ new_royalty_payee_addr,
+ };
+
+ initialize_token_event_store(creator);
+ let token_event_store = borrow_global_mut<TokenEventStoreV1>(creator_addr);
+ if (std::features::module_event_migration_enabled()) {
+ event::emit(
+ RoyaltyMutate {
+ creator: creator_addr,
+ collection,
+ token,
+ old_royalty_numerator,
+ old_royalty_denominator,
+ old_royalty_payee_addr,
+ new_royalty_numerator,
+ new_royalty_denominator,
+ new_royalty_payee_addr,
+ });
+ };
+ event::emit_event<RoyaltyMutateEvent>(
+ &mut token_event_store.royalty_mutate_events,
+ event,
+ );
+}
+
+
+
+
+public(friend) fun emit_token_maximum_mutate_event(creator: &signer, collection: string::String, token: string::String, old_maximum: u64, new_maximum: u64)
+
+
+
+
+public(friend) fun emit_token_maximum_mutate_event(
+ creator: &signer,
+ collection: String,
+ token: String,
+ old_maximum: u64,
+ new_maximum: u64,
+) acquires TokenEventStoreV1 {
+ let creator_addr = signer::address_of(creator);
+
+ let event = MaxiumMutateEvent {
+ creator: creator_addr,
+ collection,
+ token,
+ old_maximum,
+ new_maximum,
+ };
+
+ initialize_token_event_store(creator);
+ let token_event_store = borrow_global_mut<TokenEventStoreV1>(creator_addr);
+ if (std::features::module_event_migration_enabled()) {
+ event::emit(
+ MaximumMutate {
+ creator: creator_addr,
+ collection,
+ token,
+ old_maximum,
+ new_maximum,
+ });
+ };
+ event::emit_event<MaxiumMutateEvent>(
+ &mut token_event_store.maximum_mutate_events,
+ event,
+ );
+}
+
+
+
+
+pragma verify = true;
+pragma aborts_if_is_strict;
+
+
+
+
+
+
+### Function `initialize_token_event_store`
+
+
+fun initialize_token_event_store(acct: &signer)
+
+
+
+
+
+pragma verify = true;
+let addr = signer::address_of(acct);
+include InitializeTokenEventStoreAbortsIf {creator : acct};
+
+
+
+Adjust the overflow value according to the
+number of registered events
+
+
+
+
+
+schema InitializeTokenEventStoreAbortsIf {
+ creator: &signer;
+ let addr = signer::address_of(creator);
+ let account = global<Account>(addr);
+ aborts_if !exists<TokenEventStoreV1>(addr) && !exists<Account>(addr);
+ aborts_if !exists<TokenEventStoreV1>(addr) && account.guid_creation_num + 9 >= account::MAX_GUID_CREATION_NUM;
+ aborts_if !exists<TokenEventStoreV1>(addr) && account.guid_creation_num + 9 > MAX_U64;
+}
+
+
+
+
+
+
+
+
+schema TokenEventStoreAbortsIf {
+ creator: &signer;
+ let addr = signer::address_of(creator);
+ let account = global<Account>(addr);
+ aborts_if !exists<Account>(addr);
+ aborts_if account.guid_creation_num + 9 >= account::MAX_GUID_CREATION_NUM;
+ aborts_if account.guid_creation_num + 9 > MAX_U64;
+}
+
+
+
+
+
+
+### Function `emit_collection_uri_mutate_event`
+
+
+public(friend) fun emit_collection_uri_mutate_event(creator: &signer, collection: string::String, old_uri: string::String, new_uri: string::String)
+
+
+
+
+
+include InitializeTokenEventStoreAbortsIf;
+
+
+
+
+
+
+### Function `emit_collection_description_mutate_event`
+
+
+public(friend) fun emit_collection_description_mutate_event(creator: &signer, collection: string::String, old_description: string::String, new_description: string::String)
+
+
+
+
+
+include InitializeTokenEventStoreAbortsIf;
+
+
+
+
+
+
+### Function `emit_collection_maximum_mutate_event`
+
+
+public(friend) fun emit_collection_maximum_mutate_event(creator: &signer, collection: string::String, old_maximum: u64, new_maximum: u64)
+
+
+
+
+
+include InitializeTokenEventStoreAbortsIf;
+
+
+
+
+
+
+### Function `emit_token_opt_in_event`
+
+
+public(friend) fun emit_token_opt_in_event(account: &signer, opt_in: bool)
+
+
+
+
+
+include InitializeTokenEventStoreAbortsIf {creator : account};
+
+
+
+
+
+
+### Function `emit_token_uri_mutate_event`
+
+
+public(friend) fun emit_token_uri_mutate_event(creator: &signer, collection: string::String, token: string::String, old_uri: string::String, new_uri: string::String)
+
+
+
+
+
+include InitializeTokenEventStoreAbortsIf;
+
+
+
+
+
+
+### Function `emit_default_property_mutate_event`
+
+
+public(friend) fun emit_default_property_mutate_event(creator: &signer, collection: string::String, token: string::String, keys: vector<string::String>, old_values: vector<option::Option<property_map::PropertyValue>>, new_values: vector<property_map::PropertyValue>)
+
+
+
+
+
+include InitializeTokenEventStoreAbortsIf;
+
+
+
+
+
+
+### Function `emit_token_descrition_mutate_event`
+
+
+public(friend) fun emit_token_descrition_mutate_event(creator: &signer, collection: string::String, token: string::String, old_description: string::String, new_description: string::String)
+
+
+
+
+
+include InitializeTokenEventStoreAbortsIf;
+
+
+
+
+
+
+### Function `emit_token_royalty_mutate_event`
+
+
+public(friend) fun emit_token_royalty_mutate_event(creator: &signer, collection: string::String, token: string::String, old_royalty_numerator: u64, old_royalty_denominator: u64, old_royalty_payee_addr: address, new_royalty_numerator: u64, new_royalty_denominator: u64, new_royalty_payee_addr: address)
+
+
+
+
+
+include InitializeTokenEventStoreAbortsIf;
+
+
+
+
+
+
+### Function `emit_token_maximum_mutate_event`
+
+
+public(friend) fun emit_token_maximum_mutate_event(creator: &signer, collection: string::String, token: string::String, old_maximum: u64, new_maximum: u64)
+
+
+
+
+
+include InitializeTokenEventStoreAbortsIf;
+
+
+
+[move-book]: https://aptos.dev/move/book/SUMMARY
diff --git a/aptos-move/framework/aptos-token/tests/compiler-v2-doc/token_transfers.md b/aptos-move/framework/aptos-token/tests/compiler-v2-doc/token_transfers.md
new file mode 100644
index 0000000000000..db16be95cd33f
--- /dev/null
+++ b/aptos-move/framework/aptos-token/tests/compiler-v2-doc/token_transfers.md
@@ -0,0 +1,953 @@
+
+
+
+# Module `0x3::token_transfers`
+
+This module provides the foundation for transferring of Tokens
+
+
+- [Resource `PendingClaims`](#0x3_token_transfers_PendingClaims)
+- [Struct `TokenOfferId`](#0x3_token_transfers_TokenOfferId)
+- [Struct `TokenOffer`](#0x3_token_transfers_TokenOffer)
+- [Struct `TokenOfferEvent`](#0x3_token_transfers_TokenOfferEvent)
+- [Struct `TokenCancelOfferEvent`](#0x3_token_transfers_TokenCancelOfferEvent)
+- [Struct `TokenCancelOffer`](#0x3_token_transfers_TokenCancelOffer)
+- [Struct `TokenClaimEvent`](#0x3_token_transfers_TokenClaimEvent)
+- [Struct `TokenClaim`](#0x3_token_transfers_TokenClaim)
+- [Constants](#@Constants_0)
+- [Function `initialize_token_transfers`](#0x3_token_transfers_initialize_token_transfers)
+- [Function `create_token_offer_id`](#0x3_token_transfers_create_token_offer_id)
+- [Function `offer_script`](#0x3_token_transfers_offer_script)
+- [Function `offer`](#0x3_token_transfers_offer)
+- [Function `claim_script`](#0x3_token_transfers_claim_script)
+- [Function `claim`](#0x3_token_transfers_claim)
+- [Function `cancel_offer_script`](#0x3_token_transfers_cancel_offer_script)
+- [Function `cancel_offer`](#0x3_token_transfers_cancel_offer)
+- [Specification](#@Specification_1)
+ - [Function `initialize_token_transfers`](#@Specification_1_initialize_token_transfers)
+ - [Function `create_token_offer_id`](#@Specification_1_create_token_offer_id)
+ - [Function `offer_script`](#@Specification_1_offer_script)
+ - [Function `offer`](#@Specification_1_offer)
+ - [Function `claim_script`](#@Specification_1_claim_script)
+ - [Function `claim`](#@Specification_1_claim)
+ - [Function `cancel_offer_script`](#@Specification_1_cancel_offer_script)
+ - [Function `cancel_offer`](#@Specification_1_cancel_offer)
+
+
+use 0x1::account;
+use 0x1::error;
+use 0x1::event;
+use 0x1::features;
+use 0x1::signer;
+use 0x1::string;
+use 0x1::table;
+use 0x3::token;
+
+
+
+
+
+
+## Resource `PendingClaims`
+
+
+
+struct PendingClaims has key
+
+
+
+
+pending_claims: table::Table<token_transfers::TokenOfferId, token::Token>
+offer_events: event::EventHandle<token_transfers::TokenOfferEvent>
+cancel_offer_events: event::EventHandle<token_transfers::TokenCancelOfferEvent>
+claim_events: event::EventHandle<token_transfers::TokenClaimEvent>
+#[event]
+struct TokenOfferId has copy, drop, store
+
+
+
+
+to_addr: address
+token_id: token::TokenId
+#[event]
+struct TokenOffer has drop, store
+
+
+
+
+to_address: address
+token_id: token::TokenId
+amount: u64
+#[event]
+struct TokenOfferEvent has drop, store
+
+
+
+
+to_address: address
+token_id: token::TokenId
+amount: u64
+#[event]
+struct TokenCancelOfferEvent has drop, store
+
+
+
+
+to_address: address
+token_id: token::TokenId
+amount: u64
+#[event]
+struct TokenCancelOffer has drop, store
+
+
+
+
+to_address: address
+token_id: token::TokenId
+amount: u64
+#[event]
+struct TokenClaimEvent has drop, store
+
+
+
+
+to_address: address
+token_id: token::TokenId
+amount: u64
+#[event]
+struct TokenClaim has drop, store
+
+
+
+
+to_address: address
+token_id: token::TokenId
+amount: u64
+const ETOKEN_OFFER_NOT_EXIST: u64 = 1;
+
+
+
+
+
+
+## Function `initialize_token_transfers`
+
+
+
+fun initialize_token_transfers(account: &signer)
+
+
+
+
+fun initialize_token_transfers(account: &signer) {
+ move_to(
+ account,
+ PendingClaims {
+ pending_claims: table::new<TokenOfferId, Token>(),
+ offer_events: account::new_event_handle<TokenOfferEvent>(account),
+ cancel_offer_events: account::new_event_handle<TokenCancelOfferEvent>(account),
+ claim_events: account::new_event_handle<TokenClaimEvent>(account),
+ }
+ )
+}
+
+
+
+
+fun create_token_offer_id(to_addr: address, token_id: token::TokenId): token_transfers::TokenOfferId
+
+
+
+
+fun create_token_offer_id(to_addr: address, token_id: TokenId): TokenOfferId {
+ TokenOfferId {
+ to_addr,
+ token_id
+ }
+}
+
+
+
+
+public entry fun offer_script(sender: signer, receiver: address, creator: address, collection: string::String, name: string::String, property_version: u64, amount: u64)
+
+
+
+
+public entry fun offer_script(
+ sender: signer,
+ receiver: address,
+ creator: address,
+ collection: String,
+ name: String,
+ property_version: u64,
+ amount: u64,
+) acquires PendingClaims {
+ let token_id = token::create_token_id_raw(creator, collection, name, property_version);
+ offer(&sender, receiver, token_id, amount);
+}
+
+
+
+
+public fun offer(sender: &signer, receiver: address, token_id: token::TokenId, amount: u64)
+
+
+
+
+public fun offer(
+ sender: &signer,
+ receiver: address,
+ token_id: TokenId,
+ amount: u64,
+) acquires PendingClaims {
+ let sender_addr = signer::address_of(sender);
+ if (!exists<PendingClaims>(sender_addr)) {
+ initialize_token_transfers(sender)
+ };
+
+ let pending_claims =
+ &mut borrow_global_mut<PendingClaims>(sender_addr).pending_claims;
+ let token_offer_id = create_token_offer_id(receiver, token_id);
+ let token = token::withdraw_token(sender, token_id, amount);
+ if (!table::contains(pending_claims, token_offer_id)) {
+ table::add(pending_claims, token_offer_id, token);
+ } else {
+ let dst_token = table::borrow_mut(pending_claims, token_offer_id);
+ token::merge(dst_token, token);
+ };
+
+ if (std::features::module_event_migration_enabled()) {
+ event::emit(
+ TokenOffer {
+ to_address: receiver,
+ token_id,
+ amount,
+ }
+ )
+ };
+ event::emit_event<TokenOfferEvent>(
+ &mut borrow_global_mut<PendingClaims>(sender_addr).offer_events,
+ TokenOfferEvent {
+ to_address: receiver,
+ token_id,
+ amount,
+ },
+ );
+}
+
+
+
+
+public entry fun claim_script(receiver: signer, sender: address, creator: address, collection: string::String, name: string::String, property_version: u64)
+
+
+
+
+public entry fun claim_script(
+ receiver: signer,
+ sender: address,
+ creator: address,
+ collection: String,
+ name: String,
+ property_version: u64,
+) acquires PendingClaims {
+ let token_id = token::create_token_id_raw(creator, collection, name, property_version);
+ claim(&receiver, sender, token_id);
+}
+
+
+
+
+public fun claim(receiver: &signer, sender: address, token_id: token::TokenId)
+
+
+
+
+public fun claim(
+ receiver: &signer,
+ sender: address,
+ token_id: TokenId,
+) acquires PendingClaims {
+ assert!(exists<PendingClaims>(sender), ETOKEN_OFFER_NOT_EXIST);
+ let pending_claims =
+ &mut borrow_global_mut<PendingClaims>(sender).pending_claims;
+ let token_offer_id = create_token_offer_id(signer::address_of(receiver), token_id);
+ assert!(table::contains(pending_claims, token_offer_id), error::not_found(ETOKEN_OFFER_NOT_EXIST));
+ let tokens = table::remove(pending_claims, token_offer_id);
+ let amount = token::get_token_amount(&tokens);
+ token::deposit_token(receiver, tokens);
+
+ if (std::features::module_event_migration_enabled()) {
+ event::emit(
+ TokenClaim {
+ to_address: signer::address_of(receiver),
+ token_id,
+ amount,
+ }
+ )
+ };
+ event::emit_event<TokenClaimEvent>(
+ &mut borrow_global_mut<PendingClaims>(sender).claim_events,
+ TokenClaimEvent {
+ to_address: signer::address_of(receiver),
+ token_id,
+ amount,
+ },
+ );
+}
+
+
+
+
+public entry fun cancel_offer_script(sender: signer, receiver: address, creator: address, collection: string::String, name: string::String, property_version: u64)
+
+
+
+
+public entry fun cancel_offer_script(
+ sender: signer,
+ receiver: address,
+ creator: address,
+ collection: String,
+ name: String,
+ property_version: u64,
+) acquires PendingClaims {
+ let token_id = token::create_token_id_raw(creator, collection, name, property_version);
+ cancel_offer(&sender, receiver, token_id);
+}
+
+
+
+
+public fun cancel_offer(sender: &signer, receiver: address, token_id: token::TokenId)
+
+
+
+
+public fun cancel_offer(
+ sender: &signer,
+ receiver: address,
+ token_id: TokenId,
+) acquires PendingClaims {
+ let sender_addr = signer::address_of(sender);
+ let token_offer_id = create_token_offer_id(receiver, token_id);
+ assert!(exists<PendingClaims>(sender_addr), ETOKEN_OFFER_NOT_EXIST);
+ let pending_claims =
+ &mut borrow_global_mut<PendingClaims>(sender_addr).pending_claims;
+ let token = table::remove(pending_claims, token_offer_id);
+ let amount = token::get_token_amount(&token);
+ token::deposit_token(sender, token);
+
+ if (std::features::module_event_migration_enabled()) {
+ event::emit(
+ TokenCancelOffer {
+ to_address: receiver,
+ token_id,
+ amount,
+ },
+ )
+ };
+ event::emit_event<TokenCancelOfferEvent>(
+ &mut borrow_global_mut<PendingClaims>(sender_addr).cancel_offer_events,
+ TokenCancelOfferEvent {
+ to_address: receiver,
+ token_id,
+ amount,
+ },
+ );
+}
+
+
+
+
+pragma verify = true;
+pragma aborts_if_is_strict;
+
+
+
+
+
+
+### Function `initialize_token_transfers`
+
+
+fun initialize_token_transfers(account: &signer)
+
+
+
+
+
+include InitializeTokenTransfersAbortsIf;
+
+
+
+Abort according to the code
+
+
+
+
+
+schema InitializeTokenTransfersAbortsIf {
+ account: &signer;
+ let addr = signer::address_of(account);
+ aborts_if exists<PendingClaims>(addr);
+ let account = global<Account>(addr);
+ aborts_if !exists<Account>(addr);
+ aborts_if account.guid_creation_num + 3 >= account::MAX_GUID_CREATION_NUM;
+ aborts_if account.guid_creation_num + 3 > MAX_U64;
+}
+
+
+
+
+
+
+### Function `create_token_offer_id`
+
+
+fun create_token_offer_id(to_addr: address, token_id: token::TokenId): token_transfers::TokenOfferId
+
+
+
+
+
+aborts_if false;
+
+
+
+
+
+
+### Function `offer_script`
+
+
+public entry fun offer_script(sender: signer, receiver: address, creator: address, collection: string::String, name: string::String, property_version: u64, amount: u64)
+
+
+
+
+
+pragma verify = false;
+let token_id = token::create_token_id_raw(creator, collection, name, property_version);
+
+
+
+
+
+
+### Function `offer`
+
+
+public fun offer(sender: &signer, receiver: address, token_id: token::TokenId, amount: u64)
+
+
+
+
+
+pragma verify = false;
+let sender_addr = signer::address_of(sender);
+include !exists<PendingClaims>(sender_addr) ==> InitializeTokenTransfersAbortsIf{account : sender};
+let pending_claims = global<PendingClaims>(sender_addr).pending_claims;
+let token_offer_id = create_token_offer_id(receiver, token_id);
+let tokens = global<TokenStore>(sender_addr).tokens;
+aborts_if amount <= 0;
+aborts_if token::spec_balance_of(sender_addr, token_id) < amount;
+aborts_if !exists<TokenStore>(sender_addr);
+aborts_if !table::spec_contains(tokens, token_id);
+aborts_if !table::spec_contains(pending_claims, token_offer_id);
+let a = table::spec_contains(pending_claims, token_offer_id);
+let dst_token = table::spec_get(pending_claims, token_offer_id);
+aborts_if dst_token.amount + spce_get(signer::address_of(sender), token_id, amount) > MAX_U64;
+
+
+
+Get the amount from sender token
+
+
+
+
+
+fun spce_get(
+ account_addr: address,
+ id: TokenId,
+ amount: u64
+): u64 {
+ use aptos_token::token::{TokenStore};
+ use aptos_std::table::{Self};
+ let tokens = global<TokenStore>(account_addr).tokens;
+ let balance = table::spec_get(tokens, id).amount;
+ if (balance > amount) {
+ amount
+ } else {
+ table::spec_get(tokens, id).amount
+ }
+}
+
+
+
+
+
+
+### Function `claim_script`
+
+
+public entry fun claim_script(receiver: signer, sender: address, creator: address, collection: string::String, name: string::String, property_version: u64)
+
+
+
+
+
+pragma aborts_if_is_partial;
+let token_id = token::create_token_id_raw(creator, collection, name, property_version);
+aborts_if !exists<PendingClaims>(sender);
+let pending_claims = global<PendingClaims>(sender).pending_claims;
+let token_offer_id = create_token_offer_id(signer::address_of(receiver), token_id);
+aborts_if !table::spec_contains(pending_claims, token_offer_id);
+let tokens = table::spec_get(pending_claims, token_offer_id);
+include token::InitializeTokenStore{account: receiver };
+let account_addr = signer::address_of(receiver);
+let token = tokens;
+let token_store = global<TokenStore>(account_addr);
+let recipient_token = table::spec_get(token_store.tokens, token.id);
+let b = table::spec_contains(token_store.tokens, token.id);
+aborts_if token.amount <= 0;
+
+
+
+
+
+
+### Function `claim`
+
+
+public fun claim(receiver: &signer, sender: address, token_id: token::TokenId)
+
+
+
+
+
+pragma aborts_if_is_partial;
+aborts_if !exists<PendingClaims>(sender);
+let pending_claims = global<PendingClaims>(sender).pending_claims;
+let token_offer_id = create_token_offer_id(signer::address_of(receiver), token_id);
+aborts_if !table::spec_contains(pending_claims, token_offer_id);
+let tokens = table::spec_get(pending_claims, token_offer_id);
+include token::InitializeTokenStore{account: receiver };
+let account_addr = signer::address_of(receiver);
+let token = tokens;
+let token_store = global<TokenStore>(account_addr);
+let recipient_token = table::spec_get(token_store.tokens, token.id);
+let b = table::spec_contains(token_store.tokens, token.id);
+aborts_if token.amount <= 0;
+
+
+
+
+
+
+### Function `cancel_offer_script`
+
+
+public entry fun cancel_offer_script(sender: signer, receiver: address, creator: address, collection: string::String, name: string::String, property_version: u64)
+
+
+
+
+
+pragma aborts_if_is_partial;
+let token_id = token::create_token_id_raw(creator, collection, name, property_version);
+let sender_addr = signer::address_of(sender);
+aborts_if !exists<PendingClaims>(sender_addr);
+let pending_claims = global<PendingClaims>(sender_addr).pending_claims;
+let token_offer_id = create_token_offer_id(receiver, token_id);
+aborts_if !table::spec_contains(pending_claims, token_offer_id);
+include token::InitializeTokenStore{account: sender };
+let dst_token = table::spec_get(pending_claims, token_offer_id);
+let account_addr = sender_addr;
+let token = dst_token;
+let token_store = global<TokenStore>(account_addr);
+let recipient_token = table::spec_get(token_store.tokens, token.id);
+let b = table::spec_contains(token_store.tokens, token.id);
+aborts_if token.amount <= 0;
+
+
+
+
+
+
+### Function `cancel_offer`
+
+
+public fun cancel_offer(sender: &signer, receiver: address, token_id: token::TokenId)
+
+
+
+
+
+pragma aborts_if_is_partial;
+let sender_addr = signer::address_of(sender);
+aborts_if !exists<PendingClaims>(sender_addr);
+let pending_claims = global<PendingClaims>(sender_addr).pending_claims;
+let token_offer_id = create_token_offer_id(receiver, token_id);
+aborts_if !table::spec_contains(pending_claims, token_offer_id);
+include token::InitializeTokenStore{account: sender };
+let dst_token = table::spec_get(pending_claims, token_offer_id);
+let account_addr = sender_addr;
+let token = dst_token;
+let token_store = global<TokenStore>(account_addr);
+let recipient_token = table::spec_get(token_store.tokens, token.id);
+let b = table::spec_contains(token_store.tokens, token.id);
+aborts_if token.amount <= 0;
+
+
+
+[move-book]: https://aptos.dev/move/book/SUMMARY
diff --git a/aptos-move/framework/move-stdlib/tests/compiler-v2-doc/acl.md b/aptos-move/framework/move-stdlib/tests/compiler-v2-doc/acl.md
new file mode 100644
index 0000000000000..5310909afc734
--- /dev/null
+++ b/aptos-move/framework/move-stdlib/tests/compiler-v2-doc/acl.md
@@ -0,0 +1,320 @@
+
+
+
+# Module `0x1::acl`
+
+Access control list (acl) module. An acl is a list of account addresses who
+have the access permission to a certain object.
+This module uses a vector
to represent the list, but can be refactored to
+use a "set" instead when it's available in the language in the future.
+
+
+- [Struct `ACL`](#0x1_acl_ACL)
+- [Constants](#@Constants_0)
+- [Function `empty`](#0x1_acl_empty)
+- [Function `add`](#0x1_acl_add)
+- [Function `remove`](#0x1_acl_remove)
+- [Function `contains`](#0x1_acl_contains)
+- [Function `assert_contains`](#0x1_acl_assert_contains)
+- [Specification](#@Specification_1)
+ - [Struct `ACL`](#@Specification_1_ACL)
+ - [Function `add`](#@Specification_1_add)
+ - [Function `remove`](#@Specification_1_remove)
+ - [Function `contains`](#@Specification_1_contains)
+ - [Function `assert_contains`](#@Specification_1_assert_contains)
+
+
+use 0x1::error;
+use 0x1::vector;
+
+
+
+
+
+
+## Struct `ACL`
+
+
+
+struct ACL has copy, drop, store
+
+
+
+
+list: vector<address>
+const ECONTAIN: u64 = 0;
+
+
+
+
+
+
+The ACL does not contain the address.
+
+
+const ENOT_CONTAIN: u64 = 1;
+
+
+
+
+
+
+## Function `empty`
+
+Return an empty ACL.
+
+
+public fun empty(): acl::ACL
+
+
+
+
+public fun empty(): ACL {
+ ACL{ list: vector::empty<address>() }
+}
+
+
+
+
+public fun add(acl: &mut acl::ACL, addr: address)
+
+
+
+
+public fun add(acl: &mut ACL, addr: address) {
+ assert!(!vector::contains(&mut acl.list, &addr), error::invalid_argument(ECONTAIN));
+ vector::push_back(&mut acl.list, addr);
+}
+
+
+
+
+public fun remove(acl: &mut acl::ACL, addr: address)
+
+
+
+
+public fun remove(acl: &mut ACL, addr: address) {
+ let (found, index) = vector::index_of(&mut acl.list, &addr);
+ assert!(found, error::invalid_argument(ENOT_CONTAIN));
+ vector::remove(&mut acl.list, index);
+}
+
+
+
+
+public fun contains(acl: &acl::ACL, addr: address): bool
+
+
+
+
+public fun contains(acl: &ACL, addr: address): bool {
+ vector::contains(&acl.list, &addr)
+}
+
+
+
+
+public fun assert_contains(acl: &acl::ACL, addr: address)
+
+
+
+
+public fun assert_contains(acl: &ACL, addr: address) {
+ assert!(contains(acl, addr), error::invalid_argument(ENOT_CONTAIN));
+}
+
+
+
+
+struct ACL has copy, drop, store
+
+
+
+
+list: vector<address>
+invariant forall i in 0..len(list), j in 0..len(list): list[i] == list[j] ==> i == j;
+
+
+
+
+
+
+
+
+fun spec_contains(acl: ACL, addr: address): bool {
+ exists a in acl.list: a == addr
+}
+
+
+
+
+
+
+### Function `add`
+
+
+public fun add(acl: &mut acl::ACL, addr: address)
+
+
+
+
+
+aborts_if spec_contains(acl, addr) with error::INVALID_ARGUMENT;
+ensures spec_contains(acl, addr);
+
+
+
+
+
+
+### Function `remove`
+
+
+public fun remove(acl: &mut acl::ACL, addr: address)
+
+
+
+
+
+aborts_if !spec_contains(acl, addr) with error::INVALID_ARGUMENT;
+ensures !spec_contains(acl, addr);
+
+
+
+
+
+
+### Function `contains`
+
+
+public fun contains(acl: &acl::ACL, addr: address): bool
+
+
+
+
+
+ensures result == spec_contains(acl, addr);
+
+
+
+
+
+
+### Function `assert_contains`
+
+
+public fun assert_contains(acl: &acl::ACL, addr: address)
+
+
+
+
+
+aborts_if !spec_contains(acl, addr) with error::INVALID_ARGUMENT;
+
+
+
+[move-book]: https://aptos.dev/move/book/SUMMARY
diff --git a/aptos-move/framework/move-stdlib/tests/compiler-v2-doc/bcs.md b/aptos-move/framework/move-stdlib/tests/compiler-v2-doc/bcs.md
new file mode 100644
index 0000000000000..e01e1d89e1a28
--- /dev/null
+++ b/aptos-move/framework/move-stdlib/tests/compiler-v2-doc/bcs.md
@@ -0,0 +1,59 @@
+
+
+
+# Module `0x1::bcs`
+
+Utility for converting a Move value to its binary representation in BCS (Binary Canonical
+Serialization). BCS is the binary encoding for Move resources and other non-module values
+published on-chain. See https://github.com/aptos-labs/bcs#binary-canonical-serialization-bcs for more
+details on BCS.
+
+
+- [Function `to_bytes`](#0x1_bcs_to_bytes)
+- [Specification](#@Specification_0)
+
+
+
+
+
+
+
+
+## Function `to_bytes`
+
+Return the binary representation of v
in BCS (Binary Canonical Serialization) format
+
+
+public fun to_bytes<MoveValue>(v: &MoveValue): vector<u8>
+
+
+
+
+native public fun to_bytes<MoveValue>(v: &MoveValue): vector<u8>;
+
+
+
+
+native fun serialize<MoveValue>(v: &MoveValue): vector<u8>;
+
+
+
+[move-book]: https://aptos.dev/move/book/SUMMARY
diff --git a/aptos-move/framework/move-stdlib/tests/compiler-v2-doc/bit_vector.md b/aptos-move/framework/move-stdlib/tests/compiler-v2-doc/bit_vector.md
new file mode 100644
index 0000000000000..316dd066b223f
--- /dev/null
+++ b/aptos-move/framework/move-stdlib/tests/compiler-v2-doc/bit_vector.md
@@ -0,0 +1,553 @@
+
+
+
+# Module `0x1::bit_vector`
+
+
+
+- [Struct `BitVector`](#0x1_bit_vector_BitVector)
+- [Constants](#@Constants_0)
+- [Function `new`](#0x1_bit_vector_new)
+- [Function `set`](#0x1_bit_vector_set)
+- [Function `unset`](#0x1_bit_vector_unset)
+- [Function `shift_left`](#0x1_bit_vector_shift_left)
+- [Function `is_index_set`](#0x1_bit_vector_is_index_set)
+- [Function `length`](#0x1_bit_vector_length)
+- [Function `longest_set_sequence_starting_at`](#0x1_bit_vector_longest_set_sequence_starting_at)
+- [Specification](#@Specification_1)
+ - [Struct `BitVector`](#@Specification_1_BitVector)
+ - [Function `new`](#@Specification_1_new)
+ - [Function `set`](#@Specification_1_set)
+ - [Function `unset`](#@Specification_1_unset)
+ - [Function `shift_left`](#@Specification_1_shift_left)
+ - [Function `is_index_set`](#@Specification_1_is_index_set)
+ - [Function `longest_set_sequence_starting_at`](#@Specification_1_longest_set_sequence_starting_at)
+
+
+
+
+
+
+
+
+## Struct `BitVector`
+
+
+
+struct BitVector has copy, drop, store
+
+
+
+
+length: u64
+bit_field: vector<bool>
+const EINDEX: u64 = 131072;
+
+
+
+
+
+
+An invalid length of bitvector was given
+
+
+const ELENGTH: u64 = 131073;
+
+
+
+
+
+
+The maximum allowed bitvector size
+
+
+const MAX_SIZE: u64 = 1024;
+
+
+
+
+
+
+
+
+const WORD_SIZE: u64 = 1;
+
+
+
+
+
+
+## Function `new`
+
+
+
+public fun new(length: u64): bit_vector::BitVector
+
+
+
+
+public fun new(length: u64): BitVector {
+ assert!(length > 0, ELENGTH);
+ assert!(length < MAX_SIZE, ELENGTH);
+ let counter = 0;
+ let bit_field = vector::empty();
+ while ({spec {
+ invariant counter <= length;
+ invariant len(bit_field) == counter;
+ };
+ (counter < length)}) {
+ vector::push_back(&mut bit_field, false);
+ counter = counter + 1;
+ };
+ spec {
+ assert counter == length;
+ assert len(bit_field) == length;
+ };
+
+ BitVector {
+ length,
+ bit_field,
+ }
+}
+
+
+
+
+bit_index
in the bitvector
regardless of its previous state.
+
+
+public fun set(bitvector: &mut bit_vector::BitVector, bit_index: u64)
+
+
+
+
+public fun set(bitvector: &mut BitVector, bit_index: u64) {
+ assert!(bit_index < vector::length(&bitvector.bit_field), EINDEX);
+ let x = vector::borrow_mut(&mut bitvector.bit_field, bit_index);
+ *x = true;
+}
+
+
+
+
+bit_index
in the bitvector
regardless of its previous state.
+
+
+public fun unset(bitvector: &mut bit_vector::BitVector, bit_index: u64)
+
+
+
+
+public fun unset(bitvector: &mut BitVector, bit_index: u64) {
+ assert!(bit_index < vector::length(&bitvector.bit_field), EINDEX);
+ let x = vector::borrow_mut(&mut bitvector.bit_field, bit_index);
+ *x = false;
+}
+
+
+
+
+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 bit_vector::BitVector, amount: u64)
+
+
+
+
+public fun shift_left(bitvector: &mut BitVector, amount: u64) {
+ if (amount >= bitvector.length) {
+ vector::for_each_mut(&mut bitvector.bit_field, |elem| {
+ *elem = false;
+ });
+ } else {
+ let i = amount;
+
+ while (i < bitvector.length) {
+ if (is_index_set(bitvector, i)) set(bitvector, i - amount)
+ else unset(bitvector, i - amount);
+ i = i + 1;
+ };
+
+ i = bitvector.length - amount;
+
+ while (i < bitvector.length) {
+ unset(bitvector, i);
+ i = i + 1;
+ };
+ }
+}
+
+
+
+
+bit_index
in the bitvector
. true
+represents "1" and false
represents a 0
+
+
+public fun is_index_set(bitvector: &bit_vector::BitVector, bit_index: u64): bool
+
+
+
+
+public fun is_index_set(bitvector: &BitVector, bit_index: u64): bool {
+ assert!(bit_index < vector::length(&bitvector.bit_field), EINDEX);
+ *vector::borrow(&bitvector.bit_field, bit_index)
+}
+
+
+
+
+public fun length(bitvector: &bit_vector::BitVector): u64
+
+
+
+
+public fun length(bitvector: &BitVector): u64 {
+ vector::length(&bitvector.bit_field)
+}
+
+
+
+
+start_index
in the bitvector
. If there is no such
+sequence, then 0
is returned.
+
+
+public fun longest_set_sequence_starting_at(bitvector: &bit_vector::BitVector, start_index: u64): u64
+
+
+
+
+public fun longest_set_sequence_starting_at(bitvector: &BitVector, start_index: u64): u64 {
+ assert!(start_index < bitvector.length, EINDEX);
+ let index = start_index;
+
+ // Find the greatest index in the vector such that all indices less than it are set.
+ while ({
+ spec {
+ invariant index >= start_index;
+ invariant index == start_index || is_index_set(bitvector, index - 1);
+ invariant index == start_index || index - 1 < vector::length(bitvector.bit_field);
+ invariant forall j in start_index..index: is_index_set(bitvector, j);
+ invariant forall j in start_index..index: j < vector::length(bitvector.bit_field);
+ };
+ index < bitvector.length
+ }) {
+ if (!is_index_set(bitvector, index)) break;
+ index = index + 1;
+ };
+
+ index - start_index
+}
+
+
+
+
+struct BitVector has copy, drop, store
+
+
+
+
+length: u64
+bit_field: vector<bool>
+invariant length == len(bit_field);
+
+
+
+
+
+
+### Function `new`
+
+
+public fun new(length: u64): bit_vector::BitVector
+
+
+
+
+
+include NewAbortsIf;
+ensures result.length == length;
+ensures len(result.bit_field) == length;
+
+
+
+
+
+
+
+
+schema NewAbortsIf {
+ length: u64;
+ aborts_if length <= 0 with ELENGTH;
+ aborts_if length >= MAX_SIZE with ELENGTH;
+}
+
+
+
+
+
+
+### Function `set`
+
+
+public fun set(bitvector: &mut bit_vector::BitVector, bit_index: u64)
+
+
+
+
+
+include SetAbortsIf;
+ensures bitvector.bit_field[bit_index];
+
+
+
+
+
+
+
+
+schema SetAbortsIf {
+ bitvector: BitVector;
+ bit_index: u64;
+ aborts_if bit_index >= length(bitvector) with EINDEX;
+}
+
+
+
+
+
+
+### Function `unset`
+
+
+public fun unset(bitvector: &mut bit_vector::BitVector, bit_index: u64)
+
+
+
+
+
+include UnsetAbortsIf;
+ensures !bitvector.bit_field[bit_index];
+
+
+
+
+
+
+
+
+schema UnsetAbortsIf {
+ bitvector: BitVector;
+ bit_index: u64;
+ aborts_if bit_index >= length(bitvector) with EINDEX;
+}
+
+
+
+
+
+
+### Function `shift_left`
+
+
+public fun shift_left(bitvector: &mut bit_vector::BitVector, amount: u64)
+
+
+
+
+
+pragma verify = false;
+
+
+
+
+
+
+### Function `is_index_set`
+
+
+public fun is_index_set(bitvector: &bit_vector::BitVector, bit_index: u64): bool
+
+
+
+
+
+include IsIndexSetAbortsIf;
+ensures result == bitvector.bit_field[bit_index];
+
+
+
+
+
+
+
+
+schema IsIndexSetAbortsIf {
+ bitvector: BitVector;
+ bit_index: u64;
+ aborts_if bit_index >= length(bitvector) with EINDEX;
+}
+
+
+
+
+
+
+
+
+fun spec_is_index_set(bitvector: BitVector, bit_index: u64): bool {
+ if (bit_index >= length(bitvector)) {
+ false
+ } else {
+ bitvector.bit_field[bit_index]
+ }
+}
+
+
+
+
+
+
+### Function `longest_set_sequence_starting_at`
+
+
+public fun longest_set_sequence_starting_at(bitvector: &bit_vector::BitVector, start_index: u64): u64
+
+
+
+
+
+aborts_if start_index >= bitvector.length;
+ensures forall i in start_index..result: is_index_set(bitvector, i);
+
+
+
+[move-book]: https://aptos.dev/move/book/SUMMARY
diff --git a/aptos-move/framework/move-stdlib/tests/compiler-v2-doc/error.md b/aptos-move/framework/move-stdlib/tests/compiler-v2-doc/error.md
new file mode 100644
index 0000000000000..0aa82d86871a4
--- /dev/null
+++ b/aptos-move/framework/move-stdlib/tests/compiler-v2-doc/error.md
@@ -0,0 +1,500 @@
+
+
+
+# Module `0x1::error`
+
+This module defines a set of canonical error codes which are optional to use by applications for the
+abort
and assert!
features.
+
+Canonical error codes use the 3 lowest bytes of the u64 abort code range (the upper 5 bytes are free for other use).
+Of those, the highest byte represents the *error category* and the lower two bytes the *error reason*.
+Given an error category 0x1
and a reason 0x3
, a canonical abort code looks as 0x10003
.
+
+A module can use a canonical code with a constant declaration of the following form:
+
+```
+/// An invalid ASCII character was encountered when creating a string.
+const EINVALID_CHARACTER: u64 = 0x010003;
+```
+
+This code is both valid in the worlds with and without canonical errors. It can be used as a plain module local
+error reason understand by the existing error map tooling, or as a canonical code.
+
+The actual canonical categories have been adopted from Google's canonical error codes, which in turn are derived
+from Unix error codes [see here](https://cloud.google.com/apis/design/errors#handling_errors). Each code has an
+associated HTTP error code which can be used in REST apis. The mapping from error code to http code is not 1:1;
+error codes here are a bit richer than HTTP codes.
+
+
+- [Constants](#@Constants_0)
+- [Function `canonical`](#0x1_error_canonical)
+- [Function `invalid_argument`](#0x1_error_invalid_argument)
+- [Function `out_of_range`](#0x1_error_out_of_range)
+- [Function `invalid_state`](#0x1_error_invalid_state)
+- [Function `unauthenticated`](#0x1_error_unauthenticated)
+- [Function `permission_denied`](#0x1_error_permission_denied)
+- [Function `not_found`](#0x1_error_not_found)
+- [Function `aborted`](#0x1_error_aborted)
+- [Function `already_exists`](#0x1_error_already_exists)
+- [Function `resource_exhausted`](#0x1_error_resource_exhausted)
+- [Function `internal`](#0x1_error_internal)
+- [Function `not_implemented`](#0x1_error_not_implemented)
+- [Function `unavailable`](#0x1_error_unavailable)
+- [Specification](#@Specification_1)
+ - [Function `canonical`](#@Specification_1_canonical)
+
+
+
+
+
+
+
+
+## Constants
+
+
+
+
+Concurrency conflict, such as read-modify-write conflict (http: 409)
+
+
+const ABORTED: u64 = 7;
+
+
+
+
+
+
+The resource that a client tried to create already exists (http: 409)
+
+
+const ALREADY_EXISTS: u64 = 8;
+
+
+
+
+
+
+Request cancelled by the client (http: 499)
+
+
+const CANCELLED: u64 = 10;
+
+
+
+
+
+
+Internal error (http: 500)
+
+
+const INTERNAL: u64 = 11;
+
+
+
+
+
+
+Caller specified an invalid argument (http: 400)
+
+
+const INVALID_ARGUMENT: u64 = 1;
+
+
+
+
+
+
+The system is not in a state where the operation can be performed (http: 400)
+
+
+const INVALID_STATE: u64 = 3;
+
+
+
+
+
+
+A specified resource is not found (http: 404)
+
+
+const NOT_FOUND: u64 = 6;
+
+
+
+
+
+
+Feature not implemented (http: 501)
+
+
+const NOT_IMPLEMENTED: u64 = 12;
+
+
+
+
+
+
+An input or result of a computation is out of range (http: 400)
+
+
+const OUT_OF_RANGE: u64 = 2;
+
+
+
+
+
+
+client does not have sufficient permission (http: 403)
+
+
+const PERMISSION_DENIED: u64 = 5;
+
+
+
+
+
+
+Out of gas or other forms of quota (http: 429)
+
+
+const RESOURCE_EXHAUSTED: u64 = 9;
+
+
+
+
+
+
+Request not authenticated due to missing, invalid, or expired auth token (http: 401)
+
+
+const UNAUTHENTICATED: u64 = 4;
+
+
+
+
+
+
+The service is currently unavailable. Indicates that a retry could solve the issue (http: 503)
+
+
+const UNAVAILABLE: u64 = 13;
+
+
+
+
+
+
+## Function `canonical`
+
+Construct a canonical error code from a category and a reason.
+
+
+public fun canonical(category: u64, reason: u64): u64
+
+
+
+
+public fun canonical(category: u64, reason: u64): u64 {
+ (category << 16) + reason
+}
+
+
+
+
+public fun invalid_argument(r: u64): u64
+
+
+
+
+public fun invalid_argument(r: u64): u64 { canonical(INVALID_ARGUMENT, r) }
+
+
+
+
+public fun out_of_range(r: u64): u64
+
+
+
+
+public fun out_of_range(r: u64): u64 { canonical(OUT_OF_RANGE, r) }
+
+
+
+
+public fun invalid_state(r: u64): u64
+
+
+
+
+public fun invalid_state(r: u64): u64 { canonical(INVALID_STATE, r) }
+
+
+
+
+public fun unauthenticated(r: u64): u64
+
+
+
+
+public fun unauthenticated(r: u64): u64 { canonical(UNAUTHENTICATED, r) }
+
+
+
+
+public fun permission_denied(r: u64): u64
+
+
+
+
+public fun permission_denied(r: u64): u64 { canonical(PERMISSION_DENIED, r) }
+
+
+
+
+public fun not_found(r: u64): u64
+
+
+
+
+public fun not_found(r: u64): u64 { canonical(NOT_FOUND, r) }
+
+
+
+
+public fun aborted(r: u64): u64
+
+
+
+
+public fun aborted(r: u64): u64 { canonical(ABORTED, r) }
+
+
+
+
+public fun already_exists(r: u64): u64
+
+
+
+
+public fun already_exists(r: u64): u64 { canonical(ALREADY_EXISTS, r) }
+
+
+
+
+public fun resource_exhausted(r: u64): u64
+
+
+
+
+public fun resource_exhausted(r: u64): u64 { canonical(RESOURCE_EXHAUSTED, r) }
+
+
+
+
+public fun internal(r: u64): u64
+
+
+
+
+public fun internal(r: u64): u64 { canonical(INTERNAL, r) }
+
+
+
+
+public fun not_implemented(r: u64): u64
+
+
+
+
+public fun not_implemented(r: u64): u64 { canonical(NOT_IMPLEMENTED, r) }
+
+
+
+
+public fun unavailable(r: u64): u64
+
+
+
+
+public fun unavailable(r: u64): u64 { canonical(UNAVAILABLE, r) }
+
+
+
+
+public fun canonical(category: u64, reason: u64): u64
+
+
+
+
+
+pragma opaque = true;
+let shl_res = category << 16;
+ensures [concrete] result == shl_res + reason;
+aborts_if [abstract] false;
+ensures [abstract] result == category;
+
+
+
+[move-book]: https://aptos.dev/move/book/SUMMARY
diff --git a/aptos-move/framework/move-stdlib/tests/compiler-v2-doc/features.md b/aptos-move/framework/move-stdlib/tests/compiler-v2-doc/features.md
new file mode 100644
index 0000000000000..da20859bd51e0
--- /dev/null
+++ b/aptos-move/framework/move-stdlib/tests/compiler-v2-doc/features.md
@@ -0,0 +1,3803 @@
+
+
+
+# Module `0x1::features`
+
+Defines feature flags for Aptos. Those are used in Aptos specific implementations of features in
+the Move stdlib, the Aptos stdlib, and the Aptos framework.
+
+============================================================================================
+Feature Flag Definitions
+
+Each feature flag should come with documentation which justifies the need of the flag.
+Introduction of a new feature flag requires approval of framework owners. Be frugal when
+introducing new feature flags, as too many can make it hard to understand the code.
+
+Each feature flag should come with a specification of a lifetime:
+
+- a *transient* feature flag is only needed until a related code rollout has happened. This
+is typically associated with the introduction of new native Move functions, and is only used
+from Move code. The owner of this feature is obliged to remove it once this can be done.
+
+- a *permanent* feature flag is required to stay around forever. Typically, those flags guard
+behavior in native code, and the behavior with or without the feature need to be preserved
+for playback.
+
+Note that removing a feature flag still requires the function which tests for the feature
+(like code_dependency_check_enabled
below) to stay around for compatibility reasons, as it
+is a public function. However, once the feature flag is disabled, those functions can constantly
+return true.
+
+
+- [Resource `Features`](#0x1_features_Features)
+- [Resource `PendingFeatures`](#0x1_features_PendingFeatures)
+- [Constants](#@Constants_0)
+- [Function `code_dependency_check_enabled`](#0x1_features_code_dependency_check_enabled)
+- [Function `treat_friend_as_private`](#0x1_features_treat_friend_as_private)
+- [Function `get_sha_512_and_ripemd_160_feature`](#0x1_features_get_sha_512_and_ripemd_160_feature)
+- [Function `sha_512_and_ripemd_160_enabled`](#0x1_features_sha_512_and_ripemd_160_enabled)
+- [Function `get_aptos_stdlib_chain_id_feature`](#0x1_features_get_aptos_stdlib_chain_id_feature)
+- [Function `aptos_stdlib_chain_id_enabled`](#0x1_features_aptos_stdlib_chain_id_enabled)
+- [Function `get_vm_binary_format_v6`](#0x1_features_get_vm_binary_format_v6)
+- [Function `allow_vm_binary_format_v6`](#0x1_features_allow_vm_binary_format_v6)
+- [Function `get_collect_and_distribute_gas_fees_feature`](#0x1_features_get_collect_and_distribute_gas_fees_feature)
+- [Function `collect_and_distribute_gas_fees`](#0x1_features_collect_and_distribute_gas_fees)
+- [Function `multi_ed25519_pk_validate_v2_feature`](#0x1_features_multi_ed25519_pk_validate_v2_feature)
+- [Function `multi_ed25519_pk_validate_v2_enabled`](#0x1_features_multi_ed25519_pk_validate_v2_enabled)
+- [Function `get_blake2b_256_feature`](#0x1_features_get_blake2b_256_feature)
+- [Function `blake2b_256_enabled`](#0x1_features_blake2b_256_enabled)
+- [Function `get_resource_groups_feature`](#0x1_features_get_resource_groups_feature)
+- [Function `resource_groups_enabled`](#0x1_features_resource_groups_enabled)
+- [Function `get_multisig_accounts_feature`](#0x1_features_get_multisig_accounts_feature)
+- [Function `multisig_accounts_enabled`](#0x1_features_multisig_accounts_enabled)
+- [Function `get_delegation_pools_feature`](#0x1_features_get_delegation_pools_feature)
+- [Function `delegation_pools_enabled`](#0x1_features_delegation_pools_enabled)
+- [Function `get_cryptography_algebra_natives_feature`](#0x1_features_get_cryptography_algebra_natives_feature)
+- [Function `cryptography_algebra_enabled`](#0x1_features_cryptography_algebra_enabled)
+- [Function `get_bls12_381_strutures_feature`](#0x1_features_get_bls12_381_strutures_feature)
+- [Function `bls12_381_structures_enabled`](#0x1_features_bls12_381_structures_enabled)
+- [Function `get_periodical_reward_rate_decrease_feature`](#0x1_features_get_periodical_reward_rate_decrease_feature)
+- [Function `periodical_reward_rate_decrease_enabled`](#0x1_features_periodical_reward_rate_decrease_enabled)
+- [Function `get_partial_governance_voting`](#0x1_features_get_partial_governance_voting)
+- [Function `partial_governance_voting_enabled`](#0x1_features_partial_governance_voting_enabled)
+- [Function `get_delegation_pool_partial_governance_voting`](#0x1_features_get_delegation_pool_partial_governance_voting)
+- [Function `delegation_pool_partial_governance_voting_enabled`](#0x1_features_delegation_pool_partial_governance_voting_enabled)
+- [Function `fee_payer_enabled`](#0x1_features_fee_payer_enabled)
+- [Function `get_auids`](#0x1_features_get_auids)
+- [Function `auids_enabled`](#0x1_features_auids_enabled)
+- [Function `get_bulletproofs_feature`](#0x1_features_get_bulletproofs_feature)
+- [Function `bulletproofs_enabled`](#0x1_features_bulletproofs_enabled)
+- [Function `get_signer_native_format_fix_feature`](#0x1_features_get_signer_native_format_fix_feature)
+- [Function `signer_native_format_fix_enabled`](#0x1_features_signer_native_format_fix_enabled)
+- [Function `get_module_event_feature`](#0x1_features_get_module_event_feature)
+- [Function `module_event_enabled`](#0x1_features_module_event_enabled)
+- [Function `get_aggregator_v2_api_feature`](#0x1_features_get_aggregator_v2_api_feature)
+- [Function `aggregator_v2_api_enabled`](#0x1_features_aggregator_v2_api_enabled)
+- [Function `get_aggregator_snapshots_feature`](#0x1_features_get_aggregator_snapshots_feature)
+- [Function `aggregator_snapshots_enabled`](#0x1_features_aggregator_snapshots_enabled)
+- [Function `get_sponsored_automatic_account_creation`](#0x1_features_get_sponsored_automatic_account_creation)
+- [Function `sponsored_automatic_account_creation_enabled`](#0x1_features_sponsored_automatic_account_creation_enabled)
+- [Function `get_concurrent_token_v2_feature`](#0x1_features_get_concurrent_token_v2_feature)
+- [Function `concurrent_token_v2_enabled`](#0x1_features_concurrent_token_v2_enabled)
+- [Function `get_concurrent_assets_feature`](#0x1_features_get_concurrent_assets_feature)
+- [Function `concurrent_assets_enabled`](#0x1_features_concurrent_assets_enabled)
+- [Function `get_operator_beneficiary_change_feature`](#0x1_features_get_operator_beneficiary_change_feature)
+- [Function `operator_beneficiary_change_enabled`](#0x1_features_operator_beneficiary_change_enabled)
+- [Function `get_commission_change_delegation_pool_feature`](#0x1_features_get_commission_change_delegation_pool_feature)
+- [Function `commission_change_delegation_pool_enabled`](#0x1_features_commission_change_delegation_pool_enabled)
+- [Function `get_bn254_strutures_feature`](#0x1_features_get_bn254_strutures_feature)
+- [Function `bn254_structures_enabled`](#0x1_features_bn254_structures_enabled)
+- [Function `get_reconfigure_with_dkg_feature`](#0x1_features_get_reconfigure_with_dkg_feature)
+- [Function `reconfigure_with_dkg_enabled`](#0x1_features_reconfigure_with_dkg_enabled)
+- [Function `get_keyless_accounts_feature`](#0x1_features_get_keyless_accounts_feature)
+- [Function `keyless_accounts_enabled`](#0x1_features_keyless_accounts_enabled)
+- [Function `get_keyless_but_zkless_accounts_feature`](#0x1_features_get_keyless_but_zkless_accounts_feature)
+- [Function `keyless_but_zkless_accounts_feature_enabled`](#0x1_features_keyless_but_zkless_accounts_feature_enabled)
+- [Function `get_jwk_consensus_feature`](#0x1_features_get_jwk_consensus_feature)
+- [Function `jwk_consensus_enabled`](#0x1_features_jwk_consensus_enabled)
+- [Function `get_concurrent_fungible_assets_feature`](#0x1_features_get_concurrent_fungible_assets_feature)
+- [Function `concurrent_fungible_assets_enabled`](#0x1_features_concurrent_fungible_assets_enabled)
+- [Function `is_object_code_deployment_enabled`](#0x1_features_is_object_code_deployment_enabled)
+- [Function `get_max_object_nesting_check_feature`](#0x1_features_get_max_object_nesting_check_feature)
+- [Function `max_object_nesting_check_enabled`](#0x1_features_max_object_nesting_check_enabled)
+- [Function `get_keyless_accounts_with_passkeys_feature`](#0x1_features_get_keyless_accounts_with_passkeys_feature)
+- [Function `keyless_accounts_with_passkeys_feature_enabled`](#0x1_features_keyless_accounts_with_passkeys_feature_enabled)
+- [Function `get_multisig_v2_enhancement_feature`](#0x1_features_get_multisig_v2_enhancement_feature)
+- [Function `multisig_v2_enhancement_feature_enabled`](#0x1_features_multisig_v2_enhancement_feature_enabled)
+- [Function `get_delegation_pool_allowlisting_feature`](#0x1_features_get_delegation_pool_allowlisting_feature)
+- [Function `delegation_pool_allowlisting_enabled`](#0x1_features_delegation_pool_allowlisting_enabled)
+- [Function `get_module_event_migration_feature`](#0x1_features_get_module_event_migration_feature)
+- [Function `module_event_migration_enabled`](#0x1_features_module_event_migration_enabled)
+- [Function `get_transaction_context_extension_feature`](#0x1_features_get_transaction_context_extension_feature)
+- [Function `transaction_context_extension_enabled`](#0x1_features_transaction_context_extension_enabled)
+- [Function `get_coin_to_fungible_asset_migration_feature`](#0x1_features_get_coin_to_fungible_asset_migration_feature)
+- [Function `coin_to_fungible_asset_migration_feature_enabled`](#0x1_features_coin_to_fungible_asset_migration_feature_enabled)
+- [Function `get_primary_apt_fungible_store_at_user_address_feature`](#0x1_features_get_primary_apt_fungible_store_at_user_address_feature)
+- [Function `primary_apt_fungible_store_at_user_address_enabled`](#0x1_features_primary_apt_fungible_store_at_user_address_enabled)
+- [Function `aggregator_v2_is_at_least_api_enabled`](#0x1_features_aggregator_v2_is_at_least_api_enabled)
+- [Function `get_object_native_derived_address_feature`](#0x1_features_get_object_native_derived_address_feature)
+- [Function `object_native_derived_address_enabled`](#0x1_features_object_native_derived_address_enabled)
+- [Function `get_dispatchable_fungible_asset_feature`](#0x1_features_get_dispatchable_fungible_asset_feature)
+- [Function `dispatchable_fungible_asset_enabled`](#0x1_features_dispatchable_fungible_asset_enabled)
+- [Function `get_new_accounts_default_to_fa_apt_store_feature`](#0x1_features_get_new_accounts_default_to_fa_apt_store_feature)
+- [Function `new_accounts_default_to_fa_apt_store_enabled`](#0x1_features_new_accounts_default_to_fa_apt_store_enabled)
+- [Function `get_operations_default_to_fa_apt_store_feature`](#0x1_features_get_operations_default_to_fa_apt_store_feature)
+- [Function `operations_default_to_fa_apt_store_enabled`](#0x1_features_operations_default_to_fa_apt_store_enabled)
+- [Function `get_concurrent_fungible_balance_feature`](#0x1_features_get_concurrent_fungible_balance_feature)
+- [Function `concurrent_fungible_balance_enabled`](#0x1_features_concurrent_fungible_balance_enabled)
+- [Function `get_default_to_concurrent_fungible_balance_feature`](#0x1_features_get_default_to_concurrent_fungible_balance_feature)
+- [Function `default_to_concurrent_fungible_balance_enabled`](#0x1_features_default_to_concurrent_fungible_balance_enabled)
+- [Function `get_abort_if_multisig_payload_mismatch_feature`](#0x1_features_get_abort_if_multisig_payload_mismatch_feature)
+- [Function `abort_if_multisig_payload_mismatch_enabled`](#0x1_features_abort_if_multisig_payload_mismatch_enabled)
+- [Function `change_feature_flags`](#0x1_features_change_feature_flags)
+- [Function `change_feature_flags_internal`](#0x1_features_change_feature_flags_internal)
+- [Function `change_feature_flags_for_next_epoch`](#0x1_features_change_feature_flags_for_next_epoch)
+- [Function `on_new_epoch`](#0x1_features_on_new_epoch)
+- [Function `is_enabled`](#0x1_features_is_enabled)
+- [Function `set`](#0x1_features_set)
+- [Function `contains`](#0x1_features_contains)
+- [Function `apply_diff`](#0x1_features_apply_diff)
+- [Function `ensure_framework_signer`](#0x1_features_ensure_framework_signer)
+- [Specification](#@Specification_1)
+ - [Resource `Features`](#@Specification_1_Features)
+ - [Resource `PendingFeatures`](#@Specification_1_PendingFeatures)
+ - [Function `periodical_reward_rate_decrease_enabled`](#@Specification_1_periodical_reward_rate_decrease_enabled)
+ - [Function `partial_governance_voting_enabled`](#@Specification_1_partial_governance_voting_enabled)
+ - [Function `module_event_enabled`](#@Specification_1_module_event_enabled)
+ - [Function `abort_if_multisig_payload_mismatch_enabled`](#@Specification_1_abort_if_multisig_payload_mismatch_enabled)
+ - [Function `change_feature_flags_internal`](#@Specification_1_change_feature_flags_internal)
+ - [Function `change_feature_flags_for_next_epoch`](#@Specification_1_change_feature_flags_for_next_epoch)
+ - [Function `on_new_epoch`](#@Specification_1_on_new_epoch)
+ - [Function `is_enabled`](#@Specification_1_is_enabled)
+ - [Function `set`](#@Specification_1_set)
+ - [Function `contains`](#@Specification_1_contains)
+ - [Function `apply_diff`](#@Specification_1_apply_diff)
+
+
+use 0x1::error;
+use 0x1::signer;
+use 0x1::vector;
+
+
+
+
+
+
+## Resource `Features`
+
+The enabled features, represented by a bitset stored on chain.
+
+
+struct Features has key
+
+
+
+
+features: vector<u8>
+struct PendingFeatures has key
+
+
+
+
+features: vector<u8>
+const ABORT_IF_MULTISIG_PAYLOAD_MISMATCH: u64 = 70;
+
+
+
+
+
+
+
+
+const AGGREGATOR_V2_IS_AT_LEAST_API: u64 = 66;
+
+
+
+
+
+
+Whether the new aptos_stdlib::type_info::chain_id()
native for fetching the chain ID is enabled.
+This is needed because of the introduction of a new native function.
+Lifetime: transient
+
+
+const APTOS_STD_CHAIN_ID_NATIVES: u64 = 4;
+
+
+
+
+
+
+Whether enable MOVE functions to call create_auid method to create AUIDs.
+Lifetime: transient
+
+
+const APTOS_UNIQUE_IDENTIFIERS: u64 = 23;
+
+
+
+
+
+
+Whether the new BLAKE2B-256 hash function native is enabled.
+This is needed because of the introduction of new native function(s).
+Lifetime: transient
+
+
+const BLAKE2B_256_NATIVE: u64 = 8;
+
+
+
+
+
+
+Whether the generic algebra implementation for BLS12381 operations are enabled.
+
+Lifetime: transient
+
+
+const BLS12_381_STRUCTURES: u64 = 13;
+
+
+
+
+
+
+Whether the generic algebra implementation for BN254 operations are enabled.
+
+Lifetime: transient
+
+
+const BN254_STRUCTURES: u64 = 43;
+
+
+
+
+
+
+Whether the Bulletproofs zero-knowledge range proof module is enabled, and the related native function is
+available. This is needed because of the introduction of a new native function.
+Lifetime: transient
+
+
+const BULLETPROOFS_NATIVES: u64 = 24;
+
+
+
+
+
+
+Charge invariant violation error.
+Lifetime: transient
+
+
+const CHARGE_INVARIANT_VIOLATION: u64 = 20;
+
+
+
+
+
+
+Whether validation of package dependencies is enabled, and the related native function is
+available. This is needed because of introduction of a new native function.
+Lifetime: transient
+
+
+const CODE_DEPENDENCY_CHECK: u64 = 1;
+
+
+
+
+
+
+Whether migration from coin to fungible asset feature is enabled.
+
+Lifetime: transient
+
+
+const COIN_TO_FUNGIBLE_ASSET_MIGRATION: u64 = 60;
+
+
+
+
+
+
+Whether gas fees are collected and distributed to the block proposers.
+Lifetime: transient
+
+
+const COLLECT_AND_DISTRIBUTE_GAS_FEES: u64 = 6;
+
+
+
+
+
+
+Whether the operator commission rate change in delegation pool is enabled.
+Lifetime: transient
+
+
+const COMMISSION_CHANGE_DELEGATION_POOL: u64 = 42;
+
+
+
+
+
+
+Whether enable Fungible Asset creation
+to create higher throughput concurrent variants.
+Lifetime: transient
+
+
+const CONCURRENT_FUNGIBLE_ASSETS: u64 = 50;
+
+
+
+
+
+
+Whether enable concurent Fungible Balance
+to create higher throughput concurrent variants.
+Lifetime: transient
+
+
+const CONCURRENT_FUNGIBLE_BALANCE: u64 = 67;
+
+
+
+
+
+
+Whether generic algebra basic operation support in crypto_algebra.move
are enabled.
+
+Lifetime: transient
+
+
+const CRYPTOGRAPHY_ALGEBRA_NATIVES: u64 = 12;
+
+
+
+
+
+
+Whether to default new Fungible Store to the concurrent variant.
+Lifetime: transient
+
+
+const DEFAULT_TO_CONCURRENT_FUNGIBLE_BALANCE: u64 = 68;
+
+
+
+
+
+
+Whether delegation pools are enabled.
+Lifetime: transient
+
+
+const DELEGATION_POOLS: u64 = 11;
+
+
+
+
+
+
+Whether delegators allowlisting for delegation pools is supported.
+Lifetime: transient
+
+
+const DELEGATION_POOL_ALLOWLISTING: u64 = 56;
+
+
+
+
+
+
+Whether enable paritial governance voting on delegation_pool.
+Lifetime: transient
+
+
+const DELEGATION_POOL_PARTIAL_GOVERNANCE_VOTING: u64 = 21;
+
+
+
+
+
+
+Whether the dispatchable fungible asset standard feature is enabled.
+
+Lifetime: transient
+
+
+const DISPATCHABLE_FUNGIBLE_ASSET: u64 = 63;
+
+
+
+
+
+
+
+
+const EAPI_DISABLED: u64 = 2;
+
+
+
+
+
+
+Whether native_public_key_validate aborts when a public key of the wrong length is given
+Lifetime: ephemeral
+
+
+const ED25519_PUBKEY_VALIDATE_RETURN_FALSE_WRONG_LENGTH: u64 = 14;
+
+
+
+
+
+
+Deployed to production, and disabling is deprecated.
+
+
+const EFEATURE_CANNOT_BE_DISABLED: u64 = 3;
+
+
+
+
+
+
+The provided signer has not a framework address.
+
+
+const EFRAMEWORK_SIGNER_NEEDED: u64 = 1;
+
+
+
+
+
+
+
+
+const EINVALID_FEATURE: u64 = 1;
+
+
+
+
+
+
+
+
+const FEE_PAYER_ACCOUNT_OPTIONAL: u64 = 35;
+
+
+
+
+
+
+Whether alternate gas payer is supported
+Lifetime: transient
+
+
+const FEE_PAYER_ENABLED: u64 = 22;
+
+
+
+
+
+
+Deprecated by aptos_framework::jwk_consensus_config::JWKConsensusConfig
.
+
+
+const JWK_CONSENSUS: u64 = 49;
+
+
+
+
+
+
+Whether the OIDB feature is enabled, possibly with the ZK-less verification mode.
+
+Lifetime: transient
+
+
+const KEYLESS_ACCOUNTS: u64 = 46;
+
+
+
+
+
+
+Whether keyless accounts support passkey-based ephemeral signatures.
+
+Lifetime: transient
+
+
+const KEYLESS_ACCOUNTS_WITH_PASSKEYS: u64 = 54;
+
+
+
+
+
+
+Whether the ZK-less mode of the keyless accounts feature is enabled.
+
+Lifetime: transient
+
+
+const KEYLESS_BUT_ZKLESS_ACCOUNTS: u64 = 47;
+
+
+
+
+
+
+
+
+const LIMIT_MAX_IDENTIFIER_LENGTH: u64 = 38;
+
+
+
+
+
+
+Whether checking the maximum object nesting is enabled.
+
+
+const MAX_OBJECT_NESTING_CHECK: u64 = 53;
+
+
+
+
+
+
+Whether emit function in event.move
are enabled for module events.
+
+Lifetime: transient
+
+
+const MODULE_EVENT: u64 = 26;
+
+
+
+
+
+
+Whether aptos_framwork enables the behavior of module event migration.
+
+Lifetime: transient
+
+
+const MODULE_EVENT_MIGRATION: u64 = 57;
+
+
+
+
+
+
+Whether multisig accounts (different from accounts with multi-ed25519 auth keys) are enabled.
+
+
+const MULTISIG_ACCOUNTS: u64 = 10;
+
+
+
+
+
+
+Whether the Multisig V2 enhancement feature is enabled.
+
+Lifetime: transient
+
+
+const MULTISIG_V2_ENHANCEMENT: u64 = 55;
+
+
+
+
+
+
+Whether the new aptos_stdlib::multi_ed25519::public_key_validate_internal_v2()
native is enabled.
+This is needed because of the introduction of a new native function.
+Lifetime: transient
+
+
+const MULTI_ED25519_PK_VALIDATE_V2_NATIVES: u64 = 7;
+
+
+
+
+
+
+Lifetime: transient
+
+
+const NEW_ACCOUNTS_DEFAULT_TO_FA_APT_STORE: u64 = 64;
+
+
+
+
+
+
+Whether deploying to objects is enabled.
+
+
+const OBJECT_CODE_DEPLOYMENT: u64 = 52;
+
+
+
+
+
+
+Whether we use more efficient native implementation of computing object derived address
+
+
+const OBJECT_NATIVE_DERIVED_ADDRESS: u64 = 62;
+
+
+
+
+
+
+Lifetime: transient
+
+
+const OPERATIONS_DEFAULT_TO_FA_APT_STORE: u64 = 65;
+
+
+
+
+
+
+Whether allow changing beneficiaries for operators.
+Lifetime: transient
+
+
+const OPERATOR_BENEFICIARY_CHANGE: u64 = 39;
+
+
+
+
+
+
+Whether enable paritial governance voting on aptos_governance.
+Lifetime: transient
+
+
+const PARTIAL_GOVERNANCE_VOTING: u64 = 17;
+
+
+
+
+
+
+Whether reward rate decreases periodically.
+Lifetime: transient
+
+
+const PERIODICAL_REWARD_RATE_DECREASE: u64 = 16;
+
+
+
+
+
+
+
+
+const PRIMARY_APT_FUNGIBLE_STORE_AT_USER_ADDRESS: u64 = 61;
+
+
+
+
+
+
+Deprecated by aptos_framework::randomness_config::RandomnessConfig
.
+
+
+const RECONFIGURE_WITH_DKG: u64 = 45;
+
+
+
+
+
+
+Whether resource groups are enabled.
+This is needed because of new attributes for structs and a change in storage representation.
+
+
+const RESOURCE_GROUPS: u64 = 9;
+
+
+
+
+
+
+
+
+const RESOURCE_GROUPS_SPLIT_IN_VM_CHANGE_SET: u64 = 41;
+
+
+
+
+
+
+
+
+const SAFER_METADATA: u64 = 32;
+
+
+
+
+
+
+
+
+const SAFER_RESOURCE_GROUPS: u64 = 31;
+
+
+
+
+
+
+Whether the new SHA2-512, SHA3-512 and RIPEMD-160 hash function natives are enabled.
+This is needed because of the introduction of new native functions.
+Lifetime: transient
+
+
+const SHA_512_AND_RIPEMD_160_NATIVES: u64 = 3;
+
+
+
+
+
+
+Whether the fix for a counting bug in the script path of the signature checker pass is enabled.
+Lifetime: transient
+
+
+const SIGNATURE_CHECKER_V2_SCRIPT_FIX: u64 = 29;
+
+
+
+
+
+
+Fix the native formatter for signer.
+Lifetime: transient
+
+
+const SIGNER_NATIVE_FORMAT_FIX: u64 = 25;
+
+
+
+
+
+
+
+
+const SINGLE_SENDER_AUTHENTICATOR: u64 = 33;
+
+
+
+
+
+
+Whether the automatic creation of accounts is enabled for sponsored transactions.
+Lifetime: transient
+
+
+const SPONSORED_AUTOMATIC_ACCOUNT_CREATION: u64 = 34;
+
+
+
+
+
+
+Whether struct constructors are enabled
+
+Lifetime: transient
+
+
+const STRUCT_CONSTRUCTORS: u64 = 15;
+
+
+
+
+
+
+Whether the transaction context extension is enabled. This feature allows the module
+transaction_context
to provide contextual information about the user transaction.
+
+Lifetime: transient
+
+
+const TRANSACTION_CONTEXT_EXTENSION: u64 = 59;
+
+
+
+
+
+
+Whether during upgrade compatibility checking, friend functions should be treated similar like
+private functions.
+Lifetime: permanent
+
+
+const TREAT_FRIEND_AS_PRIVATE: u64 = 2;
+
+
+
+
+
+
+Whether to allow the use of binary format version v6.
+Lifetime: transient
+
+
+const VM_BINARY_FORMAT_V6: u64 = 5;
+
+
+
+
+
+
+
+
+const VM_BINARY_FORMAT_V7: u64 = 40;
+
+
+
+
+
+
+## Function `code_dependency_check_enabled`
+
+
+
+public fun code_dependency_check_enabled(): bool
+
+
+
+
+public fun code_dependency_check_enabled(): bool acquires Features {
+ is_enabled(CODE_DEPENDENCY_CHECK)
+}
+
+
+
+
+public fun treat_friend_as_private(): bool
+
+
+
+
+public fun treat_friend_as_private(): bool acquires Features {
+ is_enabled(TREAT_FRIEND_AS_PRIVATE)
+}
+
+
+
+
+public fun get_sha_512_and_ripemd_160_feature(): u64
+
+
+
+
+public fun get_sha_512_and_ripemd_160_feature(): u64 { SHA_512_AND_RIPEMD_160_NATIVES }
+
+
+
+
+public fun sha_512_and_ripemd_160_enabled(): bool
+
+
+
+
+public fun sha_512_and_ripemd_160_enabled(): bool acquires Features {
+ is_enabled(SHA_512_AND_RIPEMD_160_NATIVES)
+}
+
+
+
+
+public fun get_aptos_stdlib_chain_id_feature(): u64
+
+
+
+
+public fun get_aptos_stdlib_chain_id_feature(): u64 { APTOS_STD_CHAIN_ID_NATIVES }
+
+
+
+
+public fun aptos_stdlib_chain_id_enabled(): bool
+
+
+
+
+public fun aptos_stdlib_chain_id_enabled(): bool acquires Features {
+ is_enabled(APTOS_STD_CHAIN_ID_NATIVES)
+}
+
+
+
+
+public fun get_vm_binary_format_v6(): u64
+
+
+
+
+public fun get_vm_binary_format_v6(): u64 { VM_BINARY_FORMAT_V6 }
+
+
+
+
+public fun allow_vm_binary_format_v6(): bool
+
+
+
+
+public fun allow_vm_binary_format_v6(): bool acquires Features {
+ is_enabled(VM_BINARY_FORMAT_V6)
+}
+
+
+
+
+public fun get_collect_and_distribute_gas_fees_feature(): u64
+
+
+
+
+public fun get_collect_and_distribute_gas_fees_feature(): u64 { COLLECT_AND_DISTRIBUTE_GAS_FEES }
+
+
+
+
+public fun collect_and_distribute_gas_fees(): bool
+
+
+
+
+public fun collect_and_distribute_gas_fees(): bool acquires Features {
+ is_enabled(COLLECT_AND_DISTRIBUTE_GAS_FEES)
+}
+
+
+
+
+public fun multi_ed25519_pk_validate_v2_feature(): u64
+
+
+
+
+public fun multi_ed25519_pk_validate_v2_feature(): u64 { MULTI_ED25519_PK_VALIDATE_V2_NATIVES }
+
+
+
+
+public fun multi_ed25519_pk_validate_v2_enabled(): bool
+
+
+
+
+public fun multi_ed25519_pk_validate_v2_enabled(): bool acquires Features {
+ is_enabled(MULTI_ED25519_PK_VALIDATE_V2_NATIVES)
+}
+
+
+
+
+public fun get_blake2b_256_feature(): u64
+
+
+
+
+public fun get_blake2b_256_feature(): u64 { BLAKE2B_256_NATIVE }
+
+
+
+
+public fun blake2b_256_enabled(): bool
+
+
+
+
+public fun blake2b_256_enabled(): bool acquires Features {
+ is_enabled(BLAKE2B_256_NATIVE)
+}
+
+
+
+
+public fun get_resource_groups_feature(): u64
+
+
+
+
+public fun get_resource_groups_feature(): u64 { RESOURCE_GROUPS }
+
+
+
+
+public fun resource_groups_enabled(): bool
+
+
+
+
+public fun resource_groups_enabled(): bool acquires Features {
+ is_enabled(RESOURCE_GROUPS)
+}
+
+
+
+
+public fun get_multisig_accounts_feature(): u64
+
+
+
+
+public fun get_multisig_accounts_feature(): u64 { MULTISIG_ACCOUNTS }
+
+
+
+
+public fun multisig_accounts_enabled(): bool
+
+
+
+
+public fun multisig_accounts_enabled(): bool acquires Features {
+ is_enabled(MULTISIG_ACCOUNTS)
+}
+
+
+
+
+public fun get_delegation_pools_feature(): u64
+
+
+
+
+public fun get_delegation_pools_feature(): u64 { DELEGATION_POOLS }
+
+
+
+
+public fun delegation_pools_enabled(): bool
+
+
+
+
+public fun delegation_pools_enabled(): bool acquires Features {
+ is_enabled(DELEGATION_POOLS)
+}
+
+
+
+
+public fun get_cryptography_algebra_natives_feature(): u64
+
+
+
+
+public fun get_cryptography_algebra_natives_feature(): u64 { CRYPTOGRAPHY_ALGEBRA_NATIVES }
+
+
+
+
+public fun cryptography_algebra_enabled(): bool
+
+
+
+
+public fun cryptography_algebra_enabled(): bool acquires Features {
+ is_enabled(CRYPTOGRAPHY_ALGEBRA_NATIVES)
+}
+
+
+
+
+public fun get_bls12_381_strutures_feature(): u64
+
+
+
+
+public fun get_bls12_381_strutures_feature(): u64 { BLS12_381_STRUCTURES }
+
+
+
+
+public fun bls12_381_structures_enabled(): bool
+
+
+
+
+public fun bls12_381_structures_enabled(): bool acquires Features {
+ is_enabled(BLS12_381_STRUCTURES)
+}
+
+
+
+
+public fun get_periodical_reward_rate_decrease_feature(): u64
+
+
+
+
+public fun get_periodical_reward_rate_decrease_feature(): u64 { PERIODICAL_REWARD_RATE_DECREASE }
+
+
+
+
+public fun periodical_reward_rate_decrease_enabled(): bool
+
+
+
+
+public fun periodical_reward_rate_decrease_enabled(): bool acquires Features {
+ is_enabled(PERIODICAL_REWARD_RATE_DECREASE)
+}
+
+
+
+
+public fun get_partial_governance_voting(): u64
+
+
+
+
+public fun get_partial_governance_voting(): u64 { PARTIAL_GOVERNANCE_VOTING }
+
+
+
+
+public fun partial_governance_voting_enabled(): bool
+
+
+
+
+public fun partial_governance_voting_enabled(): bool acquires Features {
+ is_enabled(PARTIAL_GOVERNANCE_VOTING)
+}
+
+
+
+
+public fun get_delegation_pool_partial_governance_voting(): u64
+
+
+
+
+public fun get_delegation_pool_partial_governance_voting(): u64 { DELEGATION_POOL_PARTIAL_GOVERNANCE_VOTING }
+
+
+
+
+public fun delegation_pool_partial_governance_voting_enabled(): bool
+
+
+
+
+public fun delegation_pool_partial_governance_voting_enabled(): bool acquires Features {
+ is_enabled(DELEGATION_POOL_PARTIAL_GOVERNANCE_VOTING)
+}
+
+
+
+
+public fun fee_payer_enabled(): bool
+
+
+
+
+public fun fee_payer_enabled(): bool acquires Features {
+ is_enabled(FEE_PAYER_ENABLED)
+}
+
+
+
+
+public fun get_auids(): u64
+
+
+
+
+public fun get_auids(): u64 {
+ error::invalid_argument(EFEATURE_CANNOT_BE_DISABLED)
+ }
+
+
+
+
+public fun auids_enabled(): bool
+
+
+
+
+public fun auids_enabled(): bool {
+ true
+}
+
+
+
+
+public fun get_bulletproofs_feature(): u64
+
+
+
+
+public fun get_bulletproofs_feature(): u64 { BULLETPROOFS_NATIVES }
+
+
+
+
+public fun bulletproofs_enabled(): bool
+
+
+
+
+public fun bulletproofs_enabled(): bool acquires Features {
+ is_enabled(BULLETPROOFS_NATIVES)
+}
+
+
+
+
+public fun get_signer_native_format_fix_feature(): u64
+
+
+
+
+public fun get_signer_native_format_fix_feature(): u64 { SIGNER_NATIVE_FORMAT_FIX }
+
+
+
+
+public fun signer_native_format_fix_enabled(): bool
+
+
+
+
+public fun signer_native_format_fix_enabled(): bool acquires Features {
+ is_enabled(SIGNER_NATIVE_FORMAT_FIX)
+}
+
+
+
+
+public fun get_module_event_feature(): u64
+
+
+
+
+public fun get_module_event_feature(): u64 { MODULE_EVENT }
+
+
+
+
+public fun module_event_enabled(): bool
+
+
+
+
+public fun module_event_enabled(): bool acquires Features {
+ is_enabled(MODULE_EVENT)
+}
+
+
+
+
+public fun get_aggregator_v2_api_feature(): u64
+
+
+
+
+public fun get_aggregator_v2_api_feature(): u64 {
+ abort error::invalid_argument(EFEATURE_CANNOT_BE_DISABLED)
+}
+
+
+
+
+public fun aggregator_v2_api_enabled(): bool
+
+
+
+
+public fun aggregator_v2_api_enabled(): bool {
+ true
+}
+
+
+
+
+#[deprecated]
+public fun get_aggregator_snapshots_feature(): u64
+
+
+
+
+public fun get_aggregator_snapshots_feature(): u64 {
+ abort error::invalid_argument(EINVALID_FEATURE)
+}
+
+
+
+
+#[deprecated]
+public fun aggregator_snapshots_enabled(): bool
+
+
+
+
+public fun aggregator_snapshots_enabled(): bool {
+ abort error::invalid_argument(EINVALID_FEATURE)
+}
+
+
+
+
+public fun get_sponsored_automatic_account_creation(): u64
+
+
+
+
+public fun get_sponsored_automatic_account_creation(): u64 { SPONSORED_AUTOMATIC_ACCOUNT_CREATION }
+
+
+
+
+public fun sponsored_automatic_account_creation_enabled(): bool
+
+
+
+
+public fun sponsored_automatic_account_creation_enabled(): bool acquires Features {
+ is_enabled(SPONSORED_AUTOMATIC_ACCOUNT_CREATION)
+}
+
+
+
+
+public fun get_concurrent_token_v2_feature(): u64
+
+
+
+
+public fun get_concurrent_token_v2_feature(): u64 {
+ error::invalid_argument(EFEATURE_CANNOT_BE_DISABLED)
+}
+
+
+
+
+public fun concurrent_token_v2_enabled(): bool
+
+
+
+
+public fun concurrent_token_v2_enabled(): bool {
+ true
+}
+
+
+
+
+#[deprecated]
+public fun get_concurrent_assets_feature(): u64
+
+
+
+
+public fun get_concurrent_assets_feature(): u64 {
+ abort error::invalid_argument(EFEATURE_CANNOT_BE_DISABLED)
+}
+
+
+
+
+#[deprecated]
+public fun concurrent_assets_enabled(): bool
+
+
+
+
+public fun concurrent_assets_enabled(): bool {
+ abort error::invalid_argument(EFEATURE_CANNOT_BE_DISABLED)
+}
+
+
+
+
+public fun get_operator_beneficiary_change_feature(): u64
+
+
+
+
+public fun get_operator_beneficiary_change_feature(): u64 { OPERATOR_BENEFICIARY_CHANGE }
+
+
+
+
+public fun operator_beneficiary_change_enabled(): bool
+
+
+
+
+public fun operator_beneficiary_change_enabled(): bool acquires Features {
+ is_enabled(OPERATOR_BENEFICIARY_CHANGE)
+}
+
+
+
+
+public fun get_commission_change_delegation_pool_feature(): u64
+
+
+
+
+public fun get_commission_change_delegation_pool_feature(): u64 { COMMISSION_CHANGE_DELEGATION_POOL }
+
+
+
+
+public fun commission_change_delegation_pool_enabled(): bool
+
+
+
+
+public fun commission_change_delegation_pool_enabled(): bool acquires Features {
+ is_enabled(COMMISSION_CHANGE_DELEGATION_POOL)
+}
+
+
+
+
+public fun get_bn254_strutures_feature(): u64
+
+
+
+
+public fun get_bn254_strutures_feature(): u64 { BN254_STRUCTURES }
+
+
+
+
+public fun bn254_structures_enabled(): bool
+
+
+
+
+public fun bn254_structures_enabled(): bool acquires Features {
+ is_enabled(BN254_STRUCTURES)
+}
+
+
+
+
+public fun get_reconfigure_with_dkg_feature(): u64
+
+
+
+
+public fun get_reconfigure_with_dkg_feature(): u64 { RECONFIGURE_WITH_DKG }
+
+
+
+
+public fun reconfigure_with_dkg_enabled(): bool
+
+
+
+
+public fun reconfigure_with_dkg_enabled(): bool acquires Features {
+ is_enabled(RECONFIGURE_WITH_DKG)
+}
+
+
+
+
+public fun get_keyless_accounts_feature(): u64
+
+
+
+
+public fun get_keyless_accounts_feature(): u64 { KEYLESS_ACCOUNTS }
+
+
+
+
+public fun keyless_accounts_enabled(): bool
+
+
+
+
+public fun keyless_accounts_enabled(): bool acquires Features {
+ is_enabled(KEYLESS_ACCOUNTS)
+}
+
+
+
+
+public fun get_keyless_but_zkless_accounts_feature(): u64
+
+
+
+
+public fun get_keyless_but_zkless_accounts_feature(): u64 { KEYLESS_BUT_ZKLESS_ACCOUNTS }
+
+
+
+
+public fun keyless_but_zkless_accounts_feature_enabled(): bool
+
+
+
+
+public fun keyless_but_zkless_accounts_feature_enabled(): bool acquires Features {
+ is_enabled(KEYLESS_BUT_ZKLESS_ACCOUNTS)
+}
+
+
+
+
+public fun get_jwk_consensus_feature(): u64
+
+
+
+
+public fun get_jwk_consensus_feature(): u64 { JWK_CONSENSUS }
+
+
+
+
+public fun jwk_consensus_enabled(): bool
+
+
+
+
+public fun jwk_consensus_enabled(): bool acquires Features {
+ is_enabled(JWK_CONSENSUS)
+}
+
+
+
+
+public fun get_concurrent_fungible_assets_feature(): u64
+
+
+
+
+public fun get_concurrent_fungible_assets_feature(): u64 { CONCURRENT_FUNGIBLE_ASSETS }
+
+
+
+
+public fun concurrent_fungible_assets_enabled(): bool
+
+
+
+
+public fun concurrent_fungible_assets_enabled(): bool acquires Features {
+ is_enabled(CONCURRENT_FUNGIBLE_ASSETS)
+}
+
+
+
+
+public fun is_object_code_deployment_enabled(): bool
+
+
+
+
+public fun is_object_code_deployment_enabled(): bool acquires Features {
+ is_enabled(OBJECT_CODE_DEPLOYMENT)
+}
+
+
+
+
+public fun get_max_object_nesting_check_feature(): u64
+
+
+
+
+public fun get_max_object_nesting_check_feature(): u64 { MAX_OBJECT_NESTING_CHECK }
+
+
+
+
+public fun max_object_nesting_check_enabled(): bool
+
+
+
+
+public fun max_object_nesting_check_enabled(): bool acquires Features {
+ is_enabled(MAX_OBJECT_NESTING_CHECK)
+}
+
+
+
+
+public fun get_keyless_accounts_with_passkeys_feature(): u64
+
+
+
+
+public fun get_keyless_accounts_with_passkeys_feature(): u64 { KEYLESS_ACCOUNTS_WITH_PASSKEYS }
+
+
+
+
+public fun keyless_accounts_with_passkeys_feature_enabled(): bool
+
+
+
+
+public fun keyless_accounts_with_passkeys_feature_enabled(): bool acquires Features {
+ is_enabled(KEYLESS_ACCOUNTS_WITH_PASSKEYS)
+}
+
+
+
+
+public fun get_multisig_v2_enhancement_feature(): u64
+
+
+
+
+public fun get_multisig_v2_enhancement_feature(): u64 { MULTISIG_V2_ENHANCEMENT }
+
+
+
+
+public fun multisig_v2_enhancement_feature_enabled(): bool
+
+
+
+
+public fun multisig_v2_enhancement_feature_enabled(): bool acquires Features {
+ is_enabled(MULTISIG_V2_ENHANCEMENT)
+}
+
+
+
+
+public fun get_delegation_pool_allowlisting_feature(): u64
+
+
+
+
+public fun get_delegation_pool_allowlisting_feature(): u64 { DELEGATION_POOL_ALLOWLISTING }
+
+
+
+
+public fun delegation_pool_allowlisting_enabled(): bool
+
+
+
+
+public fun delegation_pool_allowlisting_enabled(): bool acquires Features {
+ is_enabled(DELEGATION_POOL_ALLOWLISTING)
+}
+
+
+
+
+public fun get_module_event_migration_feature(): u64
+
+
+
+
+public fun get_module_event_migration_feature(): u64 { MODULE_EVENT_MIGRATION }
+
+
+
+
+public fun module_event_migration_enabled(): bool
+
+
+
+
+public fun module_event_migration_enabled(): bool acquires Features {
+ is_enabled(MODULE_EVENT_MIGRATION)
+}
+
+
+
+
+public fun get_transaction_context_extension_feature(): u64
+
+
+
+
+public fun get_transaction_context_extension_feature(): u64 { TRANSACTION_CONTEXT_EXTENSION }
+
+
+
+
+public fun transaction_context_extension_enabled(): bool
+
+
+
+
+public fun transaction_context_extension_enabled(): bool acquires Features {
+ is_enabled(TRANSACTION_CONTEXT_EXTENSION)
+}
+
+
+
+
+public fun get_coin_to_fungible_asset_migration_feature(): u64
+
+
+
+
+public fun get_coin_to_fungible_asset_migration_feature(): u64 { COIN_TO_FUNGIBLE_ASSET_MIGRATION }
+
+
+
+
+public fun coin_to_fungible_asset_migration_feature_enabled(): bool
+
+
+
+
+public fun coin_to_fungible_asset_migration_feature_enabled(): bool acquires Features {
+ is_enabled(COIN_TO_FUNGIBLE_ASSET_MIGRATION)
+}
+
+
+
+
+#[deprecated]
+public fun get_primary_apt_fungible_store_at_user_address_feature(): u64
+
+
+
+
+public fun get_primary_apt_fungible_store_at_user_address_feature(
+): u64 {
+ abort error::invalid_argument(EINVALID_FEATURE)
+}
+
+
+
+
+#[deprecated]
+public fun primary_apt_fungible_store_at_user_address_enabled(): bool
+
+
+
+
+public fun primary_apt_fungible_store_at_user_address_enabled(): bool acquires Features {
+ is_enabled(PRIMARY_APT_FUNGIBLE_STORE_AT_USER_ADDRESS)
+}
+
+
+
+
+public fun aggregator_v2_is_at_least_api_enabled(): bool
+
+
+
+
+public fun aggregator_v2_is_at_least_api_enabled(): bool acquires Features {
+ is_enabled(AGGREGATOR_V2_IS_AT_LEAST_API)
+}
+
+
+
+
+public fun get_object_native_derived_address_feature(): u64
+
+
+
+
+public fun get_object_native_derived_address_feature(): u64 { OBJECT_NATIVE_DERIVED_ADDRESS }
+
+
+
+
+public fun object_native_derived_address_enabled(): bool
+
+
+
+
+public fun object_native_derived_address_enabled(): bool acquires Features {
+ is_enabled(OBJECT_NATIVE_DERIVED_ADDRESS)
+}
+
+
+
+
+public fun get_dispatchable_fungible_asset_feature(): u64
+
+
+
+
+public fun get_dispatchable_fungible_asset_feature(): u64 { DISPATCHABLE_FUNGIBLE_ASSET }
+
+
+
+
+public fun dispatchable_fungible_asset_enabled(): bool
+
+
+
+
+public fun dispatchable_fungible_asset_enabled(): bool acquires Features {
+ is_enabled(DISPATCHABLE_FUNGIBLE_ASSET)
+}
+
+
+
+
+public fun get_new_accounts_default_to_fa_apt_store_feature(): u64
+
+
+
+
+public fun get_new_accounts_default_to_fa_apt_store_feature(): u64 { NEW_ACCOUNTS_DEFAULT_TO_FA_APT_STORE }
+
+
+
+
+public fun new_accounts_default_to_fa_apt_store_enabled(): bool
+
+
+
+
+public fun new_accounts_default_to_fa_apt_store_enabled(): bool acquires Features {
+ is_enabled(NEW_ACCOUNTS_DEFAULT_TO_FA_APT_STORE)
+}
+
+
+
+
+public fun get_operations_default_to_fa_apt_store_feature(): u64
+
+
+
+
+public fun get_operations_default_to_fa_apt_store_feature(): u64 { OPERATIONS_DEFAULT_TO_FA_APT_STORE }
+
+
+
+
+public fun operations_default_to_fa_apt_store_enabled(): bool
+
+
+
+
+public fun operations_default_to_fa_apt_store_enabled(): bool acquires Features {
+ is_enabled(OPERATIONS_DEFAULT_TO_FA_APT_STORE)
+}
+
+
+
+
+public fun get_concurrent_fungible_balance_feature(): u64
+
+
+
+
+public fun get_concurrent_fungible_balance_feature(): u64 { CONCURRENT_FUNGIBLE_BALANCE }
+
+
+
+
+public fun concurrent_fungible_balance_enabled(): bool
+
+
+
+
+public fun concurrent_fungible_balance_enabled(): bool acquires Features {
+ is_enabled(CONCURRENT_FUNGIBLE_BALANCE)
+}
+
+
+
+
+public fun get_default_to_concurrent_fungible_balance_feature(): u64
+
+
+
+
+public fun get_default_to_concurrent_fungible_balance_feature(): u64 { DEFAULT_TO_CONCURRENT_FUNGIBLE_BALANCE }
+
+
+
+
+public fun default_to_concurrent_fungible_balance_enabled(): bool
+
+
+
+
+public fun default_to_concurrent_fungible_balance_enabled(): bool acquires Features {
+ is_enabled(DEFAULT_TO_CONCURRENT_FUNGIBLE_BALANCE)
+}
+
+
+
+
+public fun get_abort_if_multisig_payload_mismatch_feature(): u64
+
+
+
+
+public fun get_abort_if_multisig_payload_mismatch_feature(): u64 { ABORT_IF_MULTISIG_PAYLOAD_MISMATCH }
+
+
+
+
+public fun abort_if_multisig_payload_mismatch_enabled(): bool
+
+
+
+
+public fun abort_if_multisig_payload_mismatch_enabled(): bool acquires Features {
+ is_enabled(ABORT_IF_MULTISIG_PAYLOAD_MISMATCH)
+}
+
+
+
+
+change_feature_flags_internal()
for feature vec initialization.
+
+Governance proposals should use change_feature_flags_for_next_epoch()
to enable/disable features.
+
+
+public fun change_feature_flags(_framework: &signer, _enable: vector<u64>, _disable: vector<u64>)
+
+
+
+
+public fun change_feature_flags(_framework: &signer, _enable: vector<u64>, _disable: vector<u64>) {
+ abort (error::invalid_state(EAPI_DISABLED))
+}
+
+
+
+
+fun change_feature_flags_internal(framework: &signer, enable: vector<u64>, disable: vector<u64>)
+
+
+
+
+fun change_feature_flags_internal(framework: &signer, enable: vector<u64>, disable: vector<u64>) acquires Features {
+ assert!(signer::address_of(framework) == @std, error::permission_denied(EFRAMEWORK_SIGNER_NEEDED));
+ if (!exists<Features>(@std)) {
+ move_to<Features>(framework, Features { features: vector[] })
+ };
+ let features = &mut borrow_global_mut<Features>(@std).features;
+ vector::for_each_ref(&enable, |feature| {
+ set(features, *feature, true);
+ });
+ vector::for_each_ref(&disable, |feature| {
+ set(features, *feature, false);
+ });
+}
+
+
+
+
+public fun change_feature_flags_for_next_epoch(framework: &signer, enable: vector<u64>, disable: vector<u64>)
+
+
+
+
+public fun change_feature_flags_for_next_epoch(
+ framework: &signer,
+ enable: vector<u64>,
+ disable: vector<u64>
+) acquires PendingFeatures, Features {
+ assert!(signer::address_of(framework) == @std, error::permission_denied(EFRAMEWORK_SIGNER_NEEDED));
+
+ // Figure out the baseline feature vec that the diff will be applied to.
+ let new_feature_vec = if (exists<PendingFeatures>(@std)) {
+ // If there is a buffered feature vec, use it as the baseline.
+ let PendingFeatures { features } = move_from<PendingFeatures>(@std);
+ features
+ } else if (exists<Features>(@std)) {
+ // Otherwise, use the currently effective feature flag vec as the baseline, if it exists.
+ borrow_global<Features>(@std).features
+ } else {
+ // Otherwise, use an empty feature vec.
+ vector[]
+ };
+
+ // Apply the diff and save it to the buffer.
+ apply_diff(&mut new_feature_vec, enable, disable);
+ move_to(framework, PendingFeatures { features: new_feature_vec });
+}
+
+
+
+
+block_prologue
and governance proposals,
+who have permission to set the flag that's checked in extract()
.
+
+
+public fun on_new_epoch(framework: &signer)
+
+
+
+
+public fun on_new_epoch(framework: &signer) acquires Features, PendingFeatures {
+ ensure_framework_signer(framework);
+ if (exists<PendingFeatures>(@std)) {
+ let PendingFeatures { features } = move_from<PendingFeatures>(@std);
+ if (exists<Features>(@std)) {
+ borrow_global_mut<Features>(@std).features = features;
+ } else {
+ move_to(framework, Features { features })
+ }
+ }
+}
+
+
+
+
+#[view]
+public fun is_enabled(feature: u64): bool
+
+
+
+
+public fun is_enabled(feature: u64): bool acquires Features {
+ exists<Features>(@std) &&
+ contains(&borrow_global<Features>(@std).features, feature)
+}
+
+
+
+
+fun set(features: &mut vector<u8>, feature: u64, include: bool)
+
+
+
+
+fun set(features: &mut vector<u8>, feature: u64, include: bool) {
+ let byte_index = feature / 8;
+ let bit_mask = 1 << ((feature % 8) as u8);
+ while (vector::length(features) <= byte_index) {
+ vector::push_back(features, 0)
+ };
+ let entry = vector::borrow_mut(features, byte_index);
+ if (include)
+ *entry = *entry | bit_mask
+ else
+ *entry = *entry & (0xff ^ bit_mask)
+}
+
+
+
+
+fun contains(features: &vector<u8>, feature: u64): bool
+
+
+
+
+fun contains(features: &vector<u8>, feature: u64): bool {
+ let byte_index = feature / 8;
+ let bit_mask = 1 << ((feature % 8) as u8);
+ byte_index < vector::length(features) && (*vector::borrow(features, byte_index) & bit_mask) != 0
+}
+
+
+
+
+fun apply_diff(features: &mut vector<u8>, enable: vector<u64>, disable: vector<u64>)
+
+
+
+
+fun apply_diff(features: &mut vector<u8>, enable: vector<u64>, disable: vector<u64>) {
+ vector::for_each(enable, |feature| {
+ set(features, feature, true);
+ });
+ vector::for_each(disable, |feature| {
+ set(features, feature, false);
+ });
+}
+
+
+
+
+fun ensure_framework_signer(account: &signer)
+
+
+
+
+fun ensure_framework_signer(account: &signer) {
+ let addr = signer::address_of(account);
+ assert!(addr == @std, error::permission_denied(EFRAMEWORK_SIGNER_NEEDED));
+}
+
+
+
+
+struct Features has key
+
+
+
+
+features: vector<u8>
+pragma bv=b"0";
+
+
+
+
+
+
+### Resource `PendingFeatures`
+
+
+struct PendingFeatures has key
+
+
+
+
+features: vector<u8>
+pragma bv=b"0";
+
+
+
+
+
+
+### Function `periodical_reward_rate_decrease_enabled`
+
+
+public fun periodical_reward_rate_decrease_enabled(): bool
+
+
+
+
+
+pragma opaque;
+aborts_if [abstract] false;
+ensures [abstract] result == spec_periodical_reward_rate_decrease_enabled();
+
+
+
+
+
+
+
+
+fun spec_partial_governance_voting_enabled(): bool {
+ spec_is_enabled(PARTIAL_GOVERNANCE_VOTING)
+}
+
+
+
+
+
+
+### Function `partial_governance_voting_enabled`
+
+
+public fun partial_governance_voting_enabled(): bool
+
+
+
+
+
+pragma opaque;
+aborts_if [abstract] false;
+ensures [abstract] result == spec_partial_governance_voting_enabled();
+
+
+
+
+
+
+### Function `module_event_enabled`
+
+
+public fun module_event_enabled(): bool
+
+
+
+
+
+pragma opaque;
+aborts_if [abstract] false;
+ensures [abstract] result == spec_module_event_enabled();
+
+
+
+
+
+
+
+
+fun spec_abort_if_multisig_payload_mismatch_enabled(): bool {
+ spec_is_enabled(ABORT_IF_MULTISIG_PAYLOAD_MISMATCH)
+}
+
+
+
+
+
+
+### Function `abort_if_multisig_payload_mismatch_enabled`
+
+
+public fun abort_if_multisig_payload_mismatch_enabled(): bool
+
+
+
+
+
+pragma opaque;
+aborts_if [abstract] false;
+ensures [abstract] result == spec_abort_if_multisig_payload_mismatch_enabled();
+
+
+
+
+
+
+### Function `change_feature_flags_internal`
+
+
+fun change_feature_flags_internal(framework: &signer, enable: vector<u64>, disable: vector<u64>)
+
+
+
+
+
+pragma opaque;
+modifies global<Features>(@std);
+aborts_if signer::address_of(framework) != @std;
+
+
+
+
+
+
+### Function `change_feature_flags_for_next_epoch`
+
+
+public fun change_feature_flags_for_next_epoch(framework: &signer, enable: vector<u64>, disable: vector<u64>)
+
+
+
+
+
+aborts_if signer::address_of(framework) != @std;
+pragma opaque;
+modifies global<Features>(@std);
+modifies global<PendingFeatures>(@std);
+
+
+
+
+
+
+
+
+fun spec_contains(features: vector<u8>, feature: u64): bool {
+ ((int2bv((((1 as u8) << ((feature % (8 as u64)) as u64)) as u8)) as u8) & features[feature/8] as u8) > (0 as u8)
+ && (feature / 8) < len(features)
+}
+
+
+
+
+
+
+### Function `on_new_epoch`
+
+
+public fun on_new_epoch(framework: &signer)
+
+
+
+
+
+requires @std == signer::address_of(framework);
+let features_pending = global<PendingFeatures>(@std).features;
+let post features_std = global<Features>(@std).features;
+ensures exists<PendingFeatures>(@std) ==> features_std == features_pending;
+aborts_if false;
+
+
+
+
+
+
+
+
+fun spec_sha_512_and_ripemd_160_enabled(): bool {
+ spec_is_enabled(SHA_512_AND_RIPEMD_160_NATIVES)
+}
+
+
+
+
+
+
+### Function `is_enabled`
+
+
+#[view]
+public fun is_enabled(feature: u64): bool
+
+
+
+
+
+pragma opaque;
+aborts_if [abstract] false;
+ensures [abstract] result == spec_is_enabled(feature);
+
+
+
+
+
+
+
+
+fun spec_is_enabled(feature: u64): bool;
+
+
+
+
+
+
+
+
+fun spec_periodical_reward_rate_decrease_enabled(): bool {
+ spec_is_enabled(PERIODICAL_REWARD_RATE_DECREASE)
+}
+
+
+
+
+
+
+
+
+fun spec_fee_payer_enabled(): bool {
+ spec_is_enabled(FEE_PAYER_ENABLED)
+}
+
+
+
+
+
+
+
+
+fun spec_collect_and_distribute_gas_fees_enabled(): bool {
+ spec_is_enabled(COLLECT_AND_DISTRIBUTE_GAS_FEES)
+}
+
+
+
+
+
+
+
+
+fun spec_module_event_enabled(): bool {
+ spec_is_enabled(MODULE_EVENT)
+}
+
+
+
+
+
+
+### Function `set`
+
+
+fun set(features: &mut vector<u8>, feature: u64, include: bool)
+
+
+
+
+
+pragma bv=b"0";
+aborts_if false;
+ensures feature / 8 < len(features);
+ensures include == spec_contains(features, feature);
+
+
+
+
+
+
+### Function `contains`
+
+
+fun contains(features: &vector<u8>, feature: u64): bool
+
+
+
+
+
+pragma bv=b"0";
+aborts_if false;
+ensures result == spec_contains(features, feature);
+
+
+
+
+
+
+### Function `apply_diff`
+
+
+fun apply_diff(features: &mut vector<u8>, enable: vector<u64>, disable: vector<u64>)
+
+
+
+
+
+aborts_if [abstract] false;
+ensures [abstract] forall i in disable: !spec_contains(features, i);
+ensures [abstract] forall i in enable: !vector::spec_contains(disable, i)
+ ==> spec_contains(features, i);
+pragma opaque;
+
+
+
+[move-book]: https://aptos.dev/move/book/SUMMARY
diff --git a/aptos-move/framework/move-stdlib/tests/compiler-v2-doc/fixed_point32.md b/aptos-move/framework/move-stdlib/tests/compiler-v2-doc/fixed_point32.md
new file mode 100644
index 0000000000000..ee8010e510ccc
--- /dev/null
+++ b/aptos-move/framework/move-stdlib/tests/compiler-v2-doc/fixed_point32.md
@@ -0,0 +1,884 @@
+
+
+
+# Module `0x1::fixed_point32`
+
+Defines a fixed-point numeric type with a 32-bit integer part and
+a 32-bit fractional part.
+
+
+- [Struct `FixedPoint32`](#0x1_fixed_point32_FixedPoint32)
+- [Constants](#@Constants_0)
+- [Function `multiply_u64`](#0x1_fixed_point32_multiply_u64)
+- [Function `divide_u64`](#0x1_fixed_point32_divide_u64)
+- [Function `create_from_rational`](#0x1_fixed_point32_create_from_rational)
+- [Function `create_from_raw_value`](#0x1_fixed_point32_create_from_raw_value)
+- [Function `get_raw_value`](#0x1_fixed_point32_get_raw_value)
+- [Function `is_zero`](#0x1_fixed_point32_is_zero)
+- [Function `min`](#0x1_fixed_point32_min)
+- [Function `max`](#0x1_fixed_point32_max)
+- [Function `create_from_u64`](#0x1_fixed_point32_create_from_u64)
+- [Function `floor`](#0x1_fixed_point32_floor)
+- [Function `ceil`](#0x1_fixed_point32_ceil)
+- [Function `round`](#0x1_fixed_point32_round)
+- [Specification](#@Specification_1)
+ - [Function `multiply_u64`](#@Specification_1_multiply_u64)
+ - [Function `divide_u64`](#@Specification_1_divide_u64)
+ - [Function `create_from_rational`](#@Specification_1_create_from_rational)
+ - [Function `create_from_raw_value`](#@Specification_1_create_from_raw_value)
+ - [Function `min`](#@Specification_1_min)
+ - [Function `max`](#@Specification_1_max)
+ - [Function `create_from_u64`](#@Specification_1_create_from_u64)
+ - [Function `floor`](#@Specification_1_floor)
+ - [Function `ceil`](#@Specification_1_ceil)
+ - [Function `round`](#@Specification_1_round)
+
+
+
+
+
+
+
+
+## Struct `FixedPoint32`
+
+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.
+
+
+struct FixedPoint32 has copy, drop, store
+
+
+
+
+value: u64
+const MAX_U64: u128 = 18446744073709551615;
+
+
+
+
+
+
+The denominator provided was zero
+
+
+const EDENOMINATOR: u64 = 65537;
+
+
+
+
+
+
+The quotient value would be too large to be held in a u64
+
+
+const EDIVISION: u64 = 131074;
+
+
+
+
+
+
+A division by zero was encountered
+
+
+const EDIVISION_BY_ZERO: u64 = 65540;
+
+
+
+
+
+
+The multiplied value would be too large to be held in a u64
+
+
+const EMULTIPLICATION: u64 = 131075;
+
+
+
+
+
+
+The computed ratio when converting to a FixedPoint32
would be unrepresentable
+
+
+const ERATIO_OUT_OF_RANGE: u64 = 131077;
+
+
+
+
+
+
+## Function `multiply_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: fixed_point32::FixedPoint32): u64
+
+
+
+
+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)
+}
+
+
+
+
+public fun divide_u64(val: u64, divisor: fixed_point32::FixedPoint32): u64
+
+
+
+
+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)
+}
+
+
+
+
+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): fixed_point32::FixedPoint32
+
+
+
+
+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) }
+}
+
+
+
+
+public fun create_from_raw_value(value: u64): fixed_point32::FixedPoint32
+
+
+
+
+public fun create_from_raw_value(value: u64): FixedPoint32 {
+ FixedPoint32 { value }
+}
+
+
+
+
+public fun get_raw_value(num: fixed_point32::FixedPoint32): u64
+
+
+
+
+public fun get_raw_value(num: FixedPoint32): u64 {
+ num.value
+}
+
+
+
+
+public fun is_zero(num: fixed_point32::FixedPoint32): bool
+
+
+
+
+public fun is_zero(num: FixedPoint32): bool {
+ num.value == 0
+}
+
+
+
+
+public fun min(num1: fixed_point32::FixedPoint32, num2: fixed_point32::FixedPoint32): fixed_point32::FixedPoint32
+
+
+
+
+public fun min(num1: FixedPoint32, num2: FixedPoint32): FixedPoint32 {
+ if (num1.value < num2.value) {
+ num1
+ } else {
+ num2
+ }
+}
+
+
+
+
+public fun max(num1: fixed_point32::FixedPoint32, num2: fixed_point32::FixedPoint32): fixed_point32::FixedPoint32
+
+
+
+
+public fun max(num1: FixedPoint32, num2: FixedPoint32): FixedPoint32 {
+ if (num1.value > num2.value) {
+ num1
+ } else {
+ num2
+ }
+}
+
+
+
+
+public fun create_from_u64(val: u64): fixed_point32::FixedPoint32
+
+
+
+
+public fun create_from_u64(val: u64): FixedPoint32 {
+ let value = (val as u128) << 32;
+ assert!(value <= MAX_U64, ERATIO_OUT_OF_RANGE);
+ FixedPoint32 {value: (value as u64)}
+}
+
+
+
+
+public fun floor(num: fixed_point32::FixedPoint32): u64
+
+
+
+
+public fun floor(num: FixedPoint32): u64 {
+ num.value >> 32
+}
+
+
+
+
+public fun ceil(num: fixed_point32::FixedPoint32): u64
+
+
+
+
+public fun ceil(num: FixedPoint32): u64 {
+ let floored_num = floor(num) << 32;
+ if (num.value == floored_num) {
+ return floored_num >> 32
+ };
+ let val = ((floored_num as u128) + (1 << 32));
+ (val >> 32 as u64)
+}
+
+
+
+
+public fun round(num: fixed_point32::FixedPoint32): u64
+
+
+
+
+public fun round(num: FixedPoint32): u64 {
+ let floored_num = floor(num) << 32;
+ let boundary = floored_num + ((1 << 32) / 2);
+ if (num.value < boundary) {
+ floored_num >> 32
+ } else {
+ ceil(num)
+ }
+}
+
+
+
+
+pragma aborts_if_is_strict;
+
+
+
+
+
+
+### Function `multiply_u64`
+
+
+public fun multiply_u64(val: u64, multiplier: fixed_point32::FixedPoint32): u64
+
+
+
+
+
+pragma opaque;
+include MultiplyAbortsIf;
+ensures result == spec_multiply_u64(val, multiplier);
+
+
+
+
+
+
+
+
+schema MultiplyAbortsIf {
+ val: num;
+ multiplier: FixedPoint32;
+ aborts_if spec_multiply_u64(val, multiplier) > MAX_U64 with EMULTIPLICATION;
+}
+
+
+
+
+
+
+
+
+fun spec_multiply_u64(val: num, multiplier: FixedPoint32): num {
+ (val * multiplier.value) >> 32
+}
+
+
+
+
+
+
+### Function `divide_u64`
+
+
+public fun divide_u64(val: u64, divisor: fixed_point32::FixedPoint32): u64
+
+
+
+
+
+pragma opaque;
+include DivideAbortsIf;
+ensures result == spec_divide_u64(val, divisor);
+
+
+
+
+
+
+
+
+schema DivideAbortsIf {
+ val: num;
+ divisor: FixedPoint32;
+ aborts_if divisor.value == 0 with EDIVISION_BY_ZERO;
+ aborts_if spec_divide_u64(val, divisor) > MAX_U64 with EDIVISION;
+}
+
+
+
+
+
+
+
+
+fun spec_divide_u64(val: num, divisor: FixedPoint32): num {
+ (val << 32) / divisor.value
+}
+
+
+
+
+
+
+### Function `create_from_rational`
+
+
+public fun create_from_rational(numerator: u64, denominator: u64): fixed_point32::FixedPoint32
+
+
+
+
+
+pragma opaque;
+include CreateFromRationalAbortsIf;
+ensures result == spec_create_from_rational(numerator, denominator);
+
+
+
+
+
+
+
+
+schema CreateFromRationalAbortsIf {
+ numerator: u64;
+ denominator: u64;
+ let scaled_numerator = (numerator as u128)<< 64;
+ let scaled_denominator = (denominator as u128) << 32;
+ let quotient = scaled_numerator / scaled_denominator;
+ aborts_if scaled_denominator == 0 with EDENOMINATOR;
+ aborts_if quotient == 0 && scaled_numerator != 0 with ERATIO_OUT_OF_RANGE;
+ aborts_if quotient > MAX_U64 with ERATIO_OUT_OF_RANGE;
+}
+
+
+
+
+
+
+
+
+fun spec_create_from_rational(numerator: num, denominator: num): FixedPoint32 {
+ FixedPoint32{value: (numerator << 64) / (denominator << 32)}
+}
+
+
+
+
+
+
+### Function `create_from_raw_value`
+
+
+public fun create_from_raw_value(value: u64): fixed_point32::FixedPoint32
+
+
+
+
+
+pragma opaque;
+aborts_if false;
+ensures result.value == value;
+
+
+
+
+
+
+### Function `min`
+
+
+public fun min(num1: fixed_point32::FixedPoint32, num2: fixed_point32::FixedPoint32): fixed_point32::FixedPoint32
+
+
+
+
+
+pragma opaque;
+aborts_if false;
+ensures result == spec_min(num1, num2);
+
+
+
+
+
+
+
+
+fun spec_min(num1: FixedPoint32, num2: FixedPoint32): FixedPoint32 {
+ if (num1.value < num2.value) {
+ num1
+ } else {
+ num2
+ }
+}
+
+
+
+
+
+
+### Function `max`
+
+
+public fun max(num1: fixed_point32::FixedPoint32, num2: fixed_point32::FixedPoint32): fixed_point32::FixedPoint32
+
+
+
+
+
+pragma opaque;
+aborts_if false;
+ensures result == spec_max(num1, num2);
+
+
+
+
+
+
+
+
+fun spec_max(num1: FixedPoint32, num2: FixedPoint32): FixedPoint32 {
+ if (num1.value > num2.value) {
+ num1
+ } else {
+ num2
+ }
+}
+
+
+
+
+
+
+### Function `create_from_u64`
+
+
+public fun create_from_u64(val: u64): fixed_point32::FixedPoint32
+
+
+
+
+
+pragma opaque;
+include CreateFromU64AbortsIf;
+ensures result == spec_create_from_u64(val);
+
+
+
+
+
+
+
+
+schema CreateFromU64AbortsIf {
+ val: num;
+ let scaled_value = (val as u128) << 32;
+ aborts_if scaled_value > MAX_U64;
+}
+
+
+
+
+
+
+
+
+fun spec_create_from_u64(val: num): FixedPoint32 {
+ FixedPoint32 {value: val << 32}
+}
+
+
+
+
+
+
+### Function `floor`
+
+
+public fun floor(num: fixed_point32::FixedPoint32): u64
+
+
+
+
+
+pragma opaque;
+aborts_if false;
+ensures result == spec_floor(num);
+
+
+
+
+
+
+
+
+fun spec_floor(val: FixedPoint32): u64 {
+ let fractional = val.value % (1 << 32);
+ if (fractional == 0) {
+ val.value >> 32
+ } else {
+ (val.value - fractional) >> 32
+ }
+}
+
+
+
+
+
+
+### Function `ceil`
+
+
+public fun ceil(num: fixed_point32::FixedPoint32): u64
+
+
+
+
+
+pragma verify_duration_estimate = 120;
+pragma opaque;
+aborts_if false;
+ensures result == spec_ceil(num);
+
+
+
+
+
+
+
+
+fun spec_ceil(val: FixedPoint32): u64 {
+ let fractional = val.value % (1 << 32);
+ let one = 1 << 32;
+ if (fractional == 0) {
+ val.value >> 32
+ } else {
+ (val.value - fractional + one) >> 32
+ }
+}
+
+
+
+
+
+
+### Function `round`
+
+
+public fun round(num: fixed_point32::FixedPoint32): u64
+
+
+
+
+
+pragma verify_duration_estimate = 120;
+pragma opaque;
+aborts_if false;
+ensures result == spec_round(num);
+
+
+
+
+
+
+
+
+fun spec_round(val: FixedPoint32): u64 {
+ let fractional = val.value % (1 << 32);
+ let boundary = (1 << 32) / 2;
+ let one = 1 << 32;
+ if (fractional < boundary) {
+ (val.value - fractional) >> 32
+ } else {
+ (val.value - fractional + one) >> 32
+ }
+}
+
+
+
+[move-book]: https://aptos.dev/move/book/SUMMARY
diff --git a/aptos-move/framework/move-stdlib/tests/compiler-v2-doc/hash.md b/aptos-move/framework/move-stdlib/tests/compiler-v2-doc/hash.md
new file mode 100644
index 0000000000000..71b2950158b0c
--- /dev/null
+++ b/aptos-move/framework/move-stdlib/tests/compiler-v2-doc/hash.md
@@ -0,0 +1,65 @@
+
+
+
+# Module `0x1::hash`
+
+Module which defines SHA hashes for byte vectors.
+
+The functions in this module are natively declared both in the Move runtime
+as in the Move prover's prelude.
+
+
+- [Function `sha2_256`](#0x1_hash_sha2_256)
+- [Function `sha3_256`](#0x1_hash_sha3_256)
+
+
+
+
+
+
+
+
+## Function `sha2_256`
+
+
+
+public fun sha2_256(data: vector<u8>): vector<u8>
+
+
+
+
+native public fun sha2_256(data: vector<u8>): vector<u8>;
+
+
+
+
+public fun sha3_256(data: vector<u8>): vector<u8>
+
+
+
+
+native public fun sha3_256(data: vector<u8>): vector<u8>;
+
+
+
+
+use 0x1::vector;
+
+
+
+
+
+
+## Struct `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.
+
+
+struct Option<Element> has copy, drop, store
+
+
+
+
+vec: vector<Element>
+Option
is in an invalid state for the operation attempted.
+The Option
is Some
while it should be None
.
+
+
+const EOPTION_IS_SET: u64 = 262144;
+
+
+
+
+
+
+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 = 262145;
+
+
+
+
+
+
+Cannot construct an option from a vector with 2 or more elements.
+
+
+const EOPTION_VEC_TOO_LONG: u64 = 262146;
+
+
+
+
+
+
+## Function `none`
+
+Return an empty Option
+
+
+public fun none<Element>(): option::Option<Element>
+
+
+
+
+public fun none<Element>(): Option<Element> {
+ Option { vec: vector::empty() }
+}
+
+
+
+
+Option
containing e
+
+
+public fun some<Element>(e: Element): option::Option<Element>
+
+
+
+
+public fun some<Element>(e: Element): Option<Element> {
+ Option { vec: vector::singleton(e) }
+}
+
+
+
+
+public fun from_vec<Element>(vec: vector<Element>): option::Option<Element>
+
+
+
+
+public fun from_vec<Element>(vec: vector<Element>): Option<Element> {
+ assert!(vector::length(&vec) <= 1, EOPTION_VEC_TOO_LONG);
+ Option { vec }
+}
+
+
+
+
+t
does not hold a value
+
+
+public fun is_none<Element>(t: &option::Option<Element>): bool
+
+
+
+
+public fun is_none<Element>(t: &Option<Element>): bool {
+ vector::is_empty(&t.vec)
+}
+
+
+
+
+t
holds a value
+
+
+public fun is_some<Element>(t: &option::Option<Element>): bool
+
+
+
+
+public fun is_some<Element>(t: &Option<Element>): bool {
+ !vector::is_empty(&t.vec)
+}
+
+
+
+
+t
is equal to e_ref
+Always returns false
if t
does not hold a value
+
+
+public fun contains<Element>(t: &option::Option<Element>, e_ref: &Element): bool
+
+
+
+
+public fun contains<Element>(t: &Option<Element>, e_ref: &Element): bool {
+ vector::contains(&t.vec, e_ref)
+}
+
+
+
+
+t
+Aborts if t
does not hold a value
+
+
+public fun borrow<Element>(t: &option::Option<Element>): &Element
+
+
+
+
+public fun borrow<Element>(t: &Option<Element>): &Element {
+ assert!(is_some(t), EOPTION_NOT_SET);
+ vector::borrow(&t.vec, 0)
+}
+
+
+
+
+t
if it holds one
+Return default_ref
if t
does not hold a value
+
+
+public fun borrow_with_default<Element>(t: &option::Option<Element>, default_ref: &Element): &Element
+
+
+
+
+public fun borrow_with_default<Element>(t: &Option<Element>, default_ref: &Element): &Element {
+ let vec_ref = &t.vec;
+ if (vector::is_empty(vec_ref)) default_ref
+ else vector::borrow(vec_ref, 0)
+}
+
+
+
+
+t
if it holds one
+Return default
if t
does not hold a value
+
+
+public fun get_with_default<Element: copy, drop>(t: &option::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 (vector::is_empty(vec_ref)) default
+ else *vector::borrow(vec_ref, 0)
+}
+
+
+
+
+t
to a some option by adding e
.
+Aborts if t
already holds a value
+
+
+public fun fill<Element>(t: &mut option::Option<Element>, e: Element)
+
+
+
+
+public fun fill<Element>(t: &mut Option<Element>, e: Element) {
+ let vec_ref = &mut t.vec;
+ if (vector::is_empty(vec_ref)) vector::push_back(vec_ref, e)
+ else abort EOPTION_IS_SET
+}
+
+
+
+
+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<Element>(t: &mut option::Option<Element>): Element
+
+
+
+
+public fun extract<Element>(t: &mut Option<Element>): Element {
+ assert!(is_some(t), EOPTION_NOT_SET);
+ vector::pop_back(&mut t.vec)
+}
+
+
+
+
+t
+Aborts if t
does not hold a value
+
+
+public fun borrow_mut<Element>(t: &mut option::Option<Element>): &mut Element
+
+
+
+
+public fun borrow_mut<Element>(t: &mut Option<Element>): &mut Element {
+ assert!(is_some(t), EOPTION_NOT_SET);
+ vector::borrow_mut(&mut t.vec, 0)
+}
+
+
+
+
+t
with e
and return the old value
+Aborts if t
does not hold a value
+
+
+public fun swap<Element>(t: &mut option::Option<Element>, e: Element): Element
+
+
+
+
+public fun swap<Element>(t: &mut Option<Element>, e: Element): Element {
+ assert!(is_some(t), EOPTION_NOT_SET);
+ let vec_ref = &mut t.vec;
+ let old_value = vector::pop_back(vec_ref);
+ vector::push_back(vec_ref, e);
+ old_value
+}
+
+
+
+
+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<Element>(t: &mut option::Option<Element>, e: Element): option::Option<Element>
+
+
+
+
+public fun swap_or_fill<Element>(t: &mut Option<Element>, e: Element): Option<Element> {
+ let vec_ref = &mut t.vec;
+ let old_value = if (vector::is_empty(vec_ref)) none()
+ else some(vector::pop_back(vec_ref));
+ vector::push_back(vec_ref, e);
+ old_value
+}
+
+
+
+
+t.
If t
holds a value, return it. Returns default
otherwise
+
+
+public fun destroy_with_default<Element: drop>(t: option::Option<Element>, default: Element): Element
+
+
+
+
+public fun destroy_with_default<Element: drop>(t: Option<Element>, default: Element): Element {
+ let Option { vec } = t;
+ if (vector::is_empty(&mut vec)) default
+ else vector::pop_back(&mut vec)
+}
+
+
+
+
+t
and return its contents
+Aborts if t
does not hold a value
+
+
+public fun destroy_some<Element>(t: option::Option<Element>): Element
+
+
+
+
+public fun destroy_some<Element>(t: Option<Element>): Element {
+ assert!(is_some(&t), EOPTION_NOT_SET);
+ let Option { vec } = t;
+ let elem = vector::pop_back(&mut vec);
+ vector::destroy_empty(vec);
+ elem
+}
+
+
+
+
+t
+Aborts if t
holds a value
+
+
+public fun destroy_none<Element>(t: option::Option<Element>)
+
+
+
+
+public fun destroy_none<Element>(t: Option<Element>) {
+ assert!(is_none(&t), EOPTION_IS_SET);
+ let Option { vec } = t;
+ vector::destroy_empty(vec)
+}
+
+
+
+
+t
into a vector of length 1 if it is Some
,
+and an empty vector otherwise
+
+
+public fun to_vec<Element>(t: option::Option<Element>): vector<Element>
+
+
+
+
+public fun to_vec<Element>(t: Option<Element>): vector<Element> {
+ let Option { vec } = t;
+ vec
+}
+
+
+
+
+public fun for_each<Element>(o: option::Option<Element>, f: |Element|)
+
+
+
+
+public inline fun for_each<Element>(o: Option<Element>, f: |Element|) {
+ if (is_some(&o)) {
+ f(destroy_some(o))
+ } else {
+ destroy_none(o)
+ }
+}
+
+
+
+
+public fun for_each_ref<Element>(o: &option::Option<Element>, f: |&Element|)
+
+
+
+
+public inline fun for_each_ref<Element>(o: &Option<Element>, f: |&Element|) {
+ if (is_some(o)) {
+ f(borrow(o))
+ }
+}
+
+
+
+
+public fun for_each_mut<Element>(o: &mut option::Option<Element>, f: |&mut Element|)
+
+
+
+
+public inline fun for_each_mut<Element>(o: &mut Option<Element>, f: |&mut Element|) {
+ if (is_some(o)) {
+ f(borrow_mut(o))
+ }
+}
+
+
+
+
+public fun fold<Accumulator, Element>(o: option::Option<Element>, init: Accumulator, f: |(Accumulator, Element)|Accumulator): Accumulator
+
+
+
+
+public inline fun fold<Accumulator, Element>(
+ o: Option<Element>,
+ init: Accumulator,
+ f: |Accumulator,Element|Accumulator
+): Accumulator {
+ if (is_some(&o)) {
+ f(init, destroy_some(o))
+ } else {
+ destroy_none(o);
+ init
+ }
+}
+
+
+
+
+public fun map<Element, OtherElement>(o: option::Option<Element>, f: |Element|OtherElement): option::Option<OtherElement>
+
+
+
+
+public inline fun map<Element, OtherElement>(o: Option<Element>, f: |Element|OtherElement): Option<OtherElement> {
+ if (is_some(&o)) {
+ some(f(destroy_some(o)))
+ } else {
+ destroy_none(o);
+ none()
+ }
+}
+
+
+
+
+public fun map_ref<Element, OtherElement>(o: &option::Option<Element>, f: |&Element|OtherElement): option::Option<OtherElement>
+
+
+
+
+public inline fun map_ref<Element, OtherElement>(
+ o: &Option<Element>, f: |&Element|OtherElement): Option<OtherElement> {
+ if (is_some(o)) {
+ some(f(borrow(o)))
+ } else {
+ none()
+ }
+}
+
+
+
+
+public fun filter<Element: drop>(o: option::Option<Element>, f: |&Element|bool): option::Option<Element>
+
+
+
+
+public inline fun filter<Element:drop>(o: Option<Element>, f: |&Element|bool): Option<Element> {
+ if (is_some(&o) && f(borrow(&o))) {
+ o
+ } else {
+ none()
+ }
+}
+
+
+
+
+public fun any<Element>(o: &option::Option<Element>, p: |&Element|bool): bool
+
+
+
+
+public inline fun any<Element>(o: &Option<Element>, p: |&Element|bool): bool {
+ is_some(o) && p(borrow(o))
+}
+
+
+
+
+public fun destroy<Element>(o: option::Option<Element>, d: |Element|)
+
+
+
+
+public inline fun destroy<Element>(o: Option<Element>, d: |Element|) {
+ let vec = to_vec(o);
+ vector::destroy(vec, |e| d(e));
+}
+
+
+
+
+pragma aborts_if_is_strict;
+
+
+
+
+
+
+### Helper Schema
+
+
+
+
+
+
+schema AbortsIfNone<Element> {
+ t: Option<Element>;
+ aborts_if spec_is_none(t) with EOPTION_NOT_SET;
+}
+
+
+
+
+
+
+### Struct `Option`
+
+
+struct Option<Element> has copy, drop, store
+
+
+
+
+vec: vector<Element>
+invariant len(vec) <= 1;
+
+
+
+
+
+
+### Function `none`
+
+
+public fun none<Element>(): option::Option<Element>
+
+
+
+
+
+pragma opaque;
+aborts_if false;
+ensures result == spec_none<Element>();
+
+
+
+
+
+
+
+
+fun spec_none<Element>(): Option<Element> {
+ Option{ vec: vec() }
+}
+
+
+
+
+
+
+### Function `some`
+
+
+public fun some<Element>(e: Element): option::Option<Element>
+
+
+
+
+
+pragma opaque;
+aborts_if false;
+ensures result == spec_some(e);
+
+
+
+
+
+
+
+
+fun spec_some<Element>(e: Element): Option<Element> {
+ Option{ vec: vec(e) }
+}
+
+
+
+
+
+
+### Function `from_vec`
+
+
+public fun from_vec<Element>(vec: vector<Element>): option::Option<Element>
+
+
+
+
+
+aborts_if vector::length(vec) > 1;
+
+
+
+
+
+
+### Function `is_none`
+
+
+public fun is_none<Element>(t: &option::Option<Element>): bool
+
+
+
+
+
+pragma opaque;
+aborts_if false;
+ensures result == spec_is_none(t);
+
+
+
+
+
+
+
+
+fun spec_is_none<Element>(t: Option<Element>): bool {
+ vector::is_empty(t.vec)
+}
+
+
+
+
+
+
+### Function `is_some`
+
+
+public fun is_some<Element>(t: &option::Option<Element>): bool
+
+
+
+
+
+pragma opaque;
+aborts_if false;
+ensures result == spec_is_some(t);
+
+
+
+
+
+
+
+
+fun spec_is_some<Element>(t: Option<Element>): bool {
+ !vector::is_empty(t.vec)
+}
+
+
+
+
+
+
+### Function `contains`
+
+
+public fun contains<Element>(t: &option::Option<Element>, e_ref: &Element): bool
+
+
+
+
+
+pragma opaque;
+aborts_if false;
+ensures result == spec_contains(t, e_ref);
+
+
+
+
+
+
+
+
+fun spec_contains<Element>(t: Option<Element>, e: Element): bool {
+ is_some(t) && borrow(t) == e
+}
+
+
+
+
+
+
+### Function `borrow`
+
+
+public fun borrow<Element>(t: &option::Option<Element>): &Element
+
+
+
+
+
+pragma opaque;
+include AbortsIfNone<Element>;
+ensures result == spec_borrow(t);
+
+
+
+
+
+
+
+
+fun spec_borrow<Element>(t: Option<Element>): Element {
+ t.vec[0]
+}
+
+
+
+
+
+
+### Function `borrow_with_default`
+
+
+public fun borrow_with_default<Element>(t: &option::Option<Element>, default_ref: &Element): &Element
+
+
+
+
+
+pragma opaque;
+aborts_if false;
+ensures result == (if (spec_is_some(t)) spec_borrow(t) else default_ref);
+
+
+
+
+
+
+### Function `get_with_default`
+
+
+public fun get_with_default<Element: copy, drop>(t: &option::Option<Element>, default: Element): Element
+
+
+
+
+
+pragma opaque;
+aborts_if false;
+ensures result == (if (spec_is_some(t)) spec_borrow(t) else default);
+
+
+
+
+
+
+### Function `fill`
+
+
+public fun fill<Element>(t: &mut option::Option<Element>, e: Element)
+
+
+
+
+
+pragma opaque;
+aborts_if spec_is_some(t) with EOPTION_IS_SET;
+ensures spec_is_some(t);
+ensures spec_borrow(t) == e;
+
+
+
+
+
+
+### Function `extract`
+
+
+public fun extract<Element>(t: &mut option::Option<Element>): Element
+
+
+
+
+
+pragma opaque;
+include AbortsIfNone<Element>;
+ensures result == spec_borrow(old(t));
+ensures spec_is_none(t);
+
+
+
+
+
+
+### Function `borrow_mut`
+
+
+public fun borrow_mut<Element>(t: &mut option::Option<Element>): &mut Element
+
+
+
+
+
+include AbortsIfNone<Element>;
+ensures result == spec_borrow(t);
+ensures t == old(t);
+
+
+
+
+
+
+### Function `swap`
+
+
+public fun swap<Element>(t: &mut option::Option<Element>, e: Element): Element
+
+
+
+
+
+pragma opaque;
+include AbortsIfNone<Element>;
+ensures result == spec_borrow(old(t));
+ensures spec_is_some(t);
+ensures spec_borrow(t) == e;
+
+
+
+
+
+
+### Function `swap_or_fill`
+
+
+public fun swap_or_fill<Element>(t: &mut option::Option<Element>, e: Element): option::Option<Element>
+
+
+
+
+
+pragma opaque;
+aborts_if false;
+ensures result == old(t);
+ensures spec_borrow(t) == e;
+
+
+
+
+
+
+### Function `destroy_with_default`
+
+
+public fun destroy_with_default<Element: drop>(t: option::Option<Element>, default: Element): Element
+
+
+
+
+
+pragma opaque;
+aborts_if false;
+ensures result == (if (spec_is_some(t)) spec_borrow(t) else default);
+
+
+
+
+
+
+### Function `destroy_some`
+
+
+public fun destroy_some<Element>(t: option::Option<Element>): Element
+
+
+
+
+
+pragma opaque;
+include AbortsIfNone<Element>;
+ensures result == spec_borrow(t);
+
+
+
+
+
+
+### Function `destroy_none`
+
+
+public fun destroy_none<Element>(t: option::Option<Element>)
+
+
+
+
+
+pragma opaque;
+aborts_if spec_is_some(t) with EOPTION_IS_SET;
+
+
+
+
+
+
+### Function `to_vec`
+
+
+public fun to_vec<Element>(t: option::Option<Element>): vector<Element>
+
+
+
+
+
+pragma opaque;
+aborts_if false;
+ensures result == t.vec;
+
+
+
+[move-book]: https://aptos.dev/move/book/SUMMARY
diff --git a/aptos-move/framework/move-stdlib/tests/compiler-v2-doc/overview.md b/aptos-move/framework/move-stdlib/tests/compiler-v2-doc/overview.md
new file mode 100644
index 0000000000000..8eb0c67f05113
--- /dev/null
+++ b/aptos-move/framework/move-stdlib/tests/compiler-v2-doc/overview.md
@@ -0,0 +1,29 @@
+
+
+
+# Move Stdlib Modules
+
+
+This is the reference documentation of the Move standard library.
+For on overview of the Move language, see the [Move Book][move-book].
+
+
+
+
+## Index
+
+
+- [`0x1::acl`](acl.md#0x1_acl)
+- [`0x1::bcs`](bcs.md#0x1_bcs)
+- [`0x1::bit_vector`](bit_vector.md#0x1_bit_vector)
+- [`0x1::error`](error.md#0x1_error)
+- [`0x1::features`](features.md#0x1_features)
+- [`0x1::fixed_point32`](fixed_point32.md#0x1_fixed_point32)
+- [`0x1::hash`](hash.md#0x1_hash)
+- [`0x1::option`](option.md#0x1_option)
+- [`0x1::signer`](signer.md#0x1_signer)
+- [`0x1::string`](string.md#0x1_string)
+- [`0x1::vector`](vector.md#0x1_vector)
+
+
+[move-book]: https://aptos.dev/move/book/SUMMARY
diff --git a/aptos-move/framework/move-stdlib/tests/compiler-v2-doc/signer.md b/aptos-move/framework/move-stdlib/tests/compiler-v2-doc/signer.md
new file mode 100644
index 0000000000000..f6de8799b571c
--- /dev/null
+++ b/aptos-move/framework/move-stdlib/tests/compiler-v2-doc/signer.md
@@ -0,0 +1,94 @@
+
+
+
+# Module `0x1::signer`
+
+
+
+- [Function `borrow_address`](#0x1_signer_borrow_address)
+- [Function `address_of`](#0x1_signer_address_of)
+- [Specification](#@Specification_0)
+
+
+
+
+
+
+
+
+## Function `borrow_address`
+
+Borrows the address of the signer
+Conceptually, you can think of the signer
as being a struct wrapper around an
+address
+```
+struct signer has drop { addr: address }
+```
+borrow_address
borrows this inner field
+
+
+public fun borrow_address(s: &signer): &address
+
+
+
+
+native public fun borrow_address(s: &signer): &address;
+
+
+
+
+public fun address_of(s: &signer): address
+
+
+
+
+public fun address_of(s: &signer): address {
+ *borrow_address(s)
+}
+
+
+
+
+s
is a transaction signer. This is a spec function only available in spec.
+
+
+
+
+
+native fun is_txn_signer(s: signer): bool;
+
+
+
+Return true only if a
is a transaction signer address. This is a spec function only available in spec.
+
+
+
+
+
+native fun is_txn_signer_addr(a: address): bool;
+
+
+
+[move-book]: https://aptos.dev/move/book/SUMMARY
diff --git a/aptos-move/framework/move-stdlib/tests/compiler-v2-doc/string.md b/aptos-move/framework/move-stdlib/tests/compiler-v2-doc/string.md
new file mode 100644
index 0000000000000..b45c55afe9902
--- /dev/null
+++ b/aptos-move/framework/move-stdlib/tests/compiler-v2-doc/string.md
@@ -0,0 +1,552 @@
+
+
+
+# Module `0x1::string`
+
+The string
module defines the String
type which represents UTF8 encoded strings.
+
+
+- [Struct `String`](#0x1_string_String)
+- [Constants](#@Constants_0)
+- [Function `utf8`](#0x1_string_utf8)
+- [Function `try_utf8`](#0x1_string_try_utf8)
+- [Function `bytes`](#0x1_string_bytes)
+- [Function `is_empty`](#0x1_string_is_empty)
+- [Function `length`](#0x1_string_length)
+- [Function `append`](#0x1_string_append)
+- [Function `append_utf8`](#0x1_string_append_utf8)
+- [Function `insert`](#0x1_string_insert)
+- [Function `sub_string`](#0x1_string_sub_string)
+- [Function `index_of`](#0x1_string_index_of)
+- [Function `internal_check_utf8`](#0x1_string_internal_check_utf8)
+- [Function `internal_is_char_boundary`](#0x1_string_internal_is_char_boundary)
+- [Function `internal_sub_string`](#0x1_string_internal_sub_string)
+- [Function `internal_index_of`](#0x1_string_internal_index_of)
+- [Specification](#@Specification_1)
+ - [Function `internal_check_utf8`](#@Specification_1_internal_check_utf8)
+ - [Function `internal_is_char_boundary`](#@Specification_1_internal_is_char_boundary)
+ - [Function `internal_sub_string`](#@Specification_1_internal_sub_string)
+ - [Function `internal_index_of`](#@Specification_1_internal_index_of)
+
+
+use 0x1::option;
+use 0x1::vector;
+
+
+
+
+
+
+## Struct `String`
+
+A String
holds a sequence of bytes which is guaranteed to be in utf8 format.
+
+
+struct String has copy, drop, store
+
+
+
+
+bytes: vector<u8>
+const EINVALID_INDEX: u64 = 2;
+
+
+
+
+
+
+An invalid UTF8 encoding.
+
+
+const EINVALID_UTF8: u64 = 1;
+
+
+
+
+
+
+## Function `utf8`
+
+Creates a new string from a sequence of bytes. Aborts if the bytes do not represent valid utf8.
+
+
+public fun utf8(bytes: vector<u8>): string::String
+
+
+
+
+public fun utf8(bytes: vector<u8>): String {
+ assert!(internal_check_utf8(&bytes), EINVALID_UTF8);
+ String{bytes}
+}
+
+
+
+
+public fun try_utf8(bytes: vector<u8>): option::Option<string::String>
+
+
+
+
+public fun try_utf8(bytes: vector<u8>): Option<String> {
+ if (internal_check_utf8(&bytes)) {
+ option::some(String{bytes})
+ } else {
+ option::none()
+ }
+}
+
+
+
+
+public fun bytes(s: &string::String): &vector<u8>
+
+
+
+
+public fun bytes(s: &String): &vector<u8> {
+ &s.bytes
+}
+
+
+
+
+public fun is_empty(s: &string::String): bool
+
+
+
+
+public fun is_empty(s: &String): bool {
+ vector::is_empty(&s.bytes)
+}
+
+
+
+
+public fun length(s: &string::String): u64
+
+
+
+
+public fun length(s: &String): u64 {
+ vector::length(&s.bytes)
+}
+
+
+
+
+public fun append(s: &mut string::String, r: string::String)
+
+
+
+
+public fun append(s: &mut String, r: String) {
+ vector::append(&mut s.bytes, r.bytes)
+}
+
+
+
+
+public fun append_utf8(s: &mut string::String, bytes: vector<u8>)
+
+
+
+
+public fun append_utf8(s: &mut String, bytes: vector<u8>) {
+ append(s, utf8(bytes))
+}
+
+
+
+
+public fun insert(s: &mut string::String, at: u64, o: string::String)
+
+
+
+
+public fun insert(s: &mut String, at: u64, o: String) {
+ let bytes = &s.bytes;
+ assert!(at <= vector::length(bytes) && internal_is_char_boundary(bytes, at), EINVALID_INDEX);
+ let l = length(s);
+ let front = sub_string(s, 0, at);
+ let end = sub_string(s, at, l);
+ append(&mut front, o);
+ append(&mut front, end);
+ *s = front;
+}
+
+
+
+
+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 sub_string(s: &string::String, i: u64, j: u64): string::String
+
+
+
+
+public fun sub_string(s: &String, i: u64, j: u64): String {
+ let bytes = &s.bytes;
+ let l = vector::length(bytes);
+ assert!(
+ j <= l && i <= j && internal_is_char_boundary(bytes, i) && internal_is_char_boundary(bytes, j),
+ EINVALID_INDEX
+ );
+ String { bytes: internal_sub_string(bytes, i, j) }
+}
+
+
+
+
+length(s)
if no occurrence found.
+
+
+public fun index_of(s: &string::String, r: &string::String): u64
+
+
+
+
+public fun index_of(s: &String, r: &String): u64 {
+ internal_index_of(&s.bytes, &r.bytes)
+}
+
+
+
+
+public fun internal_check_utf8(v: &vector<u8>): bool
+
+
+
+
+public native fun internal_check_utf8(v: &vector<u8>): bool;
+
+
+
+
+fun internal_is_char_boundary(v: &vector<u8>, i: u64): bool
+
+
+
+
+native fun internal_is_char_boundary(v: &vector<u8>, i: u64): bool;
+
+
+
+
+fun internal_sub_string(v: &vector<u8>, i: u64, j: u64): vector<u8>
+
+
+
+
+native fun internal_sub_string(v: &vector<u8>, i: u64, j: u64): vector<u8>;
+
+
+
+
+fun internal_index_of(v: &vector<u8>, r: &vector<u8>): u64
+
+
+
+
+native fun internal_index_of(v: &vector<u8>, r: &vector<u8>): u64;
+
+
+
+
+public fun internal_check_utf8(v: &vector<u8>): bool
+
+
+
+
+
+pragma opaque;
+aborts_if [abstract] false;
+ensures [abstract] result == spec_internal_check_utf8(v);
+
+
+
+
+
+
+### Function `internal_is_char_boundary`
+
+
+fun internal_is_char_boundary(v: &vector<u8>, i: u64): bool
+
+
+
+
+
+pragma opaque;
+aborts_if [abstract] false;
+ensures [abstract] result == spec_internal_is_char_boundary(v, i);
+
+
+
+
+
+
+### Function `internal_sub_string`
+
+
+fun internal_sub_string(v: &vector<u8>, i: u64, j: u64): vector<u8>
+
+
+
+
+
+pragma opaque;
+aborts_if [abstract] false;
+ensures [abstract] result == spec_internal_sub_string(v, i, j);
+
+
+
+
+
+
+### Function `internal_index_of`
+
+
+fun internal_index_of(v: &vector<u8>, r: &vector<u8>): u64
+
+
+
+
+
+pragma opaque;
+aborts_if [abstract] false;
+ensures [abstract] result == spec_internal_index_of(v, r);
+
+
+
+
+
+
+
+
+fun spec_utf8(bytes: vector<u8>): String {
+ String{bytes}
+}
+
+
+
+
+
+
+
+
+fun spec_internal_check_utf8(v: vector<u8>): bool;
+
+fun spec_internal_is_char_boundary(v: vector<u8>, i: u64): bool;
+
+fun spec_internal_sub_string(v: vector<u8>, i: u64, j: u64): vector<u8>;
+
+fun spec_internal_index_of(v: vector<u8>, r: vector<u8>): u64;
+
+
+
+[move-book]: https://aptos.dev/move/book/SUMMARY
diff --git a/aptos-move/framework/move-stdlib/tests/compiler-v2-doc/vector.md b/aptos-move/framework/move-stdlib/tests/compiler-v2-doc/vector.md
new file mode 100644
index 0000000000000..eee7cac04edc8
--- /dev/null
+++ b/aptos-move/framework/move-stdlib/tests/compiler-v2-doc/vector.md
@@ -0,0 +1,2033 @@
+
+
+
+# Module `0x1::vector`
+
+A variable-sized container that can hold any type. Indexing is 0-based, and
+vectors are growable. This module has many native functions.
+Verification of modules that use this one uses model functions that are implemented
+directly in Boogie. The specification language has built-in functions operations such
+as singleton_vector
. There are some helper functions defined here for specifications in other
+modules as well.
+
+>Note: We did not verify most of the
+Move functions here because many have loops, requiring loop invariants to prove, and
+the return on investment didn't seem worth it for these simple functions.
+
+
+- [Constants](#@Constants_0)
+- [Function `empty`](#0x1_vector_empty)
+- [Function `length`](#0x1_vector_length)
+- [Function `borrow`](#0x1_vector_borrow)
+- [Function `push_back`](#0x1_vector_push_back)
+- [Function `borrow_mut`](#0x1_vector_borrow_mut)
+- [Function `pop_back`](#0x1_vector_pop_back)
+- [Function `destroy_empty`](#0x1_vector_destroy_empty)
+- [Function `swap`](#0x1_vector_swap)
+- [Function `singleton`](#0x1_vector_singleton)
+- [Function `reverse`](#0x1_vector_reverse)
+- [Function `reverse_slice`](#0x1_vector_reverse_slice)
+- [Function `append`](#0x1_vector_append)
+- [Function `reverse_append`](#0x1_vector_reverse_append)
+- [Function `trim`](#0x1_vector_trim)
+- [Function `trim_reverse`](#0x1_vector_trim_reverse)
+- [Function `is_empty`](#0x1_vector_is_empty)
+- [Function `contains`](#0x1_vector_contains)
+- [Function `index_of`](#0x1_vector_index_of)
+- [Function `find`](#0x1_vector_find)
+- [Function `insert`](#0x1_vector_insert)
+- [Function `remove`](#0x1_vector_remove)
+- [Function `remove_value`](#0x1_vector_remove_value)
+- [Function `swap_remove`](#0x1_vector_swap_remove)
+- [Function `for_each`](#0x1_vector_for_each)
+- [Function `for_each_reverse`](#0x1_vector_for_each_reverse)
+- [Function `for_each_ref`](#0x1_vector_for_each_ref)
+- [Function `zip`](#0x1_vector_zip)
+- [Function `zip_reverse`](#0x1_vector_zip_reverse)
+- [Function `zip_ref`](#0x1_vector_zip_ref)
+- [Function `enumerate_ref`](#0x1_vector_enumerate_ref)
+- [Function `for_each_mut`](#0x1_vector_for_each_mut)
+- [Function `zip_mut`](#0x1_vector_zip_mut)
+- [Function `enumerate_mut`](#0x1_vector_enumerate_mut)
+- [Function `fold`](#0x1_vector_fold)
+- [Function `foldr`](#0x1_vector_foldr)
+- [Function `map_ref`](#0x1_vector_map_ref)
+- [Function `zip_map_ref`](#0x1_vector_zip_map_ref)
+- [Function `map`](#0x1_vector_map)
+- [Function `zip_map`](#0x1_vector_zip_map)
+- [Function `filter`](#0x1_vector_filter)
+- [Function `partition`](#0x1_vector_partition)
+- [Function `rotate`](#0x1_vector_rotate)
+- [Function `rotate_slice`](#0x1_vector_rotate_slice)
+- [Function `stable_partition`](#0x1_vector_stable_partition)
+- [Function `any`](#0x1_vector_any)
+- [Function `all`](#0x1_vector_all)
+- [Function `destroy`](#0x1_vector_destroy)
+- [Function `range`](#0x1_vector_range)
+- [Function `range_with_step`](#0x1_vector_range_with_step)
+- [Function `slice`](#0x1_vector_slice)
+- [Specification](#@Specification_1)
+ - [Helper Functions](#@Helper_Functions_2)
+ - [Function `singleton`](#@Specification_1_singleton)
+ - [Function `reverse`](#@Specification_1_reverse)
+ - [Function `reverse_slice`](#@Specification_1_reverse_slice)
+ - [Function `append`](#@Specification_1_append)
+ - [Function `reverse_append`](#@Specification_1_reverse_append)
+ - [Function `trim`](#@Specification_1_trim)
+ - [Function `trim_reverse`](#@Specification_1_trim_reverse)
+ - [Function `is_empty`](#@Specification_1_is_empty)
+ - [Function `contains`](#@Specification_1_contains)
+ - [Function `index_of`](#@Specification_1_index_of)
+ - [Function `insert`](#@Specification_1_insert)
+ - [Function `remove`](#@Specification_1_remove)
+ - [Function `remove_value`](#@Specification_1_remove_value)
+ - [Function `swap_remove`](#@Specification_1_swap_remove)
+ - [Function `rotate`](#@Specification_1_rotate)
+ - [Function `rotate_slice`](#@Specification_1_rotate_slice)
+
+
+
+
+
+
+
+
+## Constants
+
+
+
+
+The index into the vector is out of bounds
+
+
+const EINDEX_OUT_OF_BOUNDS: u64 = 131072;
+
+
+
+
+
+
+The index into the vector is out of bounds
+
+
+const EINVALID_RANGE: u64 = 131073;
+
+
+
+
+
+
+The range in slice
is invalid.
+
+
+const EINVALID_SLICE_RANGE: u64 = 131076;
+
+
+
+
+
+
+The step provided in range
is invalid, must be greater than zero.
+
+
+const EINVALID_STEP: u64 = 131075;
+
+
+
+
+
+
+The length of the vectors are not equal.
+
+
+const EVECTORS_LENGTH_MISMATCH: u64 = 131074;
+
+
+
+
+
+
+## Function `empty`
+
+Create an empty vector.
+
+
+#[bytecode_instruction]
+public fun empty<Element>(): vector<Element>
+
+
+
+
+native public fun empty<Element>(): vector<Element>;
+
+
+
+
+#[bytecode_instruction]
+public fun length<Element>(v: &vector<Element>): u64
+
+
+
+
+native public fun length<Element>(v: &vector<Element>): u64;
+
+
+
+
+i
th element of the vector v
.
+Aborts if i
is out of bounds.
+
+
+#[bytecode_instruction]
+public fun borrow<Element>(v: &vector<Element>, i: u64): &Element
+
+
+
+
+native public fun borrow<Element>(v: &vector<Element>, i: u64): ∈
+
+
+
+
+e
to the end of the vector v
.
+
+
+#[bytecode_instruction]
+public fun push_back<Element>(v: &mut vector<Element>, e: Element)
+
+
+
+
+native public fun push_back<Element>(v: &mut vector<Element>, e: Element);
+
+
+
+
+i
th element in the vector v
.
+Aborts if i
is out of bounds.
+
+
+#[bytecode_instruction]
+public fun borrow_mut<Element>(v: &mut vector<Element>, i: u64): &mut Element
+
+
+
+
+native public fun borrow_mut<Element>(v: &mut vector<Element>, i: u64): &mut Element;
+
+
+
+
+v
.
+Aborts if v
is empty.
+
+
+#[bytecode_instruction]
+public fun pop_back<Element>(v: &mut vector<Element>): Element
+
+
+
+
+native public fun pop_back<Element>(v: &mut vector<Element>): Element;
+
+
+
+
+v
.
+Aborts if v
is not empty.
+
+
+#[bytecode_instruction]
+public fun destroy_empty<Element>(v: vector<Element>)
+
+
+
+
+native public fun destroy_empty<Element>(v: vector<Element>);
+
+
+
+
+i
th and j
th indices in the vector v
.
+Aborts if i
or j
is out of bounds.
+
+
+#[bytecode_instruction]
+public fun swap<Element>(v: &mut vector<Element>, i: u64, j: u64)
+
+
+
+
+native public fun swap<Element>(v: &mut vector<Element>, i: u64, j: u64);
+
+
+
+
+e
.
+
+
+public fun singleton<Element>(e: Element): vector<Element>
+
+
+
+
+public fun singleton<Element>(e: Element): vector<Element> {
+ let v = empty();
+ push_back(&mut v, e);
+ v
+}
+
+
+
+
+v
in place.
+
+
+public fun reverse<Element>(v: &mut vector<Element>)
+
+
+
+
+public fun reverse<Element>(v: &mut vector<Element>) {
+ let len = length(v);
+ reverse_slice(v, 0, len);
+}
+
+
+
+
+v
in place.
+
+
+public fun reverse_slice<Element>(v: &mut vector<Element>, left: u64, right: u64)
+
+
+
+
+public fun reverse_slice<Element>(v: &mut vector<Element>, left: u64, right: u64) {
+ assert!(left <= right, EINVALID_RANGE);
+ if (left == right) return;
+ right = right - 1;
+ while (left < right) {
+ swap(v, left, right);
+ left = left + 1;
+ right = right - 1;
+ }
+}
+
+
+
+
+other
vector into the lhs
vector.
+
+
+public fun append<Element>(lhs: &mut vector<Element>, other: vector<Element>)
+
+
+
+
+public fun append<Element>(lhs: &mut vector<Element>, other: vector<Element>) {
+ reverse(&mut other);
+ reverse_append(lhs, other);
+}
+
+
+
+
+other
vector into the lhs
vector.
+
+
+public fun reverse_append<Element>(lhs: &mut vector<Element>, other: vector<Element>)
+
+
+
+
+public fun reverse_append<Element>(lhs: &mut vector<Element>, other: vector<Element>) {
+ let len = length(&other);
+ while (len > 0) {
+ push_back(lhs, pop_back(&mut other));
+ len = len - 1;
+ };
+ destroy_empty(other);
+}
+
+
+
+
+public fun trim<Element>(v: &mut vector<Element>, new_len: u64): vector<Element>
+
+
+
+
+public fun trim<Element>(v: &mut vector<Element>, new_len: u64): vector<Element> {
+ let res = trim_reverse(v, new_len);
+ reverse(&mut res);
+ res
+}
+
+
+
+
+public fun trim_reverse<Element>(v: &mut vector<Element>, new_len: u64): vector<Element>
+
+
+
+
+public fun trim_reverse<Element>(v: &mut vector<Element>, new_len: u64): vector<Element> {
+ let len = length(v);
+ assert!(new_len <= len, EINDEX_OUT_OF_BOUNDS);
+ let result = empty();
+ while (new_len < len) {
+ push_back(&mut result, pop_back(v));
+ len = len - 1;
+ };
+ result
+}
+
+
+
+
+true
if the vector v
has no elements and false
otherwise.
+
+
+public fun is_empty<Element>(v: &vector<Element>): bool
+
+
+
+
+public fun is_empty<Element>(v: &vector<Element>): bool {
+ length(v) == 0
+}
+
+
+
+
+e
is in the vector v
.
+
+
+public fun contains<Element>(v: &vector<Element>, e: &Element): bool
+
+
+
+
+public fun contains<Element>(v: &vector<Element>, e: &Element): bool {
+ let i = 0;
+ let len = length(v);
+ while (i < len) {
+ if (borrow(v, i) == e) return true;
+ i = i + 1;
+ };
+ false
+}
+
+
+
+
+(true, i)
if e
is in the vector v
at index i
.
+Otherwise, returns (false, 0)
.
+
+
+public fun index_of<Element>(v: &vector<Element>, e: &Element): (bool, u64)
+
+
+
+
+public fun index_of<Element>(v: &vector<Element>, e: &Element): (bool, u64) {
+ let i = 0;
+ let len = length(v);
+ while (i < len) {
+ if (borrow(v, i) == e) return (true, i);
+ i = i + 1;
+ };
+ (false, 0)
+}
+
+
+
+
+(true, i)
if there's an element that matches the predicate. If there are multiple elements that match
+the predicate, only the index of the first one is returned.
+Otherwise, returns (false, 0)
.
+
+
+public fun find<Element>(v: &vector<Element>, f: |&Element|bool): (bool, u64)
+
+
+
+
+public inline fun find<Element>(v: &vector<Element>, f: |&Element|bool): (bool, u64) {
+ let find = false;
+ let found_index = 0;
+ let i = 0;
+ let len = length(v);
+ while (i < len) {
+ // Cannot call return in an inline function so we need to resort to break here.
+ if (f(borrow(v, i))) {
+ find = true;
+ found_index = i;
+ break
+ };
+ i = i + 1;
+ };
+ (find, found_index)
+}
+
+
+
+
+public fun insert<Element>(v: &mut vector<Element>, i: u64, e: Element)
+
+
+
+
+public fun insert<Element>(v: &mut vector<Element>, i: u64, e: Element) {
+ let len = length(v);
+ assert!(i <= len, EINDEX_OUT_OF_BOUNDS);
+ push_back(v, e);
+ while (i < len) {
+ swap(v, i, len);
+ i = i + 1;
+ };
+}
+
+
+
+
+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<Element>(v: &mut vector<Element>, i: u64): Element
+
+
+
+
+public fun remove<Element>(v: &mut vector<Element>, i: u64): Element {
+ let len = length(v);
+ // i out of bounds; abort
+ if (i >= len) abort EINDEX_OUT_OF_BOUNDS;
+
+ len = len - 1;
+ while (i < len) swap(v, i, { i = i + 1; i });
+ pop_back(v)
+}
+
+
+
+
+v
and return it in a vector, shifting all
+subsequent elements.
+This is O(n) and preserves ordering of elements in the vector.
+This returns an empty vector if the value isn't present in the vector.
+Note that this cannot return an option as option uses vector and there'd be a circular dependency between option
+and vector.
+
+
+public fun remove_value<Element>(v: &mut vector<Element>, val: &Element): vector<Element>
+
+
+
+
+public fun remove_value<Element>(v: &mut vector<Element>, val: &Element): vector<Element> {
+ // This doesn't cost a O(2N) run time as index_of scans from left to right and stops when the element is found,
+ // while remove would continue from the identified index to the end of the vector.
+ let (found, index) = index_of(v, val);
+ if (found) {
+ vector[remove(v, index)]
+ } else {
+ vector[]
+ }
+}
+
+
+
+
+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<Element>(v: &mut vector<Element>, i: u64): Element
+
+
+
+
+public fun swap_remove<Element>(v: &mut vector<Element>, i: u64): Element {
+ assert!(!is_empty(v), EINDEX_OUT_OF_BOUNDS);
+ let last_idx = length(v) - 1;
+ swap(v, i, last_idx);
+ pop_back(v)
+}
+
+
+
+
+public fun for_each<Element>(v: vector<Element>, f: |Element|)
+
+
+
+
+public inline fun for_each<Element>(v: vector<Element>, f: |Element|) {
+ reverse(&mut v); // We need to reverse the vector to consume it efficiently
+ for_each_reverse(v, |e| f(e));
+}
+
+
+
+
+public fun for_each_reverse<Element>(v: vector<Element>, f: |Element|)
+
+
+
+
+public inline fun for_each_reverse<Element>(v: vector<Element>, f: |Element|) {
+ let len = length(&v);
+ while (len > 0) {
+ f(pop_back(&mut v));
+ len = len - 1;
+ };
+ destroy_empty(v)
+}
+
+
+
+
+public fun for_each_ref<Element>(v: &vector<Element>, f: |&Element|)
+
+
+
+
+public inline fun for_each_ref<Element>(v: &vector<Element>, f: |&Element|) {
+ let i = 0;
+ let len = length(v);
+ while (i < len) {
+ f(borrow(v, i));
+ i = i + 1
+ }
+}
+
+
+
+
+public fun zip<Element1, Element2>(v1: vector<Element1>, v2: vector<Element2>, f: |(Element1, Element2)|)
+
+
+
+
+public inline fun zip<Element1, Element2>(v1: vector<Element1>, v2: vector<Element2>, f: |Element1, Element2|) {
+ // We need to reverse the vectors to consume it efficiently
+ reverse(&mut v1);
+ reverse(&mut v2);
+ zip_reverse(v1, v2, |e1, e2| f(e1, e2));
+}
+
+
+
+
+public fun zip_reverse<Element1, Element2>(v1: vector<Element1>, v2: vector<Element2>, f: |(Element1, Element2)|)
+
+
+
+
+public inline fun zip_reverse<Element1, Element2>(
+ v1: vector<Element1>,
+ v2: vector<Element2>,
+ f: |Element1, Element2|,
+) {
+ let len = length(&v1);
+ // We can't use the constant EVECTORS_LENGTH_MISMATCH here as all calling code would then need to define it
+ // due to how inline functions work.
+ assert!(len == length(&v2), 0x20002);
+ while (len > 0) {
+ f(pop_back(&mut v1), pop_back(&mut v2));
+ len = len - 1;
+ };
+ destroy_empty(v1);
+ destroy_empty(v2);
+}
+
+
+
+
+public fun zip_ref<Element1, Element2>(v1: &vector<Element1>, v2: &vector<Element2>, f: |(&Element1, &Element2)|)
+
+
+
+
+public inline fun zip_ref<Element1, Element2>(
+ v1: &vector<Element1>,
+ v2: &vector<Element2>,
+ f: |&Element1, &Element2|,
+) {
+ let len = length(v1);
+ // We can't use the constant EVECTORS_LENGTH_MISMATCH here as all calling code would then need to define it
+ // due to how inline functions work.
+ assert!(len == length(v2), 0x20002);
+ let i = 0;
+ while (i < len) {
+ f(borrow(v1, i), borrow(v2, i));
+ i = i + 1
+ }
+}
+
+
+
+
+public fun enumerate_ref<Element>(v: &vector<Element>, f: |(u64, &Element)|)
+
+
+
+
+public inline fun enumerate_ref<Element>(v: &vector<Element>, f: |u64, &Element|) {
+ let i = 0;
+ let len = length(v);
+ while (i < len) {
+ f(i, borrow(v, i));
+ i = i + 1;
+ };
+}
+
+
+
+
+public fun for_each_mut<Element>(v: &mut vector<Element>, f: |&mut Element|)
+
+
+
+
+public inline fun for_each_mut<Element>(v: &mut vector<Element>, f: |&mut Element|) {
+ let i = 0;
+ let len = length(v);
+ while (i < len) {
+ f(borrow_mut(v, i));
+ i = i + 1
+ }
+}
+
+
+
+
+public fun zip_mut<Element1, Element2>(v1: &mut vector<Element1>, v2: &mut vector<Element2>, f: |(&mut Element1, &mut Element2)|)
+
+
+
+
+public inline fun zip_mut<Element1, Element2>(
+ v1: &mut vector<Element1>,
+ v2: &mut vector<Element2>,
+ f: |&mut Element1, &mut Element2|,
+) {
+ let i = 0;
+ let len = length(v1);
+ // We can't use the constant EVECTORS_LENGTH_MISMATCH here as all calling code would then need to define it
+ // due to how inline functions work.
+ assert!(len == length(v2), 0x20002);
+ while (i < len) {
+ f(borrow_mut(v1, i), borrow_mut(v2, i));
+ i = i + 1
+ }
+}
+
+
+
+
+public fun enumerate_mut<Element>(v: &mut vector<Element>, f: |(u64, &mut Element)|)
+
+
+
+
+public inline fun enumerate_mut<Element>(v: &mut vector<Element>, f: |u64, &mut Element|) {
+ let i = 0;
+ let len = length(v);
+ while (i < len) {
+ f(i, borrow_mut(v, i));
+ i = i + 1;
+ };
+}
+
+
+
+
+fold(vector[1,2,3], 0, f)
will execute
+f(f(f(0, 1), 2), 3)
+
+
+public fun fold<Accumulator, Element>(v: vector<Element>, init: Accumulator, f: |(Accumulator, Element)|Accumulator): Accumulator
+
+
+
+
+public inline fun fold<Accumulator, Element>(
+ v: vector<Element>,
+ init: Accumulator,
+ f: |Accumulator,Element|Accumulator
+): Accumulator {
+ let accu = init;
+ for_each(v, |elem| accu = f(accu, elem));
+ accu
+}
+
+
+
+
+fold(vector[1,2,3], 0, f)
will execute
+f(1, f(2, f(3, 0)))
+
+
+public fun foldr<Accumulator, Element>(v: vector<Element>, init: Accumulator, f: |(Element, Accumulator)|Accumulator): Accumulator
+
+
+
+
+public inline fun foldr<Accumulator, Element>(
+ v: vector<Element>,
+ init: Accumulator,
+ f: |Element, Accumulator|Accumulator
+): Accumulator {
+ let accu = init;
+ for_each_reverse(v, |elem| accu = f(elem, accu));
+ accu
+}
+
+
+
+
+public fun map_ref<Element, NewElement>(v: &vector<Element>, f: |&Element|NewElement): vector<NewElement>
+
+
+
+
+public inline fun map_ref<Element, NewElement>(
+ v: &vector<Element>,
+ f: |&Element|NewElement
+): vector<NewElement> {
+ let result = vector<NewElement>[];
+ for_each_ref(v, |elem| push_back(&mut result, f(elem)));
+ result
+}
+
+
+
+
+public fun zip_map_ref<Element1, Element2, NewElement>(v1: &vector<Element1>, v2: &vector<Element2>, f: |(&Element1, &Element2)|NewElement): vector<NewElement>
+
+
+
+
+public inline fun zip_map_ref<Element1, Element2, NewElement>(
+ v1: &vector<Element1>,
+ v2: &vector<Element2>,
+ f: |&Element1, &Element2|NewElement
+): vector<NewElement> {
+ // We can't use the constant EVECTORS_LENGTH_MISMATCH here as all calling code would then need to define it
+ // due to how inline functions work.
+ assert!(length(v1) == length(v2), 0x20002);
+
+ let result = vector<NewElement>[];
+ zip_ref(v1, v2, |e1, e2| push_back(&mut result, f(e1, e2)));
+ result
+}
+
+
+
+
+public fun map<Element, NewElement>(v: vector<Element>, f: |Element|NewElement): vector<NewElement>
+
+
+
+
+public inline fun map<Element, NewElement>(
+ v: vector<Element>,
+ f: |Element|NewElement
+): vector<NewElement> {
+ let result = vector<NewElement>[];
+ for_each(v, |elem| push_back(&mut result, f(elem)));
+ result
+}
+
+
+
+
+public fun zip_map<Element1, Element2, NewElement>(v1: vector<Element1>, v2: vector<Element2>, f: |(Element1, Element2)|NewElement): vector<NewElement>
+
+
+
+
+public inline fun zip_map<Element1, Element2, NewElement>(
+ v1: vector<Element1>,
+ v2: vector<Element2>,
+ f: |Element1, Element2|NewElement
+): vector<NewElement> {
+ // We can't use the constant EVECTORS_LENGTH_MISMATCH here as all calling code would then need to define it
+ // due to how inline functions work.
+ assert!(length(&v1) == length(&v2), 0x20002);
+
+ let result = vector<NewElement>[];
+ zip(v1, v2, |e1, e2| push_back(&mut result, f(e1, e2)));
+ result
+}
+
+
+
+
+p(e)
is not true.
+
+
+public fun filter<Element: drop>(v: vector<Element>, p: |&Element|bool): vector<Element>
+
+
+
+
+public inline fun filter<Element:drop>(
+ v: vector<Element>,
+ p: |&Element|bool
+): vector<Element> {
+ let result = vector<Element>[];
+ for_each(v, |elem| {
+ if (p(&elem)) push_back(&mut result, elem);
+ });
+ result
+}
+
+
+
+
+public fun partition<Element>(v: &mut vector<Element>, pred: |&Element|bool): u64
+
+
+
+
+public inline fun partition<Element>(
+ v: &mut vector<Element>,
+ pred: |&Element|bool
+): u64 {
+ let i = 0;
+ let len = length(v);
+ while (i < len) {
+ if (!pred(borrow(v, i))) break;
+ i = i + 1;
+ };
+ let p = i;
+ i = i + 1;
+ while (i < len) {
+ if (pred(borrow(v, i))) {
+ swap(v, p, i);
+ p = p + 1;
+ };
+ i = i + 1;
+ };
+ p
+}
+
+
+
+
+public fun rotate<Element>(v: &mut vector<Element>, rot: u64): u64
+
+
+
+
+public fun rotate<Element>(
+ v: &mut vector<Element>,
+ rot: u64
+): u64 {
+ let len = length(v);
+ rotate_slice(v, 0, rot, len)
+}
+
+
+
+
+public fun rotate_slice<Element>(v: &mut vector<Element>, left: u64, rot: u64, right: u64): u64
+
+
+
+
+public fun rotate_slice<Element>(
+ v: &mut vector<Element>,
+ left: u64,
+ rot: u64,
+ right: u64
+): u64 {
+ reverse_slice(v, left, rot);
+ reverse_slice(v, rot, right);
+ reverse_slice(v, left, right);
+ left + (right - rot)
+}
+
+
+
+
+public fun stable_partition<Element>(v: &mut vector<Element>, p: |&Element|bool): u64
+
+
+
+
+public inline fun stable_partition<Element>(
+ v: &mut vector<Element>,
+ p: |&Element|bool
+): u64 {
+ let len = length(v);
+ let t = empty();
+ let f = empty();
+ while (len > 0) {
+ let e = pop_back(v);
+ if (p(&e)) {
+ push_back(&mut t, e);
+ } else {
+ push_back(&mut f, e);
+ };
+ len = len - 1;
+ };
+ let pos = length(&t);
+ reverse_append(v, t);
+ reverse_append(v, f);
+ pos
+}
+
+
+
+
+public fun any<Element>(v: &vector<Element>, p: |&Element|bool): bool
+
+
+
+
+public inline fun any<Element>(
+ v: &vector<Element>,
+ p: |&Element|bool
+): bool {
+ let result = false;
+ let i = 0;
+ while (i < length(v)) {
+ result = p(borrow(v, i));
+ if (result) {
+ break
+ };
+ i = i + 1
+ };
+ result
+}
+
+
+
+
+public fun all<Element>(v: &vector<Element>, p: |&Element|bool): bool
+
+
+
+
+public inline fun all<Element>(
+ v: &vector<Element>,
+ p: |&Element|bool
+): bool {
+ let result = true;
+ let i = 0;
+ while (i < length(v)) {
+ result = p(borrow(v, i));
+ if (!result) {
+ break
+ };
+ i = i + 1
+ };
+ result
+}
+
+
+
+
+public fun destroy<Element>(v: vector<Element>, d: |Element|)
+
+
+
+
+public inline fun destroy<Element>(
+ v: vector<Element>,
+ d: |Element|
+) {
+ for_each_reverse(v, |e| d(e))
+}
+
+
+
+
+public fun range(start: u64, end: u64): vector<u64>
+
+
+
+
+public fun range(start: u64, end: u64): vector<u64> {
+ range_with_step(start, end, 1)
+}
+
+
+
+
+public fun range_with_step(start: u64, end: u64, step: u64): vector<u64>
+
+
+
+
+public fun range_with_step(start: u64, end: u64, step: u64): vector<u64> {
+ assert!(step > 0, EINVALID_STEP);
+
+ let vec = vector[];
+ while (start < end) {
+ push_back(&mut vec, start);
+ start = start + step;
+ };
+ vec
+}
+
+
+
+
+public fun slice<Element: copy>(v: &vector<Element>, start: u64, end: u64): vector<Element>
+
+
+
+
+public fun slice<Element: copy>(
+ v: &vector<Element>,
+ start: u64,
+ end: u64
+): vector<Element> {
+ assert!(start <= end && end <= length(v), EINVALID_SLICE_RANGE);
+
+ let vec = vector[];
+ while (start < end) {
+ push_back(&mut vec, *borrow(v, start));
+ start = start + 1;
+ };
+ vec
+}
+
+
+
+
+v1
is equal to the result of adding e
at the end of v2
+
+
+
+
+
+fun eq_push_back<Element>(v1: vector<Element>, v2: vector<Element>, e: Element): bool {
+ len(v1) == len(v2) + 1 &&
+ v1[len(v1)-1] == e &&
+ v1[0..len(v1)-1] == v2[0..len(v2)]
+}
+
+
+
+Check if v
is equal to the result of concatenating v1
and v2
+
+
+
+
+
+fun eq_append<Element>(v: vector<Element>, v1: vector<Element>, v2: vector<Element>): bool {
+ len(v) == len(v1) + len(v2) &&
+ v[0..len(v1)] == v1 &&
+ v[len(v1)..len(v)] == v2
+}
+
+
+
+Check v1
is equal to the result of removing the first element of v2
+
+
+
+
+
+fun eq_pop_front<Element>(v1: vector<Element>, v2: vector<Element>): bool {
+ len(v1) + 1 == len(v2) &&
+ v1 == v2[1..len(v2)]
+}
+
+
+
+Check that v1
is equal to the result of removing the element at index i
from v2
.
+
+
+
+
+
+fun eq_remove_elem_at_index<Element>(i: u64, v1: vector<Element>, v2: vector<Element>): bool {
+ len(v1) + 1 == len(v2) &&
+ v1[0..i] == v2[0..i] &&
+ v1[i..len(v1)] == v2[i + 1..len(v2)]
+}
+
+
+
+Check if v
contains e
.
+
+
+
+
+
+fun spec_contains<Element>(v: vector<Element>, e: Element): bool {
+ exists x in v: x == e
+}
+
+
+
+
+
+
+### Function `singleton`
+
+
+public fun singleton<Element>(e: Element): vector<Element>
+
+
+
+
+
+aborts_if false;
+ensures result == vec(e);
+
+
+
+
+
+
+### Function `reverse`
+
+
+public fun reverse<Element>(v: &mut vector<Element>)
+
+
+
+
+
+pragma intrinsic = true;
+
+
+
+
+
+
+### Function `reverse_slice`
+
+
+public fun reverse_slice<Element>(v: &mut vector<Element>, left: u64, right: u64)
+
+
+
+
+
+pragma intrinsic = true;
+
+
+
+
+
+
+### Function `append`
+
+
+public fun append<Element>(lhs: &mut vector<Element>, other: vector<Element>)
+
+
+
+
+
+pragma intrinsic = true;
+
+
+
+
+
+
+### Function `reverse_append`
+
+
+public fun reverse_append<Element>(lhs: &mut vector<Element>, other: vector<Element>)
+
+
+
+
+
+pragma intrinsic = true;
+
+
+
+
+
+
+### Function `trim`
+
+
+public fun trim<Element>(v: &mut vector<Element>, new_len: u64): vector<Element>
+
+
+
+
+
+pragma intrinsic = true;
+
+
+
+
+
+
+### Function `trim_reverse`
+
+
+public fun trim_reverse<Element>(v: &mut vector<Element>, new_len: u64): vector<Element>
+
+
+
+
+
+pragma intrinsic = true;
+
+
+
+
+
+
+### Function `is_empty`
+
+
+public fun is_empty<Element>(v: &vector<Element>): bool
+
+
+
+
+
+pragma intrinsic = true;
+
+
+
+
+
+
+### Function `contains`
+
+
+public fun contains<Element>(v: &vector<Element>, e: &Element): bool
+
+
+
+
+
+pragma intrinsic = true;
+
+
+
+
+
+
+### Function `index_of`
+
+
+public fun index_of<Element>(v: &vector<Element>, e: &Element): (bool, u64)
+
+
+
+
+
+pragma intrinsic = true;
+
+
+
+
+
+
+### Function `insert`
+
+
+public fun insert<Element>(v: &mut vector<Element>, i: u64, e: Element)
+
+
+
+
+
+pragma intrinsic = true;
+
+
+
+
+
+
+### Function `remove`
+
+
+public fun remove<Element>(v: &mut vector<Element>, i: u64): Element
+
+
+
+
+
+pragma intrinsic = true;
+
+
+
+
+
+
+### Function `remove_value`
+
+
+public fun remove_value<Element>(v: &mut vector<Element>, val: &Element): vector<Element>
+
+
+
+
+
+pragma intrinsic = true;
+
+
+
+
+
+
+### Function `swap_remove`
+
+
+public fun swap_remove<Element>(v: &mut vector<Element>, i: u64): Element
+
+
+
+
+
+pragma intrinsic = true;
+
+
+
+
+
+
+### Function `rotate`
+
+
+public fun rotate<Element>(v: &mut vector<Element>, rot: u64): u64
+
+
+
+
+
+pragma intrinsic = true;
+
+
+
+
+
+
+### Function `rotate_slice`
+
+
+public fun rotate_slice<Element>(v: &mut vector<Element>, left: u64, rot: u64, right: u64): u64
+
+
+
+
+
+pragma intrinsic = true;
+
+
+
+[move-book]: https://aptos.dev/move/book/SUMMARY
diff --git a/aptos-move/framework/src/built_package.rs b/aptos-move/framework/src/built_package.rs
index 44ef45eca2a74..b9b7c15f91f3c 100644
--- a/aptos-move/framework/src/built_package.rs
+++ b/aptos-move/framework/src/built_package.rs
@@ -2,7 +2,7 @@
// SPDX-License-Identifier: Apache-2.0
use crate::{
- docgen::DocgenOptions,
+ docgen::{get_docgen_output_dir, DocgenOptions},
extended_checks,
natives::code::{ModuleMetadata, MoveOption, PackageDep, PackageMetadata, UpgradePolicy},
zip_metadata, zip_metadata_str, RuntimeModuleMetadataV1, APTOS_METADATA_KEY,
@@ -257,7 +257,7 @@ impl BuiltPackage {
.unwrap()
.parent()
.unwrap()
- .join("doc")
+ .join(get_docgen_output_dir())
.display()
.to_string()
})
diff --git a/aptos-move/framework/src/docgen.rs b/aptos-move/framework/src/docgen.rs
index f083489831e13..7b15f234d37b1 100644
--- a/aptos-move/framework/src/docgen.rs
+++ b/aptos-move/framework/src/docgen.rs
@@ -10,6 +10,11 @@ use move_docgen::OutputFormat;
use move_model::model::GlobalEnv;
use std::{path::PathBuf, sync::Mutex};
+pub fn get_docgen_output_dir() -> String {
+ const MVC_DOCGEN_OUTPUT_DIR: &str = "MVC_DOCGEN_OUTPUT_DIR";
+ std::env::var(MVC_DOCGEN_OUTPUT_DIR).unwrap_or_else(|_| "doc".to_owned())
+}
+
#[derive(Debug, Clone, clap::Parser, serde::Serialize, serde::Deserialize, Default)]
pub struct DocgenOptions {
/// Whether to include private declarations and implementations into the generated
@@ -70,7 +75,7 @@ impl DocgenOptions {
let _lock = MUTEX.lock();
let current_dir = std::env::current_dir()?.canonicalize()?;
std::env::set_current_dir(&package_path)?;
- let output_directory = PathBuf::from("doc");
+ let output_directory = PathBuf::from(get_docgen_output_dir());
let doc_path = doc_path
.into_iter()
.filter_map(|s| {
diff --git a/third_party/move/move-compiler-v2/tests/v1.matched b/third_party/move/move-compiler-v2/tests/v1.matched
index 4d58ac5dee0e0..08c5009b44dbf 100644
--- a/third_party/move/move-compiler-v2/tests/v1.matched
+++ b/third_party/move/move-compiler-v2/tests/v1.matched
@@ -1,6 +1,3 @@
-WARNING: test `move-compiler-v2/tests/verification/cross_module_valid.verification.move` and `move-compiler-v2/tests/verification/verify/cross_module_valid.move` share common key `verification/cross_module_valid.verification.move`, discarding former one
-WARNING: test `move-compiler-v2/tests/verification/double_annotation.verification.move` and `move-compiler-v2/tests/verification/verify/double_annotation.move` share common key `verification/double_annotation.verification.move`, discarding former one
-WARNING: test `move-compiler-v2/tests/verification/single_module_valid.verification.move` and `move-compiler-v2/tests/verification/verify/single_module_valid.move` share common key `verification/single_module_valid.verification.move`, discarding former one
move-compiler/tests/move_check/translated_ir_tests/move/borrow_tests/borrow_global_acquires_1.exp move-compiler-v2/tests/acquires-checker/v1-borrow-tests/borrow_global_acquires_1.exp
move-compiler/tests/move_check/translated_ir_tests/move/borrow_tests/borrow_global_acquires_2.exp move-compiler-v2/tests/acquires-checker/v1-borrow-tests/borrow_global_acquires_2.exp
move-compiler/tests/move_check/translated_ir_tests/move/borrow_tests/borrow_global_acquires_3.exp move-compiler-v2/tests/acquires-checker/v1-borrow-tests/borrow_global_acquires_3.exp
@@ -645,6 +642,6 @@ move-compiler/tests/move_check/verification/cross_module_valid.verification.exp
move-compiler/tests/move_check/verification/double_annotation.exp move-compiler-v2/tests/verification/noverify/double_annotation.exp
move-compiler/tests/move_check/verification/double_annotation.verification.exp move-compiler-v2/tests/verification/verify/double_annotation.exp
move-compiler/tests/move_check/verification/single_module_invalid.exp move-compiler-v2/tests/verification/noverify/single_module_invalid.exp
-move-compiler/tests/move_check/verification/single_module_invalid.verification.exp move-compiler-v2/tests/verification/single_module_invalid.verification.exp
+move-compiler/tests/move_check/verification/single_module_invalid.verification.exp move-compiler-v2/tests/verification/verify/single_module_invalid.exp
move-compiler/tests/move_check/verification/single_module_valid.exp move-compiler-v2/tests/verification/noverify/single_module_valid.exp
move-compiler/tests/move_check/verification/single_module_valid.verification.exp move-compiler-v2/tests/verification/verify/single_module_valid.exp
diff --git a/third_party/move/move-compiler-v2/tests/v1.unmatched b/third_party/move/move-compiler-v2/tests/v1.unmatched
index 8e1885290e94b..3479a58a78b1c 100644
--- a/third_party/move/move-compiler-v2/tests/v1.unmatched
+++ b/third_party/move/move-compiler-v2/tests/v1.unmatched
@@ -1,6 +1,3 @@
-WARNING: test `move-compiler-v2/tests/verification/cross_module_valid.verification.move` and `move-compiler-v2/tests/verification/verify/cross_module_valid.move` share common key `verification/cross_module_valid.verification.move`, discarding former one
-WARNING: test `move-compiler-v2/tests/verification/double_annotation.verification.move` and `move-compiler-v2/tests/verification/verify/double_annotation.move` share common key `verification/double_annotation.verification.move`, discarding former one
-WARNING: test `move-compiler-v2/tests/verification/single_module_valid.verification.move` and `move-compiler-v2/tests/verification/verify/single_module_valid.move` share common key `verification/single_module_valid.verification.move`, discarding former one
move-compiler/tests/move_check/borrow_tests/{
borrow_global_acquires_duplicate_annotation.move,
eq_bad.move,
diff --git a/third_party/move/move-compiler-v2/tests/verification/cross_module_valid.verification b/third_party/move/move-compiler-v2/tests/verification/cross_module_valid.verification
deleted file mode 100644
index e69de29bb2d1d..0000000000000
diff --git a/third_party/move/move-compiler-v2/tests/verification/double_annotation.verification b/third_party/move/move-compiler-v2/tests/verification/double_annotation.verification
deleted file mode 100644
index e69de29bb2d1d..0000000000000
diff --git a/third_party/move/move-compiler-v2/tests/verification/single_module_invalid.verification b/third_party/move/move-compiler-v2/tests/verification/single_module_invalid.verification
deleted file mode 100644
index e69de29bb2d1d..0000000000000
diff --git a/third_party/move/move-compiler-v2/tests/verification/single_module_valid.verification b/third_party/move/move-compiler-v2/tests/verification/single_module_valid.verification
deleted file mode 100644
index e69de29bb2d1d..0000000000000
diff --git a/third_party/move/move-compiler-v2/tests/verification/verify/single_module_invalid.exp b/third_party/move/move-compiler-v2/tests/verification/verify/single_module_invalid.exp
new file mode 100644
index 0000000000000..90b32906711cc
--- /dev/null
+++ b/third_party/move/move-compiler-v2/tests/verification/verify/single_module_invalid.exp
@@ -0,0 +1,2 @@
+
+============ bytecode verification succeeded ========
diff --git a/third_party/move/move-compiler-v2/tests/verification/verify/single_module_invalid.move b/third_party/move/move-compiler-v2/tests/verification/verify/single_module_invalid.move
new file mode 100644
index 0000000000000..06043755f6307
--- /dev/null
+++ b/third_party/move/move-compiler-v2/tests/verification/verify/single_module_invalid.move
@@ -0,0 +1,19 @@
+address 0x1 {
+module M {
+ #[verify_only]
+ struct Foo {}
+
+ // This should cause an unbound type error in non-verify mode
+ // as the Foo struct declaration was filtered out
+ public fun foo(): Foo {
+ Foo {}
+ }
+
+ #[verify_only]
+ public fun bar() { }
+
+ // This should cause an unbound function error in non-verify mode
+ // as `bar` was filtered out
+ public fun baz() { bar() }
+}
+}
diff --git a/third_party/move/scripts/move_pr.sh b/third_party/move/scripts/move_pr.sh
index f1f8a2abe5c41..d5caa8952ec93 100755
--- a/third_party/move/scripts/move_pr.sh
+++ b/third_party/move/scripts/move_pr.sh
@@ -5,9 +5,13 @@
# A script to check whether a local commit related to Move is ready for a PR.
+# Note that if tests aren't running for you try `cargo update` and maybe
+# `cargo install cargo-nextest`.
+
set -e
-MOVE_PR_PROFILE=ci
+MOVE_PR_PROFILE="${MOVE_PR_PROFILE:-ci}"
+MOVE_PR_NEXTEST_PROFILE="${MOVE_PR_NEXTEST_PROFILE:-smoke-test}"
BASE=$(git rev-parse --show-toplevel)
@@ -67,7 +71,6 @@ EOF
a)
INTEGRATION_TEST=1
COMPILER_V2_TEST=1
- CHECK=1
GEN_ARTIFACTS=1
GIT_CHECKS=1
esac
@@ -143,31 +146,33 @@ if [ ! -z "$CHECK" ]; then
)
fi
+CARGO_OP_PARAMS="--profile $MOVE_PR_PROFILE"
+CARGO_NEXTEST_PARAMS="--profile $MOVE_PR_NEXTEST_PROFILE --cargo-profile $MOVE_PR_PROFILE"
+
# Artifact generation needs to be run before testing as tests may depend on its result
if [ ! -z "$GEN_ARTIFACTS" ]; then
- for dir in $ARTIFACT_CRATE_PATHS; do
- echo "*************** [move-pr] Generating artifacts for crate $dir"
+ for dir in $ARTIFACT_CRATE_PATHS; do
+ echo "*************** [move-pr] Generating artifacts for crate $dir"
+ (
+ cd $MOVE_BASE/$dir
+ cargo run $CARGO_OP_PARAMS
+ )
+ done
+
+ # Add hoc treatment
(
- cd $MOVE_BASE/$dir
- cargo run --profile $MOVE_PR_PROFILE
+ cd $BASE
+ cargo build $CARGO_OP_PARAMS -p aptos-cached-packages
)
- done
- # Add hoc treatment
- (
- cd $BASE
- cargo build --profile $MOVE_PR_PROFILE -p aptos-cached-packages
- )
fi
-
-
if [ ! -z "$TEST" ]; then
echo "*************** [move-pr] Running tests"
(
# It is important to run all tests from one cargo command to keep cargo features
# stable.
cd $BASE
- cargo nextest run --cargo-profile $MOVE_PR_PROFILE \
+ cargo nextest run $CARGO_NEXTEST_PARAMS \
$MOVE_CRATES
)
fi
@@ -176,7 +181,9 @@ if [ ! -z "$INTEGRATION_TEST" ]; then
echo "*************** [move-pr] Running integration tests"
(
cd $BASE
- MOVE_COMPILER_V2=false cargo nextest run --cargo-profile $MOVE_PR_PROFILE \
+ MOVE_COMPILER_V2=false cargo build $CARGO_OP_PARAMS \
+ $MOVE_CRATES $MOVE_CRATES_V2_ENV_DEPENDENT
+ MOVE_COMPILER_V2=false cargo nextest run $CARGO_NEXTEST_PARAMS \
$MOVE_CRATES $MOVE_CRATES_V2_ENV_DEPENDENT
)
fi
@@ -185,18 +192,14 @@ if [ ! -z "$COMPILER_V2_TEST" ]; then
echo "*************** [move-pr] Running integration tests with compiler v2"
(
cd $BASE
- # Need to ensure that aptos-cached-packages is build without the v2 flag,
- # to avoid regenerating incompatible markdown docs. We really need to
- # first build and then test the exact same package set, otherwise
- # rebuild will be triggered via feature unification.
- MOVE_COMPILER_V2=false cargo build --profile $MOVE_PR_PROFILE \
- $MOVE_CRATES_V2_ENV_DEPENDENT
- MOVE_COMPILER_V2=true cargo nextest run --cargo-profile $MOVE_PR_PROFILE \
- $MOVE_CRATES_V2_ENV_DEPENDENT
+ MVC_DOCGEN_OUTPUT_DIR=tests/compiler-v2-doc MOVE_COMPILER_V2=true cargo build $CARGO_OP_PARAMS \
+ $MOVE_CRATES_V2_ENV_DEPENDENT
+ MVC_DOCGEN_OUTPUT_DIR=tests/compiler-v2-doc \
+ MOVE_COMPILER_V2=true cargo nextest run $CARGO_NEXTEST_PARAMS \
+ $MOVE_CRATES_V2_ENV_DEPENDENT
)
fi
-
if [ ! -z "$GIT_CHECKS" ]; then
echo "*************** [move-pr] Running git checks"
$BASE/scripts/git-checks.sh