diff --git a/apps/nextra/pages/zh/build/_meta.tsx b/apps/nextra/pages/zh/build/_meta.tsx index 69f4737a0..fcef86b69 100644 --- a/apps/nextra/pages/zh/build/_meta.tsx +++ b/apps/nextra/pages/zh/build/_meta.tsx @@ -1,6 +1,9 @@ export default { "get-started": "开始", - "smart-contracts": "Smart Contracts (Move)", + "smart-contracts": { + title: "Smart Contracts (Move)", + href: "/en/build/smart-contracts", + }, cli: "CLI", apis: "APIs", guides: "Guides", diff --git a/apps/nextra/pages/zh/build/apis/_meta.tsx b/apps/nextra/pages/zh/build/apis/_meta.tsx index 6ad57d471..d83fb7f42 100644 --- a/apps/nextra/pages/zh/build/apis/_meta.tsx +++ b/apps/nextra/pages/zh/build/apis/_meta.tsx @@ -6,15 +6,23 @@ export default { layout: "full", sidebar: false, }, + href: "/en/build/apis/fullnode-rest-api-reference", }, "fullnode-rest-api": "全节点 REST API 指南", "indexer-graphql-api": { title: "索引器 GraphQL API", href: "/zh/build/indexer", }, - "data-providers": "数据提供商", + "data-providers": { + title: "数据提供商", + href: "/en/build/apis/data-providers", + }, "faucet-api": { title: "水龙头 API", + href: "/en/build/apis/faucet-api", + }, + "aptos-labs-developer-portal": { + title: "API 网关", + href: "/en/build/apis/aptos-labs-developer-portal", }, - "aptos-labs-developer-portal": "API 网关", }; diff --git a/apps/nextra/pages/zh/build/apis/data-providers.mdx b/apps/nextra/pages/zh/build/apis/data-providers.mdx deleted file mode 100644 index 1b0f2e835..000000000 --- a/apps/nextra/pages/zh/build/apis/data-providers.mdx +++ /dev/null @@ -1,79 +0,0 @@ ---- -title: "Data Providers" ---- - -# Data Providers - -If you want to access aptos blockchain data but don't need it in real-time. -We have a few options that will let you access this data using SQL or UIs for building dashboards. -This type of data is often used for analytics since it allows for aggregations. - -## Review of data endpoints - -Hitting the full node directly will give the latest data (will be missing historical unless it's an archival full node) using [REST API](../apis.mdx#aptos-fullnode) - -Indexer layer on top of this will provide a [GRPC transaction stream](../indexer/txn-stream/aptos-hosted-txn-stream) - -On top of this transaction stream, we've built out some product logic tables that can be queried through [GraphQL](../indexer/) - -Since the logic to parse out transaction is [public](https://github.com/aptos-labs/aptos-indexer-processors), some vendors have implemented similar parsing logic to create a subset of tables and made them available to query. - -## SQL Tables - -Indexer defines several processors that create different database tables. - -### Core tables - -These are parsed directly from node API response, one option is to split it out into the following tables: - -- Blocks - version, block height, epoch, timestamp -- Transactions - version, sender, entry function, gas -- Signatures - signature types, signer, fee payer address -- Events - type and data for events - -We store data as table items, resources or modules - -- (write set) changes - change index, change type, resource address -- Table items - table key, table handle, key (content and type), value (content and type) -- (move) resources - resource address, resource type, data -- (move) modules - bytecode for deployed modules - -## Vendors of off-chain data - -Most of our data vendors only provide core datasets. -A [subset of vendors](https://aptosfoundation.org/currents/aptos-on-chain-data-capabilities-with-dune-nansen-and-other-providers) is listed below - -### Google bigquery public dataset - -Provides data through [google public data](https://console.cloud.google.com/marketplace/product/bigquery-public-data/crypto-aptos-mainnet-us) - -![bq_sql](/screenshots/bq_sql.png) - -We also have sample analytics queries [using the above resources](https://github.com/aptos-labs/explorer/tree/main/analytics) - -### Dune - -We have a dashboard here: https://dune.com/aptos/aptos-chain-metrics-overview - -### Flipside - -Another dashboard vendor, signatures in core tables were merged into `fact_transactions` -They also have a few more convenience tables (defi, nft, price), [table list](https://flipsidecrypto.github.io/aptos-models/#!/overview) - -### Sentio - -They have a guide here: https://docs.sentio.xyz/docs/aptos -Data is found in data source -> external project -> sentio/aptos-overview - -### Space and Time - -Data can be found here: https://app.spaceandtime.ai/data-sets?selectedChain=aptos -They also have some non-core tables (Token, Staking, etc) - -## Other vendors - -We also have some partners who target more enterprise use cases - -- [Token Terminal](https://tokenterminal.com/resources/articles/aptos-data-partnership) -- [The Tie](https://www.thetie.io/insights/news/introducing-aptos-ecosystem-dashboard-and-on-chain-data/) -- [Elliptic](https://www.elliptic.co/media-center/elliptic-partners-with-aptos-foundation-as-a-data-integration-provider-to-offer-compliance-screening-and-risk-services-for-aptos-network) diff --git a/apps/nextra/pages/zh/build/apis/faucet-api.mdx b/apps/nextra/pages/zh/build/apis/faucet-api.mdx deleted file mode 100644 index 3ab59d9ce..000000000 --- a/apps/nextra/pages/zh/build/apis/faucet-api.mdx +++ /dev/null @@ -1,75 +0,0 @@ ---- -title: "Faucet API" ---- - -# Faucet API - -The faucet allows users to get `APT` on devnet. On testnet you can only mint at the [mint page](/network/faucet). It is not available on Mainnet. - -The endpoints for each faucet are: - -- Devnet: https://faucet.devnet.aptoslabs.com - -## Using the faucet - -Each SDK has integration for devnet to use the faucet. Below are a few examples, but you can -see more information on each individual [SDK's documentation](../sdks.mdx). - -### Using the faucet in a wallet - -Most wallets, such as [Petra](https://aptosfoundation.org/ecosystem/project/petra) or [Pontem](https://aptosfoundation.org/ecosystem/project/pontem-wallet) -will have a faucet button for devnet. See full list of [Aptos Wallets](https://aptosfoundation.org/ecosystem/projects/wallets). - -### Using the faucet in the Aptos CLI - -Once you've [set up your CLI](../cli/setup-cli.mdx), you can simply call fund-with-faucet. The amount used is in Octas (1 APT = 100,000,000 Octas). - -```bash filename="Terminal" -aptos account fund-with-faucet --account 0xd0f523c9e73e6f3d68c16ae883a9febc616e484c4998a72d8899a1009e5a89d6 --amount 100000000 -``` - -### Using the faucet in the TypeScript SDK - -Here is an example funding the account `0xd0f523c9e73e6f3d68c16ae883a9febc616e484c4998a72d8899a1009e5a89d6` with 1 APT in Devnet. The amount used is in Octas (1 APT = 100,000,000 Octas). - -```ts filename="index.ts" -import { Aptos, AptosConfig, Network } from "@aptos-labs/ts-sdk"; - -const aptos = new Aptos(new AptosConfig({network: Network.Devnet})); -aptos.fundAccount({accountAddress: "0xd0f523c9e73e6f3d68c16ae883a9febc616e484c4998a72d8899a1009e5a89d6", amount: 100000000}); -``` - -### Using the faucet in the Go SDK - -Here is an example funding the account `0xd0f523c9e73e6f3d68c16ae883a9febc616e484c4998a72d8899a1009e5a89d6` with 1 APT in Devnet. The amount used is in Octas (1 APT = 100,000,000 Octas). - -```go filename="index.go" -import "github.com/aptos-labs/aptos-go-sdk" - -func main() { - client, err := aptos.NewClient(aptos.LocalnetConfig) - if err != nil { - panic(err) - } - - client.Fund("0xd0f523c9e73e6f3d68c16ae883a9febc616e484c4998a72d8899a1009e5a89d6", 100000000) -} -``` - -### Calling the faucet: Other languages not supported by SDKs - -If you are trying to call the faucet in other languages, you have two options: - -1. Generate a client from -the [OpenAPI spec](https://github.com/aptos-labs/aptos-core/blob/main/crates/aptos-faucet/doc/spec.yaml). -2. Call the faucet on your own. - -For the latter, you will want to build a query similar to this: - -```bash filename="Terminal" -curl -X POST -'https://faucet.devnet.aptoslabs.com/mint?amount=10000&address=0xd0f523c9e73e6f3d68c16ae883a9febc616e484c4998a72d8899a1009e5a89d6' -``` - -This means mint 10000 [octas](../../network/glossary.mdx#Octa) to -address `0xd0f523c9e73e6f3d68c16ae883a9febc616e484c4998a72d8899a1009e5a89d6`. diff --git a/apps/nextra/pages/zh/build/cli/trying-things-on-chain/_meta.tsx b/apps/nextra/pages/zh/build/cli/trying-things-on-chain/_meta.tsx index ea6041a32..fbe82a530 100644 --- a/apps/nextra/pages/zh/build/cli/trying-things-on-chain/_meta.tsx +++ b/apps/nextra/pages/zh/build/cli/trying-things-on-chain/_meta.tsx @@ -7,5 +7,6 @@ export default { }, ledger: { title: "使用硬件钱包", + href: "/en/build/cli/trying-things-on-chain/ledger", }, }; diff --git a/apps/nextra/pages/zh/build/cli/trying-things-on-chain/ledger.mdx b/apps/nextra/pages/zh/build/cli/trying-things-on-chain/ledger.mdx deleted file mode 100644 index 538af18d0..000000000 --- a/apps/nextra/pages/zh/build/cli/trying-things-on-chain/ledger.mdx +++ /dev/null @@ -1,617 +0,0 @@ -import { Callout, Steps } from 'nextra/components' - -# Use Hardware Ledger via the Aptos CLI - -Using a hardware wallet like Ledger is the most secure way to sign transactions on `mainnet` as your private key never leaves your device. - - -The `Ledger Nano S` has limited memory and may not be able to sign many transactions on Aptos. If you are trying to sign a transaction that is too big for your device to handle, you will get the error `Wrong raw transaction length`. - - -## Initial Setup - -You will need to do a few steps of configuration for the Aptos CLI and your Ledger device to sign transactions. - - -### Ensure you have the Aptos CLI installed. - You can install the Aptos CLI by following [these steps](../../cli.mdx) if you have not done so already. - -### Ensure you have done the basic setup for your Ledger device. - You can find those steps on [Ledger’s website](https://www.ledger.com/). For example, here are the set up instructions for the [Ledger Nano X](https://support.ledger.com/hc/en-us/articles/360018784134-Set-up-your-Ledger-Nano-X?docs=true). -### Plug your Ledger device into your computer. -### Install the Aptos App on your Ledger device by following [this guide](https://support.ledger.com/hc/en-us/articles/7326502672285-Aptos-APT?docs=true). -### Unlock your Ledger device and open the Aptos app. - - - Whenever you want to sign using your Ledger you will need to plug it in, unlock it, and open the Aptos app before running any CLI commands. - - -### Create a new Ledger profile in the Aptos CLI - - ```bash filename="Terminal" - aptos init --profile --ledger - ``` - - Then follow the terminal prompts like so: - - ```text filename="Terminal" - Configuring for profile - Choose network from [devnet, testnet, mainnet, local, custom | defaults to devnet] - - No network given, using devnet... - Please choose an index from the following 5 ledger accounts, or choose an arbitrary index that you want to use: - [0] Derivation path: m/44'/637'/0'/0'/0' (Address: 59836ba1dd0c845713bdab34346688d6f1dba290dbf677929f2fc20593ba0cfb) - [1] Derivation path: m/44'/637'/1'/0'/0' (Address: 21563230cf6d69ee72a51d21920430d844ee48235e708edbafbc69708075a86e) - [2] Derivation path: m/44'/637'/2'/0'/0' (Address: 667446181b3b980ef29f5145a7a2cc34d433fc3ee8c97fc044fd978435f2cb8d) - [3] Derivation path: m/44'/637'/3'/0'/0' (Address: 2dcf037a9f31d93e202c074229a1b69ea8ee4d2f2d63323476001c65b0ec4f31) - [4] Derivation path: m/44'/637'/4'/0'/0' (Address: 23c579a9bdde1a59f1c9d36d8d379aeefe7a5997b5b58bd5a5b0c12a4f170431) - - 0 - Account 59836ba1dd0c845713bdab34346688d6f1dba290dbf677929f2fc20593ba0cfb has been already found on-chain - - --- - Aptos CLI is now set up for account 59836ba1dd0c845713bdab34346688d6f1dba290dbf677929f2fc20593ba0cfb as profile ! Run `aptos --help` for more information about commands - { - "Result": "Success" - } - ``` - - In the example, they chose to use the first ledger account by entering `0` after the `aptos init` command. You may choose whichever account you want. - - **Common errors:** - - 1. If you see the error `Device Not Found`, make sure to unlock your Ledger then try this step again. - 2. If you see the error `Aptos ledger app is not opened`, make sure to open the Aptos app on your Ledger, then try this step again. - -### Finally, you will need to enable blind signing on your Ledger device by following [these steps](https://medium.com/@DavidLehman24/how-to-enable-disable-blind-signing-on-ledger-wallet-99113a85cdad). - 1. Blind signing allows you to confirm a smart contract interaction you cannot verify through a human-readable language. - 2. This is needed to execute transactions without limitation as some payloads are too big to display. - - -## Signing Using Ledger - -After doing the initial setup, you can sign transactions by following these steps: - -1. Plug in your ledger. -2. Unlock it. -3. Open the Aptos app. -4. Run the Aptos CLI command which requires a signature. - - -This process works for any command that requires a signature, whether that’s to transfer coins, publish a Move contract, interact with a contract, etc. - - -For example, if you wanted to publish a Move package like the `[hello_blockchain](https://github.com/aptos-labs/aptos-core/tree/main/aptos-move/move-examples/hello_blockchain)` demo contract you could follow the above steps then run: - -```bash filename="Terminal" -aptos move publish --profile --named-addresses hello_blockchain= -``` - -You should see a response like: - -```bash filename="Terminal" -Compiling, may take a little while to download git dependencies... -INCLUDING DEPENDENCY AptosFramework -INCLUDING DEPENDENCY AptosStdlib -INCLUDING DEPENDENCY MoveStdlib -BUILDING Examples -package size 1755 bytes -Do you want to submit a transaction for a range of [139600 - 209400] Octas at a gas unit price of 100 Octas? [yes/no] > - -yes - -{ - "Result": { - "transaction_hash": "0xd5a12594f85284cfd5518d547d084030b178ee926fa3d8cbf699cc0596eff538", - "gas_used": 1396, - "gas_unit_price": 100, - "sender": "59836ba1dd0c845713bdab34346688d6f1dba290dbf677929f2fc20593ba0cfb", - "sequence_number": 0, - "success": true, - "timestamp_us": 1689887104333038, - "version": 126445, - "vm_status": "Executed successfully" - } -} - -``` - -After you have approved publishing this package you will be prompted to sign the transaction on your Ledger device. Once signed, the package will be published to the network! - -One error you might run into is `Error: Wrong raw transaction length`. This means that the transaction or package size was too big for your device to sign. Currently the Aptos Ledger app can only support transactions that are smaller than 20kb. The `Ledger Nano S` device has less memory than that, which is why it is more likely to produce this error. - -## Authentication key rotation - -If you have an active account that is not secured using a hardware wallet, then -you may wish to rotate the account's authentication key so that it corresponds -to a [BIP44 account index] private key held on your Ledger. - -Alternatively, if you have an account linked with a Ledger hardware wallet that -you wish to publish a large package from, you might want to temporarily rotate -the account's authentication key to a hot key to avoid memory issues. - -This tutorial will walk you through both scenarios. - - -Before you start this tutorial make sure you have completed the -[key rotation guide](../../guides/key-rotation.mdx). - - - - -### Complete the key rotation guide - -Confirm that you have completed the -[key rotation guide](../../guides/key-rotation.mdx). - -### Verify your Ledger is ready - -1. Connect and unlock your Ledger. -1. Check what version of the Aptos app you have: `Aptos > About > Version`. -1. If you do not have version `0.6.9` or higher, update it using Ledger Live. -1. Enable blind signing: `Aptos > Settings > Enable Blind Signing`. - -### Start a localnet - -Start a localnet: - -```sh filename="Terminal" -aptos node run-localnet -``` - -The localnet is ready when it prints out: - -```sh filename="Terminal" -Applying post startup steps... - -Setup is complete, you can now use the localnet! -``` - - -If you are a power user on MacOS or Linux, the following command can be used -to start a fresh localnet as a background process: - -```sh filename="Terminal" -mkdir -p localnet-data -aptos node run-localnet \ - --assume-yes \ - --test-dir localnet-data \ - --force-restart & -export LOCALNET_PID=$! -``` - -You can then stop the localnet at any point with the following command: - -```sh filename="Terminal" -kill $LOCALNET_PID -``` - - -### Set up localnet hot wallet profile - -Create a private key corresponding to an authentication key, and thus initial -account address, that starts with the vanity prefix `0xaaa`: - -```sh filename="Terminal" -aptos key generate \ - --assume-yes \ - --output-file private-key-a \ - --vanity-prefix 0xaaa -``` - -
-Example output -```sh filename="Terminal" -{ - "Result": { - "PublicKey Path": "private-key-a.pub", - "PrivateKey Path": "private-key-a", - "Account Address:": "0xaaac71af5f2a4af4ec2639a15799bf9b945afb061c8bee102b636531c1b00eb5" - } -} -``` -
- -Use the private key to initialize a `hot-wallet-1` profile on the localnet: - -```sh filename="Terminal" -aptos init \ - --assume-yes \ - --network local \ - --private-key-file private-key-a \ - --profile hot-wallet-1 -``` - -
-Example output -```sh filename="Terminal" -Configuring for profile hot-wallet-1 -Configuring for network Local -Using command line argument for private key -Account 0xaaac71af5f2a4af4ec2639a15799bf9b945afb061c8bee102b636531c1b00eb5 doesn\'t exist, creating it and funding it with 100000000 Octas -Account 0xaaac71af5f2a4af4ec2639a15799bf9b945afb061c8bee102b636531c1b00eb5 funded successfully - ---- -Aptos CLI is now set up for account 0xaaac71af5f2a4af4ec2639a15799bf9b945afb061c8bee102b636531c1b00eb5 as profile hot-wallet-1! Run `aptos --help` for more information about commands -{ - "Result": "Success" -} -``` -
- -### Rotate the hot wallet key - -Rotate the authentication key of the hot wallet to use [BIP44 account index] -1000 on your Ledger: - -```sh filename="Terminal" -aptos account rotate-key \ - --assume-yes \ - --new-derivation-index 1000 \ - --profile hot-wallet-1 \ - --save-to-profile ledger-wallet-1000 -``` - - -As a best practice, this command uses a [BIP44 account index] that starts at a -large number (1000) to indicate that the account is secured by a rotated -authentication key on a Ledger, to ensure it does not conflict with any other -existing accounts. - -This practice aids in profile recovery, as shown below. - - -Follow the instructions from the CLI prompt: - -```sh filename="Terminal" -Approve rotation proof challenge signature on your Ledger device -``` - -
-Example output -```sh filename="Terminal" -{ - "Result": { - "message": "Saved new profile ledger-wallet-1000", - "transaction": { - "transaction_hash": "0x1a6df99651ac170bda10cfb9898fa196321d80a928033791b9d2231f77738bb2", - "gas_used": 448, - "gas_unit_price": 100, - "sender": "aaac71af5f2a4af4ec2639a15799bf9b945afb061c8bee102b636531c1b00eb5", - "sequence_number": 0, - "success": true, - "timestamp_us": 1717986382369736, - "version": 186, - "vm_status": "Executed successfully" - } - } -} -``` -
- -Compare the `hot-wallet-1` and `ledger-wallet-1000` profiles, noting that they -have the same `account` address but different `public_key` values: - -```sh filename="Terminal" -aptos config show-profiles --profile hot-wallet-1 -aptos config show-profiles --profile ledger-wallet-1000 -``` - -
-Example output -```sh filename="Terminal" -{ - "Result": { - "hot-wallet-1": { - "has_private_key": true, - "public_key": "0xffb1240fd1267207cc3ed2e1b5386e090a9ca2c844d7f9e0077b3d7dd5d5e430", - "account": "aaa271bca468fb8518f73a732a484b29a1bc296ebcb23f15639d4865a5cebe87", - "rest_url": "http://localhost:8080", - "faucet_url": "http://localhost:8081" - } - } -} -{ - "Result": { - "ledger-wallet-1000": { - "has_private_key": false, - "public_key": "0x20ba83f9b9fdab73b0ace8fda26ce24c98cf55060b72b69cfbd25add6a25d09b", - "account": "aaa271bca468fb8518f73a732a484b29a1bc296ebcb23f15639d4865a5cebe87", - "rest_url": "http://localhost:8080", - "faucet_url": "http://localhost:8081" - } - } -} -``` -
- -Since the account is no longer secured by the hot private key, delete the -private and public key files. - - -If you are using a UNIX-like machine: - -```shell filename="Terminal" -rm private-key-a -rm private-key-b -rm private-key-a.pub -rm private-key-b.pub -``` - - -Now that you have successfully rotated the authentication key of the hot wallet, -you can delete the profiles too: - -```sh filename="Terminal" -aptos config delete-profile --profile hot-wallet-1 -aptos config delete-profile --profile ledger-wallet-1000 -``` - -
-Example output -```sh filename="Terminal" -{ - "Result": "Deleted profile hot-wallet-1" -} -{ - "Result": "Deleted profile ledger-wallet-1000" -} -``` -
- -### Recover profile - -Since you know that you rotated the authentication key of the hot wallet to the -Ledger, and since you used the best practice of a [BIP44 account index] offset -of 1000, you can easily recover the profile using the [BIP44 account index] -alone: - -```sh filename="Terminal" -aptos init \ - --assume-yes \ - --derivation-index 1000 \ - --network local \ - --profile ledger-wallet-1000-recovered -``` - -
-Example output -```sh filename="Terminal" -Configuring for profile ledger-wallet-1000-recovered -Configuring for network Local -Account 0xaaac71af5f2a4af4ec2639a15799bf9b945afb061c8bee102b636531c1b00eb5 has been already found onchain - ---- -Aptos CLI is now set up for account 0xaaac71af5f2a4af4ec2639a15799bf9b945afb061c8bee102b636531c1b00eb5 as profile ledger-wallet-1000-recovered! Run `aptos --help` for more information about commands -{ - "Result": "Success" -} -``` -
- -Note that this profile corresponds to the specified `0xaaa...` vanity account -address: - -```sh filename="Terminal" -aptos config show-profiles --profile ledger-wallet-1000-recovered -``` - -
-Example output -```sh filename="Terminal" -{ - "Result": { - "ledger-wallet-1000-recovered": { - "has_private_key": false, - "public_key": "0x20ba83f9b9fdab73b0ace8fda26ce24c98cf55060b72b69cfbd25add6a25d09b", - "account": "aaac71af5f2a4af4ec2639a15799bf9b945afb061c8bee102b636531c1b00eb5", - "rest_url": "http://localhost:8080", - "faucet_url": "http://localhost:8081" - } - } -} -``` -
- - -The `aptos init` command first checks the [`account::OriginatingAddress`] table -for determining the account address associated with a public key, so as long as -you follow best practices from the -[key rotation guide](../../guides/key-rotation.mdx) and only -authenticate one account at a time with a private key, you'll easily be able to -recover your profile based on the [BIP44 account index] alone. - - -### Rotate to new hot private key - -If you have an account linked with a Ledger hardware wallet that you wish to use -for publication of a large package, you'll be unable to sign the package -publication transaction due to the Ledger's memory limitations. In this case, -you'll want to temporarily rotate to a hot wallet. - -Start by generating a new private key: - -```sh filename="Terminal" -aptos key generate \ - --assume-yes \ - --output-file private-key-b \ - --vanity-prefix 0xbbb -``` - -
-Example output -```sh filename="Terminal" -{ - "Result": { - "PublicKey Path": "private-key-b.pub", - "PrivateKey Path": "private-key-b", - "Account Address:": "0xbbbede2b4f1d49eff0b156ab0756889a6f2bb68f215399d5015da9ac45921b47" - } -} -``` -
- -Rotate the authentication key of the account linked with the Ledger to the new -private key: - -```sh filename="Terminal" -aptos account rotate-key \ - --assume-yes \ - --new-private-key-file private-key-b \ - --profile ledger-wallet-1000-recovered \ - --save-to-profile temporary-hot-wallet -``` - -Follow the instructions from the CLI prompt: - -```sh filename="Terminal" -Approve rotation proof challenge signature on your Ledger device -``` - -```sh filename="Terminal" -Approve transaction on your Ledger device -``` - -
-Example output -```sh filename="Terminal" -{ - "Result": { - "message": "Saved new profile temporary-hot-wallet", - "transaction": { - "transaction_hash": "0xe49782e92d8fd824fd6dce8f6ed42a11cf8ee84c201f3aa639c435e737c80eaa", - "gas_used": 449, - "gas_unit_price": 100, - "sender": "aaac71af5f2a4af4ec2639a15799bf9b945afb061c8bee102b636531c1b00eb5", - "sequence_number": 1, - "success": true, - "timestamp_us": 1717986617911082, - "version": 631, - "vm_status": "Executed successfully" - } - } -``` -
- -Since the CLI profile `ledger-wallet-1000-recovered` is now stale, rename it -in case you get interrupted and forget that the private key has been rotated: - -```sh filename="Terminal" -aptos config rename-profile \ - --profile ledger-wallet-1000-recovered \ - --new-profile-name ledger-wallet-1000-stale -``` - -
-Example output -```sh filename="Terminal" -{ - "Result": "Renamed profile ledger-wallet-1000-recovered to ledger-wallet-1000-stale" -} -``` -
- -### Rotate back to Ledger - -Once you've signed the large package publication transaction with the hot key, -you can then rotate the authentication key back to to the corresponding to the -private key on the Ledger at index 1000: - -```sh filename="Terminal" -aptos account rotate-key \ - --assume-yes \ - --new-derivation-index 1000 \ - --profile temporary-hot-wallet \ - --save-to-profile ledger-wallet-1000 -``` - -Follow the instructions from the CLI prompt: - -```sh filename="Terminal" -Approve rotation proof challenge signature on your Ledger device -``` - -
-Example output -```sh filename="Terminal" -{ - "Result": { - "message": "Saved new profile ledger-wallet-1000", - "transaction": { - "transaction_hash": "0x9503819d4ea13bcd9eafed25984807d86d22e8a9837565a7495b54d13890d103", - "gas_used": 449, - "gas_unit_price": 100, - "sender": "aaac71af5f2a4af4ec2639a15799bf9b945afb061c8bee102b636531c1b00eb5", - "sequence_number": 2, - "success": true, - "timestamp_us": 1717986672963544, - "version": 742, - "vm_status": "Executed successfully" - } - } -} -``` -
- -Verify that the `ledger-wallet-1000-stale` and `ledger-wallet-1000` profiles -have the same `account` address and `public_key`: - -```sh filename="Terminal" -aptos config show-profiles --profile ledger-wallet-1000-stale -aptos config show-profiles --profile ledger-wallet-1000 -``` - -Delete the `temporary-hot-wallet` and `ledger-wallet-1000-stale` profiles, -which you no longer need. - -```sh filename="Terminal" -aptos config delete-profile --profile temporary-hot-wallet -aptos config delete-profile --profile ledger-wallet-1000-stale -``` - -
-Example output -```sh filename="Terminal" -{ - "Result": "Deleted profile temporary-hot-wallet" -} -{ - "Result": "Deleted profile ledger-wallet-1000-stale" -} -``` -
- -Since you no longer need the temporary private key, delete it too. - - -If you are using a UNIX-like machine: - -```shell filename="Terminal" -rm private-key-* -``` - - -### Clean up - -Delete the remaining test profile: - -```shell filename="Terminal" -aptos config delete-profile --profile ledger-wallet-1000 -``` - -Then stop the localnet. - - -If you are using a UNIX-like machine: - -```shell filename="Terminal" -aptos config delete-profile --profile ledger-wallet-1000 -kill $LOCALNET_PID -rm -fr localnet-data -``` - - -
- -[`account::OriginatingAddress`]: https://github.com/aptos-labs/aptos-core/blob/acb6c891cd42a63b3af96561a1aca164b800c7ee/aptos-move/framework/aptos-framework/sources/account.move#L70 -[BIP44 account index]: https://github.com/bitcoin/bips/blob/master/bip-0044.mediawiki \ No newline at end of file diff --git a/apps/nextra/pages/zh/build/cli/working-with-move-contracts/_meta.tsx b/apps/nextra/pages/zh/build/cli/working-with-move-contracts/_meta.tsx index 6c8a8b27d..e3c6ab818 100644 --- a/apps/nextra/pages/zh/build/cli/working-with-move-contracts/_meta.tsx +++ b/apps/nextra/pages/zh/build/cli/working-with-move-contracts/_meta.tsx @@ -1,11 +1,13 @@ export default { "local-simulation-benchmarking-and-gas-profiling": { title: "本地模拟、基准测试和燃料分析", + href: "/en/build/cli/working-with-move-contracts/local-simulation-benchmarking-and-gas-profiling", }, "arguments-in-json-tutorial": { title: "JSON 参数教程", }, "multi-signature-tutorial": { title: "多重签名治理教程", + href: "/en/build/cli/working-with-move-contracts/multi-signature-tutorial", }, }; diff --git a/apps/nextra/pages/zh/build/cli/working-with-move-contracts/local-simulation-benchmarking-and-gas-profiling.mdx b/apps/nextra/pages/zh/build/cli/working-with-move-contracts/local-simulation-benchmarking-and-gas-profiling.mdx deleted file mode 100644 index e0591c6f2..000000000 --- a/apps/nextra/pages/zh/build/cli/working-with-move-contracts/local-simulation-benchmarking-and-gas-profiling.mdx +++ /dev/null @@ -1,255 +0,0 @@ -import { Callout, FileTree } from "nextra/components"; - -# Local Simulation, Benchmarking & Gas Profiling - -## Overview -The previous tutorial demonstrates how you can deploy and interact with Move contracts using various CLI commands. - -By default, those commands send a transaction to the remote fullnode for simulation and execution. -You can override this behavior and simulate the transaction locally, by appending one of the following command line options of your preference: -- `--local`: Simulate the transaction locally without conducting any further measurements or analysis. -- `--benchmark`: Benchmark the transaction and report the running time(s). -- `--profile-gas`: Profile the transaction for detailed gas usage. - -These additional options can be used in combination with the following CLI commands: -- `aptos move run` -- `aptos move run-script` -- `aptos move publish` - -Alternatively, if you are interested in replaying a past transaction, check out [this tutorial](../replay-past-transactions.mdx). - - -Local simulations do not result in any to the on-chain state. - - -## Deploying the Example Contract -For demonstration purposes, we will continue to use the [`hello_blockchain`](https://github.com/aptos-labs/aptos-core/tree/main/aptos-move/move-examples/hello_blockchain) package as an example. - -First, publish the package to devnet or testnet (if you haven't done so already). - -Change into the package directory. -```bash filename="Terminal" -cd aptos-move/move-examples/hello_blockchain -``` - -Then publish the package using the following command. -```bash filename="Terminal" -aptos move publish --named-addresses hello_blockchain=default --assume-yes -``` - -
-Output -```bash -{ - "Result": { - "transaction_hash": "0xe4ae0ec4ea3474b2123838885b04d7f4b046c174d14d7dc1c56916f2eb553bcf", - "gas_used": 1118, - "gas_unit_price": 100, - "sender": "dbcbe741d003a7369d87ec8717afb5df425977106497052f96f4e236372f7dd5", - "sequence_number": 5, - "success": true, - "timestamp_us": 1713914742422749, - "version": 1033819503, - "vm_status": "Executed successfully" - } -} -``` -
- -Notice that you do need to have your CLI profile set up properly and bind the named addresses correctly. Please refer to [CLI Configuration](../setup-cli.mdx) for more details. - - -Note: publishing the package to devnet/testnet is just one way to set up the stage for local simulation and is not the only one possible. -Alternatively you can use a local node, or simulate transactions that do not need to have code published first, such as scripts and even the package publishing transaction itself. - - -## Local Simulation -Next, execute the entry function message::set_message with local simulation enabled using the additional command line option `--local`. This will execute the transaction locally without conducting any further measurements or analysis. -```bash filename="Terminal" -aptos move run --function-id 'default::message::set_message' --args 'string:abc' --local -``` - -
-Output -```bash -Simulating transaction locally... -{ - "Result": { - "transaction_hash": "0x5aab20980688185eed2c9a27bab624c84b8b8117241cd4a367ba2a012069f57b", - "gas_used": 441, - "gas_unit_price": 100, - "sender": "dbcbe741d003a7369d87ec8717afb5df425977106497052f96f4e236372f7dd5", - "success": true, - "version": 1033887414, - "vm_status": "status EXECUTED of type Execution" - } -} -``` -
- -Local and remote simulation shall produce identical results. - - -## Benchmarking -To measure the running time(s) of your transaction, use the `--benchmark` option. - -```bash filename="Terminal" -aptos move run --function-id 'default::message::set_message' --args 'string:abc' --benchmark -``` - -
-Output -```bash -Benchmarking transaction locally... -Running time (cold code cache): 985.141µs -Running time (warm code cache): 848.159µs -{ - "Result": { - "transaction_hash": "0xa2fe548d37f12ee79df13e70fdd8212e37074c1b080b89b7d92e82550684ecdb", - "gas_used": 441, - "gas_unit_price": 100, - "sender": "dbcbe741d003a7369d87ec8717afb5df425977106497052f96f4e236372f7dd5", - "success": true, - "version": 1033936831, - "vm_status": "status EXECUTED of type Execution" - } -} -``` -
- -It's worth noting that these running times serve only as informational references, as they are contingent upon the specifications of your local machine and may be influenced by noise or other random factors. - -**If you are aiming to optimize your contract, you should base your decisions on the gas profiling results.** - - -To minimize measurement errors, the benchmark harness executes the same transaction multiple times. For this reason, it may take a while for the benchmark task to complete. - - -## Gas Profiling -The Aptos Gas Profiler is a powerful tool that can help you understand the gas usage of Aptos transactions. Once activated, it will simulate transactions using an instrumented VM, and generate a web-based report. - -The gas profiler can also double as a debugger since the report also includes a full execution trace. - -### Using the Gas Profiler -The gas profiler can be invoked by appending the `--profile-gas` option. - -```bash filename="Terminal" -aptos move run --function-id 'default::message::set_message' --args 'string:abc' --profile-gas -``` - -
-Output -```bash -Simulating transaction locally using the gas profiler... -Gas report saved to gas-profiling/txn-d0bc3422-0xdbcb-message-set_message. -{ - "Result": { - "transaction_hash": "0xd0bc342232f14a6a7d2d45251719aee45373bdb53f68403cfc6dc6062c74fa9e", - "gas_used": 441, - "gas_unit_price": 100, - "sender": "dbcbe741d003a7369d87ec8717afb5df425977106497052f96f4e236372f7dd5", - "success": true, - "version": 1034003962, - "vm_status": "status EXECUTED of type Execution" - } -} -``` -
- -You can then find the generated gas report in the directory `gas-profiling`: - - - - - - - - - - - - - - -`index.html` is the main page of the report, which can view using your web browser. -[Sample report](/gas-profiling/sample-report/index.html) - - -### Understanding the Gas Report - -The gas report consists of three sections that help you to understand the gas usage through different lenses. - -#### Flamegraphs - -The first section consists of visualization of the gas usage in the form of two flamegraphs: one for execution & IO, the other for storage. -The reason why we need two graphs is that these are measured in different units: one in gas units, and the other in APT. - -It is possible to interact with various elements in the graph. If you hover your cursor over an item, it will show you the precise cost and percentage. -![gas-profiling-flamegraph-0.png](/docs/gas-profiling-flamegraph-0.png) - -If you click on an item, you can zoom into it and see the child items more clearly. -You can reset the view by clicking the "Reset Zoom" button in the top-left corner. -![gas-profiling-flamegraph-1.png](/docs/gas-profiling-flamegraph-1.png) - -There is also "Search" button in the top-right corner that allows to match certain items and highlight them. -![gas-profiling-flamegraph-2.png](/docs/gas-profiling-flamegraph-2.png) - -#### Cost Break-down - -The second section is a detailed break-down of all gas costs. Data presented in this section is categorized, aggregated and sorted. -This can be especially helpful if you know what numbers to look at. - -For example, the following tables show the execution costs of all Move bytecode instructions/operations. -The percentage here is relative to the total cost of the belonging category (Exec + IO in this case). - -![gas-profiling-cost-break-down-table.png](/docs/gas-profiling-cost-break-down-table.png) - -#### Full Execution Trace - -The final section of the gas report is the full execution trace of the transaction that looks like this: - -```text filename="Example execution trace" - intrinsic 2.76 85.12% - dependencies 0.0607 1.87% - 0xdbcb..::message 0.0607 1.87% - 0xdbcb..::message::set_message 0.32416 10.00% - create_ty 0.0004 0.01% - create_ty 0.0004 0.01% - create_ty 0.0004 0.01% - create_ty 0.0004 0.01% - create_ty 0.0008 0.02% - imm_borrow_loc 0.00022 0.01% - call 0.00441 0.14% - 0x1::signer::address_of 0.007534 0.23% - create_ty 0.0008 0.02% - move_loc 0.000441 0.01% - call 0.004043 0.12% - 0x1::signer::borrow_address 0.000735 0.02% - read_ref 0.001295 0.04% - ret 0.00022 0.01% - st_loc 0.000441 0.01% - copy_loc 0.000854 0.03% - load<0xdbcb..::0xdbcb..::message::MessageHolder> 0.302385 9.33% - exists_generic 0.000919 0.03% - not 0.000588 0.02% - br_false 0.000441 0.01% - imm_borrow_loc 0.00022 0.01% - move_loc 0.000441 0.01% - pack 0.000955 0.03% - move_to_generic 0.001838 0.06% - branch 0.000294 0.01% - @28 - ret 0.00022 0.01% - ledger writes 0.097756 3.01% - transaction - events - state write ops 0.097756 3.01% - create<0xdbcb..::0xdbcb..::message::MessageHolder> 0.097756 3.01% -``` - -The left column lists all Move instructions and operations being executed, with each level of indentation indicating a function call. - -The middle column represents the gas costs associated with the operations. - -There is also a special notation `@number` that represents a jump to a particular location in the byte code. (`@28` in the snippet above) -This is purely informational and to help understand the control flow. diff --git a/apps/nextra/pages/zh/build/cli/working-with-move-contracts/multi-signature-tutorial.mdx b/apps/nextra/pages/zh/build/cli/working-with-move-contracts/multi-signature-tutorial.mdx deleted file mode 100644 index ff0517aac..000000000 --- a/apps/nextra/pages/zh/build/cli/working-with-move-contracts/multi-signature-tutorial.mdx +++ /dev/null @@ -1,767 +0,0 @@ -import { Callout } from 'nextra/components' - -# Multisig Governance Tutorial - -## Background - -This section builds upon the [Arguments in JSON tutorial](arguments-in-json-tutorial.mdx). If you have not done that, please complete that tutorial first. - -This tutorial likewise references the [`CliArgs` example package](https://github.com/aptos-labs/aptos-core/tree/main/aptos-move/move-examples/cli_args). - - -If you would like to follow along, start by completing the [Arguments in JSON](arguments-in-json-tutorial.mdx) tutorial steps! - - -For this example, Ace and Bee will conduct governance operations from a 2-of-2 "multisig v2" account (an on-chain multisig account per [`multisig_account.move`](https://github.com/aptos-labs/aptos-core/blob/main/aptos-move/framework/aptos-framework/sources/multisig_account.move)) - -## Account creation - -Since Ace's account was created during the [Arguments in JSON](arguments-in-json-tutorial.mdx) tutorial, start by mining a vanity address account for Bee too: - -```bash filename="Terminal" -aptos key generate \ - --vanity-prefix 0xbee \ - --output-file bee.key -``` - -
-Output - -```bash filename="Terminal" -{ - "Result": { - "PublicKey Path": "bee.key.pub", - "PrivateKey Path": "bee.key", - "Account Address:": "0xbeec980219d246581cef5166dc6ba5fb1e090c7a7786a5176d111a9029b16ddc" - } -} -``` - -
- - -The exact account address should vary for each run, though the vanity prefix should not. - - -Store Bee's address in a shell variable, so you can call it inline later on: - -```bash filename="Terminal" -# Your exact address should vary -bee_addr=0xbeec980219d246581cef5166dc6ba5fb1e090c7a7786a5176d111a9029b16ddc -``` - -Fund Bee's account using the faucet: - -```bash filename="Terminal" -aptos account fund-with-faucet --account $bee_addr -``` - -
-Output - -```bash filename="Terminal" -{ - "Result": "Added 100000000 Octas to account beec980219d246581cef5166dc6ba5fb1e090c7a7786a5176d111a9029b16ddc" -} -``` - -
- -Ace can now create a multisig account: - -```bash filename="Terminal" -aptos multisig create \ - --additional-owners $bee_addr \ - --num-signatures-required 2 \ - --private-key-file ace.key \ - --assume-yes -``` - -
-Output - -```bash filename="Terminal" -{ - "Result": { - "multisig_address": "57478da34604655c68b1dcb89e4f4a9124b6c0ecc1c59a0931d58cc4e60ac5c5", - "transaction_hash": "0x849cc756de2d3b57210f5d32ae4b5e7d1f80e5d376233885944b6f3cc2124a05", - "gas_used": 1524, - "gas_unit_price": 100, - "sender": "acef1b9b7d4ab208b99fed60746d18dcd74865edb7eb3c3f1428233988e4ba46", - "sequence_number": 5, - "success": true, - "timestamp_us": 1685078644186194, - "version": 528428043, - "vm_status": "Executed successfully" - } -} -``` - -
- -Store the multisig address in a shell variable: - -```bash filename="Terminal" -# Your address should vary -multisig_addr=0x57478da34604655c68b1dcb89e4f4a9124b6c0ecc1c59a0931d58cc4e60ac5c5 -``` - -## Inspect the multisig - -Use the assorted [`multisig_account.move` view functions](https://github.com/aptos-labs/aptos-core/blob/9fa0102c3e474d99ea35a0a85c6893604be41611/aptos-move/framework/aptos-framework/sources/multisig_account.move#L237) to inspect the multisig: - -```bash filename="Number of signatures required" -aptos move view \ - --function-id 0x1::multisig_account::num_signatures_required \ - --args \ - address:"$multisig_addr" -``` - -
-Output - -```bash filename="Terminal" -{ - "Result": [ - "2" - ] -} -``` - -
- -```bash filename="Owners" -aptos move view \ - --function-id 0x1::multisig_account::owners \ - --args \ - address:"$multisig_addr" -``` - -
-Output - -```bash filename="Terminal" -{ - "Result": [ - [ - "0xbeec980219d246581cef5166dc6ba5fb1e090c7a7786a5176d111a9029b16ddc", - "0xacef1b9b7d4ab208b99fed60746d18dcd74865edb7eb3c3f1428233988e4ba46" - ] - ] -} -``` - -
- -```bash filename="Last resolved sequence number" -aptos move view \ - --function-id 0x1::multisig_account::last_resolved_sequence_number \ - --args \ - address:"$multisig_addr" -``` - -
-Output - -```bash filename="Terminal" -{ - "Result": [ - "0" - ] -} -``` - -
- -```bash filename="Next sequence number" -aptos move view \ - --function-id 0x1::multisig_account::next_sequence_number \ - --args \ - address:"$multisig_addr" -``` - -
-Output - -```bash filename="Terminal" -{ - "Result": [ - "1" - ] -} -``` - -
- -## Enqueue a publication transaction - -The first multisig transaction enqueued will be a transaction for publication of the [`CliArgs` example package](https://github.com/aptos-labs/aptos-core/tree/main/aptos-move/move-examples/cli_args). -First, generate a publication payload entry function JSON file: - -```bash filename="Command" -aptos move build-publish-payload \ - --named-addresses test_account=$multisig_addr \ - --json-output-file publication.json \ - --assume-yes -``` - -
-Output - -```bash filename="Terminal" -{ - "Result": "Publication payload entry function JSON file saved to publication.json" -} -``` - -
- -Now have Ace propose publication of the package from the multisig account, storing only the payload hash on-chain: - -```bash filename="Command" -aptos multisig create-transaction \ - --multisig-address $multisig_addr \ - --json-file publication.json \ - --store-hash-only \ - --private-key-file ace.key \ - --assume-yes -``` - -
-Output - -```bash filename="Terminal" -{ - "Result": { - "transaction_hash": "0x70c75903f8e1b1c0069f1e84ef9583ad8000f24124b33a746c88d2b031f7fe2c", - "gas_used": 510, - "gas_unit_price": 100, - "sender": "acef1b9b7d4ab208b99fed60746d18dcd74865edb7eb3c3f1428233988e4ba46", - "sequence_number": 6, - "success": true, - "timestamp_us": 1685078836492390, - "version": 528429447, - "vm_status": "Executed successfully" - } -} -``` - -
- -Note that the last resolved sequence number is still 0 because no transactions have been resolved: - -```bash filename="Last resolved sequence number" -aptos move view \ - --function-id 0x1::multisig_account::last_resolved_sequence_number \ - --args \ - address:"$multisig_addr" -``` - -
-Output - -```bash filename="Terminal" -{ - "Result": [ - "0" - ] -} -``` - -
- -However, the next sequence number has been incremented because a transaction has been enqueued: - -```bash filename="Next sequence number" -aptos move view \ - --function-id 0x1::multisig_account::next_sequence_number \ - --args \ - address:"$multisig_addr" -``` - -
-Output - -```bash filename="Terminal" -{ - "Result": [ - "2" - ] -} -``` - -
- -The multisig transaction enqueued on-chain can now be inspected: - -```bash filename="Get transaction" -aptos move view \ - --function-id 0x1::multisig_account::get_transaction \ - --args \ - address:"$multisig_addr" \ - String:1 -``` - -
-Output - -```bash filename="Terminal" -{ - "Result": [ - { - "creation_time_secs": "1685078836", - "creator": "0xacef1b9b7d4ab208b99fed60746d18dcd74865edb7eb3c3f1428233988e4ba46", - "payload": { - "vec": [] - }, - "payload_hash": { - "vec": [ - "0x62b91159c1428c1ef488c7290771de458464bd665691d9653d195bc28e0d2080" - ] - }, - "votes": { - "data": [ - { - "key": "0xacef1b9b7d4ab208b99fed60746d18dcd74865edb7eb3c3f1428233988e4ba46", - "value": true - } - ] - } - } - ] -} -``` - -
- -Note from the above result that no payload is stored on-chain, and that Ace implicitly approved the transaction (voted `true`) upon the submission of the proposal. - -## Enqueue a governance parameter transaction - -Now have Bee enqueue a governance parameter setter transaction, storing the entire transaction payload on-chain: - -```bash filename="Command" -aptos multisig create-transaction \ - --multisig-address $multisig_addr \ - --function-id $multisig_addr::cli_args::set_vals \ - --type-args \ - 0x1::account::Account \ - 0x1::chain_id::ChainId \ - --args \ - u8:123 \ - "bool:[false, true, false, false]" \ - 'address:[["0xace", "0xbee"], ["0xcad"], []]' \ - --private-key-file bee.key \ - --assume-yes -``` - -
-Output - -```bash filename="Terminal" -{ - "Result": { - "transaction_hash": "0xd0a348072d5bfc5a2e5d444f92f0ecc10b978dad720b174303bc6d91342f27ec", - "gas_used": 511, - "gas_unit_price": 100, - "sender": "beec980219d246581cef5166dc6ba5fb1e090c7a7786a5176d111a9029b16ddc", - "sequence_number": 0, - "success": true, - "timestamp_us": 1685078954841650, - "version": 528430315, - "vm_status": "Executed successfully" - } -} -``` - -
- -Note the next sequence number has been incremented again: - -```bash filename="Next sequence number" -aptos move view \ - --function-id 0x1::multisig_account::next_sequence_number \ - --args \ - address:"$multisig_addr" -``` - -
-Output - -```bash filename="Terminal" -{ - "Result": [ - "3" - ] -} -``` - -
- -Now both the publication and parameter transactions are pending: - -```bash filename="Get pending transactions" -aptos move view \ - --function-id 0x1::multisig_account::get_pending_transactions \ - --args \ - address:"$multisig_addr" -``` - -
-Output - -```bash filename="Terminal" -{ - "Result": [ - [ - { - "creation_time_secs": "1685078836", - "creator": "0xacef1b9b7d4ab208b99fed60746d18dcd74865edb7eb3c3f1428233988e4ba46", - "payload": { - "vec": [] - }, - "payload_hash": { - "vec": [ - "0x62b91159c1428c1ef488c7290771de458464bd665691d9653d195bc28e0d2080" - ] - }, - "votes": { - "data": [ - { - "key": "0xacef1b9b7d4ab208b99fed60746d18dcd74865edb7eb3c3f1428233988e4ba46", - "value": true - } - ] - } - }, - { - "creation_time_secs": "1685078954", - "creator": "0xbeec980219d246581cef5166dc6ba5fb1e090c7a7786a5176d111a9029b16ddc", - "payload": { - "vec": [ - "0x0057478da34604655c68b1dcb89e4f4a9124b6c0ecc1c59a0931d58cc4e60ac5c508636c695f61726773087365745f76616c7302070000000000000000000000000000000000000000000000000000000000000001076163636f756e74074163636f756e740007000000000000000000000000000000000000000000000000000000000000000108636861696e5f696407436861696e49640003017b0504000100006403020000000000000000000000000000000000000000000000000000000000000ace0000000000000000000000000000000000000000000000000000000000000bee010000000000000000000000000000000000000000000000000000000000000cad00" - ] - }, - "payload_hash": { - "vec": [] - }, - "votes": { - "data": [ - { - "key": "0xbeec980219d246581cef5166dc6ba5fb1e090c7a7786a5176d111a9029b16ddc", - "value": true - } - ] - } - } - ] - ] -} -``` - -
- -## Execute the publication transaction - -Since only Ace has voted on the publication transaction (which he implicitly approved upon proposing) the transaction can't be executed yet: - -```bash filename="Can be executed" -aptos move view \ - --function-id 0x1::multisig_account::can_be_executed \ - --args \ - address:"$multisig_addr" \ - String:1 -``` - -
-Output - -```bash filename="Terminal" -{ - "Result": [ - false - ] -} -``` - -
- -Before Bee votes, however, she verifies that the payload hash stored on-chain matches the publication entry function JSON file: - -```bash filename="Verifying transaction proposal" -aptos multisig verify-proposal \ - --multisig-address $multisig_addr \ - --json-file publication.json \ - --sequence-number 1 -``` - -
-Output - -```bash filename="Terminal" -{ - "Result": { - "Status": "Transaction match", - "Multisig transaction": { - "creation_time_secs": "1685078836", - "creator": "0xacef1b9b7d4ab208b99fed60746d18dcd74865edb7eb3c3f1428233988e4ba46", - "payload": { - "vec": [] - }, - "payload_hash": { - "vec": [ - "0x62b91159c1428c1ef488c7290771de458464bd665691d9653d195bc28e0d2080" - ] - }, - "votes": { - "data": [ - { - "key": "0xacef1b9b7d4ab208b99fed60746d18dcd74865edb7eb3c3f1428233988e4ba46", - "value": true - } - ] - } - } - } -} -``` - -
- -Since Bee has verified that the on-chain payload hash checks out against her locally-compiled package publication JSON file, she votes yes: - -```bash filename="Approving transaction" -aptos multisig approve \ - --multisig-address $multisig_addr \ - --sequence-number 1 \ - --private-key-file bee.key \ - --assume-yes -``` - -
-Output - -```bash filename="Terminal" -{ - "Result": { - "transaction_hash": "0xa5fb49f1077de6aa6d976e6bcc05e4c50c6cd061f1c87e8f1ea74e7a04a06bd1", - "gas_used": 6, - "gas_unit_price": 100, - "sender": "beec980219d246581cef5166dc6ba5fb1e090c7a7786a5176d111a9029b16ddc", - "sequence_number": 1, - "success": true, - "timestamp_us": 1685079892130861, - "version": 528437204, - "vm_status": "Executed successfully" - } -} -``` - -
- -Now the transaction can be executed: - -```bash filename="Can be executed" -aptos move view \ - --function-id 0x1::multisig_account::can_be_executed \ - --args \ - address:"$multisig_addr" \ - String:1 -``` - -
-Output - -```bash filename="Terminal" -{ - "Result": [ - true - ] -} -``` - -
- -Now either Ace or Bee can invoke the publication transaction from the multisig account, passing the full transaction payload since only the hash was stored on-chain: - -```bash filename="Publication" -aptos multisig execute-with-payload \ - --multisig-address $multisig_addr \ - --json-file publication.json \ - --private-key-file bee.key \ - --max-gas 10000 \ - --assume-yes -``` - - -Pending the resolution of [#8304](https://github.com/aptos-labs/aptos-core/issues/8304), the transaction simulator (which is used to estimate gas costs) is broken for multisig transactions, so you will have to manually specify a max gas amount. - - -
-Output - -Also pending the resolution of [#8304](https://github.com/aptos-labs/aptos-core/issues/8304), the CLI output for a successful multisig publication transaction execution results in an API error if only the payload hash has been stored on-chain, but the transaction can be manually verified using an explorer. - -
- -## Execute the governance parameter transaction - -Since only Bee has voted on the governance parameter transaction (which she implicitly approved upon proposing), the transaction can't be executed yet: - -```bash filename="Can be executed" -aptos move view \ - --function-id 0x1::multisig_account::can_be_executed \ - --args \ - address:"$multisig_addr" \ - String:2 -``` - -
-Output - -```bash filename="Terminal" -{ - "Result": [ - false - ] -} -``` - -
- -Before Ace votes, however, he verifies that the payload stored on-chain matches the function arguments he expects: - -```bash filename="Verifying transaction proposal" -aptos multisig verify-proposal \ - --multisig-address $multisig_addr \ - --function-id $multisig_addr::cli_args::set_vals \ - --type-args \ - 0x1::account::Account \ - 0x1::chain_id::ChainId \ - --args \ - u8:123 \ - "bool:[false, true, false, false]" \ - 'address:[["0xace", "0xbee"], ["0xcad"], []]' \ - --sequence-number 2 -``` - -
-Output - -```bash filename="Terminal" -{ - "Result": { - "Status": "Transaction match", - "Multisig transaction": { - "creation_time_secs": "1685078954", - "creator": "0xbeec980219d246581cef5166dc6ba5fb1e090c7a7786a5176d111a9029b16ddc", - "payload": { - "vec": [ - "0x0057478da34604655c68b1dcb89e4f4a9124b6c0ecc1c59a0931d58cc4e60ac5c508636c695f61726773087365745f76616c7302070000000000000000000000000000000000000000000000000000000000000001076163636f756e74074163636f756e740007000000000000000000000000000000000000000000000000000000000000000108636861696e5f696407436861696e49640003017b0504000100006403020000000000000000000000000000000000000000000000000000000000000ace0000000000000000000000000000000000000000000000000000000000000bee010000000000000000000000000000000000000000000000000000000000000cad00" - ] - }, - "payload_hash": { - "vec": [] - }, - "votes": { - "data": [ - { - "key": "0xbeec980219d246581cef5166dc6ba5fb1e090c7a7786a5176d111a9029b16ddc", - "value": true - } - ] - } - } - } -} -``` - -
- -Note that the verification fails if he modifies even a single argument: - -```bash filename="Failed transaction verification with modified u8" -aptos multisig verify-proposal \ - --multisig-address $multisig_addr \ - --function-id $multisig_addr::cli_args::set_vals \ - --type-args \ - 0x1::account::Account \ - 0x1::chain_id::ChainId \ - --args \ - u8:200 \ - "bool:[false, true, false, false]" \ - 'address:[["0xace", "0xbee"], ["0xcad"], []]' \ - --sequence-number 2 -``` - -
-Output - -```bash filename="Terminal" -{ - "Error": "Unexpected error: Transaction mismatch: The transaction you provided has a payload hash of 0xe494b0072d6f940317344967cf0e818c80082375833708c773b0275f3ad07e51, but the on-chain transaction proposal you specified has a payload hash of 0x070ed7c3f812f25f585461305d507b96a4e756f784e01c8c59901871267a1580. For more info, see https://aptos.dev/move/move-on-aptos/cli#multisig-governance" -} -``` - -
- -Ace approves the transaction: - -```bash filename="Approving transaction" -aptos multisig approve \ - --multisig-address $multisig_addr \ - --sequence-number 2 \ - --private-key-file ace.key \ - --assume-yes -``` - -
-Output - -```bash filename="Terminal" -{ - "Result": { - "transaction_hash": "0x233427d95832234fa13dddad5e0b225d40168b4c2c6b84f5255eecc3e68401bf", - "gas_used": 6, - "gas_unit_price": 100, - "sender": "acef1b9b7d4ab208b99fed60746d18dcd74865edb7eb3c3f1428233988e4ba46", - "sequence_number": 7, - "success": true, - "timestamp_us": 1685080266378400, - "version": 528439883, - "vm_status": "Executed successfully" - } -} -``` - -
- -Since the payload was stored on-chain, it is not required to execute the pending transaction: - -```bash filename="Execution" -aptos multisig execute \ - --multisig-address $multisig_addr \ - --private-key-file ace.key \ - --max-gas 10000 \ - --assume-yes -``` - -
-Output - -```bash filename="Terminal" -{ - "Result": { - "transaction_hash": "0xbc99f929708a1058b223aa880d04607a78ebe503367ec4dab23af4a3bdb541b2", - "gas_used": 505, - "gas_unit_price": 100, - "sender": "acef1b9b7d4ab208b99fed60746d18dcd74865edb7eb3c3f1428233988e4ba46", - "sequence_number": 8, - "success": true, - "timestamp_us": 1685080344045461, - "version": 528440423, - "vm_status": "Executed successfully" - -``` - -
\ No newline at end of file diff --git a/apps/nextra/pages/zh/build/guides/_meta.tsx b/apps/nextra/pages/zh/build/guides/_meta.tsx index 4638f8a19..3433630f4 100644 --- a/apps/nextra/pages/zh/build/guides/_meta.tsx +++ b/apps/nextra/pages/zh/build/guides/_meta.tsx @@ -5,24 +5,31 @@ export default { }, "first-transaction": { title: "您的第一笔交易", + href: "/en/build/guides/first-transaction", }, "build-e2e-dapp": { title: "使用 Aptos 构建端到端 Dapp", + href: "/en/build/guides/build-e2e-dapp", }, "your-first-nft": { title: "您的第一个 NFT", + href: "/en/build/guides/your-first-nft", }, "first-coin": { title: "您的第一个代币", + href: "/en/build/guides/first-coin", }, "first-fungible-asset": { title: "您的第一个同质化资产", + href: "/en/build/guides/first-fungible-asset", }, "first-move-module": { title: "您的第一个 Move 模块", + href: "/en/build/guides/first-move-module", }, "first-multisig": { title: "您的第一个多重签名", + href: "/en/build/guides/first-multisig", }, "---advanced---": { type: "separator", @@ -30,18 +37,23 @@ export default { }, "multisig-managed-fungible-asset": { title: "使用多重签名管理同质化资产", + href: "/en/build/guides/multisig-managed-fungible-asset", }, "aptos-keyless": { title: "Aptos 无密钥账户", + href: "/en/build/guides/aptos-keyless", }, "sponsored-transactions": { title: "代付交易", + href: "/en/build/guides/sponsored-transactions", }, "transaction-management": { title: "交易管理", + href: "/en/build/guides/transaction-management", }, "key-rotation": { title: "账户密钥轮换", + href: "/en/build/guides/key-rotation", }, "---integration---": { type: "separator", @@ -49,8 +61,10 @@ export default { }, exchanges: { title: "交易所", + href: "/en/build/guides/integration/exchanges", }, "system-integrators-guide": { title: "应用程序", + href: "/en/build/guides/integration/system-integrators-guide", }, }; diff --git a/apps/nextra/pages/zh/build/guides/aptos-keyless.mdx b/apps/nextra/pages/zh/build/guides/aptos-keyless.mdx deleted file mode 100644 index ed6b82f31..000000000 --- a/apps/nextra/pages/zh/build/guides/aptos-keyless.mdx +++ /dev/null @@ -1,33 +0,0 @@ -# Aptos Keyless - -## Integrate with Aptos Keyless accounts - -- [Introduction](aptos-keyless/introduction.mdx) -- [OIDC Support and Configuration](aptos-keyless/oidc-support.mdx) -- [Integration Guide](aptos-keyless/integration-guide.mdx) -- [Simple Example](aptos-keyless/simple-example.mdx) -- [How Aptos Keyless works](aptos-keyless/how-keyless-works.mdx) -- [Terminology and FAQ](aptos-keyless/other.mdx) - -## Using an IAM Provider? Integrate with Aptos Federated Keyless -- [Federated Keyless](aptos-keyless/federated-keyless.mdx) - -## Example - -Visit this page to learn more [Simple Example](aptos-keyless/simple-example.mdx) - -
- - diff --git a/apps/nextra/pages/zh/build/guides/aptos-keyless/_meta.tsx b/apps/nextra/pages/zh/build/guides/aptos-keyless/_meta.tsx index 0ffcfe2a6..32c009ec0 100644 --- a/apps/nextra/pages/zh/build/guides/aptos-keyless/_meta.tsx +++ b/apps/nextra/pages/zh/build/guides/aptos-keyless/_meta.tsx @@ -1,23 +1,30 @@ export default { introduction: { title: "简介", + href: "/en/build/guides/aptos-keyless/introduction", }, "oidc-support": { title: "OIDC 支持", + href: "/en/build/guides/aptos-keyless/oidc-support", }, "integration-guide": { title: "集成指南", + href: "/en/build/guides/aptos-keyless/integration-guide", }, "simple-example": { title: "简单示例", + href: "/en/build/guides/aptos-keyless/simple-example", }, "how-keyless-works": { title: "Keyless 工作原理", + href: "/en/build/guides/aptos-keyless/how-keyless-works", }, other: { title: "术语和常见问题", + href: "/en/build/guides/aptos-keyless/other", }, "federated-keyless": { title: "联合 Keyless", + href: "/en/build/guides/aptos-keyless/federated-keyless", }, }; diff --git a/apps/nextra/pages/zh/build/guides/aptos-keyless/federated-keyless.mdx b/apps/nextra/pages/zh/build/guides/aptos-keyless/federated-keyless.mdx deleted file mode 100644 index 81b50bd64..000000000 --- a/apps/nextra/pages/zh/build/guides/aptos-keyless/federated-keyless.mdx +++ /dev/null @@ -1,16 +0,0 @@ -# Federated Keyless - -## Federated Keyless - -[AIP-96](https://github.com/aptos-foundation/AIPs/blob/main/aips/aip-96.md): Federated Keyless is an extension of Aptos Keyless to support more OpenID Connect (OIDC) providers, beyond the ones that are allow-listed in `0x1::jwks` via JWK consensus, while maintaining its decentralization. Federated keyless adds support for authenticating users via identity & access management (IAM) providers (e.g. Auth0, AWS Cognito) as long as your project uses a supported IAM provider for user authentication. - -To elaborate further, Federated Keyless enables: - 1. Extension of authentication methods - a. All authentication methods supported by the IAM are available to the dApp including email/SMS OTP and their marketplace of social login integrations like Discord, Naver, X and more. Auth0 marketplace linked [here](https://marketplace.auth0.com/) as an example. - - 2. Compatibility with existing account systems - a. Since IAMs also support custom authentication, it allows an application to bring its own username/password (Cognito [docs](https://docs.aws.amazon.com/cognito/latest/developerguide/amazon-cognito-user-pools-authentication-flow.html), Auth0 [docs](https://auth0.com/blog/Custom-Authentication-With-Auth0/)). An application can start using an existing account system already set up with an IAM or they can migrate their existing account system to an IAM to generate Keyless-compatible JWTs. - -- [Federated Keyless Key Considerations](federated-keyless/key-considerations.mdx) -- [Federated Keyless Integration Guide](federated-keyless/integration-guide.mdx) -- [Federated Keyless FAQs](federated-keyless/other.mdx) diff --git a/apps/nextra/pages/zh/build/guides/aptos-keyless/federated-keyless/_meta.tsx b/apps/nextra/pages/zh/build/guides/aptos-keyless/federated-keyless/_meta.tsx index 8b90f0281..29bd0b642 100644 --- a/apps/nextra/pages/zh/build/guides/aptos-keyless/federated-keyless/_meta.tsx +++ b/apps/nextra/pages/zh/build/guides/aptos-keyless/federated-keyless/_meta.tsx @@ -1,11 +1,14 @@ export default { "key-considerations": { title: "关键考虑因素", + href: "/en/build/guides/aptos-keyless/key-considerations", }, "integration-guide": { title: "集成指南", + href: "/en/build/guides/aptos-keyless/integration-guide", }, other: { title: "常见问题", + href: "/en/build/guides/aptos-keyless/other", }, }; diff --git a/apps/nextra/pages/zh/build/guides/aptos-keyless/federated-keyless/integration-guide.mdx b/apps/nextra/pages/zh/build/guides/aptos-keyless/federated-keyless/integration-guide.mdx deleted file mode 100644 index 6a897d6c6..000000000 --- a/apps/nextra/pages/zh/build/guides/aptos-keyless/federated-keyless/integration-guide.mdx +++ /dev/null @@ -1,73 +0,0 @@ ---- -title: "Federated Keyless Integration Guide" ---- -import { Callout, Steps } from "nextra/components"; - -# Federated Keyless Integration Guide - - -### Step 1. Setup your IAM provider - -Set up your project with your IAM to match the account structure you are looking for. - -- [Getting Started with AWS Cognito](https://aws.amazon.com/cognito/getting-started/) -- [Getting Started with Auth0](https://auth0.com/docs/get-started) - -### Step 2. Register the JSON Web Key Set (JWKS) on-chain - -Federated Keyless accounts require the JWKS to be registered on-chain. - -To register the JWKS - call the `0x1::jwks::update_federated_jwk_set` entry function with an Aptos account that will store the JWKs that will be used to validate transactions signed by federated keyless accounts. - - -**Losing access to the JWK owner account compromises the Federated Keyless accounts created with it** - -The JWK owner account is the only account that can update the JWKS. If you lose access to the JWK owner account, you will not be able to update the JWKS and the Federated Keyless accounts created with it will stop working in the case of a key rotation. Users will be unable to validate their JWT tokens as they will be signed with the new key whos public key is not registered on the Aptos blockchain. - - -The JWK set can be found as follows - - -AWS Cognito - `https://cognito-idp..amazonaws.com//.well-known/jwks.json` -Auth0 - `https:///.well-known/jwks.json` - -The typescript SDK contains functionality to simplify the process given the issuer for your IAM provider setup (the `iss` claim value on your user’s JWT tokens) and an account to use to make the update. - -```tsx -import {Aptos} from '@aptos-labs/ts-sdk'; // Requires version v1.29.1 or later - -const aptos = new Aptos(new AptosConfig({ network: Network.DEVNET })); // Configure your network here -const alice = // Derive your Aptos account here -const jwkTxn = await aptos.updateFederatedKeylessJwkSetTransaction({ sender: alice, iss }); -await aptos.signAndSubmitTransaction({ signer: alice, transaction: jwkTxn }); -``` - -You can use the interactive example provided by the SDK to easily register the JWKS for your IAM provider in devnet or testnet. This will setup the JWK owner account with a Google Keyless account. - -```bash -git clone https://github.com/aptos-labs/aptos-ts-sdk -cd aptos-ts-sdk -pnpm install && pnpm build -cd examples/typescript -pnpm install -pnpm jwk_update -``` - -To setup the JWK owner account in mainnet, you will need create an account and use it to register the JWKS. - -Save the address of the account you used to register the JWKS as you will need it for the next step. - -To learn more about the `0x1::jwks::update_federated_jwk_set` entry function, see the [reference documentation](https://aptos.dev/en/build/smart-contracts/move-reference?page=aptos-framework%2Fdoc%2Fjwks.md#0x1_jwks_update_federated_jwk_set). - - -**Handling key rotations** - -Whenever there is a key rotation of the JWKS, it is important to update the JWKS registered on chain promptly to avoid any loss of access to Federated Keyless accounts. See [here](key-considerations.mdx) for more info. - - -### Step 3. Follow the Aptos Keyless integration guide - -Now that you have registered the JWKS, you can follow the Aptos Keyless integration guide starting from step 2. Be sure to set the `jwkAddress` to the address of the account you used to register the JWKS when deriving the `KeylessAccount`. - -[Aptos Keyless Integration Guide - Step 2](../integration-guide.mdx#step-2-install-the-aptos-typescript-sdk) - - diff --git a/apps/nextra/pages/zh/build/guides/aptos-keyless/federated-keyless/key-considerations.mdx b/apps/nextra/pages/zh/build/guides/aptos-keyless/federated-keyless/key-considerations.mdx deleted file mode 100644 index 5cf5eaf56..000000000 --- a/apps/nextra/pages/zh/build/guides/aptos-keyless/federated-keyless/key-considerations.mdx +++ /dev/null @@ -1,36 +0,0 @@ ---- -title: "Federated Keyless Key Considerations" ---- - -## Federated Keyless Key Considerations - -**Supported IAMs** - -Currently, the supported IAMs are Amazon Cognito and Auth0 across devnet, testnet, and mainnet. See a table of the full set of supported IAM providers [here](../oidc-support.mdx). - -**Federated Keyless flow** - -The flow for Federated Keyless transactions is the same as described [here](../how-keyless-works.mdx). However, the difference is that in Federated Keyless, instead of the OIDC provider (e.g. Google, Apple) acting as the issuer of the JWT, the IAM provider (e.g. Auth0, Cognito) acts as the issuer. The user authenticates with the application, the IAM receives the user’s credentials, and then the IAM issues the Keyless-compatible JWT. - -**Available authentication methods** - -All authentication methods that are supported by the IAM providers are available for use - this includes SMS OTP, email link, and the traditional username + password. - -**Configuration limitations** - -A Keyless account address varies according to the `aud` (AKA application ID or client ID), and `iss` (AKA issuer). The setup of your user data within the IAM must reflect the interoperability you seek to provide to your users. JWT tokens issued for a user in the same user pool but for different applications will result in a different address derivation if the `aud` value is different. - -**JSON Web Key Set management** - -If you or the IAM platform rotates the key pairs used to signed the JWT tokens, the JWK set must be updated on chain using the same account used to instantiate your app's Federated Keyless accounts. As such it is vital to - - -1. Maintain access to your JWKS owner account -2. Update the JWK set on chain whenever a key rotation occurs - -When a keypair is rotated existing keyless account instantiations will continue to work so long as the old JWK has not been removed. Any new JWTs issued by the new keypair will not be accepted until the JWK set on chain is updated to contain its public key. - -**The trust and security model for Federated Keyless** - -Compared to the existing Keyless implementation, dApp developers utilizing Federated Keyless alongside certain authentication methods like email/SMS, OTP and email/password may have more access to user credentials when leveraging IAM providers than with the existing direct OIDC provider integrations. - -We recommend each dApp developer perform their own research and consult with their legal counsel before integrating an authentication method. Developers should also understand to what extent they may have access to user credentials and what controls they have in place. diff --git a/apps/nextra/pages/zh/build/guides/aptos-keyless/federated-keyless/other.mdx b/apps/nextra/pages/zh/build/guides/aptos-keyless/federated-keyless/other.mdx deleted file mode 100644 index f3c23c66b..000000000 --- a/apps/nextra/pages/zh/build/guides/aptos-keyless/federated-keyless/other.mdx +++ /dev/null @@ -1,23 +0,0 @@ ---- -title: "FAQ" ---- - -## Federated Keyless FAQs - -**What if I stop using my IAM for my application? What if I switch IAM providers?** - -- An account address depends on values of several variables that are specific to an IAM service, including `aud` (client ID) and `iss` (issuer). If these values are changed, then a different address will be derived. -- If you want to switch IAM providers, you will need to develop an account migration flow, resulting in a key rotation from the account derived from the prior IAM provider to the account derived from the new IAM provider. -- We recommend allowing your users to add a secondary authentication method to their accounts (e.g., back-up private key) so that they can maintain access should the authentication path into their account via Federated Keyless be disrupted via a service provider change. In order to implement this, you need to do a key rotation to a multikey account. For relevant documentation see [key rotation](https://aptos.dev/en/build/guides/key-rotation) and [multikey SDK](https://aptos-labs.github.io/aptos-ts-sdk/@aptos-labs/ts-sdk-1.35.0/classes/MultiKeyAccount.html). - -**Does using an IAM cost money?** - -- Yes, IAMs usually cost money, but they can help provide useful functionality within your application such as role-based access control (authorization), user management, user authentication, security + compliance, and analytics + monitoring. - -**In the case the dApp or IAM provider goes offline, how do I make sure my users can continue accessing their accounts?** - -- We recommend allowing your users to add a secondary authentication method to their accounts (e.g., back-up private key) so that they can maintain access should the authentication path into their account via Federated Keyless is disrupted via service provider change or other outage. - -**I use an open source IAM like Keycloak. Can I use Federated Keyless?** - -- Not today. Due to the trust placed in the IAM to have sufficient uptime and security standards, we have limited the accepted IAM set to the currently supported issuers. If you believe your provider should be included for consideration, please consider raising an AIP or contact us in the Keyless developers [telegram](https://t.me/+h5CN-W35yUFiYzkx). diff --git a/apps/nextra/pages/zh/build/guides/aptos-keyless/how-keyless-works.mdx b/apps/nextra/pages/zh/build/guides/aptos-keyless/how-keyless-works.mdx deleted file mode 100644 index 48b119582..000000000 --- a/apps/nextra/pages/zh/build/guides/aptos-keyless/how-keyless-works.mdx +++ /dev/null @@ -1,90 +0,0 @@ ---- -title: "How Keyless Works" ---- -import { YouTube } from "@components/index"; - -# How Keyless Works - -Aptos Keyless enables a dApp to **derive** and **access** a blockchain account for a user who successfully signed in to the dApp via an OIDC provider (e.g., Google). Importantly, this blockchain account is **scoped to the dApp**. This means other dApps, who can similarly sign-in the same user, via the same OIDC provider, are not able to access this account and instead get their own account. - -_But how does this work?_ - - - -## Overview - -At a very high level, a successful sign-in into the dApp via the OIDC provider will result in the dApp receiving a **JSON Web Token (JWT)** signed by the OIDC provider. The JWT will contain, among other things, three important pieces of information: - -1. The user’s identity (contained in the JWT’s `sub` field) -2. The dApp’s identity (contained in the JWT’s `aud` field) -3. Application-specific data; specifically, an **ephemeral public key (EPK)** (contained in the JWT’s `nonce` field), whose associated **ephemeral secret key (ESK)** only the user knows. - -Now, assume that the user’s blockchain account address is (more or less) a hash of the user’s identity in `sub` and the dApp’s identity in `aud` from above. - -Then, the **key observation** is that the signed JWT effectively acts as a **digital certificate**, **temporarily** binding this blockchain address to the EPK, and allowing the EPK to sign TXNs for it. In other words, it securely delegates TXN signing rights for this blockchain account to the EPK. (Note: The EPK contains an expiration date and is thus short-lived.) - -Importantly, if the user loses their ESK, the user can obtain a new signed JWT over a new EPK via the application by simply signing in again via the OIDC provider. (Or, in some cases, by requesting a new signed JWT using an OAuth refresh token.) - -With this system, the **challenge** is maintaining privacy, since revealing the JWT on-chain would leak the user’s identity. Furthermore, revealing the EPK to the OIDC provider would allow it to track the user’s TXNs on-chain. - -We explain below how Keyless accounts work and how they address these challenges. - -## Flow: Deriving a keyless account for a user in a dApp - -First, let us look at how a dApp can sign-in a user via (say) Google, derive that user’s keyless blockchain address and, for example, send that user an asset. - -![Keyless account diagram](/docs/aptos-keyless/keyless-account.png "Keyless account diagram") - -**Step 1**: The user generates an ephemeral key pair: an EPK with an expiration date, and its associated ESK. The dApp keeps the EPK and safely stores the ESK on the user-side (e.g., in the browser’s local storage, or in a trusted enclave if the ESK is a WebAuthn passkey). - -**Step 2**: The dApp commits to the EPK as $H(\mathsf{epk}, \rho)$, where $\rho$ is a blinding factor. When the user clicks on the “Sign in with Google” button, the dApp redirects the user to Google’s sign in page and, importantly, sets the `nonce` parameter in the URL to this EPK commitment. This hides the EPK from Google, maintaining privacy of the user’s TXN activity. - -**Step 3**: Typically, the user has an HTTP cookie from having previously-signed-in to their Google account, so Google merely checks this cookie. If the user has multiple Google accounts, Google asks the user to select which one they want to sign-in into the dApp. (The less common path is for the user to have to type in their Google username and password.) - -**Step 4**: Once the user has signed in, Google sends the dApp a signed JWT, which includes the user's `sub` identifier (e.g., `uid-123`), the application’s `aud` identifier (e.g., `"dapp-xyz"`) and the `nonce` with the EPK commitment. (This assumes that the dApp has previously registered with Google and received this `"dapp-xyz"` identifier.) - -**Step 5**: The dApp now has almost everything it needs to derive a keyless account for the user: the user’s identifier (`sub`) and the dApp’s identifier (`aud`). But, to preserve the privacy of the user, the dApp will use a third piece of information: a blinding factor $r$ called a **pepper**. The dApp will contact a so-called **guardian** who will deterministically derive a random $r$ from the given (`sub`, `aud`). Importantly, the guardian will only reveal $r$ to the dApp upon seeing a validly-signed JWT for the queried (`sub`, `aud`). - -**Step 6**: The dApp derives the address of the account as $\mathsf{addr} = H(\texttt{"uid-123"}, \texttt{"dapp-xyz"}, r)$, where $H$ is a cryptographic hash function. - -Note that the pepper $r$ is used to hide the user and app identity inside the address since, as we described above, only an authorized user with a valid JWT will be able to obtain this pepper. - -Also, note that the address is independent of the EPK. This is why the ESK need not be long-lived and can be lost. - -Finally, the dApp can, for example, send an NFT to the user at their address $\mathsf{addr}$. - -But how can the dApp authorize TXN from this account at $\mathsf{addr}$? We discuss that next. - -## Flow: Obtaining a zero-knowledge proof before transacting - -In the previous flow, we showed how a dApp can sign in a Google user and derive their privacy-preserving keyless address, with the help of a guardian. - -Next, we show how this dApp can obtain a zero-knowledge proof (ZKP), which will allow it to authorize transactions from this address for the user. Importantly, the transaction will hide the user’s identifying information (e.g., the `sub` field). - -![Keyless proof diagram](/docs/aptos-keyless/keyless-proof.png "Keyless proof diagram") - -**Step 1**: The dApp sends all the necessary public information (i.e., $\mathsf{epk}$, $\mathsf{GPK}$) and private information (i.e., JWT, signature $\sigma_G$ from Google, EPK blinding factor $\rho$, and pepper $r$) to the **prover service**. - -**Step 2**: The prover derives the user’s address $\mathsf{addr}$ and computes a zero-knowledge proof (ZKP) $\pi$ for the keyless relation $\mathcal{R}_\mathsf{keyless}$ (described below). This proof acts as a **privacy-preserving** digital certificate, and binds the user's address $\mathsf{addr}$ to the ephemeral public key $\mathsf{epk}$. The prover then sends $\pi$ to the dApp. - -In order to bind the $\mathsf{epk}$ with the user's address $\mathsf{addr}$, the ZKP will be used to convince the validators that the user is in possession of (1) a JWT signed by Google, (2) which commits to the $\mathsf{epk}$ in its `nonce` field, and (3) contains the same information as in the address, without leaking anything about the JWT, its signature $\sigma_G$, $\rho$, or $r$. - -More formally, the ZKP $\pi$ convinces a verifier (i.e., the blockchain), who has public inputs $(\mathsf{addr}, \mathsf{epk}, \mathsf{GPK})$, that the prover knows secret inputs $(\mathsf{jwt}, \sigma_G, \rho, r)$ such that the relation $\mathcal{R}_\mathsf{keyless}$ depicted below holds: - -![Keyless relation diagram](/docs/aptos-keyless/keyless_relation.png "Keyless relation diagram") - -Recall from before that the signed JWT itself binds the blockchain address $\mathsf{addr}$ to $\mathsf{epk}$, so that $\mathsf{epk}$ can sign transactions for $\mathsf{addr}$. However, the JWT would leak the user’s identity, so the ZKP serves to hide the JWT (and other private information) while arguing that the proper checks hold (i.e., the checks in $\mathcal{R}_\mathsf{keyless}$). - -Next, we show how the dApp can now authorize TXNs from $\mathsf{addr}$. - -## Flow: Sending a TXN from a keyless account - -The previous flow explained how a dApp can obtain a ZKP from the prover service. Next, we describe how the dApp leverages this ZKP to transact for the account. - -![Keyless signing diagram](/docs/aptos-keyless/keyless-signing.png "Keyless signing diagram") - -**Step 1**: The dApp obtains an ephemeral signature $\sigma_\mathsf{eph}$ over the TXN from the user. This could be done behind the user’s back, by the dApp itself who might manage the ESK. Or, it could be an actual signing request sent to the user, such as when the ESK is a WebAuthn passkey, which is stored on the user’s trusted hardware. - -**Step 2**: The dApp sends the TXN, the ZKP $\pi$, the ephemeral public key $\mathsf{epk}$, and the ephemeral signature $\sigma_\mathsf{eph}$ to the blockchain validators. - -**Step 3**: To check the TXN is validly-signed, the validators perform several steps: (1) check that $\mathsf{epk}$ has not expired, (2) fetch the user’s address $\mathsf{addr}$ from the TXN, (3) verify the ZKP against $(\mathsf{addr}, \mathsf{epk}, \mathsf{GPK})$, and (4) verify the ephemeral signature $\sigma_\mathsf{eph}$ on the TXN against the $\mathsf{epk}$. If all these checks pass, they can safely execute the TXN. diff --git a/apps/nextra/pages/zh/build/guides/aptos-keyless/integration-guide.mdx b/apps/nextra/pages/zh/build/guides/aptos-keyless/integration-guide.mdx deleted file mode 100644 index 7ee3cc89e..000000000 --- a/apps/nextra/pages/zh/build/guides/aptos-keyless/integration-guide.mdx +++ /dev/null @@ -1,277 +0,0 @@ ---- -title: "Keyless Integration Guide" ---- -import { Callout, Steps } from "nextra/components"; - -# Aptos Keyless Integration Guide - - - **Keyless Account Scoping** - - Use of the **_Aptos Keyless Integration Guide_** will allow for the integration of keyless accounts directly into your application. This means that blockchain accounts are scoped to your application's domain (logging in with your Google account on dApp A and logging in with your Google account on dApp B will create separate accounts). Stay tuned for more to come on Aptos’ plan to allow Keyless accounts to be used portably across applications. - - To provide feedback, get support, or be a design partner as we enhance Aptos Keyless, join us here: https://t.me/+h5CN-W35yUFiYzkx - - -At a high level, there are three steps to follow in order to integrate Keyless Accounts. - -1. **Configure your OpenID integration with your IdP.** In this step, the dApp will register with the IdP of choice (e.g. Google) and receive a `client_id` -2. **Install the Aptos TypeScript SDK.** -3. **Integrate Keyless Account support in your application client** - 1. Set up the `"Sign In with [Idp]"` flow for your user. - 2. Instantiate the user’s `KeylessAccount` - 3. Sign and submit transactions via the `KeylessAccount`. - -## Example Implementaion - -You can find an example app demonstrating basic Keyless integration with Google in the [aptos-keyless-example repository](https://github.com/aptos-labs/aptos-keyless-example/). Follow the directions in the README to start with the example. For more detailed instructions on keyless, please read the rest of this integration guide. - - -### Step 1. Configure your OpenID integration with your IdP - -The first step is to setup the configuration with your IdP(s). - -[Follow the intructions here](oidc-support.mdx) - -### Step 2. Install the Aptos TypeScript SDK - -```bash -# Keyless is supported in version 1.18.1 and above -pnpm install @aptos-labs/ts-sdk -``` - -### Step 3. Client Integration Steps - -Below are the default steps for a client to integrate Keyless Accounts - -#### 1. Present the user with a "Sign In with [IdP]" button on the UI - - 1. In the background, we create an ephemeral key pair. Store this in local storage. - - ```ts - import {EphemeralKeyPair} from '@aptos-labs/ts-sdk'; - - const ephemeralKeyPair = EphemeralKeyPair.generate(); - ``` - - 2. Save the `EphemeralKeyPair` in local storage, keyed by its `nonce`. - - ```ts - // This saves the EphemeralKeyPair in local storage - storeEphemeralKeyPair(ephemeralKeyPair); - ``` - -
-Example implementation for `storeEphemeralKeyPair` - - -This implementation is an example of how to store the `EphemeralKeyPair` in local storage. Different implementations may be used according to your application's needs. - - -```ts filename="ephemeral.ts" - -/** - * Store the ephemeral key pair in localStorage. - */ -export const storeEphemeralKeyPair = (ekp: EphemeralKeyPair): void => - localStorage.setItem("@aptos/ekp", encodeEphemeralKeyPair(ekp)); - -/** - * Retrieve the ephemeral key pair from localStorage if it exists. - */ -export const getLocalEphemeralKeyPair = (): EphemeralKeyPair | undefined => { - try { - const encodedEkp = localStorage.getItem("@aptos/ekp"); - return encodedEkp ? decodeEphemeralKeyPair(encodedEkp) : undefined; - } catch (error) { - console.warn( - "Failed to decode ephemeral key pair from localStorage", - error - ); - return undefined; - } -}; - -/** - * Stringify the ephemeral key pairs to be stored in localStorage - */ -export const encodeEphemeralKeyPair = (ekp: EphemeralKeyPair): string => - JSON.stringify(ekp, (_, e) => { - if (typeof e === "bigint") return { __type: "bigint", value: e.toString() }; - if (e instanceof Uint8Array) - return { __type: "Uint8Array", value: Array.from(e) }; - if (e instanceof EphemeralKeyPair) - return { __type: "EphemeralKeyPair", data: e.bcsToBytes() }; - return e; - }); - -/** - * Parse the ephemeral key pairs from a string - */ -export const decodeEphemeralKeyPair = (encodedEkp: string): EphemeralKeyPair => - JSON.parse(encodedEkp, (_, e) => { - if (e && e.__type === "bigint") return BigInt(e.value); - if (e && e.__type === "Uint8Array") return new Uint8Array(e.value); - if (e && e.__type === "EphemeralKeyPair") - return EphemeralKeyPair.fromBytes(e.data); - return e; - }); -``` - -
- - 3. Prepare the URL params of the login URL. Set the `redirect_uri` and `client_id` to your configured values with the IdP. Set the `nonce` to the nonce of the `EphemeralKeyPair` from step 1.1. - - ```ts - const redirectUri = 'https://.../login/callback' - const clientId = env.IDP_CLIENT_ID - // Get the nonce associated with ephemeralKeyPair - const nonce = ephemeralKeyPair.nonce - ``` - - 4. Construct the login URL for the user to authenticate with the IdP. Make sure the `openid` scope is set. Other scopes such as `email` and `profile` can be set based on your app’s needs. - - ```ts - const loginUrl = `https://accounts.google.com/o/oauth2/v2/auth?response_type=id_token&scope=openid+email+profile&nonce=${nonce}&redirect_uri=${redirectUri}&client_id=${clientId}` - ``` - - 5. When the user clicks the login button, redirect the user to the `loginUrl` that was created in step 1.4. - -#### 2. Handle the callback by parsing the token and create a Keyless account for the user - - 1. Once the user completes the login flow, they will be redirected to the `redirect_uri` set in step 1. The JWT will be set in the URL as a search parameter in a URL fragment, keyed by `id_token`. Extract the JWT from the `window` by doing the following: - - ```ts - const parseJWTFromURL = (url: string): string | null => { - const urlObject = new URL(url); - const fragment = urlObject.hash.substring(1); - const params = new URLSearchParams(fragment); - return params.get('id_token'); - }; - - // window.location.href = https://.../login/google/callback#id_token=... - const jwt = parseJWTFromURL(window.location.href) - ``` - - 2. Decode the JWT and get the extract the nonce value from the payload. - - ```ts - import { jwtDecode } from 'jwt-decode'; - - const payload = jwtDecode<{ nonce: string }>(jwt); - const jwtNonce = payload.nonce - ``` - - 3. Fetch the `EphemeralKeyPair` stored in step 1.2. Make sure to validate the nonce matches the decoded nonce and that the `EphemeralKeyPair` is not expired. - - ```ts - const ekp = getLocalEphemeralKeyPair(); - - // Validate the EphemeralKeyPair - if (!ekp || ekp.nonce !== jwtNonce || ekp.isExpired() ) { - throw new Error("Ephemeral key pair not found or expired"); - } - ``` - - 4. Instantiate the user’s `KeylessAccount` - - Depending on the type of Keyless you are using, follow the instructions below: - - 1. Normal Keyless - ```tsx - import {Aptos, AptosConfig, Network} from '@aptos-labs/ts-sdk'; - - const aptos = new Aptos(new AptosConfig({ network: Network.DEVNET })); // Configure your network here - const keylessAccount = await aptos.deriveKeylessAccount({ - jwt, - ephemeralKeyPair, - }); - ``` - - 2. Federated Keyless - ```tsx - import {Aptos, AptosConfig, Network} from '@aptos-labs/ts-sdk'; - - const aptos = new Aptos(new AptosConfig({ network: Network.DEVNET })); // Configure your network here - const keylessAccount = await aptos.deriveKeylessAccount({ - jwt, - ephemeralKeyPair, - jwkAddress: jwkOwner.accountAddress - }); - ``` - - -#### 3. Store the KeylessAccount in local storage (Optional) - - 1. After the account has been derived, store the `KeylessAccount` in local storage. This allows the user to return to the application without having to re-authenticate. - - ```ts filename="keyless.ts" - export const storeKeylessAccount = (account: KeylessAccount): void => - localStorage.setItem("@aptos/account", encodeKeylessAccount(account)); - - export const encodeKeylessAccount = (account: KeylessAccount): string => - JSON.stringify(account, (_, e) => { - if (typeof e === "bigint") return { __type: "bigint", value: e.toString() }; - if (e instanceof Uint8Array) - return { __type: "Uint8Array", value: Array.from(e) }; - if (e instanceof KeylessAccount) - return { __type: "KeylessAccount", data: e.bcsToBytes() }; - return e; - }); - ``` - - 2. Whenever the user returns back to the application, retrieve the `KeylessAccount` from local storage and use it to sign transactions. - - ```ts filename="keyless.ts" - export const getLocalKeylessAccount = (): KeylessAccount | undefined => { - try { - const encodedAccount = localStorage.getItem("@aptos/account"); - return encodedAccount ? decodeKeylessAccount(encodedAccount) : undefined; - } catch (error) { - console.warn( - "Failed to decode account from localStorage", - error - ); - return undefined; - } - }; - - export const decodeKeylessAccount = (encodedAccount: string): KeylessAccount => - JSON.parse(encodedAccount, (_, e) => { - if (e && e.__type === "bigint") return BigInt(e.value); - if (e && e.__type === "Uint8Array") return new Uint8Array(e.value); - if (e && e.__type === "KeylessAccount") - return KeylessAccount.fromBytes(e.data); - return e; - }); - ``` - - -#### 4. Submit transactions to the Aptos blockchain - - 1. Create the transaction you want to submit. Below is a simple coin transfer transaction for example: - - ```tsx - import {Account} from '@aptos-labs/ts-sdk'; - - const bob = Account.generate(); - const transaction = await aptos.transferCoinTransaction({ - sender: keylessAccount.accountAddress, - recipient: bob.accountAddress, - amount: 100, - }); - ``` - - 2. Sign and submit the transaction to the chain. - - ```tsx - const committedTxn = await aptos.signAndSubmitTransaction({ signer: keylessAccount, transaction }); - ``` - - 3. Wait for the transaction to be processed on-chain - - ```tsx - const committedTransactionResponse = await aptos.waitForTransaction({ transactionHash: committedTxn.hash }); - ``` - -
\ No newline at end of file diff --git a/apps/nextra/pages/zh/build/guides/aptos-keyless/introduction.mdx b/apps/nextra/pages/zh/build/guides/aptos-keyless/introduction.mdx deleted file mode 100644 index c2c8b0e96..000000000 --- a/apps/nextra/pages/zh/build/guides/aptos-keyless/introduction.mdx +++ /dev/null @@ -1,25 +0,0 @@ ---- -title: "Keyless Introduction" ---- - -# Introduction - -Keyless accounts represent a pivotal advancement within the Aptos ecosystem, revolutionizing the way users onboard and interact with decentralized applications (dApps). Aptos Keyless allows users to gain ownership of a **self-custodial** Aptos blockchain account from their existing OpenID Connect (OIDC) account(s) (e.g., Sign in with Google; Sign in with Apple), rather than from a traditional secret key or mnemonic. In a nutshell, with Aptos Keyless, a user’s blockchain account is their OIDC account. Over time, Keyless will evolve to support many IdPs who support the OIDC standard, but we will begin with support for the providers listed [here](oidc-support.mdx). - -At the core of the keyless accounts paradigm lies a deep understanding of user experience and security challenges prevalent in traditional blockchain systems. Managing private keys, the cornerstone of user identity and asset ownership, often proves cumbersome and error-prone for users, particularly those lacking technical expertise. Keyless accounts offer an elegant solution by obviating the need for users to grapple with the intricacies of private key management. Instead, users authenticate themselves through access to common social sign in options like Google, Apple, and many more. With this new system comes some important tradeoffs to understand on behalf of your users before implementing Keyless in your application. The following pages will expand on the benefits of Keyless accounts, how to integrate, the system architecture, and FAQs. For a more verbose and technical dive into Keyless accounts, please see [AIP-61-Keyless Accounts](https://github.com/aptos-foundation/AIPs/blob/main/aips/aip-61.md). - -There are two ways to interact with Keyless accounts in the Aptos ecosystem. Developers are able to either 1) integrate the Aptos Keyless SDK directly into their dApp or 2) integrate a wallet, like Aptos Connect, that supports Keyless account creation. This documentation will focus on case #1 and more details on #2 can be found [here](https://aptosconnect.app/docs/). Please note that a direct integration of the Keyless SDK will result in user accounts being domain specific to your dApp whereas the use of a wallet integration will allow your users to carry their accounts to any application that supports that wallet. - -Note: the Aptos Keyless SDK and Aptos Connect are representative examples of the aforementioned product experience, but developers in our ecosystem are building alternatives, like a Keyless Unity SDK and alternative wallet products with Keyless integration. - -## Aptos Keyless Benefits - -Keyless accounts are revolutionary to users for the following reasons: - -1. **Simplified login user experience**: "1-click" account creation via familiar Web2 logins like Sign In with Google. -2. **Enhanced dApp user experience**: Ability to transact on the Aptos blockchain without needing to navigate away from the application experience to download a wallet. -3. **Secure key management**: Requires no manual secret key management by the user. Users sign transactions with the JSON Web Token (JWT) token issued by OIDC providers. As such, blockchain account access is synonymous with access to one’s OIDC account -4. **Improved account recovery**: Web2-like recovery flows are available to regain access to one’s blockchain account in case the user ever loses access to their OIDC account. -5. **Seamless cross-device experiences**: Users log in with their OIDC account no matter what device they are on - no need to download wallet software on each device, import their keys and encrypt them with a password, which must be maintained. - -With these benefits, come some important structural components of Keyless accounts for developers to be aware of. You can see more on this in our FAQs. diff --git a/apps/nextra/pages/zh/build/guides/aptos-keyless/oidc-support.mdx b/apps/nextra/pages/zh/build/guides/aptos-keyless/oidc-support.mdx deleted file mode 100644 index 735da2bc2..000000000 --- a/apps/nextra/pages/zh/build/guides/aptos-keyless/oidc-support.mdx +++ /dev/null @@ -1,112 +0,0 @@ ---- -title: "Keyless OIDC Support" ---- - -import { Callout, Steps } from "nextra/components"; - -# Configure Your OIDC Provider - -Aptos Keyless supports the following IdPs and IAM providers on our network(s). Support for additional IdPs to come. Please reach out if you have need for coverage for a specific use case. - -| Identity Provider | Federated Only | Devnet | Testnet | Mainnet | -|-------------------|----------------|-----------|----------|----------| -| Google | No | Live | Live | Live | -| Apple | No | Live | Live | Live | -| Auth0 | Yes | Live | Live | Live | -| Cognito | Yes | Live | Live | Live | -| Firebase | Yes | In review | - | - | -| Microsoft | No | In review | - | - | -| Github | No | In review | - | - | -| Facebook | No | In review | - | - | - -If your identity provider is marked as "Federated Only", you will need to follow the instructions for [Federated Keyless](../aptos-keyless/federated-keyless.mdx). - -To integrate Aptos Keyless into your dApp, you must register your dApp with at least one of the available identity providers via their OIDC registration process. Each respective registration process will assign a Client ID to your application, which will serve as an identifier for your application in the Keyless architecture. - -## Registering your dApp with Google - - - -### Step 1: Sign in to Google Developer Console - -1. Navigate to the [Google Cloud Console](https://console.cloud.google.com/). -2. Sign in with your Google account credentials. - -### Step 2: Create a New Project - -1. If you don't have an existing project, click on the "Select a project" dropdown menu at the top of the page and choose "New Project." -2. Enter a name for your project and click "Create." Detailed instructions can be found [here](https://cloud.google.com/resource-manager/docs/creating-managing-projects#creating_a_project). - -### Step 3: Configure Consent Screen - -1. In the left sidebar, navigate to "APIs & Services" > "OAuth consent screen." -2. Choose "External" user type and click "Create." -3. Enter the required details such as the application name, user support email, and developer contact information. -4. Optionally, add additional details like the application logo and privacy policy URL. -5. Click "Save and continue." Detailed steps are available [here](https://developers.google.com/workspace/guides/create-credentials#configure_the_oauth_consent_screen). - -### Step 4: Register Your Application - -1. In the left sidebar, navigate to "APIs & Services" > "Credentials." - ![Google Credentials navigation screenshot](/docs/aptos-keyless/google-credentials-nav.png "Google Credentials navigation screenshot") - -2. Click on "Create Credentials" and select "OAuth client ID." - ![Google create credentials screenshot](/docs/aptos-keyless/google-create-credentials.png "Google create credentials screenshot") - -3. Choose the application type (e.g., Web application, Desktop app, or Mobile app). -4. Enter the necessary details such as the name of your application and the authorized redirect URIs. For OIDC, the redirect URIs should follow the format https://your-app-domain.com/auth/google/callback. -5. Click "Create." - -### Step 5: Obtain Client ID and Client Secret - -1. After creating the OAuth client ID, Google will provide you with a client ID and client secret. These credentials are essential for authenticating your application. -2. Note down the client ID and client secret securely. Do not expose them publicly. - -### Step 6: Configure OIDC Integration in Your Application - -1. Integrate OIDC authentication into your application using a suitable OIDC library or framework (e.g., Passport.js for Node.js, Spring Security for Java, or Auth0 for various platforms). -2. Use the client ID and client secret obtained from Google to configure OIDC authentication in your application settings. -3. Set up the appropriate callback URL (https://your-app-domain.com/auth/google/callback) for handling authentication responses from Google. - - - -## Registering your dApp with Apple - - - -### Step 1: Sign in to Apple Developer Account - -1. Go to the [Apple Developer website](https://developer.apple.com/). -2. Sign in with your Apple ID. -3. Enroll in the Apple Developer Program if not already. - ![Apple developer program enrollment screenshot](/docs/aptos-keyless/apple-dev-program.png "Apple developer program enrollment screenshot") - -### Step 2: Create a New App ID - -1. Navigate to the "Certificates, Identifiers & Profiles" section. -2. Click on "Identifiers" in the sidebar. -3. Click the "+" button to create a new App ID. -4. Fill in the details for your app, including the name and bundle ID. -5. Enable "Sign in with Apple" under the "Capabilities" section. -6. Click "Continue" and then "Register" to create the App ID. - -### Step 3: Generate a Private Key - -1. In the "Keys" section of the "Certificates, Identifiers & Profiles" page, click the "+" button to create a new key. -2. Enter a name for the key, enable the "Sign in with Apple" capability, and click "Continue." -3. Download the generated private key and securely store it. This key will be used to authenticate your app with Apple's OIDC service. - -### Step 4: Configure Redirect URIs - -1. Under the "App ID" section, locate your newly created App ID and click on it. -2. Scroll down to the "Sign in with Apple" section and click on "Edit." -3. Add the redirect URIs that your application will use for callback after authentication. The format should be https://your-app-domain.com/auth/apple/callback. -4. Click "Save" to update the settings. - -### Step 5: Set Up Your OIDC Integration - -1. Use an OIDC library or framework compatible with Apple's OIDC service (e.g., Passport.js for Node.js, Spring Security for Java). -2. Configure your application to use the client ID and private key obtained from Apple during the registration process. -3. Set up the appropriate callback URL (https://your-app-domain.com/auth/apple/callback) for handling authentication responses from Apple. - - diff --git a/apps/nextra/pages/zh/build/guides/aptos-keyless/other.mdx b/apps/nextra/pages/zh/build/guides/aptos-keyless/other.mdx deleted file mode 100644 index 3f3e39822..000000000 --- a/apps/nextra/pages/zh/build/guides/aptos-keyless/other.mdx +++ /dev/null @@ -1,56 +0,0 @@ ---- -title: "Keyless Terminology and FAQ" ---- - -## Terminology - -- **OpenID Connect (OIDC)**: is the identity authentication protocol used to enable federated identity verification. This protocol is what is used when a user goes through the "Sign in with Google" flow for example. -- **Identity Provider (IdP)**: is the trusted authority who authenticates your identity via OIDC. Supported example includes: Google. -- **JSON Web Token (JWT):** is an open standard used to share security information between two parties — a client and a server. Each JWT contains encoded JSON objects, including a set of claims. JWTs are signed using a cryptographic algorithm to ensure that the claims cannot be altered after the token is issued. - - `iss`, an identifier for the OIDC provider (e.g., https://accounts.google.com) - - `aud`, the OAuth `client_id` of the application that the user is signing in to (e.g., [Notion.so](https://notion.so)) - - `sub`, an identifier that the OIDC provider uses to identify the user - - This could be an identifier specific to this `client_id` - - Or, it could be an identifier shared across different `client_id`'s (e.g., Facebook’s OIDC does this) - - `email`, some providers might also expose the user’s email as one of the fields (e.g., Google) - - in addition, an `email_verified` field will be exposed to indicate if the provider has verified that the user owns this email address - - `nonce`, arbitrary data that the application wants the OIDC provider to sign over - - `iat`, the time the JWT was issued at. -- **Ephemeral Key Pair:** a temporary public/private key pair that is used to sign transactions for an Aptos Keyless account. The public key and its expiration date are committed in the JWT token via the `nonce` field. -- **Keyless Account:** a blockchain account that is directly-derived from (1) a user’s OIDC account (e.g., `alice@gmail.com`) and (2) an associated application’s OAuth client_id (e.g., Notion.so). Users authenticate through the OIDC flow. -- **JSON Web Key (JWK):** is the cryptographic public key of the OIDC provider. This public key is used to verify the signature on the JWTs that the OIDC provider issues to the client application. This way, the client application can verify the authenticity of the tokens and ensure that they have not been tampered with. -- **client_id:** the OAuth identifier for your application that you will receive from the IdP after registering your application with them. This will be used in our keyless architecture in the address derivation for your users. -- **redirect_uri:** the URI of the callback handler once the user successfully authenticates. Needs to be registered with your IdP. - -## Ceremony - -Aptos engaged in iterative trusted setup ceremonies to secure our Groth16 based ZK circuit. A trusted setup ceremony is a multi-party computation (MPC) that outputs the prover and verifier keys used in a zkSNARK system, common for efficient zero-knowledge proof systems. As long as a single participant in the ceremony is honest, the process is considered secure and the outputs will be valid. Our initial ceremony consisted of 140+ members of the Aptos ecosystem, which was an incredible show of the power of decentralization, security, and community - and a follow up ceremony was held following a developer feedback phase that allowed us to identify and implement an improvement to our circuit that helped us ensure Keyless is universally accessible. Our final ceremony contributions can be found in this repo [here] and verified using the process outlined [here]. - -## Frequently Asked Questions - -**What is the best way to use Keyless accounts?** - -- The best way to use Keyless accounts depends on your use case. If seamless account interoperability across our ecosystem is important to your dApp experience (think: mint an NFT on your platform and allow users to sell their NFT on an external NFT marketplace), you might want to consider integrating a wallet that supports Keyless. If you want to create a fully embedded account experience in your dApp, allowing users to transact without ever leaving your application, you might want to do a direct integration of the Aptos Keyless SDK. - -**Does Keyless work with sponsored transactions or do my users always need to pay for their own gas?** - -- Yes, Keyless works with sponsored transactions like any regular private key based account. - -**If I use the Aptos Keyless SDK, can my user’s use their accounts across other dApps?** - -- Keyless accounts are scoped to the domain they are created with as the address derivation includes a unique identifier for the application. - -**What is Aptos Connect?** - -- Account Management Infrastructure: Central to the keyless accounts paradigm is a robust account management infrastructure that facilitates the creation, deletion, and management of user accounts, alongside the storage and retrieval of associated metadata. - -- While the adoption of keyless accounts heralds a paradigm shift towards enhanced usability and security, it is imperative for developers to remain cognizant of tradeoffs associated with this system vs. common alternatives like plaintext private keys. - -**Are there dependency on external services?** - -- Yes, Keyless accounts introduce a degree of dependency on external authentication services (pepper and prover), necessitating contingency plans and fallback mechanisms to mitigate service disruptions and ensure uninterrupted user access - -**If my dApp goes down, my users cannot access their Keyless accounts. How can I help protect them in that case?** - -- We encourage dApp developers to support additional backup recovery options for your users when integrating Keyless into a dApp. Specifically, we recommend that you support adding a backup private key to Keyless accounts in your dApp. Practically, this would transform the accounts into 1 of 2 multi-signature accounts where both keys are owned by the user. This would allow users to continue using OIDC login via your dApp to access their Keyless accounts but would add the ability for your users to export their backup private key to any self custodial product, where they could sign transactions from that same account with their traditional private key. Doing this will ensure that users never lose access to their digital assets, even if your dApp shuts down or the user loses access to their OIDC account. -- You should make a determination at what point in the user journey to incorporate a back-up is appropriate for your dApp. Incorporating a backup method later in the user journey would preserve the seamless onboarding experience that Keyless offers but could result in less users receiving a recovery key. Prompting users to add a backup key during the onboarding process would likely lead to more users receiving a recovery key but could add potential friction during the onboarding process. diff --git a/apps/nextra/pages/zh/build/guides/aptos-keyless/simple-example.mdx b/apps/nextra/pages/zh/build/guides/aptos-keyless/simple-example.mdx deleted file mode 100644 index 0b23088b5..000000000 --- a/apps/nextra/pages/zh/build/guides/aptos-keyless/simple-example.mdx +++ /dev/null @@ -1,27 +0,0 @@ ---- -title: "Keyless Simple Example" ---- - -# Keyless Simple Example - -Explore the code in [aptos-keyless-example repository](https://github.com/aptos-labs/aptos-keyless-example/). - -The Keyless Simple Example is currently undergoing maintenance. Please check back later. - -This is a live Keyless example on CodeSandbox. Follow the instructions in the `README.md` to add your own Google `client_id`. Explore the code in [aptos-keyless-example repository](https://github.com/aptos-labs/aptos-keyless-example/). - -
- - diff --git a/apps/nextra/pages/zh/build/guides/build-e2e-dapp.mdx b/apps/nextra/pages/zh/build/guides/build-e2e-dapp.mdx deleted file mode 100644 index 5f1717405..000000000 --- a/apps/nextra/pages/zh/build/guides/build-e2e-dapp.mdx +++ /dev/null @@ -1,47 +0,0 @@ -# Build an End-to-End Dapp on Aptos - -A common way to learn a new framework or programming language is to build a simple todo list. In this tutorial, we will learn how to build an end-to-end todo list dapp, starting from the smart contract side through the front-end side and finally use of a wallet to interact with the two. - -See the completed code in the [my_first_dapp](https://github.com/aptos-labs/aptos-core/tree/main/aptos-move/move-examples/my_first_dapp). - -## Chapters - -After meeting the [prerequisites](#prerequisites) and [getting set up](#setup) as described below, you will follow this tutorial in this order: - -1. [Create a smart contract](build-e2e-dapp/1-create-smart-contract.mdx) -2. [Set up React app](build-e2e-dapp/2-set-up-react-app.mdx) -3. [Add Wallet support](build-e2e-dapp/3-add-wallet-support.mdx) -4. [Fetch Data from Chain](build-e2e-dapp/4-fetch-data-from-chain.mdx) -5. [Submit data to chain](build-e2e-dapp/5-submit-data-to-chain.mdx) -6. [Handle Tasks](build-e2e-dapp/6-handle-tasks.mdx) - -## Prerequisites - -You must have: - -- [Aptos CLI](../cli.mdx) -- [Aptos TypeScript SDK](../sdks/ts-sdk.mdx) -- [Aptos Wallet Adapter](../sdks/wallet-adapter.mdx) -- [Create React App](https://create-react-app.dev/) -- [node and npm](https://nodejs.org/en/) - -Although we will explain some React decisions, we are not going to deep dive into how React works; so we assume you have some previous experience with React. - -## Setup - -In this section, we will create a `my-first-dapp` directory to hold our project files, both client-side code (React based)and the Move code (our smart contract). - -1. Open a terminal and navigate to the desired directory for the project (for example, the `Desktop` directory). -2. Create a new directory called `my-first-dapp`, for example: - -```bash filename="Terminal" -mkdir my-first-dapp -``` - -3. Navigate into that directory: - -```bash filename="Terminal" -cd my-first-dapp -``` - -Now let's [create a smart contract](build-e2e-dapp/1-create-smart-contract.mdx). diff --git a/apps/nextra/pages/zh/build/guides/build-e2e-dapp/1-create-smart-contract.mdx b/apps/nextra/pages/zh/build/guides/build-e2e-dapp/1-create-smart-contract.mdx deleted file mode 100644 index ba90564f7..000000000 --- a/apps/nextra/pages/zh/build/guides/build-e2e-dapp/1-create-smart-contract.mdx +++ /dev/null @@ -1,601 +0,0 @@ -import { Callout } from 'nextra/components'; - -# 1. Create a Smart Contract - -This is the first chapter of the tutorial on [building an end-to-end dapp on Aptos](../build-e2e-dapp.mdx). If you haven’t done it, review that introduction, and ensure your environment meets the [prerequisites](../build-e2e-dapp.mdx#prerequisites) listed there. - -Now that you are all set up and at your terminal: - -1. `cd` into the `my-first-dapp` root directory, and create a new `move` directory. -2. `cd` into the new `move` directory and run: `aptos move init --name my_todo_list` - That command creates a `sources/` directory and `Move.toml` file inside the `move` directory. -3. Your new `move` directory should now resemble: - - ![move-directory](/docs/build-e2e-dapp-img-1.png) - -### What is a `Move.toml` file? - -A `Move.toml` file is a manifest file that contains metadata such as name, version, and dependencies for the package. - -Take a look at the new `Move.toml` file. You should see your package information and an `AptosFramework` dependency. Note that the `name` property is the same `--name` attribute we passed to the `aptos move init` command before. The `AptosFramework` dependency points to the `aptos-core/aptos-move/framework/aptos-framework` GitHub repo main branch. - -### Why `sources` directory? - -The `sources` directory holds a collection of `.move` modules files. And later when we want to compile the package using the CLI, the compiler will look for that `sources` directory and its `Move.toml` file. - -### Create a Move module - -An account is needed to publish a Move module. So first we need to create an account. Once we have the account's private key, we can create a module under its account address and publish the module using that account. - -1. In our `move` directory, run `aptos init --network devnet`. Press enter when prompted. - - This creates for us a `.aptos` directory with a `config.yaml` file that holds our profile information. In the `config.yaml` file, we now have our profiles list that holds a `default` profile. If you open that file, you will see content resembling: - - ```yaml filename="config.yaml" - profiles: - default: - private_key: "0xee8f387ef0b4bb0018c4b91d1c0f71776a9b85935b4c6ec2823d6c0022fbf5cb" - public_key: "0xc6c07218d79a806380ca67761905063ec7a78d41f79619f4562462a0f8b6be11" - account: cbddf398841353776903dbab2fdaefc54f181d07e114ae818b1a67af28d1b018 - rest_url: "https://api.devnet.aptoslabs.com" - faucet_url: "https://faucet.devnet.aptoslabs.com" - ``` - - From now on, whenever we run a CLI command in this `move` directory, it will run with that default profile. - We use the `devnet` network flag so eventually when we publish our package it will get published to the `devnet` network. - - - You just created a new account on the Aptos (dev) network! Yay! You can see it by going to the [Aptos Explorer](https://explorer.aptoslabs.com/?network=devnet) Devnet network view, pasting the `account` address value from your configuration file into the search field, and clicking on the dropdown option! - - -As mentioned, our `sources` directory holds our `.move` module files; so let’s add our first Move file. - -2. Open the `Move.toml` file. -3. Add the following code to that Move file, substituting your actual default profile account address from `.aptos/config.yaml`: - -```toml filename="Move.toml" -[addresses] -todolist_addr='' -``` - -If the default profile account address is `cbddf398841353776903dbab2fdaefc54f181d07e114ae818b1a67af28d1b018`, your `Move.toml` file should look like: - -```toml filename="Move.toml" -[addresses] -todolist_addr='cbddf398841353776903dbab2fdaefc54f181d07e114ae818b1a67af28d1b018' -``` - -4. Create a new `todolist.move` file within the `sources` directory and add the following to that file: - -```move filename="todolist.move" -module todolist_addr::todolist { - -} -``` - - -A Move module is stored under an address (so when it published anyone can access it using that address); the syntax for a Move module is - -```move filename="todolist.move" -module :: { - -} -``` - -In our module, the `account-address` is `todolist_addr` (a variable we just declared on the `Move.toml` file in the previous step that holds an `address`), and the `module-name` is `todolist` (a random name we selected). - - -### Our contract logic - -Before jumping into writing code, let’s first understand what we want our smart contract program to do. For ease of understanding, we will keep the logic pretty simple: - -1. An account creates a new list. -2. An account creates a new task on their list. - - Whenever someone creates a new task, emit a `task_created` event. -3. Let an account mark their task as completed. - - -Creating an event is not mandatory yet useful if dapps/users want to monitor data, such as how many people create a new task, using the [Aptos Indexer](../../indexer.mdx). - - -We can start with defining a `TodoList` struct, that holds the: - -- tasks array -- new task event -- a task counter that counts the number of created tasks (we can use that to differentiate between the tasks) - -And also create a `Task` struct that holds: - -- the task ID - derived from the TodoList task counter. -- address - the account address who created that task. -- content - the task content. -- completed - a boolean that marks whether that task is completed or not. - -On the `todolist.move` file, update the content in the module with: - -```move filename="todolist.move" -... -struct TodoList has key { - tasks: Table, - set_task_event: event::EventHandle, - task_counter: u64 - } - -struct Task has store, drop, copy { - task_id: u64, - address:address, - content: String, - completed: bool, - } -... -``` - -**What did we just add?** - -**TodoList** - -A struct that has the `key` and `store` abilities: - -- `Key` ability allows struct to be used as a storage identifier. In other words, `key` - is an ability to be stored at the top-level and act as a storage. We need it here to have `TodoList` be a resource stored in our user account. - -When a struct has the `key` ability, it turns this struct into a `resource`: - -- `Resource` is stored under the account - therefore it _exists_ only when assigned to an account and can be _accessed_ through this account only. - -**Task** - -A struct that has the `store`, `drop` and `copy`abilities. - -• `Store` - Task needs `Store` as it’s stored inside another struct (TodoList) - -• `Copy` - value can be _copied_ (or cloned by value). - -• `Drop` - value can be _dropped_ by the end of scope. - -Let’s try to compile what we have now: - -1. `cd` into the `move` directory. -2. Run: `aptos move compile` - -**Seeing errors?!** Let’s understand them. - -We have some errors on `Unbound type`- this is happening because we used some types but never imported them, and the compiler doesn't know where to get them from. - -3. On the top of the module, import those types by adding: - -```move filename="todolist.move" -... -use aptos_framework::event; -use std::string::String; -use aptos_std::table::Table; -... -``` - -That will tell the compiler where it can get those types from. - -4. Run the `aptos move compile` command again; If all goes well, we should see a response resembling (where the resulting account address is your default profile account address): - -```bash filename="Terminal" -INCLUDING DEPENDENCY AptosFramework -INCLUDING DEPENDENCY AptosStdlib -INCLUDING DEPENDENCY MoveStdlib -BUILDING myTodolist -{ -"Result": [ - "cbddf398841353776903dbab2fdaefc54f181d07e114ae818b1a67af28d1b018::todolist" - ] -} -``` - -At this point, we have successfully compiled our Move module. Yay! - -We also have a new `move/build` directory (created by the compiler) that holds our compiled modules, build information and `sources` directory. - -### Create list function - -The first thing an account can and should do with our contract is to create a new list. - -Creating a list is essentially submitting a transaction, and so we need to know the `signer` who signed and submitted the transaction: - -1. Add a `create_list` function that accepts a `signer` - -```move filename="todolist.move" -public entry fun create_list(account: &signer){ - -} -``` - -**Let’s understand the components of this function** - -- `entry` - an _entry_ function is a function that can be called via transactions. Simply put, whenever you want to submit a transaction to the chain, you should call an entry function. - -- `&signer` - The **signer** argument is injected by the Move VM as the address who signed that transaction. - -Our code has a `TodoList` resource. Resource is stored under the account; therefore, it _exists_ only when assigned to an account and can be _accessed_ only through this account. - -That means to create the `TodoList` resource, we need to assign it to an account that only this account can have access to. - -The `create_list` function can handle that `TodoList` resource creation. - -2. Add the following to the `create_list` function - -```move filename="todolist.move" -public entry fun create_list(account: &signer){ - let tasks_holder = TodoList { - tasks: table::new(), - set_task_event: account::new_event_handle(account), - task_counter: 0 - }; - // move the TodoList resource under the signer account - move_to(account, tasks_holder); -} -``` - -This function takes in a `signer`, creates a new `TodoList` resource, and uses `move_to` to have the resource stored in the provided signer account. - -### Create task function - -As mentioned before, our contract has a create task function that lets an account create a new task. Creating a task is also essentially submitting a transaction, and so we need to know the `signer` who signed and submitted the transaction. Another element we want to accept in our function is the task `content`. - -1. Add a `create_task` function that accepts a `signer` and task `content` and the function logic. - -```move filename="todolist.move" -public entry fun create_task(account: &signer, content: String) acquires TodoList { - // gets the signer address - let signer_address = signer::address_of(account); - // gets the TodoList resource - let todo_list = borrow_global_mut(signer_address); - // increment task counter - let counter = todo_list.task_counter + 1; - // creates a new Task - let new_task = Task { - task_id: counter, - address: signer_address, - content, - completed: false - }; - // adds the new task into the tasks table - table::upsert(&mut todo_list.tasks, counter, new_task); - // sets the task counter to be the incremented counter - todo_list.task_counter = counter; - // fires a new task created event - event::emit_event( - &mut borrow_global_mut(signer_address).set_task_event, - new_task, - ); - } -``` - -2. Since we now use two new modules - signer and table (you can see it being used in `signer::` and `table::`) - we need to import these modules. - At the top of the file, add those two use statements: - -```move filename="todolist.move" -use std::signer; -use aptos_std::table::{Self, Table}; // This one we already have, need to modify it -``` - -**Back to the code; what is happening here?** - -- First, we want to get the signer address, so we can get this account’s `TodoList` resource. -- Then, we retrieve the `TodoList` resource with the `signer_address`; with that we have access to the `TodoList` properties. -- We can now increment the `task_counter` property, and create a new `Task` with the `signer_address`, `counter` and the provided `content`. -- We push it to the `todo_list.tasks` table that holds all of our tasks along with the new `counter` (which is the table key) and the newly created Task. -- Then we assign the global `task_counter` to be the new incremented counter. -- Finally, we emit the `task_created` event that holds the new Task data. `emit_event` is an `aptos-framework` function that accepts a reference to the event handle and a message. In our case, we are passing the function a reference (using the sign &) to the account’s `TodoListresource` `set_task_event` property as the first argument and a second message argument which is the new Task we just created. Remember, we have a `set_task_event` property in our `TodoList` struct. - -### Complete task function - -Another function we want our contract to hold is the option to mark a task as completed. - -1. Add a `complete_task` function that accepts a `signer` and a `task_id`: - -```move filename="todolist.move" -public entry fun complete_task(account: &signer, task_id: u64) acquires TodoList { - // gets the signer address - let signer_address = signer::address_of(account); - // gets the TodoList resource - let todo_list = borrow_global_mut(signer_address); - // gets the task matches the task_id - let task_record = table::borrow_mut(&mut todo_list.tasks, task_id); - // update task as completed - task_record.completed = true; -} -``` - -**Let’s understand the code.** - -- As before in our create list function, we retrieve the `TodoList` struct by the signer address, so we can have access to the tasks table that holds all the account tasks. -- Then, we look for the task with the provided `task_id` on the `todo_list.tasks` table. -- Finally, we update that task completed property to be true. - -Now try to compile the code: - -2. Run: `aptos move compile` -3. Another `Unbound` error? To fix this, add a `use` statement to use the `account` module. - -```move filename="todolist.move" -use aptos_framework::account; -``` - -4. run `aptos move compile` again. - -### Add validations - -As this code now compiles, we want to have some validations and checks before creating a new task or updating the task as completed, so we can be sure our functions work as expected. - -1. Add a check to the `create_task` function to make sure the signer account has a list: - -```move filename="todolist.move" -public entry fun create_task(account: &signer, content: String) acquires TodoList { - // gets the signer address - let signer_address = signer::address_of(account); - - // assert signer has created a list - assert!(exists(signer_address), 1); - - ... -} -``` - -2. Add a check to the `complete_task` function to make sure the: - - signer has created a list. - - task exists. - - task is not completed. - -With: - -```move filename="todolist.move" -public entry fun complete_task(account: &signer, task_id: u64) acquires TodoList { - // gets the signer address - let signer_address = signer::address_of(account); - // assert signer has created a list - assert!(exists(signer_address), 1); - // gets the TodoList resource - let todo_list = borrow_global_mut(signer_address); - // assert task exists - assert!(table::contains(&todo_list.tasks, task_id), 2); - // gets the task matched the task_id - let task_record = table::borrow_mut(&mut todo_list.tasks, task_id); - // assert task is not completed - assert!(task_record.completed == false, 3); - // update task as completed - task_record.completed = true; -} -``` - -We just added our first `assert` statements! - -If you noticed, `assert` accepts two arguments: the first is what to check for, and the second is an error code. Instead of passing in an arbitrary number, a convention is to declare `errors` on the top of the module file and use these instead. - -On the top of the module file (under the `use` statements), add those error declarations: - -```move filename="todolist.move" -// Errors -const E_NOT_INITIALIZED: u64 = 1; -const ETASK_DOESNT_EXIST: u64 = 2; -const ETASK_IS_COMPLETED: u64 = 3; -``` - -Now we can update our asserts with these constants: - -```move filename="todolist.move" -public entry fun create_task(account: &signer, content: String) acquires TodoList { - // gets the signer address - let signer_address = signer::address_of(account); - - // assert signer has created a list - assert!(exists(signer_address), E_NOT_INITIALIZED); - - ... -} - - - -public entry fun complete_task(account: &signer, task_id: u64) acquires TodoList { - // gets the signer address - let signer_address = signer::address_of(account); - assert!(exists(signer_address), E_NOT_INITIALIZED); - // gets the TodoList resource - let todo_list = borrow_global_mut(signer_address); - // assert task exists - assert!(table::contains(&todo_list.tasks, task_id), ETASK_DOESNT_EXIST); - // gets the task matched the task_id - let task_record = table::borrow_mut(&mut todo_list.tasks, task_id); - // assert task is not completed - assert!(task_record.completed == false, ETASK_IS_COMPLETED); - // update task as completed - task_record.completed = true; -} -``` - -**WONDERFUL!!** - -Let’s stop for one moment and make sure our code compiles by running the `aptos move compile` command. If all goes well, we should output resembling: - -```bash filename="Terminal" -INCLUDING DEPENDENCY AptosFramework -INCLUDING DEPENDENCY AptosStdlib -INCLUDING DEPENDENCY MoveStdlib -BUILDING myTodolist -{ -"Result": [ - "cbddf398841353776903dbab2fdaefc54f181d07e114ae818b1a67af28d1b018::todolist" - ] -} -``` - -If you encounter errors, make sure you followed the steps above correctly and try to determine the cause of the issues. - -### Write tests - -Now that we have our smart contract logic ready, we need to add some tests for it. - -Test functions use the `#[test]` annotation. - -1. Add the following code to the bottom of the file: - -```move filename="todolist.move" -#[test] -public entry fun test_flow() { - -} -``` - - -we need to use `entry` here because we are testing an `entry` function. - - -2. For simplicity, and because we don't have much code to test, we use one function to test the whole flow of the app. - The test steps are: - -```move filename="todolist.move" - // create a list - // create a task - // update task as completed -``` - -Update the test function to be: - -```move filename="todolist.move" -#[test(admin = @0x123)] -public entry fun test_flow(admin: signer) acquires TodoList { - // creates an admin @todolist_addr account for test - account::create_account_for_test(signer::address_of(&admin)); - // initialize contract with admin account - create_list(&admin); - - // creates a task by the admin account - create_task(&admin, string::utf8(b"New Task")); - let task_count = event::counter(&borrow_global(signer::address_of(&admin)).set_task_event); - assert!(task_count == 1, 4); - let todo_list = borrow_global(signer::address_of(&admin)); - assert!(todo_list.task_counter == 1, 5); - let task_record = table::borrow(&todo_list.tasks, todo_list.task_counter); - assert!(task_record.task_id == 1, 6); - assert!(task_record.completed == false, 7); - assert!(task_record.content == string::utf8(b"New Task"), 8); - assert!(task_record.address == signer::address_of(&admin), 9); - - // updates task as completed - complete_task(&admin, 1); - let todo_list = borrow_global(signer::address_of(&admin)); - let task_record = table::borrow(&todo_list.tasks, 1); - assert!(task_record.task_id == 1, 10); - assert!(task_record.completed == true, 11); - assert!(task_record.content == string::utf8(b"New Task"), 12); - assert!(task_record.address == signer::address_of(&admin), 13); -} -``` - -Our `#[test]` annotation has changed and declares an account variable. - -Additionally, the function itself now accepts a signer argument. - -**Let’s understand our tests.** - -Since our tests run outside an account scope, we need to _create_ accounts to use in our tests. The `#[test]` annotation gives us the option to declare those accounts. We use an `admin` account and set it to a random account address (`@0x123`). The function accepts this signer (account) and creates it by using a built-in function to create an account for test. - -Then we simply go through the flow by: - -- creating a list -- creating a task -- updating a task as completed - -And assert the expected data/behavior at each step. - -Before running the tests again, we need to import (`use`) some new modules we are now employing in our code: - -3. At the top of the file, add this `use` statement: - -```move filename="todolist.move" -use std::string::{Self, String}; // already have it, need to modify -``` - -4. Run the `aptos move test` command. If all goes right, we should see a success message like: - -```move filename="todolist.move" -Running Move unit tests -[ PASS ] 0xcbddf398841353776903dbab2fdaefc54f181d07e114ae818b1a67af28d1b018::todolist::test_flow -Test result: OK. Total tests: 1; passed: 1; failed: 0 -{ - "Result": "Success" -} -``` - -5. Let’s add one more test to make sure our `complete_task` function works as expected. Add another test function with: - -```move filename="todolist.move" -#[test(admin = @0x123)] -#[expected_failure(abort_code = E_NOT_INITIALIZED)] -public entry fun account_can_not_update_task(admin: signer) acquires TodoList { - // creates an admin @todolist_addr account for test - account::create_account_for_test(signer::address_of(&admin)); - // account can not toggle task as no list was created - complete_task(&admin, 2); -} -``` - -This test confirms that an account can’t use that function if they haven’t created a list before. - -The test also uses a special annotation `#[expected_failure]` that, as the name suggests, expects to fail with an `E_NOT_INITIALIZED` error code. - -6. Run the `aptos move test` command. If all goes right, we should see a success message like: - -```bash filename="Terminal" -Running Move unit tests -[ PASS ] 0xcbddf398841353776903dbab2fdaefc54f181d07e114ae818b1a67af28d1b018::todolist::account_can_not_update_task -[ PASS ] 0xcbddf398841353776903dbab2fdaefc54f181d07e114ae818b1a67af28d1b018::todolist::test_flow -Test result: OK. Total tests: 2; passed: 2; failed: 0 -{ - "Result": "Success" -} -``` - -Now that everything works, we can compile the Move modules and publish the Move package to chain so our React app (and everyone else) can interact with our smart contract! - -### Publish todolist module to chain - -For now, the easiest way to publish a Move package to chain is using the CLI: - -1. `cd` into our `move` directory, and run: `aptos move compile` - -We are getting some _Unused alias_ errors. This is because we added the `string` alias before since we use it in our tests. But we don't use this alias in our smart contract code. - -This is why we are getting this error when we want to compile the module but not are getting it when we only run tests. - -To fix it, we can add a `use` statement that would be used only in tests. - -Add the following `use` statement where we have all of our import statements. - -```move filename="todolist.move" -use std::string::String; // change to this -... -#[test_only] -use std::string; // add this -``` - -2. Run: `aptos move test` and `aptos move compile` - all should work without errors. -3. Run: `aptos move publish` -4. Enter `yes` in the prompt. -5. That will compile, simulate and finally publish your module into devnet. You should see a success message: - -```bash filename="Terminal" -{ - "Result": { - "transaction_hash": "0x96b84689a53a28db7be6346627a99967f719946bc22766811a674e69da7783fa", - "gas_used": 7368, - "gas_unit_price": 100, - "sender": "cbddf398841353776903dbab2fdaefc54f181d07e114ae818b1a67af28d1b018", - "sequence_number": 2, - "success": true, - "timestamp_us": 1674246585276143, - "version": 651327, - "vm_status": "Executed successfully" - } -} -``` - -6. You can now head to the [Aptos Explorer](https://explorer.aptoslabs.com/), change the dropdown on the top right to the _Devnet_ network and look for that `transaction_hash` value - this will show you the transaction details. - -Now let's [set up a React app](2-set-up-react-app.mdx) in chapter 2. diff --git a/apps/nextra/pages/zh/build/guides/build-e2e-dapp/2-set-up-react-app.mdx b/apps/nextra/pages/zh/build/guides/build-e2e-dapp/2-set-up-react-app.mdx deleted file mode 100644 index 36975a1c8..000000000 --- a/apps/nextra/pages/zh/build/guides/build-e2e-dapp/2-set-up-react-app.mdx +++ /dev/null @@ -1,84 +0,0 @@ -# 2. Set up React App - -This is the second chapter of the tutorial on [building an end-to-end dapp on Aptos](../build-e2e-dapp.mdx) where you have already [created a smart contract](1-create-smart-contract.mdx) and are now setting up a React app. - -## Set up the app - -We will use the `react` library to build the client side with [Create React App](https://create-react-app.dev/docs/getting-started#creating-an-app). - -For the UI, we will use [Ant Design](https://ant.design/). This is just a personal decision; you are welcome to use any different UI library/framework you wish. - -1. In the root folder of the `my-first-dapp` project, run: - -```bash filename="Terminal" -npx create-react-app client --template typescript -``` - -That will create a new `client` folder in the current path: - -2. Your file structure should look something like: - - ![client-folder](/docs/build-e2e-dapp-img-2.png) - -3. Run: `cd client` -4. Run: `npm start` - - At this point you should have your app running on [http://localhost:3000](http://localhost:3000), which displays the default React layout. - -5. In the `client/src` directory, find all the React app files. Let’s clean it up a bit. -6. Open the `App.tsx` file and update its content to be: - -```ts filename="App.tsx" -function App() { - return
My app goes here
; -} - -export default App; -``` - -Once you save the changes, you should see that the app content has changed in the browser and displays `My app goes here`. - -7. Open the `App.tsx` file and remove the `import './App.css';` and `import logo from './logo.svg';` lines. Since we remove the default imports on this file, we can remove some files in our project. Delete the files `App.css` and `logo.svg`. -8. Open the `index.tsx` file and remove the `import './index.css';` line at the top of the file. - Now you can also delete the `src/index.css` file. - -## Our dapp UI - -First we will build the dapp UI layout. We have two UI states for the app: - -- When an account hasn't created a list yet (on the left). -- When an account has created a list and can now add tasks to it (on the right). - ![dapp-ui](/docs/build-e2e-dapp-img-3.png) - -We will use the [Ant Design](https://ant.design/) library for our UI: - -1. Stop the local server if running. -2. On to the `client` directory and install our UI library package: `npm i antd@5.1.4` -3. Update `App.tsx` with the initial state UI: - -```ts filename="App.tsx" -return ( - <> - - - -

Our todolist

- - -

Connect Wallet

- -
-
- -); -``` - -4. Don't forget to import the _components_ we just added: - -```ts filename="App.tsx" -import { Layout, Row, Col } from "antd"; -``` - -5. Run the local server with `npm start`, you should see the _header_ that matches our UI mockup. - -It is now time to [add wallet support](3-add-wallet-support.mdx) in chapter 3. diff --git a/apps/nextra/pages/zh/build/guides/build-e2e-dapp/3-add-wallet-support.mdx b/apps/nextra/pages/zh/build/guides/build-e2e-dapp/3-add-wallet-support.mdx deleted file mode 100644 index 31e0f5536..000000000 --- a/apps/nextra/pages/zh/build/guides/build-e2e-dapp/3-add-wallet-support.mdx +++ /dev/null @@ -1,96 +0,0 @@ -import { Callout } from 'nextra/components'; - -# 3. Add Wallet Support - -In the third chapter of the tutorial on [building an end-to-end dapp on Aptos](../build-e2e-dapp.mdx), you will be adding _wallet_ support to your [React app](2-set-up-react-app.mdx). You now need a wallet to submit a transaction to the blockchain. - -Aptos provides a [wallet adapter](../../sdks/wallet-adapter.mdx) that supports many ecosystem wallets to offering a common interface and UI package that can be used to add a wallet connect button and a wallet selector modal. - -1. Stop the local server if running. -2. In the `client` folder, run: - -```bash filename="Terminal" -npm i @aptos-labs/wallet-adapter-react -``` - -```bash filename="Terminal" -npm i @aptos-labs/wallet-adapter-ant-design -``` - -This installs two packages: - -- the adapter React provider that holds the logic. -- a wallet connect UI package. - -3. We now need to add wallets to our app. There is a list of [wallets the adapter supports](https://github.com/aptos-labs/aptos-wallet-adapter#supported-wallet-packages); but to keep this tutorial simple, we will use only one wallet. - Still in the `client` folder, run - -```bash filename="Terminal" -npm i petra-plugin-wallet-adapter -``` - - -If you haven't installed the Petra wallet extension yet: - -1. [Install Petra Wallet](https://petra.app) and open the Chrome extension. -2. Follow the [user instructions](https://petra.app/docs/use) on petra.app for help. -3. Switch to the Devnet network by clicking **Settings** > **Network** and selecting **devnet**. -4. Click the **Faucet** button to ensure you can receive test tokens. - - - -4. Open `Index.tsx` file. At the top of the file, add the following: - -```tsx filename="Index.tsx" -import { PetraWallet } from "petra-plugin-wallet-adapter"; -import { AptosWalletAdapterProvider } from "@aptos-labs/wallet-adapter-react"; -``` - -5. Still in `Index.tsx`, add a constant that holds an array of wallets: - -```tsx filename="Index.tsx" -... -const wallets = [new PetraWallet()]; -... -``` - -6. Inside the `render` method, update the code with the following: - -```tsx filename="Index.tsx" -... - - - -... -``` - -That wraps our app with the adapter provider and initializes it with our wallets. It also sets the provider to autoConnect a wallet. - -7. Open the `App.tsx` file and import the wallet connect UI package we installed in the previous step. At the top of the file add the following: - -```tsx filename="App.tsx" -import { WalletSelector } from "@aptos-labs/wallet-adapter-ant-design"; -``` - -8. The UI package uses a style `.css` file; let's import that one also at the bottom of the import statements. - -```tsx filename="App.tsx" -... -import "@aptos-labs/wallet-adapter-ant-design/dist/index.css"; -``` - -9. In the `return` statement, remove the `

Connect Wallet

` text and add the `WalletSelector` component: - -```tsx filename="App.tsx" -... - - - -... -``` - -10. Start the local server with `npm start` and open the app in the browser. - -We now have a working Wallet connect button and a wallet selector modal. Feel free to play with it and connect a wallet with it. - -Then learn how to [fetch data from chain](4-fetch-data-from-chain.mdx) in chapter 4. diff --git a/apps/nextra/pages/zh/build/guides/build-e2e-dapp/4-fetch-data-from-chain.mdx b/apps/nextra/pages/zh/build/guides/build-e2e-dapp/4-fetch-data-from-chain.mdx deleted file mode 100644 index 5fa424a3d..000000000 --- a/apps/nextra/pages/zh/build/guides/build-e2e-dapp/4-fetch-data-from-chain.mdx +++ /dev/null @@ -1,167 +0,0 @@ -import { Callout } from 'nextra/components'; - -# 4. Fetch Data from Chain - -In the fourth chapter of the tutorial on [building an end-to-end dapp on Aptos](../build-e2e-dapp.mdx), you will be learning to fetch data from chain. - -Our UI logic relies on whether the connected account has created a todo list. If the account has created a todo list, our app should display that list; if not, the app should display a button offering the option to create a new list. - -For that, we first need to check if the connected account has a `TodoList` resource. In our smart contract, whenever someone creates a todo list we create and assign a `TodoList` resource to their account. - -To fetch data from chain, we can use the [Aptos TypeScript SDK](../../sdks/ts-sdk.mdx). The SDK provides classes and functions for us to easily interact and query the Aptos chain. - -To get started: - -1. Stop the local server if running. -2. In the `client` directory, run: `npm i @aptos-labs/ts-sdk` -3. In the `App.tsx` file, import the `Aptos` class like so: - -```tsx filename="App.tsx" -import { Aptos } from "@aptos-labs/ts-sdk"; -``` - -The TypeScript SDK provides us with a `Aptos` class which is the main entry point into Aptos's API. By initializing `Aptos` we can query the Aptos chain. - - -Read more about the [`Aptos`](../../sdks/ts-sdk.mdx) class in the Aptos TypeScript SDK docs. - - -1. In the `App.tsx` file, add: - -```tsx filename="App.tsx" -const aptos = new Aptos(); -``` - -This will initialize a `Aptos` instance for us. - - -By default, `Aptos` will interact with the `devnet` network, to set up a [different network](../../../network/nodes/networks.mdx), we can use `AptosConfig` class. - -```tsx filename="App.tsx" -import { Aptos, AptosConfig, Network } from "@aptos-labs/ts-sdk"; - -const aptosConfig = new AptosConfig({ network: Network.MAINNET }); -const aptos = new Aptos(aptosConfig); -``` - - - -Our app displays different UIs based on a user resource (i.e if a user has a list ⇒ if a user has a `TodoList` resource). For that, we need to know the current account connected to our app. - -1. Import wallet from the wallet adapter React provider: - -```tsx filename="App.tsx" -import { useWallet } from "@aptos-labs/wallet-adapter-react"; -``` - -2. Extract the account object from the wallet adapter: - -```tsx filename="App.tsx" -function App ( - const { account } = useWallet(); - ... -) -``` - -The `account` object is `null` if there is no account connected; when an account is connected, the `account` object holds the account information, including the account address. - -3. Next, we want to fetch the account’s TodoList resource. - Begin by importing `useEffect` by using `jsx import useEffect from "react"; ` - Let’s add a `useEffect` hook to our file that would call a function to fetch the resource whenever our account address changes: - -```tsx filename="App.tsx" -function App() { - ... - useEffect(() => { - fetchList(); - }, [account?.address]); - ... -} -``` - -4. Before creating our `fetchList` function, let’s also create a local state to store whether the account has a list: - -```tsx filename="App.tsx" -function App ( - ... - const [accountHasList, setAccountHasList] = useState(false); - ... -) -``` - -also import `useEffect` using -`import { useState, useEffect } from "react"; ` - -5. Our `useEffect` hook is calling a `fetchList` function; let’s create it: - -```tsx filename="App.tsx" -const fetchList = async () => { - if (!account) return []; - // change this to be your module account address - const moduleAddress = "0xcbddf398841353776903dbab2fdaefc54f181d07e114ae818b1a67af28d1b018"; - try { - const todoListResource = await aptos.getAccountResource( - { - accountAddress:account?.address, - resourceType:`${moduleAddress}::todolist::TodoList` - } - ); - setAccountHasList(true); - } catch (e: any) { - setAccountHasList(false); - } -}; -``` - -The `moduleAddress` is the address we publish the module under, i.e the account address you have in your `Move.toml` file (`myaddr`). - -The `provider.getAccountResource()`expects an _account address_ that holds the resource we are looking for and a string representation of an on-chain _Move struct type_. - -- account address - is the current connected account (we are getting it from the wallet account object) -- Move struct type string syntax: - - The account address who holds the move module = our profile account address (You might want to change the `moduleAddress` const to be your own account address) - - The module name the resource lives in = `todolist` - - The resource name = `TodoList` - -If the request succeeds and there is a resource for that account, we want to set our local state to `true`; otherwise, we would set it to `false`. - -6. Let’s update `import { Layout, Row, Col } from "antd"; ` to import Button: - `import { Layout, Row, Col, Button } from "antd"; ` - -7. Let’s update our UI based on the `accountHasList` state: - -```tsx filename="App.tsx" -return ( - <> - - - -

Our todolist

- - - - -
-
- {!accountHasList && ( - - - - - - )} - -); -``` - -We now have an **Add new list** button that appears only if the account doesn’t have a list. - -Start the local server with `npm start`. You should see the **Add new list** button. - -Next, let’s understand how to create a new list by [submitting data to chain](5-submit-data-to-chain.mdx) in chapter 5. diff --git a/apps/nextra/pages/zh/build/guides/build-e2e-dapp/5-submit-data-to-chain.mdx b/apps/nextra/pages/zh/build/guides/build-e2e-dapp/5-submit-data-to-chain.mdx deleted file mode 100644 index 3458e4fec..000000000 --- a/apps/nextra/pages/zh/build/guides/build-e2e-dapp/5-submit-data-to-chain.mdx +++ /dev/null @@ -1,167 +0,0 @@ -# 5. Submit Data to Chain - -In the fifth chapter of the tutorial on [building an end-to-end dapp on Aptos](../build-e2e-dapp.mdx), you will be submitting data to the chain. - -So now we have an **Add new list** button that appears if the connected account hasn’t created a list yet. We still don't have a way for an account to create a list, so let’s add that functionality. - -1. First, our wallet adapter provider has a `signAndSubmitTransaction` function; let’s extract it by updating the following: - -```tsx filename="App.tsx" -const { account, signAndSubmitTransaction } = useWallet(); -``` - -2. Add an `onClick` event to the new list button: - -```tsx filename="App.tsx" - -``` - -3. Update the import statement from `@aptos-labs/wallet-adapter-react` to also import the `InputTransactionData` type and - -```tsx filename="App.tsx" -import { - useWallet, - InputTransactionData, -} from "@aptos-labs/wallet-adapter-react"; -``` - -4. Add the `addNewList` function: - -```tsx filename="App.tsx" - -const addNewList = async () => { - if (!account) return []; - - const transaction:InputTransactionData = { - data: { - function:`${moduleAddress}::todolist::create_list`, - functionArguments:[] - } - } - try { - // sign and submit transaction to chain - const response = await signAndSubmitTransaction(transaction); - // wait for transaction - await aptos.waitForTransaction({transactionHash:response.hash}); - setAccountHasList(true); - } catch (error: any) { - setAccountHasList(false); - } -}; -``` - -5. Since our new function also uses `moduleAddress`, let’s get it out of the `fetchList` function scope to the global scope so it can be used globally. - -In our `fetchList` function, find the line: - -```tsx filename="App.tsx" -// replace with your own address -const moduleAddress = - "0xcbddf398841353776903dbab2fdaefc54f181d07e114ae818b1a67af28d1b018"; -``` - -And move it to outside of the main `App` function, right beneath our const `provider` declarations. - -```tsx filename="App.tsx" -export const aptos = new Aptos(); -// change this to be your module account address -export const moduleAddress = - "0xcbddf398841353776903dbab2fdaefc54f181d07e114ae818b1a67af28d1b018"; -``` - -**Let’s go over the `addNewList` function code.** - -First, we use the `account` property from our wallet provider to make sure there is an account connected to our app. - -Then we build our transaction data to be submitted to chain: - -```tsx filename="App.tsx" -const transaction:InputTransactionData = { - data: { - function:`${moduleAddress}::todolist::create_list`, - functionArguments:[] - } - } -``` - -- `function`- is built from the module address, module name and the function name. -- `functionArguments` - the arguments the function expects, in our case it doesn’t expect any arguments. - -Next, we submit the transaction payload and wait for its response. The response returned from the `signAndSubmitTransaction` function holds the transaction hash. Since it can take a bit for the transaction to be fully executed on chain and we also want to make sure it is executed successfully, we `waitForTransaction`. And only then we can set our local `accountHasList` state to `true`. - -6. Before testing our app, let’s tweak our UI a bit and add a Spinner component to show up while we are waiting for the transaction. - Add a local state to keep track whether a transaction is in progress: - -```tsx filename="App.tsx" -const [transactionInProgress, setTransactionInProgress] = - useState(false); -``` - -7. Update our `addNewList` function to update the local state: - -```tsx filename="App.tsx" -const addNewList = async () => { - if (!account) return []; - setTransactionInProgress(true); - const transaction:InputTransactionData = { - data: { - function:`${moduleAddress}::todolist::create_list`, - functionArguments:[] - } - } - try { - // sign and submit transaction to chain - const response = await signAndSubmitTransaction(transaction); - // wait for transaction - await aptos.waitForTransaction({transactionHash:response.hash}); - setAccountHasList(true); - } catch (error: any) { - setAccountHasList(false); - } finally { - setTransactionInProgress(false); - } -}; -``` - -8. Update the important statement from `antd` to also import `Spin` - -```tsx filename="App.tsx" -import { Layout, Row, Col, Button, Spin } from "antd"; -``` - -9. Update our UI with the following: - -```tsx filename="App.tsx" -return ( - <> - ... - - {!accountHasList && ( - - - - - - )} - - -); -``` - -Now you can head over to our app, and add a new list! - -Since you haven’t made the user interface able to handle cases where an account has created a list, you will do so next [handling tasks](6-handle-tasks.mdx) in chapter 6. diff --git a/apps/nextra/pages/zh/build/guides/build-e2e-dapp/6-handle-tasks.mdx b/apps/nextra/pages/zh/build/guides/build-e2e-dapp/6-handle-tasks.mdx deleted file mode 100644 index eba4ece91..000000000 --- a/apps/nextra/pages/zh/build/guides/build-e2e-dapp/6-handle-tasks.mdx +++ /dev/null @@ -1,431 +0,0 @@ -# 6. Handle Tasks - -In the sixth and final chapter of the tutorial on [building an end-to-end dapp on Aptos](../build-e2e-dapp.mdx), you will add functionality to the app so the user interface is able to handle cases where an account has created a list. - -We have covered how to [fetch data](4-fetch-data-from-chain.mdx) (an account’s todo list) from chain and how to [submit a transaction](5-submit-data-to-chain.mdx) (new todo list) to chain using Wallet. - -Let’s finish building our app by implementing fetch tasks and adding a task function. - -## Fetch tasks - -1. Create a local state `tasks` that will hold our tasks. It will be a state of a Task type (that has the same properties we set on our smart contract): - -```ts filename="App.tsx" -type Task = { - address: string; - completed: boolean; - content: string; - task_id: string; -}; - -function App() { - const [tasks, setTasks] = useState([]); - ... -} -``` - -2. Update our `fetchList` function to fetch the tasks in the account’s `TodoList` resource: - -```ts filename="App.tsx" -const fetchList = async () => { - if (!account) return []; - try { - const todoListResource = await aptos.getAccountResource( - {accountAddress:account?.address, - resourceType:`${moduleAddress}::todolist::TodoList`} - ); - setAccountHasList(true); - // tasks table handle - const tableHandle = (todoListResource as any).tasks.handle; - // tasks table counter - const taskCounter = (todoListResource as any).task_counter; - - let tasks = []; - let counter = 1; - while (counter <= taskCounter) { - const tableItem = { - key_type: "u64", - value_type: `${moduleAddress}::todolist::Task`, - key: `${counter}`, - }; - const task = await aptos.getTableItem({handle:tableHandle, data:tableItem}); - tasks.push(task); - counter++; - } - // set tasks in local state - setTasks(tasks); - } catch (e: any) { - setAccountHasList(false); - } -}; -``` - -**This part is a bit confusing, so stick with us!** - -Tasks are stored in a table (this is how we built our contract). To fetch a table item (i.e a task), we need that task's table handle. We also need the `task_counter` in that resource so we can loop over and fetch the task with the `task_id` that matches the `task_counter`. - -```ts filename="App.tsx" -const tableHandle = (TodoListResource as any).data.tasks.handle; -const taskCounter = (TodoListResource as any).data.task_counter; -``` - -Now that we have our tasks table handle and our `task_counter` variable, lets loop over the `taskCounter` . We define a `counter` and set it to 1 as the task_counter / task_id is never less than 1. - -We loop while the `counter` is less then the `taskCounter` and fetch the table item and push it to the tasks array: - -```ts filename="App.tsx" -let tasks = []; -let counter = 1; -while (counter <= taskCounter) { - const tableItem = { - key_type: "u64", - value_type: `${moduleAddress}::todolist::Task`, - key: `${counter}`, - }; - const task = await provider.getTableItem(tableHandle, tableItem); - tasks.push(task); - counter++; -} -``` - -We build a `tableItem` object to fetch. If we take a look at our table structure from the contract: - -```ts filename="App.tsx" -tasks: Table, -``` - -We see that it has a `key` type `u64` and a `value` of type `Task`. And whenever we create a new task, we assign the `key` to be the incremented task counter. - -```move filename="todolist.move" -// adds the new task into the tasks table -table::upsert(&mut todo_list.tasks, counter, new_task); -``` - -So the object we built is: - -```ts filename="App.tsx" -{ - key_type: "u64", - value_type:`${moduleAddress}::todolist::Task`, - key: `${taskCounter}`, -} -``` - -Where `key_type` is the table `key` type, `key` is the key value we are looking for, and the `value_type` is the table `value` which is a `Task` struct. The Task struct uses the same format from our previous resource query: - -- The account address who holds that module = our profile account address -- The module name the resource lives in = `todolist` -- The struct name = `Task` - -The last thing we want to do is display the tasks we just fetched. - -6. In our `App.tsx` file, update our UI with the following code: - -```tsx filename="App.tsx" -{ - !accountHasList ? ( - - - - - - ) : ( - - - {tasks && ( - ( - ]}> - {`${task.address.slice(0, 6)}...${task.address.slice(-5)}`} - } - /> - - )} - /> - )} - - - ); -} -``` - -That will display the **Add new list** button if account doesn’t have a list or instead the tasks if the account has a list. - -Go ahead and refresh your browser - see the magic! - -We haven’t added any tasks yet, so we simply see a box of empty data. Let’s add some tasks! - -## Add task - -1. Update our UI with an _add task_ input: - -Import `Input` from `antd` - `import { Input } from "antd";` - -```tsx filename="App.tsx" -{!accountHasList ? ( - ... -) : ( - - // Add this! - - - - - - - ... - -)} -``` - -We have added a text input to write the task and a button to add the task. - -2. Create a new local state that holds the task content: - -```tsx filename="App.tsx" -function App() { - ... - const [newTask, setNewTask] = useState(""); - ... -} -``` - -3. Add an `onWriteTask` function that will get called whenever a user types something in the input text: - -```tsx filename="App.tsx" -function App() { - ... - const [newTask, setNewTask] = useState(""); - - const onWriteTask = (event: React.ChangeEvent) => { - const value = event.target.value; - setNewTask(value); - }; - ... -} -``` - -4. Find our `` component, add the `onChange` event to it, pass it our `onWriteTask` function and set the input value to be the `newTask` local state: - -```tsx filename="App.tsx" - onWriteTask(event)} // add this - style={{ width: "calc(100% - 60px)" }} - placeholder="Add a Task" - size="large" - value={newTask} // add this -/> -``` - -Cool! Now we have a working flow that when the user types something on the Input component, a function will get fired and set our local state with that content. - -5. Let’s also add a function that submits the typed task to chain! Find our Add ` -``` - -That adds an `onClickevent` that triggers an `onTaskAdded` function. - -When someones adds a new task we: - -- want to verify they are connected with a wallet. -- build a transaction payload that would be submitted to chain. -- submit it to chain using our wallet. -- wait for the transaction. -- update our UI with that new task (without the need to refresh the page). - -6. Add an `onTaskAdded` function with: - -```tsx filename="App.tsx" - const onTaskAdded = async () => { - // check for connected account - if (!account) return; - setTransactionInProgress(true); - const transaction:InputTransactionData = { - data:{ - function:`${moduleAddress}::todolist::create_task`, - functionArguments:[newTask] - } - } - - // hold the latest task.task_id from our local state - const latestId = tasks.length > 0 ? parseInt(tasks[tasks.length - 1].task_id) + 1 : 1; - - // build a newTaskToPush object into our local state - const newTaskToPush = { - address: account.address, - completed: false, - content: newTask, - task_id: latestId + "", - }; - - try { - // sign and submit transaction to chain - const response = await signAndSubmitTransaction(transaction); - // wait for transaction - await aptos.waitForTransaction({transactionHash:response.hash}); - - // Create a new array based on current state: - let newTasks = [...tasks]; - - // Add item to the tasks array - newTasks.push(newTaskToPush); - // Set state - setTasks(newTasks); - // clear input text - setNewTask(""); - } catch (error: any) { - console.log("error", error); - } finally { - setTransactionInProgress(false); - } - }; -``` - -**Let’s go over on what is happening.** - -First, note we use the `account` property from our wallet provider to make sure there is an account connected to our app. - -Then we build our transaction data to be submitted to chain: - -```tsx filename="App.tsx" -const transaction:InputTransactionData = { - data:{ - function:`${moduleAddress}::todolist::create_task`, - functionArguments:[newTask] - } - } -``` - -- `function`- is built from the module address, module name and the function name. -- `functionArguments` - the arguments the function expects, in our case the task content. - -Then, within our try/catch block, we use a wallet provider function to submit the transaction to chain and an SDK function to wait for that transaction. -If all goes well, we want to find the current latest task ID so we can add it to our current tasks state array. We will also create a new task to push to the current tasks state array (so we can display the new task in our tasks list on the UI without the need to refresh the page). - -TRY IT! - -Type a new task in the text input, click **Add**, approve the transaction and see it being added to the tasks list. - -## Mark task as completed - -Next, we can implement the `complete_task` function. We have the checkbox in our UI so users can mark a task as completed. - -1. Update the `` component with an `onCheck` property that would call an `onCheckboxChange` function once it is checked: - -Import `List` from `antd` - `import { List } from "antd";` - -```tsx filename="App.tsx" - onCheckboxChange(event, task.task_id)}/> -]}> -``` - -2. Create the `onCheckboxChange` function (make sure to import `CheckboxChangeEvent` from `antd` - `import { CheckboxChangeEvent } from "antd/es/checkbox";`): - -```tsx filename="App.tsx" -const onCheckboxChange = async ( - event: CheckboxChangeEvent, - taskId: string - ) => { - if (!account) return; - if (!event.target.checked) return; - setTransactionInProgress(true); - const transaction:InputTransactionData = { - data:{ - function:`${moduleAddress}::todolist::complete_task`, - functionArguments:[taskId] - } - } - - try { - // sign and submit transaction to chain - const response = await signAndSubmitTransaction(transaction); - // wait for transaction - await aptos.waitForTransaction({transactionHash:response.hash}); - - setTasks((prevState) => { - const newState = prevState.map((obj) => { - // if task_id equals the checked taskId, update completed property - if (obj.task_id === taskId) { - return { ...obj, completed: true }; - } - - // otherwise return object as is - return obj; - }); - - return newState; - }); - } catch (error: any) { - console.log("error", error); - } finally { - setTransactionInProgress(false); - } - }; -``` - -Here we basically do the same thing we did when we created a new list or a new task. - -We make sure there is an account connected, set the transaction in progress state, build the transaction payload, submit the transaction, wait for it and update the task on the UI as completed. - -3. Update the `Checkbox` component to be checked by default if a task has already marked as completed: - -Import `Checkbox` from `antd` - `import { Checkbox } from "antd";` - -```tsx filename="App.tsx" -... - - {task.completed ? ( - - ) : ( - - onCheckboxChange(event, task.task_id) - } - /> - )} - , - ]} -> -... -``` - -Try it! Check a task’s checkbox, approve the transaction and see the task marked as completed. - -You have now learned how to build a dapp on Aptos from end to end. Congratulations! Tell your friends. :-) diff --git a/apps/nextra/pages/zh/build/guides/build-e2e-dapp/_meta.tsx b/apps/nextra/pages/zh/build/guides/build-e2e-dapp/_meta.tsx index be0328c17..5fcbcd12c 100644 --- a/apps/nextra/pages/zh/build/guides/build-e2e-dapp/_meta.tsx +++ b/apps/nextra/pages/zh/build/guides/build-e2e-dapp/_meta.tsx @@ -1,20 +1,26 @@ export default { "1-create-smart-contract": { title: "1. 创建智能合约", + href: "/en/build/guides/build-e2e-dapp/1-create-smart-contract", }, "2-set-up-react-app": { title: "2. 设置 React 应用", + href: "/en/build/guides/build-e2e-dapp/2-set-up-react-app", }, "3-add-wallet-support": { title: "3. 添加钱包支持", + href: "/en/build/guides/build-e2e-dapp/3-add-wallet-support", }, "4-fetch-data-from-chain": { title: "4. 从链上获取数据", + href: "/en/build/guides/build-e2e-dapp/4-fetch-data-from-chain", }, "5-submit-data-to-chain": { title: "5. 向链上提交数据", + href: "/en/build/guides/build-e2e-dapp/5-submit-data-to-chain", }, "6-handle-tasks": { title: "6. 处理任务", + href: "/en/build/guides/build-e2e-dapp/6-handle-tasks", }, }; diff --git a/apps/nextra/pages/zh/build/guides/exchanges.mdx b/apps/nextra/pages/zh/build/guides/exchanges.mdx deleted file mode 100644 index 257aa0fbf..000000000 --- a/apps/nextra/pages/zh/build/guides/exchanges.mdx +++ /dev/null @@ -1,1198 +0,0 @@ ---- -title: "Exchange Integration Guide" ---- - -import { Callout } from "nextra/components"; - -# Exchange Integration Guide - -This describes how to integrate Aptos and Aptos assets into an exchange. It provides -generic information for tracking balances, transferring assets, and testing the integration. - -## Overview - -This document will guide you through the following tasks to integrate with Aptos: -* Infrastructure -* Address standards -* Asset standards -* Retrieving balances -* Tracking balance changes -* Transferring assets -* Testing the integration - -## Infrastructure - -It's suggested that you run your own [full node](../../network/nodes/full-node.mdx) to interact with the Aptos blockchain. -This will allow you to query the blockchain for the latest state and submit transactions. -You can also use the [Indexer](../indexer.mdx) to query for on-chain data efficiently. - -## Address Standards - -### Addresses -A single address can be represented in three ways. We recommend you show all leading zeros, and the `0x`. Here is an example of all three representations for the framework address `0x1`: -* `0x00000000000000000000000000000001` - A full representation of 32-bytes in hex with a leading `0x`. This is preferred. -* `0x1` - The short representation of the address with a leading `0x`. This is kept around for compatibility, but preferred with all leading 0s. -* `00000000000000000000000000000001` - A full representation of 32-bytes in hex without a leading `0x`. This is kept around for compatibility, but preferred with leading 0x. - -For example SDKs will handle this parsing automatically, and we suggest you use the SDKs directly to handle it for you. - -```ts filename="example.ts" -import { AccountAddress } from "@aptos-labs/ts-sdk"; -const address = AccountAddress.from("0x1"); -address.toStringLong(); // 0x00000000000000000000000000000001 -``` - -There is additionally, Aptos Name Service (ANS) for friendly .apt names. For more information about addresses -and Aptos Names, see our page on [Accounts](../../network/blockchain/accounts). - -## Account Standards - -Accounts must exist prior to sending a transaction to the blockchain. This is done by creating an account resource, which can -be created by simply calling `0x1::aptos_account::transfer` with a zero amount to the account you want to create. Optionally, -`0x1::aptos_account::create_account` can be used to create an account with a zero balance. - -```ts filename="example.ts" -import { Aptos, Ed25519Account, Ed25519PrivateKey } from "@aptos-labs/ts-sdk"; - -const aptos = new Aptos(); -const account = new Ed25519Account({privateKey: new Ed25519PrivateKey("private key")}) -const transaction = await aptos.transferCoinTransaction({sender: account.accountAddress, recipient: "receiver address", amount: 100000000}) -const pendingTransaction = await aptos.transaction.signAndSubmitTransaction({signer: account, transaction}) -const committedTransaction = await aptos.waitForTransaction({transactionHash: pendingTransaction.hash}); -``` - -## Asset Standards - -Aptos provides two standards for fungible tokens, similar to ERC-20 tokens on Ethereum: - -* An earlier [Coin standard](../smart-contracts/aptos-coin.mdx) used by assets on Aptos. -* A newer [Fungible Asset Standard](../smart-contracts/fungible-asset.mdx) which is more featured. - -Additionally, there is a migratory period for assets from Coin to Fungible Asset standards. -We will call this from now on **migrated coins**. Migrated coins may have two forms, but either -can be used interchangeably with Coin standards. This is important to note when querying balances, to -use coin functions and not fungible asset functions. The FA standard can only deal with the FA form. - - -APT, the native token of Aptos, is a migrated coin. This means it can be used -with both the Coin and Fungible Asset standards. - - -### Coin Standard (tl;dr) - -A **coin** has an associated contract that holds the on-chain struct that represents the coin. The coin is -represented as a struct name e.g. `0x1::aptos_coin::AptosCoin` for `APT`. - -All coins are stored in an account resource called `0x1::coin::CoinStore`. Coins must be registered -prior to using the `CoinStore`, but if using the proper functions e.g. `0x1::aptos_account::transfer` -or `0x1::aptos_account::transfer_coins`, this will be done automatically. - -Coins can be *migrated* to a fungible asset. In order to support a migrated asset, -continue calling the coin functions as will be mentioned later. - -More info can be found at: [Coin Standard](../smart-contracts/aptos-coin.mdx) - -### Fungible Asset Standard (tl;dr) - -A **fungible asset** has an associated metadata address that holds the metadata for the fungible asset. This is commonly called the -fa metadata address. The asset is represented as an address e.g. `0xA` for `APT`. - -All fungible assets are stored in an `object`, which is called a `fungible asset store`. - -For exchanges, the most important store is `primary_fungible_store`, which is the default store for fungible assets. -This is directly connected to an owner. From this point on in this guide, we will -only talk about supporting `primary_fungible_store` for fungible assets. - -More info can be found at: [Fungible Asset Standard](../smart-contracts/fungible-asset.mdx) - -## Retrieving Balances - -Retrieving current balances for assets are different for each standard. Integration is considered complete when it can handle both. - -Balances are always returned in their subunits. For example, `APT` is returned in `octas` (1e-8 APT). So, when an API -returns a balance of `100000000`, this is `1 APT`. If it returns `100`, this is `0.000001 APT`. - -### Coin (and migrated coins) Balances - - - Note: This includes APT and any other coin that was migrated to a fungible - asset. If the asset is a migrated coin, use this over fungible asset balance. The - fungible asset balance will not include the coin portion of the balance. - - -To retrieve the balance of a coin, or a coin that was migrated to a fungible asset, you can use -the `0x1::coin::balance(account address)` view function. This will combine the coin and coin migrated to fungible asset balances. - -```ts filename="example.ts" -import { Aptos, AptosConfig, Network } from "@aptos-labs/ts-sdk"; - -const config = new AptosConfig({ network: Network.DEVNET }); -const aptos = new Aptos(config); - -const coinType = "0x1::aptos_coin::AptosCoin"; -const account = "0x00000000000000000000000000000001"; -const [balanceStr] = await aptos.view<[string]>({ - payload: { - function: "0x1::coin::balance", - typeArguments: [coinType], - functionArguments: [account] - } -}); -const balance = parseInt(balanceStr, 10); -``` - -A specific ledger version (transaction height) can be provided to get the balance at that point in time. The below example shows for ledger version `1,000,000`. - -```ts filename="example.ts" -import { Aptos, AptosConfig, Network } from "@aptos-labs/ts-sdk"; - -const config = new AptosConfig({ network: Network.DEVNET }); -const aptos = new Aptos(config); - -const coinType = "0x1::aptos_coin::AptosCoin"; -const account = "0x00000000000000000000000000000001"; -const [balanceStr] = await aptos.view<[string]>({ - payload: { - function: "0x1::coin::balance", - typeArguments: [coinType], - functionArguments: [account], - options: { - ledgerVersion: 1_000_000 - } - } -}); -const balance = parseInt(balanceStr, 10); -``` - -### Fungible Asset Balances - -To retrieve the balance of a fungible asset, you can use -the `0x1::primary_fungible_store::balance<0x1::object::ObjectCore>(account address, fungible asset metadata address)` view function. -Note, that this will not include the balance of coins if it's a migrated coin. - -```ts filename="example.ts" -import { Aptos, AptosConfig, Network } from "@aptos-labs/ts-sdk"; - -const config = new AptosConfig({ network: Network.DEVNET }); -const aptos = new Aptos(config); - -const faMetadataAddress = "0xA"; -const account = "0x00000000000000000000000000000001"; -const [balanceStr] = await aptos.view<[string]>({ - payload: { - function: "0x1::primary_fungible_store::balance", - typeArguments: ["0x1::object::ObjectCore"], - functionArguments: [account, faMetadataAddress] - } -}); -const balance = parseInt(balanceStr, 10); -``` - -A specific ledger version (transaction height) can be provided to get the balance at that point in time. The below example shows for ledger version `1,000,000`. - -```ts filename="example.ts" -import { Aptos, AptosConfig, Network } from "@aptos-labs/ts-sdk"; - -const config = new AptosConfig({ network: Network.DEVNET }); -const aptos = new Aptos(config); - -const faMetadataAddress = "0xA"; -const account = "0x00000000000000000000000000000001"; -const [balanceStr] = await aptos.view<[string]>({ - payload: { - function: "0x1::primary_fungible_store::balance", - typeArguments: ["0x1::object::ObjectCore"], - functionArguments: [account, faMetadataAddress] - }, - options: { - ledgerVersion: 1_000_000 - } -}); -const balance = parseInt(balanceStr, 10); -``` - -## Tracking Balance Changes - -Balance changes can be queried in one of two ways: -1. By watching for events that change the balance for each transaction. -2. By querying the indexer for indexed balance change events. - -In the past, it was able to use the `events` endpoint for an account to get the -transactions that changed the balance. This is still possible, but will be deprecated -in the future, and is not recommended for new integrations. - -### Coin Balance Changes - -Coin balances are tracked as two items, write set changes, and events. Write set -changes are end state of the coin balance, and events are the events that are -emitted when a coin is withdrawn or deposited. - -Here is an [example of a coin transfer](https://explorer.aptoslabs.com/txn/1747361321?network=mainnet). -The coin transfer can be tracked as an individual transaction -[here](https://fullnode.mainnet.aptoslabs.com/v1/transactions/by_version/1747361321) -from the REST API. - -We'll break it down into a few parts: - -1. The general transaction details tell information about the transaction. The -most important thing here is the transaction version is `1747361321`. This gives -us total order of all transactions on the blockchain. Think of it like block -height, but for transactions. -
- Transaction Details - ```json - { - "version": "1747361321", - "hash": "0x7c56ad56c7d02bb11887e535b9f1b221626d5b0d4cb5a1ffbadc358c1db515ea", - "state_change_hash": "0xc901b5e9e0965201e8205977720d7dea8a3709ee0d818fd5ec752cac13eaf18a", - "event_root_hash": "0x0077cb7df9db9ee7194c489db177fe9a325bcf3f1309ea99ed934085e5592041", - "state_checkpoint_hash": null, - "gas_used": "999", - "success": true, - "vm_status": "Executed successfully", - "accumulator_root_hash": "0xb531e918441ff0a37b49856e0f1b80c329146461582287cf9788964d25e31a68", - } - ``` -
-2. The Write set `changes` are the end state of the transaction. It shows all -resources that were modified by the transaction, and what it's final state was. - -In this case, we only care about coin store changes. - -
-Coin Store Changes -```json - "changes": [ - { - "address": "0x559d4f690c683fca7c539237aa8dc4c6ec09886b7016bf66f2cdeffef55468f0", - "state_key_hash": "0xb2bfa7198457291a0e582b912be2bf8577feff08e352c9f16935a55ebd202dcc", - "data": { - "type": "0x1::coin::CoinStore<0x1::aptos_coin::AptosCoin>", - "data": { - "coin": { - "value": "903837250" - }, - "deposit_events": { - "counter": "10", - "guid": { - "id": { - "addr": "0x559d4f690c683fca7c539237aa8dc4c6ec09886b7016bf66f2cdeffef55468f0", - "creation_num": "2" - } - } - }, - "frozen": false, - "withdraw_events": { - "counter": "52485", - "guid": { - "id": { - "addr": "0x559d4f690c683fca7c539237aa8dc4c6ec09886b7016bf66f2cdeffef55468f0", - "creation_num": "3" - } - } - } - } - }, - "type": "write_resource" - }, - { - "address": "0x5d6233bb8d7f8bd714af196339e9fb3104c9d66f38867b2a0585c4f7b9d04d28", - "state_key_hash": "0xa45b7cfe18cc0ef1d6588f0f548a6a6a260d5e6bbab174507ed40cd21b7bd082", - "data": { - "type": "0x1::coin::CoinStore<0x1::aptos_coin::AptosCoin>", - "data": { - "coin": { - "value": "10" - }, - "deposit_events": { - "counter": "1", - "guid": { - "id": { - "addr": "0x5d6233bb8d7f8bd714af196339e9fb3104c9d66f38867b2a0585c4f7b9d04d28", - "creation_num": "2" - } - } - }, - "frozen": false, - "withdraw_events": { - "counter": "0", - "guid": { - "id": { - "addr": "0x5d6233bb8d7f8bd714af196339e9fb3104c9d66f38867b2a0585c4f7b9d04d28", - "creation_num": "3" - } - } - } - } - }, - "type": "write_resource" - }], -``` -
- -3. Events are the events that were emitted by the transaction. In this case, we -only care about the `0x1::coin::deposit` and `0x1::coin::withdraw` events. - -The Coin deposit event is emitted when coins are deposited into an account. The -account's balance will increase by that amount in the field `data.amoount`. To -determine the matching asset, you must match the `guid` in the `deposit_events` -to the `guid` in the `changes` section for a `CoinStore`. - -
- Coin Deposit Event - ```json - { - "events": [{ - "guid": { - "creation_number": "2", - "account_address": "0x5d6233bb8d7f8bd714af196339e9fb3104c9d66f38867b2a0585c4f7b9d04d28" - }, - "sequence_number": "0", - "type": "0x1::coin::DepositEvent", - "data": { - "amount": "10" - } - }] - } - ``` -
- -The Coin withdraw event is emitted when coins are withdrawn from an account. The -account's balance will decrease by that amount in the field `data.amount`. To -determine the matching asset, you must match the `guid` in the `deposit_events` -to the `guid` in the `changes` section for a `CoinStore`. - -
- Coin Withdraw Event - ```json - { - "events": [{ - "guid": { - "creation_number": "3", - "account_address": "0x559d4f690c683fca7c539237aa8dc4c6ec09886b7016bf66f2cdeffef55468f0" - }, - "sequence_number": "52484", - "type": "0x1::coin::WithdrawEvent", - "data": { - "amount": "10" - } - }] - } - ``` -
- -4. Gas usage only is tracked for APT. There is no direct event for tracking gas, -but it can be calculated from the transaction. Using the `gas_used` field, and -the `gas_unit_price` field, you can calculate the total gas used. In this case, -the `gas_used` is `999` and the `gas_unit_price` is `100`, so the total gas deducted -from the sender(`0x559d4f690c683fca7c539237aa8dc4c6ec09886b7016bf66f2cdeffef55468f0`) -is `999 * 100 = 99900 subunits` Remember that the subunits are used here. The -value in the gas token `APT` is `0.00099900 APT`. - - -
- Gas Information -```json - { - "gas_used": "999", - "max_gas_amount": "100000", - "gas_unit_price": "100", - "sender": "0x559d4f690c683fca7c539237aa8dc4c6ec09886b7016bf66f2cdeffef55468f0", -} -``` -
- - -5. Overall, you need both the events and the changes to determine the amount transferred -of the account. The final balances will show in the changes alone. If you watch -all of these events, you will be able to handle all possible transactions. Below -is the full example of the transaction response. - -
- Full Response - ```json - { - "version": "1747361321", - "hash": "0x7c56ad56c7d02bb11887e535b9f1b221626d5b0d4cb5a1ffbadc358c1db515ea", - "state_change_hash": "0xc901b5e9e0965201e8205977720d7dea8a3709ee0d818fd5ec752cac13eaf18a", - "event_root_hash": "0x0077cb7df9db9ee7194c489db177fe9a325bcf3f1309ea99ed934085e5592041", - "state_checkpoint_hash": null, - "gas_used": "999", - "success": true, - "vm_status": "Executed successfully", - "accumulator_root_hash": "0xb531e918441ff0a37b49856e0f1b80c329146461582287cf9788964d25e31a68", - "changes": [ - { - "address": "0x559d4f690c683fca7c539237aa8dc4c6ec09886b7016bf66f2cdeffef55468f0", - "state_key_hash": "0xb2bfa7198457291a0e582b912be2bf8577feff08e352c9f16935a55ebd202dcc", - "data": { - "type": "0x1::coin::CoinStore<0x1::aptos_coin::AptosCoin>", - "data": { - "coin": { - "value": "903837250" - }, - "deposit_events": { - "counter": "10", - "guid": { - "id": { - "addr": "0x559d4f690c683fca7c539237aa8dc4c6ec09886b7016bf66f2cdeffef55468f0", - "creation_num": "2" - } - } - }, - "frozen": false, - "withdraw_events": { - "counter": "52485", - "guid": { - "id": { - "addr": "0x559d4f690c683fca7c539237aa8dc4c6ec09886b7016bf66f2cdeffef55468f0", - "creation_num": "3" - } - } - } - } - }, - "type": "write_resource" - }, - { - "address": "0x559d4f690c683fca7c539237aa8dc4c6ec09886b7016bf66f2cdeffef55468f0", - "state_key_hash": "0xa3f2635d084b3cc01ae545c96ee15901549dab594363a46bf18e3d575c83102d", - "data": { - "type": "0x1::account::Account", - "data": { - "authentication_key": "0x559d4f690c683fca7c539237aa8dc4c6ec09886b7016bf66f2cdeffef55468f0", - "coin_register_events": { - "counter": "1", - "guid": { - "id": { - "addr": "0x559d4f690c683fca7c539237aa8dc4c6ec09886b7016bf66f2cdeffef55468f0", - "creation_num": "0" - } - } - }, - "guid_creation_num": "4", - "key_rotation_events": { - "counter": "0", - "guid": { - "id": { - "addr": "0x559d4f690c683fca7c539237aa8dc4c6ec09886b7016bf66f2cdeffef55468f0", - "creation_num": "1" - } - } - }, - "rotation_capability_offer": { - "for": { - "vec": [] - } - }, - "sequence_number": "104628", - "signer_capability_offer": { - "for": { - "vec": [] - } - } - } - }, - "type": "write_resource" - }, - { - "address": "0x5d6233bb8d7f8bd714af196339e9fb3104c9d66f38867b2a0585c4f7b9d04d28", - "state_key_hash": "0xa45b7cfe18cc0ef1d6588f0f548a6a6a260d5e6bbab174507ed40cd21b7bd082", - "data": { - "type": "0x1::coin::CoinStore<0x1::aptos_coin::AptosCoin>", - "data": { - "coin": { - "value": "10" - }, - "deposit_events": { - "counter": "1", - "guid": { - "id": { - "addr": "0x5d6233bb8d7f8bd714af196339e9fb3104c9d66f38867b2a0585c4f7b9d04d28", - "creation_num": "2" - } - } - }, - "frozen": false, - "withdraw_events": { - "counter": "0", - "guid": { - "id": { - "addr": "0x5d6233bb8d7f8bd714af196339e9fb3104c9d66f38867b2a0585c4f7b9d04d28", - "creation_num": "3" - } - } - } - } - }, - "type": "write_resource" - }, - { - "address": "0x5d6233bb8d7f8bd714af196339e9fb3104c9d66f38867b2a0585c4f7b9d04d28", - "state_key_hash": "0xba04f5a13812778031f67322e9801be65a846224e46f1360a6008402fcd0e0e0", - "data": { - "type": "0x1::account::Account", - "data": { - "authentication_key": "0x5d6233bb8d7f8bd714af196339e9fb3104c9d66f38867b2a0585c4f7b9d04d28", - "coin_register_events": { - "counter": "1", - "guid": { - "id": { - "addr": "0x5d6233bb8d7f8bd714af196339e9fb3104c9d66f38867b2a0585c4f7b9d04d28", - "creation_num": "0" - } - } - }, - "guid_creation_num": "4", - "key_rotation_events": { - "counter": "0", - "guid": { - "id": { - "addr": "0x5d6233bb8d7f8bd714af196339e9fb3104c9d66f38867b2a0585c4f7b9d04d28", - "creation_num": "1" - } - } - }, - "rotation_capability_offer": { - "for": { - "vec": [] - } - }, - "sequence_number": "0", - "signer_capability_offer": { - "for": { - "vec": [] - } - } - } - }, - "type": "write_resource" - }, - { - "state_key_hash": "0x6e4b28d40f98a106a65163530924c0dcb40c1349d3aa915d108b4d6cfc1ddb19", - "handle": "0x1b854694ae746cdbd8d44186ca4929b2b337df21d1c74633be19b2710552fdca", - "key": "0x0619dc29a0aac8fa146714058e8dd6d2d0f3bdf5f6331907bf91f3acd81e6935", - "value": "0x9f9835f429758d010000000000000000", - "data": null, - "type": "write_table_item" - } - ], - "sender": "0x559d4f690c683fca7c539237aa8dc4c6ec09886b7016bf66f2cdeffef55468f0", - "sequence_number": "104627", - "max_gas_amount": "100000", - "gas_unit_price": "100", - "expiration_timestamp_secs": "1727826277", - "payload": { - "function": "0x1::aptos_account::transfer", - "type_arguments": [], - "arguments": [ - "0x5d6233bb8d7f8bd714af196339e9fb3104c9d66f38867b2a0585c4f7b9d04d28", - "10" - ], - "type": "entry_function_payload" - }, - "signature": { - "public_key": "0xfd448fada2bac29c5f3213277e001ca8851d5644578e79484b0426c41357a457", - "signature": "0x40d8a6ee9150aa5736bee23ce1b1b851790bc0aa7e2485c0760d5808027040a2ef4170b88962867b045197576c5e89a4c640bf43586e6b3ead2b510b59acc20a", - "type": "ed25519_signature" - }, - "events": [ - { - "guid": { - "creation_number": "0", - "account_address": "0x5d6233bb8d7f8bd714af196339e9fb3104c9d66f38867b2a0585c4f7b9d04d28" - }, - "sequence_number": "0", - "type": "0x1::account::CoinRegisterEvent", - "data": { - "type_info": { - "account_address": "0x1", - "module_name": "0x6170746f735f636f696e", - "struct_name": "0x4170746f73436f696e" - } - } - }, - { - "guid": { - "creation_number": "3", - "account_address": "0x559d4f690c683fca7c539237aa8dc4c6ec09886b7016bf66f2cdeffef55468f0" - }, - "sequence_number": "52484", - "type": "0x1::coin::WithdrawEvent", - "data": { - "amount": "10" - } - }, - { - "guid": { - "creation_number": "2", - "account_address": "0x5d6233bb8d7f8bd714af196339e9fb3104c9d66f38867b2a0585c4f7b9d04d28" - }, - "sequence_number": "0", - "type": "0x1::coin::DepositEvent", - "data": { - "amount": "10" - } - }, - { - "guid": { - "creation_number": "0", - "account_address": "0x0" - }, - "sequence_number": "0", - "type": "0x1::transaction_fee::FeeStatement", - "data": { - "execution_gas_units": "6", - "io_gas_units": "5", - "storage_fee_octas": "98800", - "storage_fee_refund_octas": "0", - "total_charge_gas_units": "999" - } - } - ], - "timestamp": "1727825677775812", - "type": "user_transaction" - } - ``` -
- - -### Fungible Asset Balance Changes - -For fungible assets, the balance changes are tracked in the `primary_fungible_store`. -The primary fungible store address is deterministic, and will always be tracked by -the owner of the store. - -An example: https://api.mainnet.aptoslabs.com/v1/transactions/by_version/1750174030 - -There are a few steps when tracking fungible assets: - -1. There will be two types of events for fungible assets. `0x1::fungible_asset::Deposit` and `0x1::fungible_asset::Withdraw`. - -`Withdraw` events are similar to the coin events, where the balance will decrease by the amount in the `data.amount` field. -And similarly `Deposit` events will increase the balance by the amount in the `data.amount` field. - -Note that, I've omitted the sequence number, and GUID fields, as they do not apply to module events. - -Each event has a `store` field, which in this case is `0x8a9d57692a9d4deb1680eaf107b83c152436e10f7bb521143fa403fa95ef76a`. -This is the address of the `FungibleStore` for the asset, where the balance is stored. Note this, for the next step. - -
- Fungible Asset Events -```json -{ - "events": [ - { - "type": "0x1::fungible_asset::Withdraw", - "data": { - "amount": "1", - "store": "0x8a9d57692a9d4deb1680eaf107b83c152436e10f7bb521143fa403fa95ef76a" - } - }, - { - "type": "0x1::fungible_asset::Deposit", - "data": { - "amount": "1", - "store": "0x8a9d57692a9d4deb1680eaf107b83c152436e10f7bb521143fa403fa95ef76a" - } - } - ] -} -``` -
- -2. Next, we take a look at the `0x1::fungible_asset::FungibleStore` changes. This -will show the end state of the balance for the fungible asset. The balance is in -the `data.balance` field. The `address` field will match the `store` field from -the events. The identifier of the fungible asset, is the `metadata` field. It -is the address of the `metadata` for the fungible asset. - -Additionally, to figure out the actual owner of the assets, you will need to look -at the owner of the store. In this case, you will need the `0x1::object::ObjectCore`, where -the `address` field matches the `store` field from the events. The `owner` field -will show the asset owner's address. - -
- Fungible Asset Changes -```json -{ - "changes":[ - { - "address": "0x8a9d57692a9d4deb1680eaf107b83c152436e10f7bb521143fa403fa95ef76a", - "state_key_hash": "0x5b587931247dd5b43874ab29c3305c0ee7d26e7571fed3aea409375530e3a62c", - "data": { - "type": "0x1::fungible_asset::FungibleStore", - "data": { - "balance": "126691270443", - "frozen": false, - "metadata": { - "inner": "0x2ebb2ccac5e027a87fa0e2e5f656a3a4238d6a48d93ec9b610d570fc0aa0df12" - } - } - }, - "type": "write_resource" - }, - { - "address": "0x8a9d57692a9d4deb1680eaf107b83c152436e10f7bb521143fa403fa95ef76a", - "state_key_hash": "0x5b587931247dd5b43874ab29c3305c0ee7d26e7571fed3aea409375530e3a62c", - "data": { - "type": "0x1::object::ObjectCore", - "data": { - "allow_ungated_transfer": false, - "guid_creation_num": "1125899906842628", - "owner": "0xc67545d6f3d36ed01efc9b28cbfd0c1ae326d5d262dd077a29539bcee0edce9e", - "transfer_events": { - "counter": "0", - "guid": { - "id": { - "addr": "0x8a9d57692a9d4deb1680eaf107b83c152436e10f7bb521143fa403fa95ef76a", - "creation_num": "1125899906842624" - } - } - } - } - }, - "type": "write_resource" - } - ] -} -``` -
- - -### Coins migrated to Fungible Asset Balance Changes - -For coins migrated to fungible assets, it is just simply tracking of the two above. -A coin migrated to a fungible asset will have both the coin store changes and the -primary fungible asset store changes. The amounts would need to be aggregated -together, and otherwise, handled as a coin. - -The Fungible asset metadata address is the hash of the coin type and 0xA - -``` -address = sha3_256(0xA | coin_type | 0xFE) -``` - -Here is an example of a migrated coin with APT: https://api.mainnet.aptoslabs.com/v1/transactions/by_version/1642580695 -
- Full response - ```json - { - "version": "1642580695", - "hash": "0xe67ba1c4242d5c1de42eb8419558c4edf2318e185a3940a00f4150b519d06508", - "state_change_hash": "0x07c5ec97afdf731c2778fccb37fe209369b28dcf6dcf11c3cf13b83c962f7f96", - "event_root_hash": "0xad349cbea90bef601dfae9df822f5698af296951fc5f94359fcacc1e69e9fa3d", - "state_checkpoint_hash": null, - "gas_used": "545", - "success": true, - "vm_status": "Executed successfully", - "accumulator_root_hash": "0x88e81bde70f32a86e46b288a917a44b2868a46973fac7fad16b5e780f48b0e67", - "changes": [ - { - "address": "0xa", - "state_key_hash": "0x1db5441d8fa4229c5844f73fd66da4ad8176cb8793d8b3a7f6ca858722030043", - "data": { - "type": "0x1::coin::PairedCoinType", - "data": { - "type": { - "account_address": "0x1", - "module_name": "0x6170746f735f636f696e", - "struct_name": "0x4170746f73436f696e" - } - } - }, - "type": "write_resource" - }, - { - "address": "0xa", - "state_key_hash": "0x1db5441d8fa4229c5844f73fd66da4ad8176cb8793d8b3a7f6ca858722030043", - "data": { - "type": "0x1::coin::PairedFungibleAssetRefs", - "data": { - "burn_ref_opt": { - "vec": [ - { - "metadata": { - "inner": "0xa" - } - } - ] - }, - "mint_ref_opt": { - "vec": [ - { - "metadata": { - "inner": "0xa" - } - } - ] - }, - "transfer_ref_opt": { - "vec": [ - { - "metadata": { - "inner": "0xa" - } - } - ] - } - } - }, - "type": "write_resource" - }, - { - "address": "0xa", - "state_key_hash": "0x1db5441d8fa4229c5844f73fd66da4ad8176cb8793d8b3a7f6ca858722030043", - "data": { - "type": "0x1::fungible_asset::ConcurrentSupply", - "data": { - "current": { - "max_value": "340282366920938463463374607431768211455", - "value": "47948384" - } - } - }, - "type": "write_resource" - }, - { - "address": "0xa", - "state_key_hash": "0x1db5441d8fa4229c5844f73fd66da4ad8176cb8793d8b3a7f6ca858722030043", - "data": { - "type": "0x1::fungible_asset::Metadata", - "data": { - "decimals": 8, - "icon_uri": "", - "name": "Aptos Coin", - "project_uri": "", - "symbol": "APT" - } - }, - "type": "write_resource" - }, - { - "address": "0xa", - "state_key_hash": "0x1db5441d8fa4229c5844f73fd66da4ad8176cb8793d8b3a7f6ca858722030043", - "data": { - "type": "0x1::object::ObjectCore", - "data": { - "allow_ungated_transfer": true, - "guid_creation_num": "1125899906842625", - "owner": "0x1", - "transfer_events": { - "counter": "0", - "guid": { - "id": { - "addr": "0xa", - "creation_num": "1125899906842624" - } - } - } - } - }, - "type": "write_resource" - }, - { - "address": "0xa", - "state_key_hash": "0x1db5441d8fa4229c5844f73fd66da4ad8176cb8793d8b3a7f6ca858722030043", - "data": { - "type": "0x1::primary_fungible_store::DeriveRefPod", - "data": { - "metadata_derive_ref": { - "self": "0xa" - } - } - }, - "type": "write_resource" - }, - { - "address": "0x7ed92ce166e251fc133f6b4d46a6b41307962e3b6864c2231110b3808648188", - "state_key_hash": "0x5ce89e323a23fb5570694dfb687d474d44563638c5ef774a2364d8347f5732b8", - "data": { - "type": "0x1::coin::MigrationFlag", - "data": { - "dummy_field": false - } - }, - "type": "write_resource" - }, - { - "address": "0x7ed92ce166e251fc133f6b4d46a6b41307962e3b6864c2231110b3808648188", - "state_key_hash": "0x5ce89e323a23fb5570694dfb687d474d44563638c5ef774a2364d8347f5732b8", - "data": { - "type": "0x1::fungible_asset::FungibleStore", - "data": { - "balance": "37949184", - "frozen": false, - "metadata": { - "inner": "0xa" - } - } - }, - "type": "write_resource" - }, - { - "address": "0x7ed92ce166e251fc133f6b4d46a6b41307962e3b6864c2231110b3808648188", - "state_key_hash": "0x5ce89e323a23fb5570694dfb687d474d44563638c5ef774a2364d8347f5732b8", - "data": { - "type": "0x1::object::ObjectCore", - "data": { - "allow_ungated_transfer": false, - "guid_creation_num": "1125899906842625", - "owner": "0xa746e980ae21949a4f084db7403430f00bce3c9a1da4101ffcf0bf45ebd35e7e", - "transfer_events": { - "counter": "0", - "guid": { - "id": { - "addr": "0x7ed92ce166e251fc133f6b4d46a6b41307962e3b6864c2231110b3808648188", - "creation_num": "1125899906842624" - } - } - } - } - }, - "type": "write_resource" - }, - { - "address": "0x8a4613c356c21a45045e06dcc404bfee363aabd65a774d4d43defd71289239b2", - "state_key_hash": "0x7c2d6e31d4ac5bbf93e19412437c0c288766b240674f71f457b9e3ef68be5003", - "data": { - "type": "0x1::fungible_asset::FungibleStore", - "data": { - "balance": "10000", - "frozen": false, - "metadata": { - "inner": "0xa" - } - } - }, - "type": "write_resource" - }, - { - "address": "0x8a4613c356c21a45045e06dcc404bfee363aabd65a774d4d43defd71289239b2", - "state_key_hash": "0x7c2d6e31d4ac5bbf93e19412437c0c288766b240674f71f457b9e3ef68be5003", - "data": { - "type": "0x1::object::ObjectCore", - "data": { - "allow_ungated_transfer": false, - "guid_creation_num": "1125899906842625", - "owner": "0x5", - "transfer_events": { - "counter": "0", - "guid": { - "id": { - "addr": "0x8a4613c356c21a45045e06dcc404bfee363aabd65a774d4d43defd71289239b2", - "creation_num": "1125899906842624" - } - } - } - } - }, - "type": "write_resource" - }, - { - "address": "0xa746e980ae21949a4f084db7403430f00bce3c9a1da4101ffcf0bf45ebd35e7e", - "state_key_hash": "0xfb7c1f2762da89f00a222f93bd771b478edb4361475c4a518178564be8616dd6", - "data": { - "type": "0x1::account::Account", - "data": { - "authentication_key": "0xa746e980ae21949a4f084db7403430f00bce3c9a1da4101ffcf0bf45ebd35e7e", - "coin_register_events": { - "counter": "14", - "guid": { - "id": { - "addr": "0xa746e980ae21949a4f084db7403430f00bce3c9a1da4101ffcf0bf45ebd35e7e", - "creation_num": "0" - } - } - }, - "guid_creation_num": "44", - "key_rotation_events": { - "counter": "0", - "guid": { - "id": { - "addr": "0xa746e980ae21949a4f084db7403430f00bce3c9a1da4101ffcf0bf45ebd35e7e", - "creation_num": "1" - } - } - }, - "rotation_capability_offer": { - "for": { - "vec": [] - } - }, - "sequence_number": "52", - "signer_capability_offer": { - "for": { - "vec": [] - } - } - } - }, - "type": "write_resource" - } - ], - "sender": "0xa746e980ae21949a4f084db7403430f00bce3c9a1da4101ffcf0bf45ebd35e7e", - "sequence_number": "51", - "max_gas_amount": "817", - "gas_unit_price": "100", - "expiration_timestamp_secs": "1724196316", - "payload": { - "function": "0x1::primary_fungible_store::transfer", - "type_arguments": [ - "0x1::fungible_asset::Metadata" - ], - "arguments": [ - { - "inner": "0xa" - }, - "0x5", - "10000" - ], - "type": "entry_function_payload" - }, - "signature": { - "public_key": "0x330e75a102e37270b788caee8dd819e5badedd5fa17fe9f72017732e9bb98c60", - "signature": "0xd4666df2887cf2d8192230e4a03d842ea75a86ffbc46a9a16a9baede6ff646c6b2bcafc524d3a4a7a66c223b5db576beb5cfefbd549620e69097c0a364c7a800", - "type": "ed25519_signature" - }, - "events": [ - { - "guid": { - "creation_number": "0", - "account_address": "0x0" - }, - "sequence_number": "0", - "type": "0x1::fungible_asset::Withdraw", - "data": { - "amount": "10000", - "store": "0x7ed92ce166e251fc133f6b4d46a6b41307962e3b6864c2231110b3808648188" - } - }, - { - "guid": { - "creation_number": "0", - "account_address": "0x0" - }, - "sequence_number": "0", - "type": "0x1::fungible_asset::Deposit", - "data": { - "amount": "10000", - "store": "0x8a4613c356c21a45045e06dcc404bfee363aabd65a774d4d43defd71289239b2" - } - }, - { - "guid": { - "creation_number": "0", - "account_address": "0x0" - }, - "sequence_number": "0", - "type": "0x1::fungible_asset::Withdraw", - "data": { - "amount": "54500", - "store": "0x7ed92ce166e251fc133f6b4d46a6b41307962e3b6864c2231110b3808648188" - } - }, - { - "guid": { - "creation_number": "0", - "account_address": "0x0" - }, - "sequence_number": "0", - "type": "0x1::transaction_fee::FeeStatement", - "data": { - "execution_gas_units": "6", - "io_gas_units": "7", - "storage_fee_octas": "53240", - "storage_fee_refund_octas": "0", - "total_charge_gas_units": "545" - } - } - ], - "timestamp": "1724196287102837", - "type": "user_transaction" - } - ``` -
- -## Transferring Assets - -### Coin (or migrated coin) Transfers - - - APT, the native token of Aptos, is a migrated coin. Please use the `aptos_account::transfer` functions - to transfer APT tokens. - - -We suggest you use `0x1::aptos_account::transfer_coins(receiver address, amount)` for transferring coins. It will -register the coin if it hasn't been registered yet, and create the associated account if it hasn't been created yet. -This will continue to work with any coins that were migrated to a fungible asset, including APT. - -Coins can be transferred in the following ways: -* [`0x1::aptos_account::transfer_coins(receiver address, amount)`](https://github.com/aptos-labs/aptos-core/blob/main/aptos-move/framework/aptos-framework/sources/aptos_account.move#L108-L112) - Transfer a coin to another account. -* [`0x1::aptos_account::batch_transfer_coins(receiver addresses, amounts)`](https://github.com/aptos-labs/aptos-core/blob/main/aptos-move/framework/aptos-framework/sources/aptos_account.move#L93-L106) - Transfer a coin to multiple accounts. -* [`0x1::aptos_account::transfer(receiver address, amount)`](https://github.com/aptos-labs/aptos-core/blob/main/aptos-move/framework/aptos-framework/sources/aptos_account.move#L74-L91) - Transfer specifically APT to another account. - -{/* TODO examples */} - -### Fungible Asset Transfers - -We suggest you use `0x1::primary_fungible_store::transfer<0x1::object::ObjectCore>(receiver address, amount)` for transferring fungible assets. -It will send the associated fungible asset, and create a primary store for the asset if it hasn't been created yet. - -{/* TODO examples */} - - - Note: This will not create an account for the user if it hasn't been created - yet. You will need to call - `0x1::aptos_account::create_account(account address)` to create the account - before the user can - submit transactions. - - -{ /* TODO Staking some other day */ } - -## Testing - -In order to check that everything is working correctly, we've provided these checks. - -### Balance Checks - -To test balance checks, you can check the balance for the account `0x5` for the asset `0x1::aptos_coin::AptosCoin`. -The balance should show `0.002 APT`, where 0.001 APT is a coin, and 0.001 APT is a migrated coin (fungible asset). - -If your balance is not correct, see [Coin and Migrated Coin Balances](#coin-and-migrated-coin-balances) for more information. - -### Balance Change / Transfer Checks - -#### Check Coin Transfer - -To test a transfer, create a transaction to transfer 0.001 APT to another account. -The transaction should be successful, and the balance should be updated, where -the balance is 0.001 APT smaller and minus the gas cost associated. - -#### Check Fungible Asset Transfer - -To test a transfer, you can fund an account with the fungible asset here https://test-token-faucet.vercel.app/ -and then transfer the fungible asset to another account. The balance should be updated -according to the change, and you should be able to track the mint on the website. - -## Stablecoin Addresses - -| Token Name | Token Symbol | Token Address | Source of Address | -|----------------------|--------------|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------|-------------------------------------------------------------------------------------------------------| -| Tether USD | USDt | [0x357b0b74bc833e95a115ad22604854d6b0fca151cecd94111770e5d6ffc9dc2b](https://explorer.aptoslabs.com/fungible_asset/0x357b0b74bc833e95a115ad22604854d6b0fca151cecd94111770e5d6ffc9dc2b?network=mainnet) | [Aptos Foundation](https://aptosfoundation.org/currents/global-finance-moves-faster-on-aptos) | -| USDC | USDC | [0xbae207659db88bea0cbead6da0ed00aac12edcdda169e591cd41c94180b46f3b](https://explorer.aptoslabs.com/fungible_asset/0xbae207659db88bea0cbead6da0ed00aac12edcdda169e591cd41c94180b46f3b?network=mainnet) | [Circle](https://developers.circle.com/stablecoins/usdc-on-main-networks) | -| Ondo US Dollar Yield | USDY | [0xcfea864b32833f157f042618bd845145256b1bf4c0da34a7013b76e42daa53cc::usdy::USDY](https://explorer.aptoslabs.com/coin/0xcfea864b32833f157f042618bd845145256b1bf4c0da34a7013b76e42daa53cc::usdy::USDY?network=mainnet) | [Ondo Finance](https://ondo.finance/usdy) | - -## FAQ - -### What is the finality of a transaction? - -Aptos uses a BFT consensus algorithm, so transactions are finalized immediately -after committing to the blockchain. - -### What is the transaction fee on a transaction? - -Transaction fees are variable, but for most cases here are fixed. Check out -[simulating transactions](../../network/blockchain/gas-txn-fee#estimating-gas-consumption-via-simulation) -to get an idea of the fee. diff --git a/apps/nextra/pages/zh/build/guides/first-coin.mdx b/apps/nextra/pages/zh/build/guides/first-coin.mdx deleted file mode 100644 index e96f854e3..000000000 --- a/apps/nextra/pages/zh/build/guides/first-coin.mdx +++ /dev/null @@ -1,398 +0,0 @@ - -import { Callout, Tabs } from 'nextra/components'; - -# Your First Coin - -This tutorial introduces how you can compile, deploy, and mint your own coin (as defined [here](https://github.com/aptos-labs/aptos-core/blob/main/aptos-move/framework/aptos-framework/sources/coin.move)), named [MoonCoin](https://github.com/aptos-labs/aptos-core/tree/main/aptos-move/move-examples/moon_coin). - -## Step 1: Pick an SDK - -Install your preferred SDK from the below list: - -- [TypeScript SDK](../sdks/ts-sdk.mdx) -- [Python SDK](../sdks/python-sdk.mdx) - ---- - -## Step 2: Install the CLI - -[Install the precompiled binary for the Aptos CLI](../cli.mdx). - ---- - -## Step 3: Run the example - - - - -Clone the `aptos-ts-sdk` repo and build it: - -```bash filename="Terminal" -git clone https://github.com/aptos-labs/aptos-ts-sdk.git -cd aptos-ts-sdk -pnpm install -pnpm build -``` - -Navigate to the TypeScript examples directory: - -```bash filename="Terminal" -cd examples/typescript/ -``` - -Install the necessary dependencies: - -```bash filename="Terminal" -pnpm install -``` - -Run the TypeScript [`your_coin`](https://github.com/aptos-labs/aptos-ts-sdk/blob/main/examples/typescript/your_coin.ts) example: - -```bash filename="Terminal" -pnpm run your_coin -``` - -The application will complete, printing: - -```bash filename="Terminal" -Bob's initial MoonCoin balance: 0. -Alice mints herself 100 MoonCoin. -Alice transfers 100 MoonCoin to Bob. -Bob's updated MoonCoin balance: 100. -``` - - - - -Clone the `aptos-core` repo: - -```bash filename="Terminal" -git clone https://github.com/aptos-labs/aptos-core -``` - -Navigate to the Python SDK directory: - -```bash filename="Terminal" -cd aptos-core/ecosystem/python/sdk -``` - -Install the necessary dependencies: - -```bash filename="Terminal" -curl -sSL https://install.python-poetry.org | python3 -poetry install -``` - -Run the Python [`your_coin`](https://github.com/aptos-labs/aptos-python-sdk/blob/main/examples/your_coin.py) example: - -```bash filename="Terminal" -poetry run python -m examples.your_coin ~/aptos-core/aptos-move/move-examples/moon_coin -``` - -### Step 3.1: Build the package - -The example run will pause with the following output: - -```bash filename="Terminal" -=== Addresses === -Alice: 0x5e603a89cf690d7134cf2f24fdb16ba90c4f5686333721c12e835fb6c76bc7ba -Bob: 0xc8421fa4a99153f955e50f1de2a6acff2f3fd0bb33aa17ba1f5b32b699f6c825 - -Update the package with Alice's address, compile, and press enter. -``` - -At this point, open another terminal and change directories to the MoonCoin package's directory: - -```bash filename="Terminal" -cd ~/aptos-core/aptos-move/move-examples/moon_coin -``` - -Next, build the package using the CLI: - -```bash filename="Terminal" -aptos move compile --named-addresses MoonCoin=0x5e603a89cf690d7134cf2f24fdb16ba90c4f5686333721c12e835fb6c76bc7ba --save-metadata -``` - -The `--named-addresses` is a list of address mappings that must be translated in order for the package to be compiled to be stored in Alice's account. Notice how `MoonCoin` is set to Alice's address printed above. Also `--save-metadata` is required to publish the package. - ---- - -### Step 3.2: Completing the example - -Returning to the previous prompt, press ENTER as the package is now ready to be published. - -The application will complete, printing: - -```bash filename="Terminal" - -Publishing MoonCoin package. - -Bob registers the newly created coin so he can receive it from Alice. -Bob's initial MoonCoin balance: 0. -Alice mints Bob some of the new coin. -Bob's updated MoonCoin balance: 100. -``` - - - - ---- - -## Step 4: MoonCoin in depth - -### Step 4.1: Building and publishing the MoonCoin package - -Move contracts are effectively a set of Move modules known as a package. When deploying or upgrading a new package, the compiler must be invoked with `--save-metadata` to publish the package. In the case of MoonCoin, the following output files are critical: - -- `build/Examples/package-metadata.bcs`: Contains the metadata associated with the package. -- `build/Examples/bytecode_modules/moon_coin.mv`: Contains the bytecode for the `moon_coin.move` module. - -These are read by the example and published to the Aptos blockchain: - - - - -In the TypeScript example, we use `aptos move build-publish-payload` command to compile and build the module. -That command builds the `build` folder that contains the `package-metadata.bcs` and the bytecode for the `moon_coin.mv` module. The command also builds a publication transaction payload and stores it in a JSON output file that we can later read from to get the `metadataBytes` and `byteCode` to publish the contract to chain with. - -Compile the package: - -```ts filename="example.ts" -export function compilePackage( - packageDir: string, - outputFile: string, - namedAddresses: Array<{ name: string; address: AccountAddress }>, -) { - const addressArg = namedAddresses - .map(({ name, address }) => `${name}=${address}`) - .join(" "); - // Assume-yes automatically overwrites the previous compiled version, only do this if you are sure you want to overwrite the previous version. - const compileCommand = `aptos move build-publish-payload --json-output-file ${outputFile} --package-dir ${packageDir} --named-addresses ${addressArg} --assume-yes`; - execSync(compileCommand); -} - -compilePackage("move/moonCoin", "move/moonCoin/moonCoin.json", [ - { name: "MoonCoin", address: alice.accountAddress }, -]); -``` - -Publish the package to chain: - -```ts filename="example.ts" -export function getPackageBytesToPublish(filePath: string) { - // current working directory - the root folder of this repo - const cwd = process.cwd(); - // target directory - current working directory + filePath (filePath JSON file is generated with the previous, compilePackage, CLI command) - const modulePath = path.join(cwd, filePath); - - const jsonData = JSON.parse(fs.readFileSync(modulePath, "utf8")); - - const metadataBytes = jsonData.args[0].value; - const byteCode = jsonData.args[1].value; - - return { metadataBytes, byteCode }; -} - -const { metadataBytes, byteCode } = getPackageBytesToPublish( - "move/moonCoin/moonCoin.json", -); - -// Publish MoonCoin package to chain -const transaction = await aptos.publishPackageTransaction({ - account: alice.accountAddress, - metadataBytes, - moduleBytecode: byteCode, -}); - -const pendingTransaction = await aptos.signAndSubmitTransaction({ - signer: alice, - transaction, -}); - -await aptos.waitForTransaction({ transactionHash: pendingTransaction.hash }); -``` - - - - -```python filename="example.py" -module_path = os.path.join( - moon_coin_path, "build", "Examples", "bytecode_modules", "moon_coin.mv" -) -with open(module_path, "rb") as f: - module = f.read() - -metadata_path = os.path.join( - moon_coin_path, "build", "Examples", "package-metadata.bcs" -) -with open(metadata_path, "rb") as f: - metadata = f.read() - -print("\nPublishing MoonCoin package.") -package_publisher = PackagePublisher(rest_client) -txn_hash = await package_publisher.publish_package(alice, metadata, [module]) -await rest_client.wait_for_transaction(txn_hash) -``` - - - - ---- - -### Step 4.2: Understanding the MoonCoin module - -The MoonCoin module defines the `MoonCoin` struct, or the distinct type of coin type. In addition, it contains a function called `init_module`. The `init_module` function is called when the module is published. In this case, MoonCoin initializes the `MoonCoin` coin type as a `ManagedCoin`, which is maintained by the owner of the account. - - -ManagedCoin framework -[`ManagedCoin`](https://github.com/aptos-labs/aptos-core/blob/f81ccb01f00227f9c0f36856fead4879f185a9f6/aptos-move/framework/aptos-framework/sources/managed_coin.move#L1) is a simple coin management framework for coins directly managed by users. It provides convenience wrappers around `mint` and `burn`. - - -```move filename="moon_coin.mv" -module MoonCoin::moon_coin { - struct MoonCoin {} - - fun init_module(sender: &signer) { - aptos_framework::managed_coin::initialize( - sender, - b"Moon Coin", - b"MOON", - 6, - false, - ); - } -} -``` - ---- - -### Step 4.3: Understanding coins - -Coins have several primitives: - -- **Minting**: Creating new coins. -- **Burning**: Deleting coins. -- **Freezing**: Preventing an account from storing coins in `CoinStore`. -- **Registering**: Creating a `CoinStore` resource on an account for storing coins. -- **Transferring**: Withdrawing and depositing coins into `CoinStore`. - - - -The entity that creates a new coin gains the capabilities for minting, burning, and freezing. - - ---- - -#### Step 4.3.1: Initializing a coin - -Once a coin type has been published to the Aptos blockchain, the entity that published that coin type can initialize it: - -```move -module 0x1::coin { - public fun initialize( - account: &signer, - name: string::String, - symbol: string::String, - decimals: u8, - monitor_supply: bool, - ): (BurnCapability, FreezeCapability, MintCapability) { - let account_addr = signer::address_of(account); - - assert!( - coin_address() == account_addr, - error::invalid_argument(ECOIN_INFO_ADDRESS_MISMATCH), - ); - - assert!( - !exists>(account_addr), - error::already_exists(ECOIN_INFO_ALREADY_PUBLISHED), - ); - - let coin_info = CoinInfo { - name, - symbol, - decimals, - supply: if (monitor_supply) { option::some(optional_aggregator::new(MAX_U128, false)) } else { option::none() }, - }; - move_to(account, coin_info); - - (BurnCapability{ }, FreezeCapability{ }, MintCapability{ }) - } -} -``` - -This ensures that this coin type has never been initialized before. Notice the check on lines 10 and 15 to ensure that the caller to `initialize` is the same one that actually published this module, and that there is no `CoinInfo` stored on their account. If both those conditions check, then a `CoinInfo` is stored and the caller obtains capabilities for burning, freezing, and minting. - - -MoonCoin calls this `initialize` function automatically upon package publishing. - - ---- - -#### Step 4.3.2: Registering a coin - -To use a coin, an entity must register a `CoinStore` for it on their account: - -```move" -public entry fun registerCoinType(account: &signer) { -``` - -MoonCoin uses `ManagedCoin` that provides an entry function wrapper: `managed_coin::register`. Here is an example script for registration: - -```move -script { - fun register(account: &signer) { - aptos_framework::managed_coin::register(account) - } -} -``` - ---- - -#### Step 4.3.3: Minting a coin - -Minting coins requires the mint capability that was produced during initialization. the function `mint` (see below) takes in that capability and an amount, and returns back a `Coin` struct containing that amount of coins. If the coin tracks supply, it will be updated. - -```move -module 0x1::coin { - public fun mint( - amount: u64, - _cap: &MintCapability, - ): Coin acquires CoinInfo { - if (amount == 0) { - return zero() - }; - - let maybe_supply = &mut borrow_global_mut>(coin_address()).supply; - if (option::is_some(maybe_supply)) { - let supply = option::borrow_mut(maybe_supply); - optional_aggregator::add(supply, (amount as u128)); - }; - - Coin { value: amount } - } -} -``` - -`ManagedCoin` makes this easier by providing an entry function `managed_coin::mint`. - ---- - -#### Step 4.3.4: Transferring a coin - -Aptos provides several building blocks to support coin transfers: - -- `coin::deposit`: Allows any entity to deposit a coin into an account that has already called `coin::register`. -- `coin::withdraw`: Allows any entity to extract a coin amount from their account. -- `aptos_account::transfer_coins`: Transfer coins of specific CoinType to a receiver. - - -There are two separate withdraw and deposit events instead of a single transfer event. - - -## Supporting documentation - -- [Aptos CLI](../cli.mdx) -- [TypeScript SDK](../sdks/ts-sdk.mdx) -- [Python SDK](../sdks/python-sdk.mdx) -- [REST API specification](../../network/nodes/aptos-api-spec.mdx) diff --git a/apps/nextra/pages/zh/build/guides/first-fungible-asset.mdx b/apps/nextra/pages/zh/build/guides/first-fungible-asset.mdx deleted file mode 100644 index fc6c92deb..000000000 --- a/apps/nextra/pages/zh/build/guides/first-fungible-asset.mdx +++ /dev/null @@ -1,248 +0,0 @@ ---- -title: "Your First Fungible Asset" ---- -import { Callout, Steps } from 'nextra/components' - -# Your First Fungible Asset - -This tutorial will teach you how to create your own Fungible Asset (FA) named [FACoin](https://github.com/aptos-labs/aptos-core/tree/main/aptos-move/move-examples/fungible_asset/fa_coin). The [Fungible Asset Standard](../smart-contracts/fungible-asset.mdx) provides built-in support for minting, transferring, burning, and tracking account balances, so is useful for representing fungible assets. We will use the [TypeScript SDK](../sdks/ts-sdk.mdx) to deploy the contract and test it once it is on-chain. - -At a high level, the Fungible Asset Standard works through two main Objects: - -1. A `Metadata` Object to store information about the fungible asset. -2. `FungibleStore`s for each account that has the fungible asset to track their current account balance. - -Sending a fungible asset to someone will cause them to receive a `FungibleStore` and update the balances in both accounts accordingly. - -## Seeing Fungible Assets In Action - -Here we will modify, deploy, and test the example [FACoin](https://github.com/aptos-labs/aptos-ts-sdk/blob/main/examples/typescript/move/facoin/sources/fa_coin.move) contract to see how the Fungible Asset Standard works. If you are writing your own fungible asset contract, you may also want to reference the Stablecoin example contract [here](https://learn.aptoslabs.com/en/code-examples/stablecoin). - - -### Install the [Aptos CLI](../../build/cli.mdx). - -This will be used by the deploy scripts to publish the `FACoin` contract onchain. - -### Clone the TypeScript SDK repo. - -This repo contains the Fungible Asset example code. - -```bash filename="Terminal" -git clone https://github.com/aptos-labs/aptos-ts-sdk.git -``` - -### Navigate to the top-level of the cloned repository. - -```bash filename="Terminal" -cd aptos-ts-sdk -``` - -### Install the SDKs dependencies. - -```bash filename="Terminal" -pnpm install -``` - -### Build the TypeScript SDK. - -The example requires the local build of the TypeScript SDK. - -```bash filename="Terminal" -pnpm build -``` - -### Open `fa_coin.move` in an editor. - -You can find `fa_coin.move` at `examples/typescript/move/facoin/sources/fa_coin.move`. - -This is the Move file which contains the bulk of the contract logic. We will dive into the details of how this contract works after showing you an example of it in action. - -### Edit the `ASSET_NAME` to be the name of your new fungible asset. - -Ex. “Tutorial Token”. The values you set here will show up in the deployed contract and when we are testing how things work. - -### Navigate to `examples/typescript`. - -```bash filename="Terminal" -cd examples/typescript -``` - -### Install the dependencies for the examples. - -```bash filename="Terminal" -pnpm install -``` - -### Run `your_fungible_asset`. - -```bash filename="Terminal" -pnpm run your_fungible_asset -``` - -You should see an output demonstrating how the fungible assets are created and transferred that looks like this: - -```bash filename="Terminal" -=== Addresses === -Alice: 0x0c5dd7abbd67db06325fa1a2f37a1833f9a92ff2beb90f32495a9d80972429cd -Bob: 0x2a796f4255d5c23684fe6cc521069d684516031bb5ae1ad2061ddc5414450807 -Charlie: 0xd824909be65a224f651ff6e9b82ec99ad5707fcef739d1003be20fc69fb93d7a - -=== Compiling FACoin package locally === -In order to run compilation, you must have the `aptos` CLI installed. -Running the compilation locally, in a real situation you may want to compile this ahead of time. -aptos move build-publish-payload --json-output-file move/facoin/facoin.json --package-dir move/facoin --named-addresses FACoin=0x0c5dd7abbd67db06325fa1a2f37a1833f9a92ff2beb90f32495a9d80972429cd --assume-yes -Compiling, may take a little while to download git dependencies... -UPDATING GIT DEPENDENCY https://github.com/aptos-labs/aptos-core.git -INCLUDING DEPENDENCY AptosFramework -INCLUDING DEPENDENCY AptosStdlib -INCLUDING DEPENDENCY MoveStdlib -BUILDING facoin - -===Publishing FACoin package=== -Transaction hash: 0x0c8a24987bdf2e5e40d8a00f6c97ac55419757bc440097d76959a64dbeafc351 -metadata address: 0x2e0e90c701233467f27150f42d365e27e72eb0be8e2a74ee529c31b813bbb321 -All the balances in this example refer to balance in primary fungible stores of each account. -Alice's initial balance: 0. -Bob's initial balance: 0. -Charlie's initial balance: 0. -Alice mints Charlie 100 coins. -Charlie's updated "Tutorial Token" primary fungible store balance: 0. -Alice freezes Bob's account. -Alice as the admin forcefully transfers the newly minted coins of Charlie to Bob ignoring that Bob's account is frozen. -Bob's updated "Tutorial Token" balance: 0. -Alice unfreezes Bob's account. -Alice burns 50 coins from Bob. -Bob's updated "Tutorial Token" balance: 0. -Bob transfers 10 coins to Alice as the owner. -Alice's updated "Tutorial Token" balance: 0. -Bob's updated "Tutorial Token" balance: 0. -done. -``` - - - - -If you change the name of the token in the `fa_coin.move` contract you will see the output update with that name. - - -## Understanding the `fa_coin.move` Example Contract - -The full contract for FACoin.move can be found [here](https://github.com/aptos-labs/aptos-ts-sdk/blob/main/examples/typescript/move/facoin/sources/fa_coin.move). - -Let’s go step by step through how this contract is written. - - -### Move.toml - -The Move.toml file allows Move to import dependencies, determine which addresses to use, and includes metadata about the contract. - -Regardless of which features you add to your fungible asset, your Move.toml will likely have similar fields to this at a minimum. In this case, we have the primary contract address `FACoin` that needs specifying at deploy time (indicated by leaving the value as “_”). It also includes the GitHub dependency to import the Fungible Asset standard from “AptosFramework”. - -```toml filename="Move.toml" -[package] -name = "facoin" -version = "1.0.0" -authors = [] - -[addresses] -FACoin = "_" - -[dependencies.AptosFramework] -git = "https://github.com/aptos-labs/aptos-core.git" -rev = "mainnet" -subdir = "aptos-move/framework/aptos-framework" -``` - -### Imports - -The FACoin module uses several important modules: - -1. `fungible_asset` contains the logic for granting permission to mint, transfer, burn, and create your FungibleAsset. -2. `object` allows for creating Aptos Objects. -3. `primary_fungible_store` contains the logic to track account balances for the new Fungible Asset. - -```move filename="fa_coin.move" -module FACoin::fa_coin { - use aptos_framework::fungible_asset::{Self, MintRef, TransferRef, BurnRef, Metadata, FungibleAsset}; - use aptos_framework::object::{Self, Object}; - use aptos_framework::primary_fungible_store; - use std::error; - use std::signer; - use std::string::utf8; - use std::option; - //... -} -``` - -These imports are defined in the `Move.toml` file as GitHub dependencies. - -### `init_module` - -This function is called when the module is initially published in order to set up the proper permissions and Objects. For FACoin, this is used to initialize the asset’s `MetaData` Object (which contains things like the asset’s name and symbol), as well as getting the relevant ref’s for how our fungible asset will be used. - -The `ManagedFungibleAsset` standard helps keep track of which permissions this Module is allowed to use. - -```move filename="fa_coin.move" -fun init_module(admin: &signer) { - let constructor_ref = &object::create_named_object(admin, ASSET_SYMBOL); - primary_fungible_store::create_primary_store_enabled_fungible_asset( - constructor_ref, - option::none(), - utf8(ASSET_NAME), - utf8(ASSET_SYMBOL), - 8, - utf8(b"http://example.com/favicon.ico"), - utf8(b"http://example.com"), - ); - - let mint_ref = fungible_asset::generate_mint_ref(constructor_ref); - let burn_ref = fungible_asset::generate_burn_ref(constructor_ref); - let transfer_ref = fungible_asset::generate_transfer_ref(constructor_ref); - let metadata_object_signer = object::generate_signer(constructor_ref); - move_to( - &metadata_object_signer, - ManagedFungibleAsset { mint_ref, transfer_ref, burn_ref } - ) -} -``` - -### View Functions - -When creating your own fungible asset, it can be helpful to add view functions for any data that is needed later on. In this case, we wanted to see the name of the asset in order to report which asset was being traded in our example scenario. - -```move filename="fa_coin.move" -#[view] -public fun get_metadata(): Object { - let asset_address = object::create_object_address(&@FACoin, ASSET_SYMBOL); - object::address_to_object(asset_address) -} - -#[view] -public fun get_name(): string::String { - let metadata = get_metadata(); - fungible_asset::name(metadata) -} -``` - -### Entry Functions - -Every fungible asset has a similar interface (mint, transfer, burn, freeze, unfreeze, deposit, and withdraw). Here’s an example of a minimal mint function, which mints and transfers the funds to the proper recipient: - -```move filename="fa_coin.move" -public entry fun mint(admin: &signer, to: address, amount: u64) acquires ManagedFungibleAsset { - let asset = get_metadata(); - let managed_fungible_asset = authorized_borrow_refs(admin, asset); - let to_wallet = primary_fungible_store::ensure_primary_store_exists(to, asset); - let fa = fungible_asset::mint(&managed_fungible_asset.mint_ref, amount); - fungible_asset::deposit_with_ref(&managed_fungible_asset.transfer_ref, to_wallet, fa); -} -``` - - -## Summary - -If you want to build your own Fungible Asset, you can use [`fa_coin.move`](https://github.com/aptos-labs/aptos-ts-sdk/tree/main/examples/typescript/move/facoin) as a starting point, or look to other code examples [here](https://learn.aptoslabs.com/en/code-examples). - -Regardless, the Fungible Asset Standard will help you mint, transfer, burn, and keep track of balances automatically for whichever fungible assets you want to represent on-chain. - -You can find the [Move reference for Fungible Assets](../smart-contracts/move-reference.mdx?branch=mainnet&page=aptos-framework%2Fdoc%2Ffungible_asset.md) for more details on the function signatures and implementation details. diff --git a/apps/nextra/pages/zh/build/guides/first-move-module.mdx b/apps/nextra/pages/zh/build/guides/first-move-module.mdx deleted file mode 100644 index be2646684..000000000 --- a/apps/nextra/pages/zh/build/guides/first-move-module.mdx +++ /dev/null @@ -1,229 +0,0 @@ -import { Callout } from 'nextra/components'; - -# Your First Move Module - -This tutorial details how to compile, test, publish and interact with Move modules on the Aptos blockchain. The steps in summary are: - -1. Install the precompiled binary for the Aptos CLI. -2. Create an account on the Aptos blockchain and fund it. -3. Compile and test a Move module. -4. Publish a Move module to the Aptos blockchain. -5. Interact with a Move module. - -## Step 1: Install the CLI - -[Install the precompiled binary for the Aptos CLI](../cli.mdx). - ---- - -## Step 2: Create an account and fund it - -After installing the CLI binary, create and fund an account on the Aptos blockchain. - -Start a new terminal and run the following command to initialize a new local account: - -```bash filename="Terminal" -aptos init -``` - -You will see output asking to choose a network: - -```bash filename="Terminal" -Choose network from [devnet, testnet, mainnet, local, custom | defaults to devnet] -``` - -Press **return** to accept the default devnet network or specify the network of your choosing: - -```bash filename="Terminal" -No network given, using devnet... -``` - -See and respond to the prompt for your private key by accepting the default to create a new or by entering an existing key: - -```bash filename="Terminal" -Enter your private key as a hex literal (0x...) [Current: None | No input: Generate new key (or keep one if present)] -``` - -Assuming you elected to create a new, you will see: - -```bash filename="Terminal" -No key given, generating key... -Account a345dbfb0c94416589721360f207dcc92ecfe4f06d8ddc1c286f569d59721e5a doesn't exist, creating it and funding it with 100000000 Octas -Account a345dbfb0c94416589721360f207dcc92ecfe4f06d8ddc1c286f569d59721e5a funded successfully - ---- -Aptos CLI is now set up for account a345dbfb0c94416589721360f207dcc92ecfe4f06d8ddc1c286f569d59721e5a as profile default! Run `aptos --help` for more information about commands -{ - "Result": "Success" -} -``` - -The account address in the above output `a345dbfb0c94416589721360f207dcc92ecfe4f06d8ddc1c286f569d59721e5a` is your new account and is aliased as the profile `default`. This account address will be different for you as it is generated randomly. From now on, either `default` or `0xa345dbfb0c94416589721360f207dcc92ecfe4f06d8ddc1c286f569d59721e5a` are used interchangeably in this document. Of course, substitute your own address as needed. - -Now fund this account by running this command: - -```bash filename="Terminal" -aptos account fund-with-faucet --account default -``` - -You will see output resembling: - -```bash filename="Terminal" -{ - "Result": "Added 100000000 Octas to account a345dbfb0c94416589721360f207dcc92ecfe4f06d8ddc1c286f569d59721e5a" -} -``` - ---- - -## Step 3: Compile and test the module - -Several example Move modules are available in the [aptos-core/aptos-move/move-examples](https://github.com/aptos-labs/aptos-core/tree/main/aptos-move/move-examples) directory for your use. Open a terminal and change directories into the [`hello_blockchain`](https://github.com/aptos-labs/aptos-core/tree/main/aptos-move/move-examples/hello_blockchain) directory: - -```bash filename="Terminal" -cd aptos-core/aptos-move/move-examples/hello_blockchain -``` - -Run the below command to compile the `hello_blockchain` module: - -```bash filename="Terminal" -aptos move compile --named-addresses hello_blockchain=default -``` - -You will see output resembling: - -```bash filename="Terminal" -{ - "Result": [ - "a345dbfb0c94416589721360f207dcc92ecfe4f06d8ddc1c286f569d59721e5a::message" - ] -} -``` - -The `compile` command must contain `--named-addresses` as above because the [`Move.toml`](https://github.com/aptos-labs/aptos-core/blob/main/aptos-move/move-examples/hello_blockchain/Move.toml) file leaves this as undefined (see below). - -To test the module run: - -```bash filename="Terminal" -aptos move test --named-addresses hello_blockchain=default -``` - -And receive output like: - -```bash filename="Terminal" -INCLUDING DEPENDENCY AptosFramework -INCLUDING DEPENDENCY AptosStdlib -INCLUDING DEPENDENCY MoveStdlib -BUILDING Examples -Running Move unit tests -[ PASS ] 0x1a42874787568af30c785622899a27dacce066d671fa487e7fb958d6d0c85077::message::sender_can_set_message -[ PASS ] 0x1a42874787568af30c785622899a27dacce066d671fa487e7fb958d6d0c85077::message_tests::sender_can_set_message -Test result: OK. Total tests: 2; passed: 2; failed: 0 -{ - "Result": "Success" -} -``` - - -To prepare the module for the account created in the previous step, we specify that the named address `hello_blockchain` is set to our account address, using the `default` profile alias. - -```toml filename="Move.toml" -[addresses] -hello_blockchain = "_" -``` - - ---- - -## Step 4: Publish the Move module - -After the code is compiled and tested, we can publish the module to the account created for this tutorial with the command: - -```bash filename="Terminal" -aptos move publish --named-addresses hello_blockchain=default -``` - -You will see the output similar to: - -```bash filename="Terminal" -package size 1631 bytes -{ - "Result": { - "transaction_hash": "0x45d682997beab297a9a39237c588d31da1cd2c950c5ab498e37984e367b0fc25", - "gas_used": 13, - "gas_unit_price": 1, - "pending": null, - "sender": "a345dbfb0c94416589721360f207dcc92ecfe4f06d8ddc1c286f569d59721e5a", - "sequence_number": 8, - "success": true, - "timestamp_us": 1661320216343795, - "version": 3977, - "vm_status": "Executed successfully" - } -} -``` - -At this point, the module is now stored on the account in the Aptos blockchain. - ---- - -## Step 5: Interact with the Move module - -Move modules expose access points, known as _entry functions_. These entry functions can be called via transactions. The Aptos CLI allows for seamless access to these entry functions. The example Move module `hello_blockchain` exposes a `set_message` entry function that takes in a `string`. This can be called via the CLI: - -```bash filename="Terminal" -aptos move run \ - --function-id 'default::message::set_message' \ - --args 'string:hello, blockchain' -``` - -Upon success, the CLI will print out the following: - -```bash filename="Terminal" -{ - "Result": { - "transaction_hash": "0x1fe06f61c49777086497b199f3d4acbee9ea58976d37fdc06d1ea48a511a9e82", - "gas_used": 1, - "gas_unit_price": 1, - "pending": null, - "sender": "a345dbfb0c94416589721360f207dcc92ecfe4f06d8ddc1c286f569d59721e5a", - "sequence_number": 1, - "success": true, - "timestamp_us": 1661320878825763, - "version": 5936, - "vm_status": "Executed successfully" - } -} -``` - -The `set_message` function modifies the `hello_blockchain` `MessageHolder` resource. A resource is a data structure that is stored in [global storage](../smart-contracts/book/structs-and-resources.mdx#storing-resources-in-global-storage). The resource can be read by querying the following REST API: - -```bash filename="Terminal" - -https://api.devnet.aptoslabs.com/v1/accounts/a345dbfb0c94416589721360f207dcc92ecfe4f06d8ddc1c286f569d59721e5a/resource/0xa345dbfb0c94416589721360f207dcc92ecfe4f06d8ddc1c286f569d59721e5a::message::MessageHolder -``` - -After the first execution, this should contain: - -```bash filename="Terminal" -{ - "type": "0xa345dbfb0c94416589721360f207dcc92ecfe4f06d8ddc1c286f569d59721e5a::message::MessageHolder", - "data": { - "message": "hello, blockchain" - } -} -``` - -Notice that the `message` field contains `hello, blockchain`. - -Each successful call to `set_message` after the first call results in the `MessageChange` event being emitted. `MessageChange` is a [Module Event](../../network/blockchain/events.mdx#module-events). Module Events for a given account can be accessed via the [GraphQL API](../indexer.mdx). - - -Other accounts can reuse the published module by calling the exact same function as in this example. It is left as an exercise to the reader. - - -## Supporting documentation - -- [Account basics](../../network/blockchain/accounts.mdx) -- [TypeScript SDK](../sdks/ts-sdk.mdx) -- [REST API specification](../../network/nodes/aptos-api-spec.mdx) diff --git a/apps/nextra/pages/zh/build/guides/first-multisig.mdx b/apps/nextra/pages/zh/build/guides/first-multisig.mdx deleted file mode 100644 index 5f7731025..000000000 --- a/apps/nextra/pages/zh/build/guides/first-multisig.mdx +++ /dev/null @@ -1,838 +0,0 @@ -import { Callout } from 'nextra/components'; - -# Your First Multisig - -This tutorial introduces assorted [K-of-N multi-signer authentication](../../network/blockchain/accounts.mdx#multied25519-authentication) operations and supplements content from the following tutorials: - -- [Your First Transaction](first-transaction.mdx) -- [Your First Coin](first-coin.mdx) -- [Your First Move Module](first-move-module.mdx) - - - -Try out the above tutorials (which include dependency installations) before moving on to multisig operations. - - -## Step 1: Pick an SDK - -This tutorial, a community contribution, was created for the [Python SDK](../sdks/python-sdk.mdx). - -Other developers are invited to add support for the [TypeScript SDK](../sdks/ts-sdk.mdx), [Rust SDK](../sdks/rust-sdk.mdx), and [Unity SDK](../sdks/unity-sdk.mdx)! - -## Step 2: Start the example - -Navigate to the Python SDK directory: - -```bash filename="Terminal" -cd /aptos-core/ecosystem/python/sdk/ -``` - -Run the `multisig.py` example: - -```bash filename="Terminal" -poetry run python -m examples.multisig -``` - - -This example uses the Aptos devnet, which has historically been reset each Thursday. -Make sure devnet is live when you try running the example! - - -## Step 3: Generate single signer accounts - -First, we will generate single signer accounts for Alice, Bob, and Chad: - -```python filename="multisig.py" -alice = Account.generate() -bob = Account.generate() -chad = Account.generate() - -print("\n=== Account addresses ===") -print(f"Alice: {alice.address()}") -print(f"Bob: {bob.address()}") -print(f"Chad: {chad.address()}") - -print("\n=== Authentication keys ===") -print(f"Alice: {alice.auth_key()}") -print(f"Bob: {bob.auth_key()}") -print(f"Chad: {chad.auth_key()}") - -print("\n=== Public keys ===") -print(f"Alice: {alice.public_key()}") -print(f"Bob: {bob.public_key()}") -print(f"Chad: {chad.public_key()}") -``` - -Fresh accounts are generated for each example run, but the output should resemble: - -```bash filename="Terminal" -=== Account addresses === -Alice: 0x93c1b7298d53dd0d517f503f2d3188fc62f6812ab94a412a955720c976fecf96 -Bob: 0x85eb913e58d0885f6a966d98c76e4d00714cf6627f8db5903e1cd38cc86d1ce0 -Chad: 0x14cf8dc376878ac268f2efc7ba65a2ee0ac13ceb43338b6106dd88d8d23e087a - -=== Authentication keys === -Alice: 0x93c1b7298d53dd0d517f503f2d3188fc62f6812ab94a412a955720c976fecf96 -Bob: 0x85eb913e58d0885f6a966d98c76e4d00714cf6627f8db5903e1cd38cc86d1ce0 -Chad: 0x14cf8dc376878ac268f2efc7ba65a2ee0ac13ceb43338b6106dd88d8d23e087a - -=== Public keys === -Alice: 0x3f23f869632aaa4378f3d68560e08d18b1fc2e80f91d6f9d1b39d720b0ef7a3f -Bob: 0xcf21e85337a313bdac33d068960a3e52d22ce0e6190e9acc03a1c9930e1eaf3e -Chad: 0xa1a2aef8525eb20655387d3ed50b9a3ea1531ef6117f579d0da4bcf5a2e1f76d -``` - -For each user, note the [account address](../../network/blockchain/accounts.mdx#account-address) and [authentication key](../../network/blockchain/accounts.mdx#authentication-key) are identical, but the [public key](../../network/blockchain/accounts.mdx#creating-an-account) is different. - -## Step 4: Generate a multisig account - -Next generate a [K-of-N multi-signer](../../network/blockchain/accounts.mdx#multied25519-authentication) public key and account address for a multisig account requiring two of the three signatures: - -```python filename="multisig.py" -threshold = 2 - -multisig_public_key = MultiPublicKey( - [alice.public_key(), bob.public_key(), chad.public_key()], threshold -) - -multisig_address = AccountAddress.from_multi_ed25519(multisig_public_key) - -print("\n=== 2-of-3 Multisig account ===") -print(f"Account public key: {multisig_public_key}") -print(f"Account address: {multisig_address}") -``` - -The multisig account address depends on the public keys of the single signers. (Hence, it will be different for each example.) But the output should resemble: - -```bash filename="Terminal" -=== 2-of-3 Multisig account === -Account public key: 2-of-3 Multi-Ed25519 public key -Account address: 0x08cac3b7b7ce4fbc5b18bc039279d7854e4c898cbf82518ac2650b565ad4d364 -``` - -## Step 5: Fund all accounts - -Next fund all accounts: - -```python filename="multisig.py" -print("\n=== Funding accounts ===") -alice_start = 10_000_000 -bob_start = 20_000_000 -chad_start = 30_000_000 -multisig_start = 40_000_000 - -alice_fund = faucet_client.fund_account(alice.address(), alice_start) -bob_fund = faucet_client.fund_account(bob.address(), bob_start) -chad_fund = faucet_client.fund_account(chad.address(), chad_start) -multisig_fund = faucet_client.fund_account(multisig_address, multisig_start) -await asyncio.gather(*[alice_fund, bob_fund, chad_fund, multisig_fund]) - -alice_balance = rest_client.account_balance(alice.address()) -bob_balance = rest_client.account_balance(bob.address()) -chad_balance = rest_client.account_balance(chad.address()) -multisig_balance = rest_client.account_balance(multisig_address) -[alice_balance, bob_balance, chad_balance, multisig_balance] = await asyncio.gather( - *[alice_balance, bob_balance, chad_balance, multisig_balance] -) - -print(f"Alice's balance: {alice_balance}") -print(f"Bob's balance: {bob_balance}") -print(f"Chad's balance: {chad_balance}") -print(f"Multisig balance: {multisig_balance}") -``` - -```bash filename="Terminal" -=== Funding accounts === -Alice's balance: 10000000 -Bob's balance: 20000000 -Chad's balance: 30000000 -Multisig balance: 40000000 -``` - -## Step 6: Send coins from the multisig - -This transaction will send 100 [octas](../../network/glossary.mdx#Octa) from the multisig account to Chad's account. -Since it is a two-of-three multisig account, signatures are required from only two individual signers. - -### Step 6.1: Gather individual signatures - -First generate a raw transaction, signed by Alice and Bob, but not by Chad. - -```python filename="multisig.py" -entry_function = EntryFunction.natural( - module="0x1::coin", - function="transfer", - ty_args=[TypeTag(StructTag.from_str("0x1::aptos_coin::AptosCoin"))], - args=[ - TransactionArgument(chad.address(), Serializer.struct), - TransactionArgument(100, Serializer.u64), - ], -) - -chain_id = await rest_client.chain_id() -raw_transaction = RawTransaction( - sender=multisig_address, - sequence_number=0, - payload=TransactionPayload(entry_function), - max_gas_amount=rest_client.client_config.max_gas_amount, - gas_unit_price=rest_client.client_config.gas_unit_price, - expiration_timestamps_secs=( - int(time.time()) + rest_client.client_config.expiration_ttl - ), - chain_id=chain_id, -) - -alice_signature = alice.sign(raw_transaction.keyed()) -bob_signature = bob.sign(raw_transaction.keyed()) - -assert raw_transaction.verify(alice.public_key(), alice_signature) -assert raw_transaction.verify(bob.public_key(), bob_signature) - -print("\n=== Individual signatures ===") -print(f"Alice: {alice_signature}") -print(f"Bob: {bob_signature}") -``` - -Again, signatures vary for each example run: - -```bash filename="Terminal" -=== Individual signatures === -Alice: 0x41b9dd65857df2d8d8fba251336357456cc9f17974de93292c13226f560102eac1e70ddc7809a98cd54ddee9b79853e8bf7d18cfef23458f23e3a335c3189e0d -Bob: 0x6305101f8f3ad5a75254a8fa74b0d9866756abbf359f9e4f2b54247917caf8c52798a36c5a81c77505ebc1dc9b80f2643e8fcc056bcc4f795e80b229fa41e509 -``` - -### Step 6.2: Submit the multisig transaction - -Next generate a multisig authenticator and submit the transaction: - -```python filename="multisig.py" -sig_map = [ # Map from signatory public key to signature. - (alice.public_key(), alice_signature), - (bob.public_key(), bob_signature), -] - -multisig_signature = MultiSignature(multisig_public_key, sig_map) - -authenticator = Authenticator( - MultiEd25519Authenticator(multisig_public_key, multisig_signature) -) - -signed_transaction = SignedTransaction(raw_transaction, authenticator) - -print("\n=== Submitting transfer transaction ===") - -tx_hash = await rest_client.submit_bcs_transaction(signed_transaction) -await rest_client.wait_for_transaction(tx_hash) -print(f"Transaction hash: {tx_hash}") -``` - -```bash filename="Terminal" -=== Submitting transfer transaction === -Transaction hash: 0x3ff2a848bf6145e6df3abb3ccb8b94fefd07ac16b4acb0c694fa7fa30b771f8c -``` - -### Step 6.3: Check balances - -Check the new account balances: - -```python filename="multisig.py" -print("\n=== New account balances===") - -alice_balance = rest_client.account_balance(alice.address()) -bob_balance = rest_client.account_balance(bob.address()) -chad_balance = rest_client.account_balance(chad.address()) -multisig_balance = rest_client.account_balance(multisig_address) -[alice_balance, bob_balance, chad_balance, multisig_balance] = await asyncio.gather( - *[alice_balance, bob_balance, chad_balance, multisig_balance] -) - -print(f"Alice's balance: {alice_balance}") -print(f"Bob's balance: {bob_balance}") -print(f"Chad's balance: {chad_balance}") -print(f"Multisig balance: {multisig_balance}") -``` - -```bash filename="Terminal" -=== New account balances=== -Alice's balance: 10000000 -Bob's balance: 20000000 -Chad's balance: 30000100 -Multisig balance: 39999300 -``` - -Note that even though Alice and Bob signed the transaction, their account balances have not changed. -Chad, however, has received 100 [octas](../../network/glossary.mdx#Octa) from the multisig account, which assumed the gas costs of the transaction and thus has had more than 100 octas deducted. - -## Step 7: Create a vanity address multisig - -In this section, a fourth user named Deedee will generate a vanity address, then rotate her account to the two-of-three multisig. - -### Step 7.1 Generate a vanity address - -A fourth user, Deedee, wants her account address to start with `0xdd..`, so she generates random accounts until she finds one with a matching account address: - -```python filename="multisig.py" -print("\n=== Funding vanity address ===") - -deedee = Account.generate() - -while str(deedee.address())[2:4] != "dd": - deedee = Account.generate() - -print(f"Deedee's address: {deedee.address()}") -print(f"Deedee's public key: {deedee.public_key()}") - -deedee_start = 50_000_000 - -await faucet_client.fund_account(deedee.address(), deedee_start) -deedee_balance = await rest_client.account_balance(deedee.address()) -print(f"Deedee's balance: {deedee_balance}") -``` - -```bash filename="Terminal" -=== Funding vanity address === -Deedee's address: 0xdd86860ae7f77f58d08188e1c39fbc6a2f7cec782f09f6767f8367d84357ed57 -Deedee's public key: 0xdbf02311c45903f0217e9ab76ca64007c2876363118bb422922410d4cfe9964c -Deedee's balance: 50000000 -``` - -### Step 7.2 Sign a rotation proof challenge - -Deedee and the two-of-three multisig must both sign a `RotationProofChallenge`, yielding two signatures. -Deedee's signature, `cap_rotate_key`, verifies that she approves of the authentication key rotation. -The multisig signature, `cap_update_table`, verifies that the multisig approves of the authentication key rotation. -Here, Bob and Chad provide individual signatures for the multisig: - -```python filename="multisig.py" -print("\n=== Signing rotation proof challenge ===") - -rotation_proof_challenge = RotationProofChallenge( - sequence_number=0, - originator=deedee.address(), - current_auth_key=deedee.address(), - new_public_key=multisig_public_key.to_bytes(), -) - -serializer = Serializer() -rotation_proof_challenge.serialize(serializer) -rotation_proof_challenge_bcs = serializer.output() - -cap_rotate_key = deedee.sign(rotation_proof_challenge_bcs).data() - -cap_update_table = MultiSignature( - multisig_public_key, - [ - (bob.public_key(), bob.sign(rotation_proof_challenge_bcs)), - (chad.public_key(), chad.sign(rotation_proof_challenge_bcs)), - ], -).to_bytes() - -cap_rotate_key_hex = f"0x{cap_rotate_key.hex()}" -cap_update_table_hex = f"0x{cap_update_table.hex()}" - -print(f"cap_rotate_key: {cap_rotate_key_hex}") -print(f"cap_update_table: {cap_update_table_hex}") -``` - -```bash filename="Terminal" -=== Signing rotation proof challenge === -cap_rotate_key: 0x3b2906df78bb79f210051e910985c358572c2ec7cdd03f688480fb6adf8d538df48a52787d5651d85f2959dcca88d58da49709c9c0dc9c3c58b67169ec1e1c01 -cap_update_table: 0x965fd11d7afe14396e5af40b8ffb78e6eb6f7caa1f1b1d8c7b819fdd6045864e70258788ec1670a3989c85f8cc604f4b54e43e1ce173a59aa0a6f7cf124fd902dcbb2ad53467d05c144260b2be1814777c082d8db437698b00e6a2109a015a565ff5783e827a21a4c07ae332b56398b69dfdbcc08b8ad5585dc1ac649b74730760000000 -``` - -### Step 7.3 Rotate the authentication key - -Now that the relevant signatures have been gathered, the authentication key rotation transaction can be submitted. -After it executes, the rotated authentication key matches the address of the first multisig account (the one that sent [octas](../../network/glossary.mdx#Octa) to Chad): - -```python filename="multisig.py" -print("\n=== Submitting authentication key rotation transaction ===") - -from_scheme = Authenticator.ED25519 -from_public_key_bytes = deedee.public_key().key.encode() -to_scheme = Authenticator.MULTI_ED25519 -to_public_key_bytes = multisig_public_key.to_bytes() - -entry_function = EntryFunction.natural( - module="0x1::account", - function="rotate_authentication_key", - ty_args=[], - args=[ - TransactionArgument(from_scheme, Serializer.u8), - TransactionArgument(from_public_key_bytes, Serializer.to_bytes), - TransactionArgument(to_scheme, Serializer.u8), - TransactionArgument(to_public_key_bytes, Serializer.to_bytes), - TransactionArgument(cap_rotate_key, Serializer.to_bytes), - TransactionArgument(cap_update_table, Serializer.to_bytes), - ], -) - -signed_transaction = await rest_client.create_bcs_signed_transaction( - deedee, TransactionPayload(entry_function) -) - -account_data = await rest_client.account(deedee.address()) -print(f"Auth key pre-rotation: {account_data['authentication_key']}") - -tx_hash = await rest_client.submit_bcs_transaction(signed_transaction) -await rest_client.wait_for_transaction(tx_hash) -print(f"Transaction hash: {tx_hash}") - -account_data = await rest_client.account(deedee.address()) -print(f"New auth key: {account_data['authentication_key']}") -print(f"1st multisig address: {multisig_address}") -``` - -```bash filename="Terminal" -=== Submitting authentication key rotation transaction === -Auth key pre-rotation: 0xdd86860ae7f77f58d08188e1c39fbc6a2f7cec782f09f6767f8367d84357ed57 -Transaction hash: 0x57c66089a1b81e2895a2d6919ab19eb90c4d3c3cbe9fecab8169eaeedff2c6e6 -New auth key: 0x08cac3b7b7ce4fbc5b18bc039279d7854e4c898cbf82518ac2650b565ad4d364 -1st multisig address: 0x08cac3b7b7ce4fbc5b18bc039279d7854e4c898cbf82518ac2650b565ad4d364 -``` - -In other words, Deedee generated an account with a vanity address so that Alice, Bob, and Chad could use it as a multisig account. -Then Deedee and the Alice/Bob/Chad group (under the authority of Bob and Chad) approved to rotate the vanity account's authentication key to the authentication key of the first multisig account. - -## Step 8: Perform Move package governance - -In this section, the multisig vanity account will publish a simple package, upgrade it, then invoke a Move script. - -Move source code for this section is found in the [`upgrade_and_govern`](https://github.com/aptos-labs/aptos-core/tree/main/aptos-move/move-examples/upgrade_and_govern) directory. - -### Step 8.1: Review genesis package - -The `UpgradeAndGovern` genesis package (version `1.0.0`) contains a simple `.toml` manifest and a single Move source file: - -```toml filename="Move.toml" -[package] -name = 'UpgradeAndGovern' -version = '1.0.0' - -[addresses] -upgrade_and_govern = '_' - -[dependencies] -AptosFramework = { git = "https://github.com/aptos-labs/aptos-framework.git", rev = "mainnet", subdir = "aptos-framework" } - -``` - -```rust filename="parameters.move" -/// Mock on-chain governance parameters. -module upgrade_and_govern::parameters { - - struct GovernanceParameters has key { - parameter_1: u64, - parameter_2: u64 - } - - const GENESIS_PARAMETER_1: u64 = 123; - const GENESIS_PARAMETER_2: u64 = 456; - - fun init_module( - upgrade_and_govern: &signer - ) { - let governance_parameters = GovernanceParameters{ - parameter_1: GENESIS_PARAMETER_1, - parameter_2: GENESIS_PARAMETER_2}; - move_to( - upgrade_and_govern, governance_parameters); - } - - public fun get_parameters(): - (u64, u64) - acquires GovernanceParameters { - let governance_parameters_ref = - borrow_global(@upgrade_and_govern); - (governance_parameters_ref.parameter_1, - governance_parameters_ref.parameter_2) - } - -} -``` - -As soon as the package is published, a `GovernanceParameters` resource is moved to the `upgrade_and_govern` package account with the values specified by `GENESIS_PARAMETER_1` and `GENESIS_PARAMETER_2`. -Then, the `get_parameters()` function can be used to look up the governance parameters, but note that in this version there is no setter function. -The setter function will be added later. - -### Step 8.2: Publish genesis package - -Here, Alice and Chad will sign off on the publication transaction. - -All compilation and publication operations are handled via the ongoing Python script: - -```python filename="multisig.py" -print("\n=== Genesis publication ===") - -packages_dir = "../../../aptos-move/move-examples/upgrade_and_govern/" - -command = ( - f"aptos move compile " - f"--save-metadata " - f"--package-dir {packages_dir}genesis " - f"--named-addresses upgrade_and_govern={str(deedee.address())}" -) - -print(f"Running aptos CLI command: {command}\n") -subprocess.run(command.split(), stdout=subprocess.PIPE) - -build_path = f"{packages_dir}genesis/build/UpgradeAndGovern/" - -with open(f"{build_path}package-metadata.bcs", "rb") as f: - package_metadata = f.read() - -with open(f"{build_path}bytecode_modules/parameters.mv", "rb") as f: - parameters_module = f.read() - -modules_serializer = Serializer.sequence_serializer(Serializer.to_bytes) - -payload = EntryFunction.natural( - module="0x1::code", - function="publish_package_txn", - ty_args=[], - args=[ - TransactionArgument(package_metadata, Serializer.to_bytes), - TransactionArgument([parameters_module], modules_serializer), - ], -) - -raw_transaction = RawTransaction( - sender=deedee.address(), - sequence_number=1, - payload=TransactionPayload(payload), - max_gas_amount=rest_client.client_config.max_gas_amount, - gas_unit_price=rest_client.client_config.gas_unit_price, - expiration_timestamps_secs=( - int(time.time()) + rest_client.client_config.expiration_ttl - ), - chain_id=chain_id, -) - -alice_signature = alice.sign(raw_transaction.keyed()) -chad_signature = chad.sign(raw_transaction.keyed()) - -sig_map = [ # Map from signatory public key to signature. - (alice.public_key(), alice_signature), - (chad.public_key(), chad_signature), -] - -multisig_signature = MultiSignature(multisig_public_key, sig_map) - -authenticator = Authenticator( - MultiEd25519Authenticator(multisig_public_key, multisig_signature) -) - -signed_transaction = SignedTransaction(raw_transaction, authenticator) - -tx_hash = await rest_client.submit_bcs_transaction(signed_transaction) -await rest_client.wait_for_transaction(tx_hash) -print(f"\nTransaction hash: {tx_hash}") - -registry = await rest_client.account_resource( - deedee.address(), "0x1::code::PackageRegistry" -) - -package_name = registry["data"]["packages"][0]["name"] -n_upgrades = registry["data"]["packages"][0]["upgrade_number"] - -print(f"Package name from on-chain registry: {package_name}") -print(f"On-chain upgrade number: {n_upgrades}") -``` - -```bash filename="Terminal" -=== Genesis publication === -Running aptos CLI command: aptos move compile --save-metadata --package-dir ../../../../aptos-move/move-examples/upgrade_and_govern/genesis --named-addresses upgrade_and_govern=0xdd86860ae7f77f58d08188e1c39fbc6a2f7cec782f09f6767f8367d84357ed57 - -Compiling, may take a little while to download git dependencies... -INCLUDING DEPENDENCY AptosFramework -INCLUDING DEPENDENCY AptosStdlib -INCLUDING DEPENDENCY MoveStdlib -BUILDING UpgradeAndGovern - -Transaction hash: 0x3c65c681194d6c64d73dc5d0cbcbad62e99a997b8600b8edad6847285e580c13 -Package name from on-chain registry: UpgradeAndGovern -On-chain upgrade number: 0 -``` - -### Step 8.3: Review package upgrades - -The `UpgradeAndGovern` upgrade package adds the following parameter setter functionality at the end of `parameters.move`: - -```rust filename="parameters.move - use std::signer::address_of; - - const E_INVALID_AUTHORITY: u64 = 0; - - public entry fun set_parameters( - upgrade_and_govern: &signer, - parameter_1: u64, - parameter_2: u64 - ) acquires GovernanceParameters { - assert!(address_of(upgrade_and_govern) == @upgrade_and_govern, - E_INVALID_AUTHORITY); - let governance_parameters_ref_mut = - borrow_global_mut(@upgrade_and_govern); - governance_parameters_ref_mut.parameter_1 = parameter_1; - governance_parameters_ref_mut.parameter_2 = parameter_2; - } - -} -``` - -Here, the account that the package is published under, `upgrade_and_govern`, has the authority to change the `GovernanceParameter` values from the genesis values to the new `parameter_1` and `parameter_2` values. - -There is also a new module, `transfer.move`: - -```rust filename="transfer.move -/// Mock coin transfer module that invokes governance parameters. -module upgrade_and_govern::transfer { - - use aptos_framework::aptos_coin::AptosCoin; - use aptos_framework::coin; - use upgrade_and_govern::parameters; - - public entry fun transfer_octas( - from: &signer, - to_1: address, - to_2: address - ) { - let (amount_1, amount_2) = parameters::get_parameters(); - coin::transfer(from, to_1, amount_1); - coin::transfer(from, to_2, amount_2); - } - -} -``` - -This module simply looks up the `GovernanceParameter` values, and treats them as the amount of [octas](../../network/glossary.mdx#Octa) to send to two recipients. - -Lastly, the manifest has been updated with the new version number `1.1.0`: - -```toml filename="Move.toml -[package] -name = 'UpgradeAndGovern' -version = '1.1.0' - -[addresses] -upgrade_and_govern = '_' - -[dependencies] -AptosFramework = { git = "https://github.com/aptos-labs/aptos-framework.git", rev = "mainnet", subdir = "aptos-framework" } -``` - -### Step 8.4: Upgrade the package - -Alice, Bob, and Chad will all sign off on this publication transaction, which results in an upgrade. -This process is almost identical to that of the genesis publication, with the new `transfer` module listed after the `parameters` module: - -```python filename="multisig.py" -print("\n=== Upgrade publication ===") - -command = ( - f"aptos move compile " - f"--save-metadata " - f"--package-dir {packages_dir}upgrade " - f"--named-addresses upgrade_and_govern={str(deedee.address())}" -) - -print(f"Running aptos CLI command: {command}\n") -subprocess.run(command.split(), stdout=subprocess.PIPE) - -build_path = f"{packages_dir}upgrade/build/UpgradeAndGovern/" - -with open(f"{build_path}package-metadata.bcs", "rb") as f: - package_metadata = f.read() - -with open(f"{build_path}bytecode_modules/parameters.mv", "rb") as f: - parameters_module = f.read() - -with open(f"{build_path}bytecode_modules/transfer.mv", "rb") as f: - transfer_module = f.read() - -payload = EntryFunction.natural( - module="0x1::code", - function="publish_package_txn", - ty_args=[], - args=[ - TransactionArgument(package_metadata, Serializer.to_bytes), - TransactionArgument( # Transfer module listed second. - [parameters_module, transfer_module], - Serializer.sequence_serializer(Serializer.to_bytes), - ), - ], -) - -raw_transaction = RawTransaction( - sender=deedee.address(), - sequence_number=2, - payload=TransactionPayload(payload), - max_gas_amount=rest_client.client_config.max_gas_amount, - gas_unit_price=rest_client.client_config.gas_unit_price, - expiration_timestamps_secs=( - int(time.time()) + rest_client.client_config.expiration_ttl - ), - chain_id=chain_id, -) - -alice_signature = alice.sign(raw_transaction.keyed()) -bob_signature = bob.sign(raw_transaction.keyed()) -chad_signature = chad.sign(raw_transaction.keyed()) - -sig_map = [ # Map from signatory public key to signature. - (alice.public_key(), alice_signature), - (bob.public_key(), bob_signature), - (chad.public_key(), chad_signature), -] - -multisig_signature = MultiSignature(multisig_public_key, sig_map) - -authenticator = Authenticator( - MultiEd25519Authenticator(multisig_public_key, multisig_signature) -) - -signed_transaction = SignedTransaction(raw_transaction, authenticator) - -tx_hash = await rest_client.submit_bcs_transaction(signed_transaction) -await rest_client.wait_for_transaction(tx_hash) -print(f"\nTransaction hash: {tx_hash}") - -registry = await rest_client.account_resource( - deedee.address(), "0x1::code::PackageRegistry" -) - -n_upgrades = registry["data"]["packages"][0]["upgrade_number"] - -print(f"On-chain upgrade number: {n_upgrades}") -``` - - -Modules that `use` other modules must be listed _after_ the modules they use. - - -```bash filename="Terminal" -=== Upgrade publication === -Running aptos CLI command: aptos move compile --save-metadata --package-dir ../../../../aptos-move/move-examples/upgrade_and_govern/upgrade --named-addresses upgrade_and_govern=0xdd86860ae7f77f58d08188e1c39fbc6a2f7cec782f09f6767f8367d84357ed57 - -Compiling, may take a little while to download git dependencies... -INCLUDING DEPENDENCY AptosFramework -INCLUDING DEPENDENCY AptosStdlib -INCLUDING DEPENDENCY MoveStdlib -BUILDING UpgradeAndGovern - -Transaction hash: 0x0f0ea3bb7271ddeaceac5b49ff5503d6c652d4746c1510e47665ceee5a89aaf0 -On-chain upgrade number: 1 -``` - -Note that the on-chain upgrade number has been incremented by 1. - -### Step 8.6: Review the governance script - -The `UpgradeAndGovern` upgrade package also includes a Move script at `set_and_transfer.move`: - -```rust filename="set_and_transfer.move -script { - use upgrade_and_govern::parameters; - use upgrade_and_govern::transfer; - - const PARAMETER_1: u64 = 300; - const PARAMETER_2: u64 = 200; - - fun set_and_transfer( - upgrade_and_govern: &signer, - to_1: address, - to_2: address - ) { - parameters::set_parameters( - upgrade_and_govern, PARAMETER_1, PARAMETER_2); - transfer::transfer_octas(upgrade_and_govern, to_1, to_2); - } -} -``` - -This script calls the governance parameter setter using hard-coded values defined at the top of the script, then calls the [octa](../../network/glossary.mdx#Octa) transfer function. -The script accepts as arguments the signature of the account hosting the package, as well as two target addresses for the transfer operation. - -Note that both functions in the script are `public entry fun` functions, which means that everything achieved in the script could be performed without a script. -However, a non-script approach would require two transactions instead of just one, and would complicate the signature aggregation process: -in practical terms, Alice, Bob, and/or Chad would likely have to send single-signer transaction signatures around through off-chain communication channels, and a _scribe_ for the group would then have to submit a multisig `Authenticator` (for _each_ `public entry fun` call). -Hence, in a non-script approach, extra operational complexity can quickly introduce opportunities for consensus failure. - -A Move script, by contrast, collapses multiple governance function calls into a single transaction; and moreover, Move scripts can be published in a public forum like GitHub so that all signatories can review the actual function calls before they sign the script. - -### Step 8.5: Execute the governance script - -Alice and Bob sign off on the Move script, which sends coins from the vanity multisig account to their personal accounts. -Here, the amounts sent to each account are specified in the hard-coded values from the script. - -```python filename="multisig.py" -print("\n=== Invoking Move script ===") - -with open(f"{build_path}bytecode_scripts/set_and_transfer.mv", "rb") as f: - script_code = f.read() - -payload = Script( - code=script_code, - ty_args=[], - args=[ - ScriptArgument(ScriptArgument.ADDRESS, alice.address()), - ScriptArgument(ScriptArgument.ADDRESS, bob.address()), - ], -) - -raw_transaction = RawTransaction( - sender=deedee.address(), - sequence_number=3, - payload=TransactionPayload(payload), - max_gas_amount=rest_client.client_config.max_gas_amount, - gas_unit_price=rest_client.client_config.gas_unit_price, - expiration_timestamps_secs=( - int(time.time()) + rest_client.client_config.expiration_ttl - ), - chain_id=chain_id, -) - -alice_signature = alice.sign(raw_transaction.keyed()) -bob_signature = bob.sign(raw_transaction.keyed()) - -sig_map = [ # Map from signatory public key to signature. - (alice.public_key(), alice_signature), - (bob.public_key(), bob_signature), -] - -multisig_signature = MultiSignature(multisig_public_key, sig_map) - -authenticator = Authenticator( - MultiEd25519Authenticator(multisig_public_key, multisig_signature) -) - -signed_transaction = SignedTransaction(raw_transaction, authenticator) - -tx_hash = await rest_client.submit_bcs_transaction(signed_transaction) -await rest_client.wait_for_transaction(tx_hash) -print(f"Transaction hash: {tx_hash}") - -alice_balance = rest_client.account_balance(alice.address()) -bob_balance = rest_client.account_balance(bob.address()) -chad_balance = rest_client.account_balance(chad.address()) -multisig_balance = rest_client.account_balance(multisig_address) -[alice_balance, bob_balance, chad_balance, multisig_balance] = await asyncio.gather( - *[alice_balance, bob_balance, chad_balance, multisig_balance] -) - -print(f"Alice's balance: {alice_balance}") -print(f"Bob's balance: {bob_balance}") -print(f"Chad's balance: {chad_balance}") -print(f"Multisig balance: {multisig_balance}") -``` - -```bash filename="Terminal" -=== Invoking Move script === -Transaction hash: 0xd06de4bd9fb12a9f3cbd8ce1b9a9fd47ea2b923a8cfac21f9788869430e4149b -Alice's balance: 10000300 -Bob's balance: 20000200 -Chad's balance: 30000100 -``` - ---- - -Congratulations on completing the tutorial on K-of-N multi-signer authentication operations! diff --git a/apps/nextra/pages/zh/build/guides/first-transaction.mdx b/apps/nextra/pages/zh/build/guides/first-transaction.mdx deleted file mode 100644 index 2d28b5601..000000000 --- a/apps/nextra/pages/zh/build/guides/first-transaction.mdx +++ /dev/null @@ -1,696 +0,0 @@ -import { Callout, Tabs } from 'nextra/components'; - -# Your First Transaction - -This tutorial describes how to generate and submit transactions to the Aptos -blockchain, and verify these submitted transactions. The `transfer-coin` example -used in this tutorial is built with the Aptos SDKs. - -## Step 1: Pick an SDK - -Install your preferred SDK from the below list: - -- [TypeScript SDK](../sdks/ts-sdk.mdx) -- [Python SDK](../sdks/python-sdk.mdx) -- [Rust SDK](../sdks/rust-sdk.mdx) - ---- - -## Step 2: Run the example - - - - -Clone the `aptos-ts-sdk` repo and build it: - -```bash filename="Terminal" -git clone https://github.com/aptos-labs/aptos-ts-sdk.git -cd aptos-ts-sdk -pnpm install -pnpm build -``` - -Navigate to the Typescript examples directory: - -```bash filename="Terminal" -cd examples/typescript -``` - -Install the necessary dependencies: - -```bash filename="Terminal" -pnpm install -``` - -Run the [`transfer_coin`](https://github.com/aptos-labs/aptos-ts-sdk/blob/main/examples/typescript/transfer_coin.ts) example: - -```bash filename="Terminal" -pnpm run transfer_coin -``` - - - - -Clone the `aptos-python-sdk` repo: - -```bash filename="Terminal" -git clone https://github.com/aptos-labs/aptos-python-sdk.git -``` - -Install the necessary dependencies: - -```bash filename="Terminal" -curl -sSL https://install.python-poetry.org | python3 -poetry install -``` - -Run the [`transfer-coin`](https://github.com/aptos-labs/aptos-python-sdk/blob/main/examples/transfer_coin.py) example: - -```bash filename="Terminal" -poetry run python -m examples.transfer_coin -``` - - - - -Clone the `aptos-core` repo: - -```bash filename="Terminal" -git clone https://github.com/aptos-labs/aptos-core.git -``` - -Navigate to the Rust SDK directory: - -```bash filename="Terminal" -cd aptos-core/sdk -``` - -Run the [`transfer-coin`](https://github.com/aptos-labs/aptos-core/blob/main/sdk/examples/transfer-coin.rs) example: - -```bash filename="Terminal" -cargo run --example transfer-coin -``` - - - - ---- - -## Step 3: Understand the output - - - - An output very similar to the following will appear after executing the above command: - -```bash filename="Terminal" -=== Addresses === - -Alice's address is: 0xbd20517751571ba3fd06326c23761bc0bc69cf450898ffb43412fbe670c28806 -Bob's address is: 0x8705f98a74f5efe17740276ed75031927402c3a965e10f2ee16cda46d99d8f7f - -=== Initial Balances === - -Alice's balance is: 100000000 -Bob's balance is: 0 - -=== Transfer 1000000 from Alice to Bob === - -Committed transaction: 0xc0d348afdfc34ae2c48971b253ece727cc9980dde182e2f2c42834552cbbf04c - -=== Balances after transfer === - -Alice's balance is: 98899100 -Bob's balance is: 1000000 -``` - -The above output demonstrates that the `transfer-coin` example executes the following steps: - -- Initializing the Aptos client. -- The creation of two accounts: Alice and Bob. -- The funding and creation of Alice's account from a faucet. -- The transferring of 1000000 coins from Alice to Bob. -- The 1100900 coins of gas paid for by Alice to make that transfer. - - - - - An output very similar to the following will appear after executing the above command: - -```bash filename="Terminal" -=== Addresses === -Alice: 0xbd20517751571ba3fd06326c23761bc0bc69cf450898ffb43412fbe670c28806 -Bob: 0x8705f98a74f5efe17740276ed75031927402c3a965e10f2ee16cda46d99d8f7f - -=== Initial Balances === -Alice: 100000000 -Bob: 0 - -=== Intermediate Balances === -Alice: 99944900 -Bob: 1000 - -=== Final Balances === -Alice: 99889800 -Bob: 2000 -``` - -The above output demonstrates that the `transfer-coin` example executes the following steps: - -- Initializing the REST and faucet clients. -- The creation of two accounts: Alice and Bob. -- The funding and creation of Alice's account from a faucet. -- The creation of Bob's account from a faucet. -- The transferring of 1000 coins from Alice to Bob. -- The 54100 coins of gas paid for by Alice to make that transfer. -- Another transfer of 1000 coins from Alice to Bob. -- The additional 54100 coins of gas paid for by Alice to make that transfer. - -Now see the below walkthrough of the SDK functions used to accomplish the above steps. - - - - An output very similar to the following will appear after executing the above command: - -```bash filename="Terminal" -=== Addresses === -Alice: 0xbd20517751571ba3fd06326c23761bc0bc69cf450898ffb43412fbe670c28806 -Bob: 0x8705f98a74f5efe17740276ed75031927402c3a965e10f2ee16cda46d99d8f7f - -=== Initial Balances === -Alice: 100000000 -Bob: 0 - -=== Intermediate Balances === -Alice: 99944900 -Bob: 1000 - -=== Final Balances === -Alice: 99889800 -Bob: 2000 -``` - -The above output demonstrates that the `transfer-coin` example executes the following steps: - -- Initializing the REST and faucet clients. -- The creation of two accounts: Alice and Bob. -- The funding and creation of Alice's account from a faucet. -- The creation of Bob's account from a faucet. -- The transferring of 1000 coins from Alice to Bob. -- The 54100 coins of gas paid for by Alice to make that transfer. -- Another transfer of 1000 coins from Alice to Bob. -- The additional 54100 coins of gas paid for by Alice to make that transfer. - -Now see the below walkthrough of the SDK functions used to accomplish the above steps. - - - ---- - -## Step 4: The SDK in depth - -The `transfer-coin` example code uses helper functions to interact with the [REST API](../../network/nodes/aptos-api-spec.mdx). This section reviews each of the calls and gives insights into functionality. - - - - - -See the TypeScript [`transfer_coin`](https://github.com/aptos-labs/aptos-ts-sdk/blob/main/examples/typescript/transfer_coin.ts) for the complete code as you follow the below steps. - - - - - -See the Python [`transfer_coin`](https://github.com/aptos-labs/aptos-python-sdk/blob/main/examples/transfer_coin.py) for the complete code as you follow the below steps. - - - - - -See the Rust [`transfer-coin`](https://github.com/aptos-labs/aptos-core/blob/main/sdk/examples/transfer-coin.rs) for the complete code as you follow the below steps. - - - - ---- - -### Step 4.1: Initializing the clients - - - - -In the first step, the `transfer_coin` example initializes the Aptos client: - -```ts filename="transfer_coin.ts" -const APTOS_NETWORK: Network = - NetworkToNetworkName[process.env.APTOS_NETWORK] || Network.DEVNET; -const config = new AptosConfig({ network: APTOS_NETWORK }); -const aptos = new Aptos(config); -``` - - -By default, the Aptos client points to Aptos devnet services. However, it can be configured with the `network` input argument - - - - - In the first step, the `transfer-coin` example initializes both the REST and faucet clients: - -- The REST client interacts with the REST API. -- The faucet client interacts with the devnet Faucet service for creating and funding accounts. - -```python filename="transfer-coin.py" -rest_client = RestClient(NODE_URL) -faucet_client = FaucetClient(FAUCET_URL, rest_client) -``` - -[`common.py`](https://github.com/aptos-labs/aptos-python-sdk/blob/main/examples/common.py) initializes these values as follows: - -```python filename="common.py" -NODE_URL = os.getenv("APTOS_NODE_URL", "https://api.devnet.aptoslabs.com/v1") -FAUCET_URL = os.getenv( - "APTOS_FAUCET_URL", - "https://faucet.devnet.aptoslabs.com", -) -``` - - - -By default, the URLs for both the services point to Aptos devnet services. However, they can be configured with the following environment variables: - -- `APTOS_NODE_URL` -- `APTOS_FAUCET_URL` - - - - In the first step, the `transfer-coin` example initializes both the REST and faucet clients: - -- The REST client interacts with the REST API. -- The faucet client interacts with the devnet Faucet service for creating and funding accounts. - -```rust filename="transfer-coin.rs" -let rest_client = Client::new(NODE_URL.clone()); -let faucet_client = FaucetClient::new(FAUCET_URL.clone(), NODE_URL.clone()); -``` - -Using the API client we can create a `CoinClient`, which we use for common coin operations such as transferring coins and checking balances. - -```rust filename="transfer-coin.rs" -let coin_client = CoinClient::new(&rest_client); -``` - -In the example we initialize the URL values as such: - -```rust filename="transfer-coin.rs" -static NODE_URL: Lazy = Lazy::new(|| { - Url::from_str( - std::env::var("APTOS_NODE_URL") - .as_ref() - .map(|s| s.as_str()) - .unwrap_or("https://api.devnet.aptoslabs.com"), - ) - .unwrap() -}); - -static FAUCET_URL: Lazy = Lazy::new(|| { - Url::from_str( - std::env::var("APTOS_FAUCET_URL") - .as_ref() - .map(|s| s.as_str()) - .unwrap_or("https://faucet.devnet.aptoslabs.com"), - ) - .unwrap() -}); - -``` - - - -By default, the URLs for both the services point to Aptos devnet services. However, they can be configured with the following environment variables: - -- `APTOS_NODE_URL` -- `APTOS_FAUCET_URL` - - - - ---- - -### Step 4.2: Creating local accounts - -The next step is to create two accounts locally. [Accounts](../../network/blockchain/accounts.mdx) represent both on and off-chain state. Off-chain state consists of an address and the public/private key pair used to authenticate ownership. This step demonstrates how to generate that off-chain state. - - - - -```ts filename="example.ts" -const alice = Account.generate(); -const bob = Account.generate(); -``` - - - - -```python filename="example.py" -alice = Account.generate() -bob = Account.generate() -``` - - - - -```rust filename="example.rs" -let mut alice = LocalAccount::generate(&mut rand::rngs::OsRng); -let bob = LocalAccount::generate(&mut rand::rngs::OsRng); -``` - - - - ---- - -### Step 4.3: Creating blockchain accounts - -In Aptos, each account must have an on-chain representation in order to receive tokens and coins and interact with other dapps. An account represents a medium for storing assets; hence, it must be explicitly created. This example leverages the Faucet to create and fund Alice's account and to create but not fund Bob's account: - - - - -```ts filename="example.ts" -await aptos.fundAccount({ - accountAddress: alice.accountAddress, - amount: 100_000_000, -}); -``` - - - - -```python filename="example.py" -alice_fund = faucet_client.fund_account(alice.address(), 100_000_000) -bob_fund = faucet_client.fund_account(bob.address(), 0) -``` - - - - -```rust filename="example.rs" -faucet_client - .fund(alice.address(), 100_000_000) - .await - .context("Failed to fund Alice's account")?; -faucet_client - .create_account(bob.address()) - .await - .context("Failed to fund Bob's account")?; -``` - - - - ---- - -### Step 4.4: Reading balances - -In this step, the SDK translates a single call into the process of querying a resource and reading a field from that resource. - - - - -```ts filename="example.ts" -const aliceBalance = await balance("Alice", alice.accountAddress); -const bobBalance = await balance("Bob", bob.accountAddress); -``` - -Behind the scenes, the `balance` function uses the SDK `getAccountAPTAmount` function that queries the Indexer service and reads the current stored value: - -```ts filename="example.ts" -const balance = async ( - name: string, - accountAddress: AccountAddress, -): Promise => { - const amount = await aptos.getAccountAPTAmount({ - accountAddress, - }); - console.log(`${name}'s balance is: ${amount}`); - return amount; -}; -``` - - - - -```python filename="example.py" -alice_balance = rest_client.account_balance(alice.address()) -bob_balance = rest_client.account_balance(bob.address()) -[alice_balance, bob_balance] = await asyncio.gather(*[alice_balance, bob_balance]) -print(f"Alice: {alice_balance}") -print(f"Bob: {bob_balance}") -``` - -Behind the scenes, the SDK queries the CoinStore resource for the AptosCoin and reads the current stored value: - -```python filename="example.py" -def account_balance(self, account_address: str) -> int: - """Returns the test coin balance associated with the account""" - return self.account_resource( - account_address, "0x1::coin::CoinStore<0x1::aptos_coin::AptosCoin>" - )["data"]["coin"]["value"] -``` - - - - -```rust filename="example.rs" -println!( - "Alice: {:?}", - coin_client - .get_account_balance(&alice.address()) - .await - .context("Failed to get Alice's account balance the second time")? -); -println!( - "Bob: {:?}", - coin_client - .get_account_balance(&bob.address()) - .await - .context("Failed to get Bob's account balance the second time")? -); -``` - -Behind the scenes, the SDK queries the CoinStore resource for the AptosCoin and reads the current stored value: - -```rust filename="example.rs" -let balance = self - .get_account_resource(address, "0x1::coin::CoinStore<0x1::aptos_coin::AptosCoin>") - .await?; -``` - - - - ---- - -### Step 4.5: Transferring - -Like the previous step, this is another helper step that constructs a transaction transferring the coins from Alice to Bob. The SDK provides a helper function to generate a `transferCoinTransaction` transaction that can be simulated or submitted to chain. Once a transaction has been submitted to chain, the API will return a transaction hash that can be used in the subsequent step to check on the transaction status. The Aptos blockchain does perform a handful of validation checks on submission; and if any of those fail, the user will instead be given an error. These validations use the transaction signature and unused sequence number, and submitting the transaction to the appropriate chain. - - - -```ts filename="example.ts" -const transaction = await aptos.transferCoinTransaction({ - sender: alice, - recipient: bob.accountAddress, - amount: TRANSFER_AMOUNT, -}); -const pendingTxn = await aptos.signAndSubmitTransaction({ - signer: alice, - transaction, -}); -``` - -Behind the scenes, the `transferCoinTransaction` function generates a transaction payload that can be simulated or submitted to chain: - -```ts filename="example.ts" -export async function transferCoinTransaction(args: { - aptosConfig: AptosConfig; - sender: Account; - recipient: AccountAddressInput; - amount: AnyNumber; - coinType?: MoveStructId; - options?: InputGenerateTransactionOptions; -}): Promise { - const { aptosConfig, sender, recipient, amount, coinType, options } = args; - const coinStructType = coinType ?? APTOS_COIN; - const transaction = await generateTransaction({ - aptosConfig, - sender: sender.accountAddress, - data: { - function: "0x1::aptos_account::transfer_coins", - typeArguments: [coinStructType], - functionArguments: [recipient, amount], - }, - options, - }); - - return transaction; -} -``` - -Breaking the above down into pieces: - -1. `transfer_coins` internally is a `EntryFunction` in the [Aptos Account Move module](https://github.com/aptos-labs/aptos-core/blob/main/aptos-move/framework/aptos-framework/sources/aptos_account.move#L92), i.e. an entry function in Move that is directly callable. -2. The Move function is stored on the aptos_account module: `0x1::aptos_account`. -3. The `transfer_coins` functions uses the [Coin Move module](https://github.com/aptos-labs/aptos-core/blob/main/aptos-move/framework/aptos-framework/sources/coin.move) -4. Because the Coin module can be used by other coins, the `transferCoinTransaction` must explicitly specify which coin type to transfer. If not specified with `coinType` it defaults to `0x1::aptos_coin::AptosCoin`. - - - -Like the previous step, this is another helper step that constructs a transaction transferring the coins from Alice to Bob. For correctly generated transactions, the API will return a transaction hash that can be used in the subsequent step to check on the transaction status. The Aptos blockchain does perform a handful of validation checks on submission; and if any of those fail, the user will instead be given an error. These validations use the transaction signature and unused sequence number, and submitting the transaction to the appropriate chain. - -```python filename="example.py" -txn_hash = await rest_client.transfer(alice, bob.address(), 1_000) -``` - -Behind the scenes the Python SDK generates, signs, and submits a transaction: - -```python filename="example.py" -async def bcs_transfer( - self, - sender: Account, - recipient: AccountAddress, - amount: int, - sequence_number: Optional[int] = None, -) -> str: - transaction_arguments = [ - TransactionArgument(recipient, Serializer.struct), - TransactionArgument(amount, Serializer.u64), - ] - - payload = EntryFunction.natural( - "0x1::aptos_account", - "transfer", - [], - transaction_arguments, - ) - - signed_transaction = await self.create_bcs_signed_transaction( - sender, TransactionPayload(payload), sequence_number=sequence_number - ) - return await self.submit_bcs_transaction(signed_transaction) -``` - -Breaking the above down into pieces: - -1. `transfer` internally is a `EntryFunction` in the [Coin Move module](https://github.com/aptos-labs/aptos-core/blob/main/aptos-move/framework/aptos-framework/sources/coin.move#L412), i.e. an entry function in Move that is directly callable. -1. The Move function is stored on the coin module: `0x1::coin`. -1. Because the Coin module can be used by other coins, the transfer must explicitly use a `TypeTag` to define which coin to transfer. -1. The transaction arguments must be placed into `TransactionArgument`s with type specifiers (`Serializer.{type}`), that will serialize the value into the appropriate type at transaction generation time. - - - -Like the previous step, this is another helper step that constructs a transaction transferring the coins from Alice to Bob. For correctly generated transactions, the API will return a transaction hash that can be used in the subsequent step to check on the transaction status. The Aptos blockchain does perform a handful of validation checks on submission; and if any of those fail, the user will instead be given an error. These validations use the transaction signature and unused sequence number, and submitting the transaction to the appropriate chain. - -```rust filename="example.rs" -let txn_hash = coin_client - .transfer(&mut alice, bob.address(), 1_000, None) - .await - .context("Failed to submit transaction to transfer coins")?; -``` - -Behind the scenes the Rust SDK generates, signs, and submits a transaction: - -```rust filename="example.rs" -let chain_id = self - .api_client - .get_index() - .await - .context("Failed to get chain ID")? - .inner() - .chain_id; -let transaction_builder = TransactionBuilder::new( - TransactionPayload::EntryFunction(EntryFunction::new( - ModuleId::new(AccountAddress::ONE, Identifier::new("coin").unwrap()), - Identifier::new("transfer").unwrap(), - vec![TypeTag::from_str(options.coin_type).unwrap()], - vec![ - bcs::to_bytes(&to_account).unwrap(), - bcs::to_bytes(&amount).unwrap(), - ], - )), - SystemTime::now() - .duration_since(UNIX_EPOCH) - .unwrap() - .as_secs() - + options.timeout_secs, - ChainId::new(chain_id), -) -.sender(from_account.address()) -.sequence_number(from_account.sequence_number()) -.max_gas_amount(options.max_gas_amount) -.gas_unit_price(options.gas_unit_price); -let signed_txn = from_account.sign_with_transaction_builder(transaction_builder); -Ok(self - .api_client - .submit(&signed_txn) - .await - .context("Failed to submit transfer transaction")? - .into_inner()) -``` - -Breaking the above down into pieces: - -1. First, we fetch the chain ID, necessary for building the transaction payload. -1. `transfer` internally is a `EntryFunction` in the [Coin Move module](https://github.com/aptos-labs/aptos-core/blob/main/aptos-move/framework/aptos-framework/sources/coin.move#L412), i.e. an entry function in Move that is directly callable. -1. The Move function is stored on the coin module: `0x1::coin`. -1. Because the Coin module can be used by other coins, the transfer must explicitly use a `TypeTag` to define which coin to transfer. -1. The transaction arguments, such as `to_account` and `amount`, must be encoded as BCS to use with the `TransactionBuilder`. - - - - ---- - -### Step 4.6: Waiting for transaction resolution - - - - -In the TypeScript SDK, just calling `waitForTransaction` is sufficient to wait for the transaction to complete. The function will return the `Transaction` returned by the API once it is processed (either successfully or unsuccessfully) or throw an error if processing time exceeds the timeout. - -```ts filename="example.ts" -const response = await aptos.waitForTransaction({ - transactionHash: pendingTxn.hash, -}); -``` - - - - -The transaction hash can be used to query the status of a transaction: - -```python filename="example.py" -await rest_client.wait_for_transaction(txn_hash) -``` - - - - -The transaction hash can be used to query the status of a transaction: - -```rust filename="example.rs" -rest_client - .wait_for_transaction(&txn_hash) - .await - .context("Failed when waiting for the transfer transaction")?; -``` - - - - -## Supporting documentation - -- [Account basics](../../network/blockchain/accounts.mdx) -- [TypeScript SDK](../sdks/ts-sdk.mdx) -- [Python SDK](../sdks/python-sdk.mdx) -- [Rust SDK](../sdks/rust-sdk.mdx) -- [REST API specification](../../network/nodes/aptos-api-spec.mdx) diff --git a/apps/nextra/pages/zh/build/guides/key-rotation.mdx b/apps/nextra/pages/zh/build/guides/key-rotation.mdx deleted file mode 100644 index 74074e1c2..000000000 --- a/apps/nextra/pages/zh/build/guides/key-rotation.mdx +++ /dev/null @@ -1,870 +0,0 @@ ---- -title: "Account Key Rotation" ---- - -import { Callout, Steps } from "nextra/components"; - -# Account Key Rotation - - - Account key rotation is an advanced feature that should be used with caution. - Most users will never need to use this feature. - - -Aptos Move accounts have a public address, an authentication key, a public key, -and a private key. The public address is permanent, always matching the -account's initial authentication key, which is derived from the original private -key. - -The Aptos account model facilitates the unique ability to rotate an account's -private key. Since an account's address is the _initial_ authentication key, the -ability to sign for an account can be transferred to another private key without -changing its public address. - -In this guide, we show examples of how to rotate an account's authentication key -using the CLI and few of the various Aptos SDKs. - -Here are the installation links for the SDKs we will cover in this example: - -- [Aptos CLI](../cli.mdx) -- [Typescript SDK](../sdks/ts-sdk.mdx) -- [Python SDK](../sdks/python-sdk.mdx) - - -Some of the following examples use private keys. Do not share your private -keys with anyone. - - -## Proven and unproven key rotations - -The onchain logic for key rotation is implemented through two Move APIs: - -1. [`account::rotate_authentication_key`], which executes a "proven" rotation. -1. [`account::rotate_authentication_key_call`], which executes an "unproven" - rotation. - -### Proven key rotations - -The [`account::rotate_authentication_key`] API requires a signed -[`account::RotationProofChallenge`], which proves that the rotation operation is -approved by the private key from both before _and_ after the operation. When the -operation is successful, the [`account::OriginatingAddress`] table is updated -with an entry that maps from the new authentication key to the corresponding -account address. - -The [`account::OriginatingAddress`] table is a reverse lookup table that allows -users to query an account address associated with a given authentication key, -and only allows for one entry per authentication key. Hence the requirement of a -signed [`account::RotationProofChallenge`] to ensure that a malicious actor does -not rotate an account's authentication key to a key that is already in the -table, as this attack would prevent lookup of the valid originating address that -the holder of an authentication key had previously approved. - -Notably, the [`account::OriginatingAddress`] table is _only_ updated upon key -rotation, not upon standard account generation. This means that with proven key -rotations, a given private key can theoretically authenticate up to two accounts -at the same time: - -1. The account address derived from the private key during standard account - generation, assuming the account has not undergone any key rotations. -1. A second arbitrary address, which has had its authentication key rotated to - the given private key. - -However, it is considered best practice to only authenticate _one_ account with -a given private key at a time, because whenever the -[`account::OriginatingAddress`] table is updated, the underlying logic first -checks if the rotating account's initial authentication key is in the table, and -if so, verifies that the rotating account's address is the one mapped to in the -table. - -This means that if an arbitrary account's authentication key is rotated to -a given private key, the standard account whose address is originally derived -from the private key will not be able to execute its first authentication key -rotation while the associated authentication key is mapped to a second arbitrary -account address in the [`account::OriginatingAddress`] table, because this -operation would fail the check that the rotating account's address is the one -mapped to in the table (since the table is only updated during rotation, not -upon standard account generation). - -To prevent this issue and ensure best practices are followed, you can always run -[`account::set_originating_address`] after generating a new account (see below -CLI tutorial). - -### Unproven key rotations - -Unlike [`account::rotate_authentication_key`], the -[`account::rotate_authentication_key_call`] does _not_ require a signed -[`account::RotationProofChallenge`]. This means that the operation is not proven -in the sense the the private key from _after_ the operation has approved the -key rotation. Hence the [`account::OriginatingAddress`] table is _not_ updated -for unproven key rotations, and there is thus no restriction on the number of -accounts that can be authenticated with a given private key. Note that the -`aptos` CLI does not currently support unproven key rotations. - - -The [`account::rotate_authentication_key_call`] was introduced to support -non-standard key algorithms, like passkeys, which cannot produce proofs of -knowledge during rotation operations. - - -While it is technically possible to authenticate as many accounts as you want -with a given authentication key via unproven key rotations, it is not considered -best practice because this approach does not ensure one-to-one mapping. - -If you execute an unproven key rotation, it is suggested that you follow up with -[`account::set_originating_address`] to ensure a one-to-one mapping from -authentication key to account address for ease of originating address lookup -(see below CLI tutorial). - -## Key rotation with the Aptos CLI - - - -### Start a localnet - -Start a localnet: - -```sh filename="Terminal" -aptos node run-localnet -``` - -The localnet is ready when it prints out: - -```sh filename="Terminal" -Applying post startup steps... - -Setup is complete, you can now use the localnet! -``` - - -If you are on a UNIX-like system, the following command can be used to start a -fresh localnet as a background process: - -```sh filename="Terminal" -mkdir -p localnet-data -aptos node run-localnet \ - --assume-yes \ - --test-dir localnet-data \ - --force-restart & -export LOCALNET_PID=$! -``` - -You can then stop the localnet at any point with the following command: - -```sh filename="Terminal" -kill $LOCALNET_PID -``` - - - -### Generate a private key - -Create a private key corresponding to an authentication key, and thus initial -account address, that starts with the vanity prefix `0xaaa`: - -```sh filename="Terminal" -aptos key generate \ - --assume-yes \ - --output-file private-key-a \ - --vanity-prefix 0xaaa -``` - -
-Example output -```sh filename="Terminal" -{ - "Result": { - "Account Address:": "0xaaa5131b4d3fcef8d33ee465c4ee65727e36039f283455be87b1164200572e5b", - "PublicKey Path": "private-key-a.pub", - "PrivateKey Path": "private-key-a" - } -} -``` -
- -This will generate two files: - -1. A private key at `private-key-a`. -1. A public key at `private-key-a.pub`. - -Since there is not yet an account associated with the authentication key, the -following command should fail with a corresponding messsage: - -```sh filename="Terminal" -aptos account lookup-address \ - --public-key-file private-key-a.pub \ - --url http://localhost:8080 -``` - -
-Example output -```sh filename="Terminal" -{ - "Error": "API error: API error Error(AccountNotFound): Account not found by Address(0xaaafb224eb00e4d0ef520ce02038ede850893622562a4189b7f6e5d94454ccd9) and Ledger version(1206)" -} -``` -
- -### Initialize a profile - -Use the private key to initialize `test-profile-1` on the localnet: - -```sh filename="Terminal" -aptos init \ - --assume-yes \ - --network local \ - --private-key-file private-key-a \ - --profile test-profile-1 -``` - -
-Example output -```sh filename="Terminal" -Configuring for profile test-profile-1 -Configuring for network Local -Using command line argument for private key -Account 0xaaa5131b4d3fcef8d33ee465c4ee65727e36039f283455be87b1164200572e5b doesn\'t exist, creating it and funding it with 100000000 Octas -Account 0xaaa5131b4d3fcef8d33ee465c4ee65727e36039f283455be87b1164200572e5b funded successfully - ---- -Aptos CLI is now set up for account 0xaaa5131b4d3fcef8d33ee465c4ee65727e36039f283455be87b1164200572e5b as profile test-profile-1! Run `aptos --help` for more information about commands -{ - "Result": "Success" -} -``` -
- -Note that you can always view the profile with: - -```sh filename="Terminal" -aptos config show-profiles --profile test-profile-1 -``` - -
-Example output -```sh filename="Terminal" -{ - "Result": { - "test-profile-1": { - "has_private_key": true, - "public_key": "0xe0bfe46f41c5be40e7a068e8dff4d6016126b226d947a39262f5b2347217a7e3", - "account": "aaa5131b4d3fcef8d33ee465c4ee65727e36039f283455be87b1164200572e5b", - "rest_url": "http://localhost:8080", - "faucet_url": "http://localhost:8081" - } - } -} -``` -
- -However, this will not show the private key, which is hidden by default. If you -would like to show the private key: - -```sh filename="Terminal" -aptos config show-private-key --profile test-profile-1 -``` - -
-Example output -```sh filename="Terminal" -{ - "Result": "0xcc3b0c38ad99e171263a7af930464313d1fb105d0d8e6a4b13f9b1140563a7dd" -} -``` -
- -### Look up address - -Now that there is an onchain account associated with the authentication key, -you can look up the account address using `aptos account lookup-address`: - -```sh filename="Terminal" -aptos account lookup-address \ - --public-key-file private-key-a.pub \ - --url http://localhost:8080 -``` - -
-Example output -```sh filename="Terminal" -{ - "Result": "aaa5131b4d3fcef8d33ee465c4ee65727e36039f283455be87b1164200572e5b" -} -``` -
- -Store this address in a shell variable: - -```sh filename="Terminal" -ADDRESS_A=aaa... -``` - - -If you are using a UNIX-like machine that has `jq`, you can easily store the account address via: - -```sh filename="Terminal" -export ADDRESS_A=$( - aptos account lookup-address \ - --public-key-file private-key-a.pub \ - --url http://localhost:8080 \ - | jq -r '.Result' -) -echo $ADDRESS_A -``` - - -### Look up authentication key - -Recall that the address of an account is identical to its authentication key -when it is initially created, which means that the account address `aaa...` is -identical to the account's authentication key: - -```sh filename="Terminal" -aptos move view \ - --args address:$ADDRESS_A \ - --function-id 0x1::account::get_authentication_key \ - --url http://localhost:8080 -``` - -
-Example output -```sh filename="Terminal" -{ - "Result": [ - "0xaaa5131b4d3fcef8d33ee465c4ee65727e36039f283455be87b1164200572e5b" - ] -} -``` -
- -Hence, store the authentication key in a shell variable: - -```sh -AUTH_KEY_A=$ADDRESS_A -``` - -Note, however, since the account has not yet had its authentication key rotated, -there is no corresponding entry in the [`account::OriginatingAddress`] table: - -```sh filename="Terminal" -aptos move view \ - --args address:$AUTH_KEY_A \ - --function-id 0x1::account::originating_address \ - --url http://localhost:8080 -``` - -
-Example output -```sh filename="Terminal" -{ - "Result": [ - { - "vec": [] - } - ] -} -``` -
- -### Set originating address - -To ensure an entry in the [`account::OriginatingAddress`] table for this new account, -you can run [`account::set_originating_address`]: - -```sh filename="Terminal" -aptos move run \ - --assume-yes \ - --function-id 0x1::account::set_originating_address \ - --profile test-profile-1 -``` - -
-Example output -```sh filename="Terminal" -{ - "Result": { - "transaction_hash": "0x216992ef37a3c2f42aa9f8fed8f94d9f945a00e952dfe96b46123bb5c387ab6c", - "gas_used": 444, - "gas_unit_price": 100, - "sender": "aaa5131b4d3fcef8d33ee465c4ee65727e36039f283455be87b1164200572e5b", - "sequence_number": 0, - "success": true, - "timestamp_us": 1717809169531279, - "version": 3268, - "vm_status": "Executed successfully" - } -} -``` -
- -Then you should see an entry in the [`account::OriginatingAddress`] table: - -```sh filename="Terminal" -aptos move view \ - --args address:$AUTH_KEY_A \ - --function-id 0x1::account::originating_address \ - --url http://localhost:8080 -``` - -
-Example output -```sh filename="Terminal" -{ - "Result": [ - { - "vec": [ - "0xaaa5131b4d3fcef8d33ee465c4ee65727e36039f283455be87b1164200572e5b" - ] - } - ] -} -``` -
- -### Rotate authentication key - -Generate a new private key: - -```sh filename="Terminal" -aptos key generate \ - --assume-yes \ - --output-file private-key-b \ - --vanity-prefix 0xbbb -``` - -
-Example output -```sh filename="Terminal" -{ - "Result": { - "PrivateKey Path": "private-key-b", - "Account Address:": "0xbbbdb12f4fa23b8fe8711b77f4ab7108f3a22077c5dfe787eed3d048a0b82734", - "PublicKey Path": "private-key-b.pub" - } -} -``` -
- -Rotate the authentication key of the existing onchain account to the new -private key: - -```sh filename="Terminal" -aptos account rotate-key \ - --assume-yes \ - --new-private-key-file private-key-b \ - --profile test-profile-1 \ - --save-to-profile test-profile-2 -``` - -
-Example output -```sh filename="Terminal" -{ - "Result": { - "message": "Saved new profile test-profile-2", - "transaction": { - "transaction_hash": "0xe561b710390511203511d15eee6f019a2e43ba32f8e3b7ce6bf812232e3bd27f", - "gas_used": 449, - "gas_unit_price": 100, - "sender": "aaa8dc0f5e7a6e820f7b1906d99864412b12274ed259ad06bc2c2d8ee7b51e51", - "sequence_number": 1, - "success": true, - "timestamp_us": 1717810059696079, - "version": 1109, - "vm_status": "Executed successfully" - } - } -} -``` -
- -### Compare profiles - -Compare `test-profile-1` (which is now stale) with `test-profile-2` (which is -current) noting that the public key has changed, but not the account address: - -```sh filename="Terminal" -aptos config show-profiles --profile test-profile-1 -aptos config show-profiles --profile test-profile-2 -``` - -
-Example output -```sh filename="Terminal" -{ - "Result": { - "test-profile-1": { - "has_private_key": true, - "public_key": "0xb517173e68f4116e99c7fa1677058a6ee786a3b9e12447000db7fd85ab99dbdd", - "account": "aaa8dc0f5e7a6e820f7b1906d99864412b12274ed259ad06bc2c2d8ee7b51e51", - "rest_url": "http://localhost:8080", - "faucet_url": "http://localhost:8081" - } - } -} -{ - "Result": { - "test-profile-2": { - "has_private_key": true, - "public_key": "0xadc3dd795fdd8569f59dc7b9900b38a5d7b95348b815de4eb5f00e2c2da07916", - "account": "aaa8dc0f5e7a6e820f7b1906d99864412b12274ed259ad06bc2c2d8ee7b51e51", - "rest_url": "http://localhost:8080", - "faucet_url": "http://localhost:8081" - } - } -} -``` -
- -Lookup the new authentication key: - -```sh filename="Terminal" -aptos move view \ - --args address:$ADDRESS_A \ - --function-id 0x1::account::get_authentication_key \ - --url http://localhost:8080 -``` - -
-Example output -```sh filename="Terminal" -{ - "Result": [ - "0xbbbdb12f4fa23b8fe8711b77f4ab7108f3a22077c5dfe787eed3d048a0b82734" - ] -} -``` -
- -Store the authentication key in a shell variable: - -```sh filename="Terminal" -AUTH_KEY_B=bbb... -``` - - -If you are using a UNIX-like machine that has `jq`, you can easily store the authentication key via: - -```sh filename="Terminal" -export AUTH_KEY_B=$( - aptos move view \ - --args address:$ADDRESS_A \ - --function-id 0x1::account::get_authentication_key \ - --url http://localhost:8080 \ - | jq -r '.Result[0]' -) -echo $AUTH_KEY_B -``` - - -### Look up originating addresses - -Check the originating address for the new authentication key: - -```sh filename="Terminal" -aptos move view \ - --args address:$AUTH_KEY_B \ - --function-id 0x1::account::originating_address \ - --url http://localhost:8080 -``` - -
-Example output -```sh filename="Terminal" -{ - "Result": [ - { - "vec": [ - "0xaaa8dc0f5e7a6e820f7b1906d99864412b12274ed259ad06bc2c2d8ee7b51e51" - ] - } - ] -} -``` -
- -Check the originating address for the old authentication key: - -```sh filename="Terminal" -aptos move view \ - --args address:$AUTH_KEY_A \ - --function-id 0x1::account::originating_address \ - --url http://localhost:8080 -``` - -
-Example output -```sh filename="Terminal" -{ - "Result": [ - { - "vec": [] - } - ] -} -``` -
- -### Attempt invalid rotation (same key) - -Attempt an invalid rotation where the current authentication key is identical -to the new authentication key: - -```sh filename="Terminal" -aptos account rotate-key \ - --assume-yes \ - --new-private-key-file private-key-b \ - --profile test-profile-2 \ - --skip-saving-profile -``` - -
-Example output -```sh filename="Terminal" -{ - "Error": "Invalid arguments: New public key cannot be the same as the current public key" -} -``` -
- -### Attempt invalid rotation (new key already mapped) - -Create another private key: - -```sh filename="Terminal" -aptos key generate \ - --assume-yes \ - --output-file private-key-c \ - --vanity-prefix 0xccc -``` - -
-Example output -```sh filename="Terminal" -{ - "Result": { - "PrivateKey Path": "private-key-c", - "PublicKey Path": "private-key-c.pub", - "Account Address:": "0xccc79d46b2963cb87f2ff32c51eb6c6361e8aa108d334d3183c3016389542958" - } -} -``` -
- -Initialize a new profile: - -```sh filename="Terminal" -aptos init \ - --assume-yes \ - --network local \ - --private-key-file private-key-c \ - --profile test-profile-3 -``` - -
-Example output -```sh filename="Terminal" -Configuring for profile test-profile-3 -Configuring for network Local -Using command line argument for private key -Account 0xccc79d46b2963cb87f2ff32c51eb6c6361e8aa108d334d3183c3016389542958 doesn\'t exist, creating it and funding it with 100000000 Octas -Account 0xccc79d46b2963cb87f2ff32c51eb6c6361e8aa108d334d3183c3016389542958 funded successfully - ---- -Aptos CLI is now set up for account 0xccc79d46b2963cb87f2ff32c51eb6c6361e8aa108d334d3183c3016389542958 as profile test-profile-3! Run `aptos --help` for more information about commands -{ - "Result": "Success" -} -``` -
- -Attempt an invalid rotation where the new authentication key is already mapped: - -```sh filename="Terminal" -aptos account rotate-key \ - --assume-yes \ - --max-gas 100000 \ - --new-private-key-file private-key-b \ - --profile test-profile-3 \ - --skip-saving-profile -``` - -(`--max-gas` is specified here to skip local simulation, which does not print -out as descriptive of an error as the actual transaction.) - -
-Example output -```sh filename="Terminal" -{ - "Error": "API error: Unknown error Transaction committed on chain, but failed execution: Move abort in 0x1::account: ENEW_AUTH_KEY_ALREADY_MAPPED(0x10015): The new authentication key already has an entry in the `OriginatingAddress` table" -} -``` -
- -### Attempt invalid rotation (invalid originating address) - -Rotate the authentication key for account `0xaaa...` to use the authentication -key for account `0xccc...`: - -```sh filename="Terminal" -aptos account rotate-key \ - --assume-yes \ - --new-private-key-file private-key-c \ - --profile test-profile-2 \ - --save-to-profile test-profile-4 -``` - -
-Example output -```sh filename="Terminal" -{ - "Result": { - "message": "Saved new profile test-profile-4", - "transaction": { - "transaction_hash": "0xa5dec792d82ef7471cdf82b9c957fc79b5815da770ad1dd9232ae4692e4f0895", - "gas_used": 449, - "gas_unit_price": 100, - "sender": "aaa8dc0f5e7a6e820f7b1906d99864412b12274ed259ad06bc2c2d8ee7b51e51", - "sequence_number": 2, - "success": true, - "timestamp_us": 1717812312772580, - "version": 5355, - "vm_status": "Executed successfully" - } - } -} -``` -
- -Then try to rotate the authentication key for account `0xccc...` for the first -time, an operation that is blocked because an entry for the authentication key -was established in the [`account::OriginatingAddress`] table during the last -operation: - -```sh filename="Terminal" -aptos account rotate-key \ - --assume-yes \ - --max-gas 100000 \ - --new-private-key-file private-key-b \ - --profile test-profile-3 \ - --skip-saving-profile -``` - -(`--max-gas` is specified here to skip local simulation, which does not print -out as descriptive of an error as the actual transaction.) - -
-Example output -```sh filename="Terminal" -{ - "Error": "API error: Unknown error Transaction committed on chain, but failed execution: Move abort in 0x1::account: EINVALID_ORIGINATING_ADDRESS(0x6000d): Abort the transaction if the expected originating address is different from the originating address on-chain" -} -``` -
- -### Clean up - -Delete the test profiles: - -```shell filename="Terminal" -aptos config delete-profile --profile test-profile-1 -aptos config delete-profile --profile test-profile-2 -aptos config delete-profile --profile test-profile-3 -aptos config delete-profile --profile test-profile-4 -``` - -Then you can stop the localnet and delete the private and public key files. - - -If you are using a UNIX-like machine: - -```shell filename="Terminal" -aptos config delete-profile --profile test-profile-1 -aptos config delete-profile --profile test-profile-2 -aptos config delete-profile --profile test-profile-3 -aptos config delete-profile --profile test-profile-4 -rm private-key-* -kill $LOCALNET_PID -rm -fr localnet-data -``` - - -### Rotate keys for a Ledger - -You can also perform authentication key rotation with a private key that is -securely stored on a Ledger hardware wallet. For more information, see the -[Ledger authentication key rotation guide](../cli/trying-things-on-chain/ledger.mdx#authentication-key-rotation). - -
- -## TypeScript key rotation example - This program creates two accounts on devnet, Alice and Bob, funds them, then - rotates the Alice's authentication key to that of Bob's. - - View the full example for this code - [here](https://github.com/aptos-labs/aptos-core/blob/main/ecosystem/typescript/sdk/examples/typescript-esm/rotate_key.ts). - - The function to rotate is very simple: - - {/* TODO CODE EXAMPLE */} - - Commands to run the example script: - - ### Navigate to the typescript SDK directory, install dependencies and run - rotate_key.ts - ```bash filename="Terminal" - cd ~/aptos-core/ecosystem/typescript/sdk/examples/typescript-esm - pnpm install && pnpm rotate_key - ``` - - ### rotate_key.ts output - ```shell filename="Terminal" - Account Address Auth Key Private Key Public Key - ------------------------------------------------------------------------------------------------ - Alice 0x213d...031013 '0x213d...031013' '0x00a4...b2887b' '0x859e...08d2a9' - Bob 0x1c06...ac3bb3 0x1c06...ac3bb3 0xf2be...9486aa 0xbbc1...abb808 - - ...rotating... - - Alice 0x213d...031013 '0x1c06...ac3bb3' '0xf2be...9486aa' '0xbbc1...abb808' - Bob 0x1c06...ac3bb3 0x1c06...ac3bb3 0xf2be...9486aa 0xbbc1...abb808 - ``` - -## Python key rotation example - This program creates two accounts on devnet, Alice and Bob, funds them, then - rotates the Alice's authentication key to that of Bob's. - - View the full example for this code - [here](https://github.com/aptos-labs/aptos-core/blob/main/ecosystem/python/sdk/examples/rotate_key.py). - - Here's the relevant code that rotates Alice's keys to Bob's: - - {/* TODO CODE EXAMPLE */} - - Commands to run the example script: - - ### Navigate to the python SDK directory, install dependencies and run - rotate_key.ts - ```bash filename="Terminal" - cd aptos-core/ecosystem/python/sdk - poetry install && poetry run python -m examples.rotate-key - ``` - - ### rotate_key.py output - ```bash filename="Terminal" - Account Address Auth Key Private Key Public Key - ------------------------------------------------------------------------------------------------ - Alice 0x213d...031013 '0x213d...031013' '0x00a4...b2887b' '0x859e...08d2a9' - Bob 0x1c06...ac3bb3 0x1c06...ac3bb3 0xf2be...9486aa 0xbbc1...abb808 - - ...rotating... - - Alice 0x213d...031013 '0x1c06...ac3bb3' '0xf2be...9486aa' '0xbbc1...abb808' - Bob 0x1c06...ac3bb3 0x1c06...ac3bb3 0xf2be...9486aa 0xbbc1...abb808 - ``` - -[`account::rotate_authentication_key`]: https://github.com/aptos-labs/aptos-core/blob/acb6c891cd42a63b3af96561a1aca164b800c7ee/aptos-move/framework/aptos-framework/sources/account.move#L326 -[`account::rotate_authentication_key_call`]: https://github.com/aptos-labs/aptos-core/blob/acb6c891cd42a63b3af96561a1aca164b800c7ee/aptos-move/framework/aptos-framework/sources/account.move#L294 -[`account::RotationProofChallenge`]: https://github.com/aptos-labs/aptos-core/blob/acb6c891cd42a63b3af96561a1aca164b800c7ee/aptos-move/framework/aptos-framework/sources/account.move#L79 -[`account::OriginatingAddress`]: https://github.com/aptos-labs/aptos-core/blob/acb6c891cd42a63b3af96561a1aca164b800c7ee/aptos-move/framework/aptos-framework/sources/account.move#L70 -[`account::set_originating_address`]: https://github.com/alnoki/aptos-core/blob/5ba4a8d1344b0bb6e22665525a96e787b9a44e55/aptos-move/framework/aptos-framework/sources/account.move#L528 \ No newline at end of file diff --git a/apps/nextra/pages/zh/build/guides/multisig-managed-fungible-asset.mdx b/apps/nextra/pages/zh/build/guides/multisig-managed-fungible-asset.mdx deleted file mode 100644 index a1a049eaf..000000000 --- a/apps/nextra/pages/zh/build/guides/multisig-managed-fungible-asset.mdx +++ /dev/null @@ -1,181 +0,0 @@ ---- -title: "Manage Fungible Assets with Multisig" ---- - -import { Callout } from "nextra/components"; - -# Manage Fungible Assets with Multisig - -This tutorial introduces a practical use case that combines Aptos framework -multisig account with fungible asset standard to enhance the security margin of -the management of fungible assets. Make sure you have understood module -publishing and Aptos framework multisig account before moving on to the -tutorial. If not, it is highly recommended to try out the following tutorials -first: - -- [Your First Move Module](first-move-module.mdx) - -## Step 1: Pick an SDK - -This tutorial was created for the [TypeScript SDK](../sdks/ts-sdk.mdx). - -Other developers are invited to add support for -the [Python SDK](../sdks/python-sdk.mdx), [Rust SDK](../sdks/rust-sdk.mdx), -[Go SDK](../sdks/go-sdk.mdx) and [Unity SDK](../sdks/unity-sdk.mdx)! - -## Step 2: Publish the module - -To create a fungible asset controlled by an Aptos framework multisig account -with all the administrative operations (mint, transfer, burn, freeze/unfreeze), -a well-designed smart contract based on fungible asset standard is a -prerequisite. The Aptos team provides an example code in `aptos-core` repo. - -Clone the `aptos-core` repo: - -```bash filename="Terminal" -git clone git@github.com:aptos-labs/aptos-core.git ~/aptos-core -``` - -Navigate to the `managed_fungible_asset` directory and then publish this package -onto your `default` account using CLI: - -```bash filename="Terminal" -cd ~/aptos-core/aptos-move/move-examples/fungible_asset/managed_fungible_asset -aptos move publish --named-addresses example_addr=default -``` - -Navigate to the `multisig_managed_coin` directory and then publish this package -onto your `default` account using CLI too: - -```bash filename="Terminal" -cd ~/aptos-core/aptos-move/move-examples/fungible_asset/multisig_managed_coin -aptos move publish --named-addresses example_addr=default -``` - -For this tutorial, `multisig_managed_coin` need to call functions defined -in `managed_fungible_asset` on the same address. So both modules have to be -published. - - - Do not forget to fund the account with faucet before publishing modules. - - -## Step 3: Start The example - -```bash filename="Terminal" -cd ~/aptos-core/ecosystem/typescript/sdk/examples/typescript -``` - -Run the `multisig_managed_coin` example: - -```bash filename="Terminal" -MODULE_ADDR=${DEFAULT_ACCOUNT_ADDRESS} pnpm run multisig_managed_coin -``` - - - This example uses the Aptos devnet, which has historically been reset each - Thursday. - Make sure devnet is live when you try running the example! If you are running - localnet with faucet, you can run the following command instead: - - ```bash filename="Terminal" - export APTOS_NODE_URL=http://0.0.0.0:8080 - export APTOS_FAUCET_URL=http://0.0.0.0:8081 - export MODULE_ADDR=${DEFAULT_ACCOUNT_ADDRESS} - pnpm run multisig_managed_coin - ``` - - - -The example script should execute successfully without any errors. Then you are -able to see what it did by searching the `owner1` and `owner2` addresses printed -to the console on Aptos explorer. - -Let's follow the script to understand what it does: - -### Generate single signer accounts - -First, we will generate three single signer accounts, owner1, owner2 and owner3 -who will co-own an Aptos framework multisig account. - -{/* TODO Code snippet -typescript title=Generate 3 single signers" -/sdks/typescript/examples/typescript/multisig_managed_coin.ts -section_1 -*/} - -### Create an Aptos framework multisig account with a managed fungible asset - -Next, let owner1 call the `initialize()` function defined -in `multisig_managed_coin.move`, which first create an Aptos framework multisig -account owned by owner1 and add both owner2 and owner3 as owners. Also, it -creates a fungible asset called "meme coin" with customized settings denoted in -the argument list and make the multisig account the admin of the fungible asset. -Also, each proposal needs at least 2 approvals to execute. - -{/* TODO Code snippet -typescript title=Query the multisig account and then call the initialize function" -/sdks/typescript/examples/typescript/multisig_managed_coin.ts -section_2 -*/} - -### Mint - -Then we mint 1000 and 2000 meme coin to owner2 and owner3, respectively. The -proposed transaction is submitted by owner2 and gets an additional approval from -owner3. - -{/* TODO Code snippet -typescript title="Mint 1000 to owner2 and 2000 to owner3" -/sdks/typescript/examples/typescript/multisig_managed_coin.ts -section_3 -*/} - - -### Freeze - -After minting, the example shows how to freeze account owner1. The proposed -transaction is again submitted by owner2 and approved by owner3 in addition. - -{/* TODO Code snippet -typescript title=""Freeze owner1" -/sdks/typescript/examples/typescript/multisig_managed_coin.ts -section_4 -*/} - - - Unfreeze is similar that just replace the last argument of - `set_primary_stores_frozen_status` function to `false`. - - -### Force transfer - -When owner1 is frozen, normal transfer cannot withdraw from or deposit to that -account. But as the admin of "meme coin", the multisig account has the -capability to do that. -Next, Owner2 proposed a transaction to force transfer 1000 meme coins from -owner3 to owner1. This time, owner1 approves it. - -{/* TODO Code snippet -typescript title=""Force transfer 1000 meme coins from owner3 to owner1" -/sdks/typescript/examples/typescript/multisig_managed_coin.ts -section_5 -*/} - -### Burn - -Finally, all the three owners have 1000 meme coins. Let's burn all the coins! -Owner2 makes the proposal and owner1 approves it. - -{/* TODO Code snippet -typescript title="Burn 1000 meme coins from all the three owners' accounts" -/sdks/typescript/examples/typescript/multisig_managed_coin.ts -section_6 -*/} - -## Conclusion - -This tutorial shows an e2e flow of using Aptos framework multisig account to -administrate fungible asset. Similarly, you can create your own module and -leverage our powerful SDK to create the administration schema that fits your -needs. diff --git a/apps/nextra/pages/zh/build/guides/oracles.mdx b/apps/nextra/pages/zh/build/guides/oracles.mdx deleted file mode 100644 index 49ccedab3..000000000 --- a/apps/nextra/pages/zh/build/guides/oracles.mdx +++ /dev/null @@ -1,127 +0,0 @@ ---- -Title: Use Oracles in Your Aptos Applications ---- - -import { Callout } from "nextra/components"; - -# Oracles -This reference guide presents various Oracles that you can utilize while building on Aptos. Oracles supply offchain data to the blockchain, enabling smart contracts to access a diverse range of information. - -## Pyth Network - -The [Pyth Network](https://pyth.network/) is one of the largest first-party Oracle network, delivering real-time data across [a vast number of chains](https://docs.pyth.network/price-feeds/contract-addresses). - -The network comprises some of the world’s [largest exchanges, market makers, and financial services providers](https://pyth.network/publishers). These publish proprietary data on-chain for aggregation and distribution to smart contract applications. - -## How to Use Pyth Real-Time Data in Aptos Contracts - -This guide explains how to use real-time Pyth data in Aptos applications. - -## Configuring the `Move.toml` file - -Add the Pyth Contract to your project dependencies in the `Move.toml` file: - -```toml copy -[dependencies] -Pyth = { git = "https://github.com/pyth-network/pyth-crosschain.git", subdir = "target_chains/aptos/contracts", rev = "main" } -``` - -The named addresses of `pyth`, `wormhole`, and `deployers` must be defined at compile time. These addresses are used to interact with the Pyth contract on Aptos. - -```toml copy -[addresses] -pyth = "0x7e783b349d3e89cf5931af376ebeadbfab855b3fa239b7ada8f5a92fbea6b387" -deployer = "0xb31e712b26fd295357355f6845e77c888298636609e93bc9b05f0f604049f434" -wormhole = "0x5bc11445584a763c1fa7ed39081f1b920954da14e04b32440cba863d03e19625" -``` - -Consult [Aptos Contract Addresses](https://docs.pyth.network/price-feeds/contract-addresses/aptos) for the complete list of contract addresses on different Aptos networks. - -## Write Contract Code - -The code snippet below provides an example module fetching the BTC/USD price from Pyth price feeds: - -```rust {21} copy -module example::example { - use pyth::pyth; - use pyth::price::Price; - use pyth::price_identifier; - use aptos_framework::coin; - - // Add the pyth_price_update argument to any method on your contract that needs to read the Pyth price. - // See https://docs.pyth.network/price-feeds/fetch-price-updates for more information on how to fetch the pyth_price_update. - public fun get_btc_usd_price(user: &signer, pyth_price_update: vector>): Price { - - // First update the Pyth price feeds - let coins = coin::withdraw(user, pyth::get_update_fee(&pyth_price_update)); - pyth::update_price_feeds(pyth_price_update, coins); - - // Read the current price from a price feed. - // Each price feed (e.g., BTC/USD) is identified by a price feed ID. - // The complete list of feed IDs is available at https://pyth.network/developers/price-feed-ids - // Note: Aptos uses the Pyth price feed ID without the `0x` prefix. - let btc_price_identifier = x"e62df6c8b4a85fe1a67db44dc12de5db330f7ac66b72dc658afedf0f4a415b43"; - let btc_usd_price_id = price_identifier::from_byte_vec(btc_price_identifier); - pyth::get_price(btc_usd_price_id) - } -} - -``` - - - The `pyth_price_update` argument contains verified prices from Pyth. Calling - `pyth::update_price_feeds` with this value updates the on-chain Pyth price and - ensures your application has recent price data. The pyth_price_update can be - fetched from Hermes; Consult [Fetch Price Updates](../fetch-price-updates) for - more information on how to fetch the `pyth_price_update`. - - -The code snippet above does the following things: - -1. Call `pyth::get_update_fee` to get the fee required to update the Pyth price feeds. -1. Call `pyth::update_price_feeds` and pass `pyth_price_update` to update the Pyth price feeds. -1. Call `pyth::get_price` to read the current price, providing the [price feed ID](https://pyth.network/developers/price-feed-ids) you wish to read. - -## Additional Resources - -You may find these additional resources helpful for developing your Aptos application. - -## Sponsored Feeds on Aptos - -The price feeds listed in the table below are currently sponsored in **Aptos mainnet**. - -Update Parameters: **1 second heartbeat or 0.5% price deviation** - -| Name | Price Feed Id | -| --------- | ------------------------------------------------------------------ | -| APT/USD | `03ae4db29ed4ae33d323568895aa00337e658e348b37509f5372ae51f0af00d5` | -| BTC/USD | `e62df6c8b4a85fe1a67db44dc12de5db330f7ac66b72dc658afedf0f4a415b43` | -| ETH/USD | `ff61491a931112ddf1bd8147cd1b641375f79f5825126d665480874634fd0ace` | -| SOL/USD | `ef0d8b6fda2ceba41da15d4095d1da392a0d2f8ed0c6c7bc0f4cfac8c280b56d` | -| USDC/USD | `eaa020c61cc479712813461ce153894a96a6c00b21ed0cfc2798d1f9a9e9c94a` | -| USDT/USD | `2b89b9dc8fdf9f34709a5b106b472f0f39bb6ca9ce04b0fd7f2e971688e2e53b` | -| CAKE/USD | `2356af9529a1064d41e32d617e2ce1dca5733afa901daba9e2b68dee5d53ecf9` | -| SUI/USD | `23d7315113f5b1d3ba7a83604c44b94d79f4fd69af77f804fc7f920a6dc65744` | -| CETUS/USD | `e5b274b2611143df055d6e7cd8d93fe1961716bcd4dca1cad87a83bc1e78c1ef` | -| BNB/USD | `2f95862b045670cd22bee3114c39763a4a08beeb663b145d283c31d7d1101c4f` | -| WBTC/USD | `c9d8b075a5c69303365ae23633d4e085199bf5c520a3b90fed1322a0342ffc33` | -| THL/USD | `74e3fbb0d33e0ed8c0078b56134dcebdae38852f0858a8ea4de4c5ea7474bd42` | -| USDY/USD | `e393449f6aff8a4b6d3e1165a7c9ebec103685f3b41e60db4277b5b6d10e7326` | -| WETH/USD | `9d4294bbcd1174d6f2003ec365831e64cc31d9f6f15a2b85399db8d5000960f6` | -| THAPT/USD | `b29276972267db5d64ae718fb7f107ad9e72a79cabf9992f0e9bc75ad451a7f6` | -| EZETH/USD | `06c217a791f5c4f988b36629af4cb88fad827b2485400a358f3b02886b54de92` | -| WEETH/USD | `9ee4e7c60b940440a261eb54b6d8149c23b580ed7da3139f7f08f4ea29dad395` | -| USDM/USD | `a6a0dfa49b6b3a93510658245618099f5e842514970f596cf64fad9e0d658193` | -| STONE/USD | `4dcc2fb96fb89a802ef9712f6bd2246d3607cf95ca5540cb24490d37003f8c46` | - -For more details on sponsored feeds, check [here](https://docs.pyth.network/price-feeds/sponsored-feeds) - - -### API Reference - -The [Aptos API reference](https://docs.pyth.network/price-feeds/api-reference/aptos) lets you interactively explore the complete API of the Pyth contract. - -### Example Applications - -- [Minimal on-chain contract](https://github.com/pyth-network/pyth-examples/blob/main/price_feeds/aptos/fetch_btc_price/sources/example.move), which updates and returns the BTC/USD price from Pyth price feeds. -- [Mint NFT](https://github.com/pyth-network/pyth-examples/tree/main/price_feeds/aptos/mint_nft) that use Pyth price feeds to mint an NFT. diff --git a/apps/nextra/pages/zh/build/guides/sponsored-transactions.mdx b/apps/nextra/pages/zh/build/guides/sponsored-transactions.mdx deleted file mode 100644 index 6261c6d15..000000000 --- a/apps/nextra/pages/zh/build/guides/sponsored-transactions.mdx +++ /dev/null @@ -1,113 +0,0 @@ -# Sponsored Transactions - -As outlined -in [AIP-39](https://github.com/aptos-foundation/AIPs/blob/main/aips/aip-39.md), -sponsored transactions allow one account to pay the fees associated with -executing a transaction for another account, essentially setting up a fee payer. -Sponsored transactions simplify the process for onboarding users into applications -by allowing the application to cover all associated fees for interacting with the -Aptos blockchain. Here are two examples: - -- [MerkleTrade](https://merkle.trade/) offers low cost trading to those with - Ethereum wallets by creating an Aptos wallet for users and covering all - transaction fees so that the user does not need to acquire utility tokens for - Aptos. -- Community engagement applications - like [Graffio](https://medium.com/aptoslabs/graffio-web3s-overnight-sensation-81a6cf18b626) - offered to cover transaction fees for custodial accounts to support the - collaborative drawing application for those without wallets. - -## Process Overview - -The process for sending a sponsored transaction follows: - -- The sender of the transaction determines upon an operation, as defined by - a `RawTransaction`. -- The sender generates a `RawTransactionWithData::MultiAgentWithFeePayer` - structure - - Prior to the framework 1.8 release, this must contain the fee payer's - address. - - After framework release 1.8, this can optionally be set to `0x0`. -- (Optionally) the sender aggregates signatures from other signers. -- The sender can forward the signed transaction to the fee payer to sign and - forward it to the blockchain. -- Upon execution of the transaction, the sequence number of the sender account - is incremented, all gas fees are deducted from the gas fee payer, and all - refunds are sent to the gas fee payer. - -Alternatively, if the fee payer knows the operation and all signers involved, -the fee payer could generate and sign the transaction and send it back to the -other signers to sign. - -## Technical Details - -In Aptos, a sponsored transaction reuses the same SignedTransaction as any other -user transaction: - -```rust -pub struct SignedTransaction { - /// The raw transaction - raw_txn: RawTransaction, - - /// Public key and signature to authenticate - authenticator: TransactionAuthenticator, -} -``` - -The difference is in the `TransactionAuthenticator`, which stores the -authorization from the fee payer of the transaction to extract utility fees from -their account: - -```rust -pub enum TransactionAuthenticator { -... - /// Optional Multi-agent transaction with a fee payer. - FeePayer { - sender: AccountAuthenticator, - secondary_signer_addresses: Vec, - secondary_signers: Vec, - fee_payer_address: AccountAddress, - fee_payer_signer: AccountAuthenticator, - }, -... -} -``` - -To prepare a sponsored transaction for an account, the account must first exist -on-chain. This is a requirement that is being removed with the 1.8 framework -release. - -As of the 1.8 framework release, an account does not need to exist on-chain. -However, the first transaction for an account requires enough gas to not only -execute the transaction and cover the costs associated with account creation, -even if an account already exists. Future improvements to the account model -intend to eliminate this requirement. - -During signing of the transaction, all parties sign the following: - -```rust -pub enum RawTransactionWithData { -... - MultiAgentWithFeePayer { - raw_txn: RawTransaction, - secondary_signer_addresses: Vec, - fee_payer_address: AccountAddress, - }, -} -``` - -Prior to framework release 1.8, all signers were required to know the actual fee -payer address prior to signing. As of framework release 1.8, signers can -optionally set the address to `0x0` and only the fee payer must sign with their -address set. - -## SDK Support - -These are demonstrations of sponsored transactions: - -- The TypeScript SDK - has [several examples](https://github.com/aptos-labs/aptos-ts-sdk/tree/main/examples/typescript-esm/sponsored_transactions) -- The Python SDK has an example - in [fee_payer_transfer_coin.py](https://github.com/aptos-labs/aptos-python-sdk/blob/main/examples/fee_payer_transfer_coin.py). -- The Rust SDK has a test case - in [the API tests](https://github.com/aptos-labs/aptos-core/blob/0a62e54e13bc5da604ceaf39efed5c012a292078/api/src/tests/transactions_test.rs#L255). diff --git a/apps/nextra/pages/zh/build/guides/system-integrators-guide.mdx b/apps/nextra/pages/zh/build/guides/system-integrators-guide.mdx deleted file mode 100644 index 905d4e5c8..000000000 --- a/apps/nextra/pages/zh/build/guides/system-integrators-guide.mdx +++ /dev/null @@ -1,627 +0,0 @@ ---- -title: "Application Integration Guide" ---- - -import { Callout } from "nextra/components"; - -# Application Integration Guide - - - This guide is currently in progress of being replaced. Please check out the [exchange integration guide](./exchanges.mdx) - for more up-to-date information. - - -If you provide blockchain services to your customers and wish to add the Aptos -blockchain to your platform, then this guide is for you. This system integrators -guide will walk you through all you need to integrate the Aptos blockchain into -your platform. - -## Overview - -This document will guide you through the following tasks to integrate with -Aptos: - -1. Prepare an environment for testing. -2. Create an account on the blockchain. -3. Exchange account identifiers with another entity on the blockchain, for -example, to perform swaps. -4. Create a transaction. -5. Obtain a gas estimate and validate the transaction for correctness. -6. Submit the transaction to the blockchain. -7. Wait for the outcome of the transaction. -8. Query historical transactions and interactions for a given account with a -specific account, i.e., withdraws and deposits. - -## Getting Started - -In order to get started you'll need to select a network and pick your set of -tools. There are also a handful of SDKs to help accelerate development. - -### SDKs and tools - -Aptos has multiple SDKs across many different languages and platforms, please -check out [SDKs](../sdks.mdx) for more information. - -Almost all developers will benefit from exploring the CLI. -[Using the CLI](../cli.mdx) demonstrates how the CLI can be used to create -accounts, transfer coins, publish Move modules, and more. - -## Accounts on Aptos - -An [account](../../network/blockchain/accounts.mdx) represents -an entity on the Aptos blockchain that can send transactions. Each account is -identified by a particular 32-byte account address and is a container for -[Move modules and resources](../../network/blockchain/resources.mdx). -On Aptos, accounts must be created on-chain prior to any blockchain operations -involving that account. The Aptos framework supports implicitly creating -accounts when transferring Aptos coin via [`aptos_account::transfer`](https://github.com/aptos-labs/aptos-core/blob/88c9aab3982c246f8aa75eb2caf8c8ab1dcab491/aptos-move/framework/aptos-framework/sources/aptos_account.move#L18) -or explicitly via [`aptos_account::create_account`](https://github.com/aptos-labs/aptos-core/blob/88c9aab3982c246f8aa75eb2caf8c8ab1dcab491/aptos-move/framework/aptos-framework/sources/aptos_account.move#L13). - -At creation, an [Aptos account](https://github.com/aptos-labs/aptos-core/blob/88c9aab3982c246f8aa75eb2caf8c8ab1dcab491/aptos-move/framework/aptos-framework/sources/account.move#L23) contains: - -- A [resource containing Aptos Coin](https://github.com/aptos-labs/aptos-core/blob/60751b5ed44984178c7163933da3d1b18ad80388/aptos-move/framework/aptos-framework/sources/coin.move#L50) -and deposit and withdrawal of coins from that resource. -- An authentication key associated with their current public, private key(s). -- A strictly -increasing [sequence number](../../network/blockchain/accounts.mdx) -that represents the account's next transaction's sequence number to prevent -replay attacks. -- A strictly increasing number that represents the next distinct GUID creation -number. -- An [event handle](../../network/blockchain/events.mdx) for all new types of coins added to -the account. -- An event handle for all key rotations for the account. - -Read more about [Accounts](../../network/blockchain/accounts.mdx) -and [set one up](../cli/setup-cli.mdx). - -## Transactions - -Aptos [transactions](../../network/blockchain/txns-states.mdx) are encoded -in [Binary Canonical Serialization (BCS)](https://github.com/diem/bcs). -Transactions contain information such as the sender’s account address, -authentication from the sender, the desired operation to be performed on the -Aptos blockchain, and the amount of gas the sender is willing to pay to execute -the transaction. - -Read more in [Transactions and States](../../network/blockchain/txns-states.mdx). - -### Generating transactions - -Aptos supports two methods for constructing transactions: - -- Using the Aptos client libraries to generate native BCS transactions. -- Constructing JSON-encoded objects and interacting with the REST API to -generate native transactions. - -The preferred approach is to directly generate native BCS transactions. -Generating them via the REST API enables rapid development at the cost of -trusting the fullnode to generate the transaction correctly. - -#### BCS-encoded transactions - -BCS-encoded transactions can be submitted to the `/transactions` endpoint but -must specify `Content-Type: application/x.aptos.signed_transaction+bcs` in the -HTTP headers. This will return a transaction submission result that, if -successful, contains a transaction hash in -the `hash` [field](https://github.com/aptos-labs/aptos-core/blob/9b85d41ed8ef4a61a9cd64f9de511654fcc02024/ecosystem/python/sdk/aptos_sdk/client.py#L138). - -### Types of transactions - -Within a given transaction, the target of execution can be one of two types: - -- An entry function -- A Move script - -All official SDKs support the generation of transactions that target entry functions. This guide -points out many of those entry functions, such as `aptos_account::transfer` -and `aptos_account::create_account`. - -Most basic operations on the Aptos blockchain should be available via entry -point calls. While one could submit multiple transactions calling entry points -in series, such operations benefit from being called atomically from a single -transaction. A script payload transaction can call any public (entry) function -defined within any module. Here's an -example [Move script](https://github.com/aptos-labs/aptos-core/tree/main/aptos-move/move-examples/scripts/two_by_two_transfer) -that uses a MultiAgent transaction to extract funds from two accounts and -deposit them into two other accounts. This is -a [Python example](https://github.com/aptos-labs/aptos-python-sdk/blob/main/examples/transfer_two_by_two.py) -that uses the bytecode generated by compiling that script. - -### Status of a transaction - -Obtain transaction status by querying the -API [`/transactions/by_hash/{hash}`](https://api.devnet.aptoslabs.com/v1/spec#/operations/get_transaction_by_hash) -with the hash returned during the submission of the transaction. - -A reasonable strategy for submitting transactions is to limit their lifetime to -30 to 60 seconds, and polling that API at regular intervals until success or -several seconds after that time has elapsed. If there is no commitment on-chain, -the transaction was likely discarded. - -All SDKs support this automatically for waiting for transactions. - -### Testing transactions or transaction pre-execution - -To facilitate evaluation of transactions as well as gas estimation, Aptos -supports a simulation API that does not require and should not contain valid -signatures on transactions. - -The simulation API is a synchronous API that executes a transaction and returns -the output inclusive of gas usage. The simulation API can be accessed by -submitting a transaction -to [`/transactions/simulate`](https://api.devnet.aptoslabs.com/v1/spec#/operations/simulate_transaction). - -Both -the [Typescript SDK](https://github.com/aptos-labs/aptos-ts-sdk/blob/main/src/api/transactionSubmission/simulate.ts) -and [Python SDK](https://github.com/aptos-labs/aptos-python-sdk/blob/main/examples/simulate_transfer_coin.py) -support the simulation API. Note the output and gas used may change based upon -the state of the account. For gas estimations, we recommend that the maximum gas -amount be larger than the amount quoted by this API. - -## Viewing current and historical state - -Most integrations into the Aptos blockchain benefit from a holistic and -comprehensive overview of the current and historical state of the blockchain. -Aptos provides historical transactions, state, and events, all the result of -transaction execution. - -- Historical transactions specify the execution status, output, and tie to -related events. Each transaction has a unique version number associated with -it that dictates its global sequential ordering in the history of the -blockchain ledger. -- The state is the representation of all transaction outputs up to a specific -version. In other words, a state version is the accumulation of all -transactions inclusive of that transaction version. -- As transactions execute, they may emit events. [Events](../../network/blockchain/events.mdx) -are hints about changes in on-chain data. - -The storage service on a node employs two forms of pruning that erase data from -nodes: - -- state -- events, transactions, and everything else - -While either of these may be disabled, storing the state versions is not -particularly sustainable. - -Events and transactions pruning can be disabled via setting -the [`enable_ledger_pruner`](https://github.com/aptos-labs/aptos-core/blob/cf0bc2e4031a843cdc0c04e70b3f7cd92666afcf/config/src/config/storage_config.rs#L141) -to `false`. This is default behavior in Mainnet. In the near future, Aptos will -provide indexers that mitigate the need to directly query from a node. - -The REST API offers querying transactions and events in these ways: - -- [Transactions for an account](https://api.devnet.aptoslabs.com/v1/spec#/operations/get_account_transactions) -- [Transaction by version](https://api.devnet.aptoslabs.com/v1/spec#/operations/get_transaction_by_version) -- [Events by event handle](https://api.devnet.aptoslabs.com/v1/spec#/operations/get_events_by_event_handle) - -## Exchanging and tracking fungible assets - -Aptos has a -standard [Fungible Asset](https://github.com/aptos-labs/aptos-core/blob/main/aptos-move/framework/aptos-framework/sources/fungible_asset.move). -Different types of fungible asset (FA) can be represented in this standard -through the use of distinct metadata object. - -A user's FA is stored in `FungibleStore` objects owned by them. For each type of -FA, every account has one primary store for that FA and -optional multiple secondary stores. The difference between primary and secondary -stores is the address of primary store -is deterministic based on the addresses of user account and metadata object. - -### Transferring FAs between users - -FAs, including APT, can be transferred between users' primary stores via -the [`primary_fungible_store::transfer`](https://github.com/aptos-labs/aptos-core/blob/main/aptos-move/framework/aptos-framework/sources/primary_fungible_store.move#L142) -function. -For any `FungibleStore` -s, [`fungible_asset::transfer`](https://github.com/aptos-labs/aptos-core/blob/main/aptos-move/framework/aptos-framework/sources/fungible_asset.move#L347) -would be invoked with `FungibleStore` object addresses. - -### Current balance for Fungible Asset - -The current balance for an APT FA of FungibleStore is available at the account resources URL: `https://{rest_api_server}/accounts/{fungible_store_object_address}/resource/0x1::fungible_asset::FungibleStore`. The balance is stored as `balance`. The resource also contains a metadata object of the FA type and the frozen status. The address of the primary fungible store can be calculated as `sha3_256(32-byte account address | 32-byte metadata object address | 0xFC)`. The metadata object address of APT FA is `0xA`. - -Aptos users have the option to upgrade to concurrent fungible balance to allow parallelization of balance updates, improving the performance of a single account. When a user has upgraded a fungible store balance to support concurrent update, the fungible store object will have another resource `ConcurrentFungibleBalance` that contains the balance of the store, and the `balance` field of FungibleStore will be set to 0. The current balance for an APT FA of `ConcurrentFungibleBalance` (if exists) is available at the account resources URL: `https://{rest_api_server}/accounts/{fungible_store_object_address}/resource/0x1::fungible_asset::ConcurrentFungibleBalance`. - -Therefore, to get the total balance of a fungible asset, it is either the non-zero balance of `FungibleStore` or the `balance` field of `ConcurrentFungibleBalance` if it exists and the balance of `FungibleStore` is 0. - -```json -{ - "type": "0x1::fungible_asset::FungibleStore", - "data": { - "balance": "233910778869", - "frozen": false, - "metadata": { - "inner": "0xedc2704f2cef417a06d1756a04a16a9fa6faaed13af469be9cdfcac5a21a8e2e" - } - } -} -``` - -```json -{ - "type": "0x1::fungible_asset::ConcurrentFungibleBalance", - "data": { - "balance": "233910778869" - } -} -``` - -## Exchanging and tracking coins - -Aptos has a standard -[Coin type](https://github.com/aptos-labs/aptos-core/blob/main/aptos-move/framework/aptos-framework/sources/coin.move). -Different types of coins can be represented in this type through the use of -distinct structs that represent the type parameter or generic for `Coin`. - -Coins are stored within an account under the resource `CoinStore`. At account -creation, each user has the resource `CoinStore<0x1::aptos_coin::AptosCoin>` -or `CoinStore`, for short. Within this resource is the Aptos -coin: `Coin`. - -### Transferring coins between users - -Coins, including APT, can be transferred between users via -the [`aptos_account::transfer_coins`](https://github.com/aptos-labs/aptos-core/blob/d1610e1bb5214689a37a9cab59cf9254e8eb2be1/aptos-move/framework/aptos-framework/sources/aptos_account.move#L92) -function for all coins -and [`aptos_account::transfer`](https://github.com/aptos-labs/aptos-core/blob/88c9aab3982c246f8aa75eb2caf8c8ab1dcab491/aptos-move/framework/aptos-framework/sources/aptos_account.move#L18) -for Aptos coins. - - - It is important to note that if an account has not registered a - `CoinStore` - for a given `T`, then any transfer of type `T` to that account will fail. - - -### Current balance for a coin - -To retrieve the balance of a coin, or a coin that was migrated to a fungible asset, you can use -the `0x1::coin::balance(account address)` view function. This will combine the coin and coin migrated to fungible asset balances. - -```ts filename="example.ts" -import { Aptos, AptosConfig, Network } from "@aptos-labs/ts-sdk"; - -const config = new AptosConfig({ network: Network.DEVNET }); -const aptos = new Aptos(config); - -const coinType = "0x1::aptos_coin::AptosCoin"; -const account = "0x00000000000000000000000000000001"; -const [balanceStr] = await aptos.view<[string]>({ - payload: { - function: "0x1::coin::balance", - typeArguments: [coinType], - functionArguments: [account] - } -}); -const balance = parseInt(balanceStr, 10); -``` - -### Querying transactions - -In Aptos, each transaction is committed as a distinct version to the -blockchain. This allows for the convenience of sharing committed transactions -by their version number; to do so, query: -`https://{rest_server_api}/transactions/by_version/{version}` - -Transactions submitted by an account can also be queried via the following URL -where the `sequence_number` matches the sequence number of the transaction: -`https://{rest_server_api}/account/{address}/transactions?start={sequence_number}&limit=1` - -A transfer transaction would appear as follows: - - ```json - { - "version": "13629679", - "gas_used": "4", - "success": true, - "vm_status": "Executed successfully", - "changes": [ - { - "address": "0xb258b91eee04111039320a85b0c24a2dd433909e14a6b5c32ee722e0fdecfddc", - "data": { - "type": "0x1::coin::CoinStore<0x1::aptos_coin::AptosCoin>", - "data": { - "coin": { - "value": "1000" - }, - "deposit_events": { - "counter": "1", - "guid": { - "id": { - "addr": "0x5098df8e7969b58ab3bd2d440c6203f64c60a1fd5c08b9d4abe6ae4216246c3e", - "creation_num": "2" - } - } - }, - ... - } - }, - "type": "write_resource" - }, - ... - ], - "sender": "0x810026ca8291dd88b5b30a1d3ca2edd683d33d06c4a7f7c451d96f6d47bc5e8b", - "sequence_number": "0", - "max_gas_amount": "2000", - "gas_unit_price": "1", - "expiration_timestamp_secs": "1660616127", - "payload": { - "function": "0x1::aptos_account::transfer", - "arguments": [ - "0x5098df8e7969b58ab3bd2d440c6203f64c60a1fd5c08b9d4abe6ae4216246c3e", - "1000" - ], - "type": "entry_function_payload" - }, - "events": [ - { - "key": "0x0300000000000000810026ca8291dd88b5b30a1d3ca2edd683d33d06c4a7f7c451d96f6d47bc5e8b", - "guid": { - "id": { - "addr": "0x810026ca8291dd88b5b30a1d3ca2edd683d33d06c4a7f7c451d96f6d47bc5e8b", - "creation_num": "3" - } - }, - "sequence_number": "0", - "type": "0x1::coin::WithdrawEvent", - "data": { - "amount": "1000" - } - }, - { - "key": "0x02000000000000005098df8e7969b58ab3bd2d440c6203f64c60a1fd5c08b9d4abe6ae4216246c3e", - "guid": { - "id": { - "addr": "0x5098df8e7969b58ab3bd2d440c6203f64c60a1fd5c08b9d4abe6ae4216246c3e", - "creation_num": "2" - } - }, - "sequence_number": "0", - "type": "0x1::coin::DepositEvent", - "data": { - "amount": "1000" - } - } - ], - "timestamp": "1660615531147935", - "type": "user_transaction" - } - ``` - -Here is a breakdown of the information in a transaction: - - - `version` indicates the globally unique identifier for this transaction, its -ordered position in all the committed transactions on the blockchain - - `sender` is the account address of the entity that submitted the transaction - - `gas_used` is the units paid for executing the transaction - - `success` and `vm_status` indicate whether the transaction successfully -executed and any reasons why it might not have - - `changes` include the final values for any state resources that have been -modified during the execution of the transaction - - `events` contain all the events emitted during the transaction execution - - `timestamp` is the near real-time timestamp of the transaction's execution - -If `success` is false, then `vm_status` will contain an error code or message -that resulted in the transaction failing to succeed. When `success` is -false, `changes` will be limited to gas deducted from the account and the -sequence number incrementing. There will be no `events`. - -Each event in `events` is differentiated by a `key`. The `key` is derived from -the `guid` in `changes`. Specifically, the `key` is a 40-byte hex string where -the first eight bytes (or 16 characters) are the little-endian representation -of the `creation_num` in the `guid` of the `changes` event, and the remaining -characters are the account address. - -As events do not dictate what emitted them, it is imperative to track the path -in `changes` to determine the source of an event. In particular, -each `CoinStore` has both a `WithdrawEvent` and a `DepositEvent`, based -upon the type of coin. In order to determine which coin type is used in a -transaction, an indexer can compare the `guid::creation_num` in a `changes` -event combined with the address to the `key` for events in `events`. - -Using the above example, `events[1].guid` is equivalent -to `changes[0].data.data.deposit_events.guid`, which -is - ```json - {"addr": "0x5098df8e7969b58ab3bd2d440c6203f64c60a1fd5c08b9d4abe6ae4216246c3e", "creation_num": "2"} - ``` - - - The `key` field will be going away in favor of `guid` - - -### Querying events - -Aptos provides clear and canonical events for all withdraw and deposit of -coins. This can be used in coordination with the associated transactions to -present to a user the change of their account balance over time, when that -happened, and what caused it. With some amount of additional parsing, metadata -such as the transaction type and the other parties involved can also be shared. - -Query events by handle -URL: `https://{rest_api_server}/accounts/{address}/events/0x1::coin::CoinStore<0x1::aptos_coin::AptosCoin>/withdraw_events` - ```json - [ - { - "version":"13629679", - "key": "0x0300000000000000cb2f940705c44ba110cd3b4f6540c96f2634938bd5f2aabd6946abf12ed88457", - "guid": { - "id": { - "addr": "0x810026ca8291dd88b5b30a1d3ca2edd683d33d06c4a7f7c451d96f6d47bc5e8b", - "creation_num": "3" - } - }, - "sequence_number": "0", - "type": "0x1::coin::WithdrawEvent", - "data": { - "amount": "1000" - } - } - ] - ``` - -Gather more information from the transaction that generated the event by -querying `https://{rest_server_api}/transactions/by_version/{version}` -where `{version}` is the same value as the `{version}` in the event query. - - - When tracking full movement of coins, normally events are - sufficient. `0x1::aptos_coin::AptosCoin`, however, requires - considering `gas_used` for each transaction sent from the given account - since it represents gas in Aptos. To reduce unnecessary overhead, extracting - gas fees due to transactions does not emit an event. All transactions for an - account can be retrieved from this API: - `https://{rest_server_api}/accounts/{address}/transactions` - - -### Tracking coin balance changes - -Consider the transaction from the earlier section, but now with an arbitrary -coin `0x1337::my_coin::MyCoin` and some gas parameters changed: - - ```json - { - "version": "13629679", - "gas_used": "20", - "success": true, - "vm_status": "Executed successfully", - "changes": [ - { - "address": "0xb258b91eee04111039320a85b0c24a2dd433909e14a6b5c32ee722e0fdecfddc", - "data": { - "type": "0x1::coin::CoinStore<0x1337::my_coin::MyCoin>", - "data": { - "coin": { - "value": "1000" - }, - "deposit_events": { - "counter": "1", - "guid": { - "id": { - "addr": "0x5098df8e7969b58ab3bd2d440c6203f64c60a1fd5c08b9d4abe6ae4216246c3e", - "creation_num": "2" - } - } - }, - ... - } - }, - "type": "write_resource" - }, - ... - ], - "sender": "0x810026ca8291dd88b5b30a1d3ca2edd683d33d06c4a7f7c451d96f6d47bc5e8b", - "sequence_number": "0", - "max_gas_amount": "2000", - "gas_unit_price": "110", - "expiration_timestamp_secs": "1660616127", - "payload": { - "function": "0x1::aptos_account::transfer_coins", - "type_arguments": [ - "0x1337::my_coin::MyCoin" - ], - "arguments": [ - "0x5098df8e7969b58ab3bd2d440c6203f64c60a1fd5c08b9d4abe6ae4216246c3e", - "1000" - ], - "type": "entry_function_payload" - }, - "events": [ - { - "key": "0x0300000000000000810026ca8291dd88b5b30a1d3ca2edd683d33d06c4a7f7c451d96f6d47bc5e8b", - "guid": { - "id": { - "addr": "0x810026ca8291dd88b5b30a1d3ca2edd683d33d06c4a7f7c451d96f6d47bc5e8b", - "creation_num": "3" - } - }, - "sequence_number": "0", - "type": "0x1::coin::WithdrawEvent", - "data": { - "amount": "1000" - } - }, - { - "key": "0x02000000000000005098df8e7969b58ab3bd2d440c6203f64c60a1fd5c08b9d4abe6ae4216246c3e", - "guid": { - "id": { - "addr": "0x5098df8e7969b58ab3bd2d440c6203f64c60a1fd5c08b9d4abe6ae4216246c3e", - "creation_num": "2" - } - }, - "sequence_number": "0", - "type": "0x1::coin::DepositEvent", - "data": { - "amount": "1000" - } - } - ], - "timestamp": "1660615531147935", - "type": "user_transaction" - } - ``` - -There are three balance changes in this transaction: - - 1. A withdrawal of `1000` of `0x1337::my_coin::MyCoin` from the transaction -sending account `0x810026ca8291dd88b5b30a1d3ca2edd683d33d06c4a7f7c451d96f6d47bc5e8b` - 2. A deposit of `1000` of `0x1337::my_coin::MyCoin` to receiving account `0x5098df8e7969b58ab3bd2d440c6203f64c60a1fd5c08b9d4abe6ae4216246c3e` - 3. A gas fee `2200` of `0x1::aptos_coin::AptosCoin` from the sending account `0x810026ca8291dd88b5b30a1d3ca2edd683d33d06c4a7f7c451d96f6d47bc5e8b` - -To retrieve the withdrawal information: - - 1. Scan the `changes` for `0x1::coin::CoinStore`. Note the -`CoinType` -is a generic signifying which coin is stored in the store. In this example, -the `CoinType` is `0x1337::my_coin::MyCoin`. - 2. Retrieve the `guid` for `withdraw_events`. In this example, the `guid` -contains `addr` -`0x810026ca8291dd88b5b30a1d3ca2edd683d33d06c4a7f7c451d96f6d47bc5e8b` -and `creation_num` `3`. - 3. Scan for events with this `guid` and extract the event associated with it. -In -this example, it is the `0x1::coin::WithdrawEvent`. - 4. Note the `amount` field will be the number of `CoinType` removed from the -account in the `guid`. In this example, it is `1000`. - -To retrieve the deposit information, it's the same as withdrawal except: - - 1. The `guid` used is under `deposit_events` - 2. The `amount` will be a positive increase on the account's balance. - 3. The event's name will be: `0x1::coin::DepositEvent` - -To retrieve the gas fee: - - 1. The `gas_used` field must be multiplied times the `gas_unit_price`. In this -example, `gas_used=20` and `gas_unit_price=110` so the total gas coins -withdrawn is `2200`. - 2. Gas is always: `0x1::aptos_coin::AptosCoin` - -To retrieve information about the number of decimals of the coin: - - 1. You can retrieve the number of decimals for a coin via -its: `0x1::coin::CoinInfo` - 2. This will be located at the address of the coin type. In this example, you -would need to look up `0x1::coin::CoinInfo<0x1337::my_coin::MyCoin>` at address `0x1337`. - - - If you always use the events in this manner, you won't miss any balance - changes - for an account. - By monitoring the events, you will find all balance changes in - the `0x1::coin::CoinStore`: - - 1. Coin mints - 2. Coin burns - 3. Coin transfers - 4. Staking coins - 5. Withdrawing staked coins - 6. Transfers not derived from `coin::transfer` - - - -To create some sample data to explore, -conduct ["Your first transaction"](../guides/first-transaction.mdx). -To learn more about coin creation, -make ["Your First Coin"](../guides/first-coin.mdx). diff --git a/apps/nextra/pages/zh/build/guides/transaction-management.mdx b/apps/nextra/pages/zh/build/guides/transaction-management.mdx deleted file mode 100644 index d5d4bd8ff..000000000 --- a/apps/nextra/pages/zh/build/guides/transaction-management.mdx +++ /dev/null @@ -1,193 +0,0 @@ -# Transaction Management - -This guide explains how to build a transaction management harness that can scale -on the Aptos blockchain. - -## Background - -In Aptos, transactions are mapped back to an account in terms of the entity that -signs or authorizes that transaction and provides an account-based sequence -number. When the Aptos network receives a new transaction, several rules are -followed with respect to this: - -- The transaction sent from an account must be authorized correctly by that - account. -- The current time as defined by the most recent ledger update must be before - the expiration timestamp of the transaction. -- The transaction's sequence number must be equal to or greater than the - sequence number on-chain for that account. - -Once the initial node has accepted a transaction, the transaction makes its way -through the system by an additional rule. If a transactions sequence number is -higher than the current on-chain sequence number, it can only progress toward -consensus if every node in the path has seen a transaction with the sequence -number between the on-chain state and the current sequence number. - -Example: - -Alice owns an account whose current on-chain sequence number is 5. - -Alice submits a transaction to node Bob with sequence number 6. - -Bob the node accepts the transaction but does not forward it, because Bob has -not seen 5. - -In order to make progress, Alice must either send Bob transaction number 5 or -Bob must be notified from consensus that 5 was committed. In the latter, Alice -submitted the transaction through another node. - -Beyond this there are two remaining principles: - -- A single account can have at most 100 uncommitted transactions submitted to - the blockchain. Any more than that and the transactions will be rejected. This - can happen silently if Alice submits the first 100 to Bob the node and the - next 100 to Carol the node. If both those nodes share a common upstream, then - that upstream will accept Alice's 100 sent via Bob but silently reject Alice's - 100 sent via Carol. -- Submitting to distinct transactions to multiple nodes will result in slow - resolution as transactions will not make progress from the submitted node - until the submitted knows that all preceding transactions have been committed. - For example, if Alice sends the first 50 via Bob and the next 50 via Carol. - -## Building a Transaction Manager - -Now that we understand the nuances of transactions, let's dig into building a -robust transaction manager. This consists of the following core components: - -- A sequence number generator that allocates and manages available sequence - numbers for a single account. -- A transaction manager that receives payloads from an application or a user, - sequence numbers from the sequence number generator, and has access to the - account key to combine the three pieces together into a viable signed - transaction. It then also takes the responsibility for pushing the transaction - to the blockchain. -- An on-chain worker, leader harness that lets multiple accounts share the - signer of a single shared account. - -Currently, this framework assumes that the network builds no substantial queue, -that is a transaction that is submitted executes and commits with little to no -delay. In order to address high demand, this work needs to be extended with the -following components: - -- Optimizing `base_gas_unit` price to ensure priority transactions can be - committed to the blockchain. -- Further handling of transaction processing rates to ensure that the expiration - timer is properly set. -- Handling of transaction failures to either be ignored or resubmitted based - upon desired outcome. - -Note, an account should be managed by a single instance of the transaction -manager. Otherwise, each instance of the transaction manager will likely have -stale in-memory state resulting in overlapping sequence numbers. - -### Implementations - -- Python - - [Sequence number manager](https://github.com/aptos-labs/aptos-core/pull/7987) - - [Transaction manager](https://github.com/aptos-labs/aptos-core/pull/7987) -- [Worker-leader smart contract](https://github.com/aptos-labs/aptos-core/pull/7986) - -### Managing Sequence Numbers - -Each transaction requires a distinct sequence number that is sequential to -previously submitted transactions. This can be provided by the following -process: - -1. At startup, query the blockchain for the account’s current sequence number. -2. Support up to 100 transactions in flight at the same time, that is 100 - sequence numbers can be allocated without confirming that any have been - committed. -3. If there are 100 transactions in flight, determine the actual committed state - by querying the network. This will update the current sequence number. -4. If there are less than 100 transactions in flight, return to step 2. -5. Otherwise, sleep for .1 seconds and continue to re-evaluate the current - on-chain sequence number. -6. All transactions should have an expiration time. If the expiration time has - passed, assume that there has been a failure and reset the sequence number. - The trivial case is to only monitor for failures when the maximum number of - transactions are in flight and to let other services manages this otherwise. - -In parallel, monitor new transactions submitted. Once the earliest transaction -expiration time has expired synchronize up to that transaction. Then repeat the -process for the next transaction. - -If there is any failure, wait until all outstanding transactions have timed out -and leave it to the application to decide how to proceed, e.g., replay failed -transactions. The best method to waiting for outstanding transactions is to -query the ledger timestamp and ensure it is at least elapsed the maximum timeout -from the last transactions submit time. From there, validate with mempool that -all transactions since the last known committed transaction are either committed -or no longer exist within the mempool. This can be done by querying the REST API -for transactions of a specific account, specifying the currently being evaluated -sequence number and setting a limit to 1. Once these checks are complete, the -local transaction number can be resynchronized. - -These failure handling steps are critical for the following reasons: - -- Mempool does not immediate evict expired transactions. -- A new transaction cannot overwrite an existing transaction, even if it is - expired. -- Consensus, i.e., the ledger timestamp, dictates expirations, the local node - will only expire after it sees a committed timestamp after the transactions - expiration time and a garbage collection has happened. - -### Managing Transactions - -Once a transaction has been submitted it goes through a variety of steps: - -1. Submission to a REST endpoint. -2. Pre-execution validation in the Mempool during submission. -3. Transmission from Mempool to Mempool with pre-execution validation happening - on each upstream node. -4. Inclusion in a consensus proposal. -5. One more pre-execution validation. -6. Execution and committing to storage. - -There are many potential failure cases that must be considered: - -- Failure during transaction submission (1 and 2): - - Visibility: The application will receive an error either that the network is - unavailable or that the transaction failed pre-execution validation. - - If the error is related to availability or duplicate sequence numbers, wait - until access is available and the sequence number has re-synchronized. - - Pre-execution validation failures are currently out of scope, outside of - those related to duplicate sequence numbers, account issues are likely - related to an invalid key for the account or the account lacks sufficient - funds for gas. -- Failure between submission and execution (3, 4, and 5): - - Visibility: Only known by waiting until the transaction has expired. - - These are the same as other pre-execution validation errors due to changes - to the account as earlier transactions execute. It is likely either - duplicate sequence numbers or the account lacks sufficient funds for gas. -- Failure during execution (6): - - Visibility: These are committed to the blockchain. - - These errors occur as a result of on-chain state issues, these tend to be - application specific, such as an auction where a new bid might not actually - be higher than the current bid. - -### Workers and Identity - -Using the above framework, a single account can push upwards of 100 transactions -from the start of a block to the end of a block. Assuming that all 100 -transactions are consumed within 1 block, it will take a bit of time for the -next 100 slots to be available. This is due to the network delays as well as the -multi-staged validator pipeline. - -To fully leverage the blockchain for massive throughput, using a single user -account is not enough. Instead, Aptos supports the concept of worker accounts -that can share the responsibility of pushing work through a shared account, also -known as a resource account. - -In this model, each worker has access to the `SignerCap` of the shared account, -which enables them to impersonate the shared account or generate the `signer` -for the shared account. Upon gaining the `signer`, the transaction can execute -the logic that is gated by the signer of the shared account. - -Another model, if viable, is to decouple the `signer` altogether away from -permissions and to make an application specific capability. Then this capability -can be given to each worker that lets them operate on the shared infrastructure. - -Note that parallelization on the shared infrastructure can be limited if any -transaction would have any read or write conflicts. This won’t prevent multiple -transactions from executing within a block, but can impact maximum blockchain -performance. diff --git a/apps/nextra/pages/zh/build/guides/your-first-nft.mdx b/apps/nextra/pages/zh/build/guides/your-first-nft.mdx deleted file mode 100644 index a3ca0e1f8..000000000 --- a/apps/nextra/pages/zh/build/guides/your-first-nft.mdx +++ /dev/null @@ -1,768 +0,0 @@ - -import { Callout, Tabs } from 'nextra/components'; - -# Your First NFT - -This tutorial describes how to create and transfer non-fungible assets on the Aptos blockchain. The Aptos no-code implementation for non-fungible digital assets can be found in the [aptos_token.move](https://github.com/aptos-labs/aptos-core/blob/main/aptos-move/framework/aptos-token-objects/sources/aptos_token.move) Move module. - -## Step 1: Pick an SDK - -Install your preferred SDK from the below list: - -- [TypeScript SDK](../sdks/ts-sdk.mdx) -- [Python SDK](../sdks/python-sdk.mdx) - ---- - -## Step 2: Run the example - - - - -Clone the `aptos-ts-sdk` repo and build it: - -```bash filename="Terminal" -git clone https://github.com/aptos-labs/aptos-ts-sdk.git -cd aptos-ts-sdk -pnpm install -pnpm build -``` - -Navigate to the Typescript ESM examples directory: - -```bash filename="Terminal" -cd examples/typescript-esm -``` - -Install the necessary dependencies and build it: - -```bash filename="Terminal" -pnpm install -pnpm build -``` - -Run the TypeScript [`simple_digital_asset`](https://github.com/aptos-labs/aptos-ts-sdk/blob/main/examples/typescript-esm/simple_digital_asset.ts) example: - -```bash filename="Terminal" -pnpm run simple_digital_asset -``` - - - - -Clone the `aptos-python-sdk` repo: - -```bash filename="Terminal" -git clone https://github.com/aptos-labs/aptos-python-sdk.git -``` - -Install the necessary dependencies: - -```bash filename="Terminal" -curl -sSL https://install.python-poetry.org | python3 -poetry install -``` - -Run the Python [`simple_aptos_token`](https://github.com/aptos-labs/aptos-python-sdk/blob/main/examples/simple_aptos_token.py) example: - -```bash filename="Terminal" -poetry run python -m examples.simple_aptos_token -``` - - - - ---- - -## Step 3: Understand the output - - - - -The following output should appear after executing the `simple_digital_asset` example, though some values will be different: - -```bash filename="Terminal" -=== Addresses === - -Alice's address is: 0x770dbeb6101056eac5a19de9a73ad72fac512e0de909e7bcb13a9d9241d1d162 - -=== Create the collection === - -Alice's collection: { - "collection_id": "0x23ece6c35415f5c5a720dc4de2820cabece0a6f1768095db479f657ad2c05753", - "collection_name": "Example Collection", - "creator_address": "0x770dbeb6101056eac5a19de9a73ad72fac512e0de909e7bcb13a9d9241d1d162", - "current_supply": 0, - "description": "Example description.", - "last_transaction_timestamp": "2023-11-29T21:26:03.204874", - "last_transaction_version": 8001101, - "max_supply": 18446744073709552000, - "mutable_description": true, - "mutable_uri": true, - "table_handle_v1": null, - "token_standard": "v2", - "total_minted_v2": 0, - "uri": "aptos.dev" -} - -=== Alice Mints the digital asset === - -Alice's digital assets balance: 1 -Alice's digital asset: { - "token_standard": "v2", - "token_properties_mutated_v1": null, - "token_data_id": "0x9f4460e29a66b4e41cef1671767dc8a5e8c52a2291e36f84b8596e0d1205fd8c", - "table_type_v1": null, - "storage_id": "0x9f4460e29a66b4e41cef1671767dc8a5e8c52a2291e36f84b8596e0d1205fd8c", - "property_version_v1": 0, - "owner_address": "0x770dbeb6101056eac5a19de9a73ad72fac512e0de909e7bcb13a9d9241d1d162", - "last_transaction_version": 8001117, - "last_transaction_timestamp": "2023-11-29T21:26:04.521624", - "is_soulbound_v2": false, - "is_fungible_v2": false, - "amount": 1, - "current_token_data": { - "collection_id": "0x23ece6c35415f5c5a720dc4de2820cabece0a6f1768095db479f657ad2c05753", - "description": "Example asset description.", - "is_fungible_v2": false, - "largest_property_version_v1": null, - "last_transaction_timestamp": "2023-11-29T21:26:04.521624", - "last_transaction_version": 8001117, - "maximum": null, - "supply": 0, - "token_data_id": "0x9f4460e29a66b4e41cef1671767dc8a5e8c52a2291e36f84b8596e0d1205fd8c", - "token_name": "Example Asset", - "token_properties": {}, - "token_standard": "v2", - "token_uri": "aptos.dev/asset", - "current_collection": { - "collection_id": "0x23ece6c35415f5c5a720dc4de2820cabece0a6f1768095db479f657ad2c05753", - "collection_name": "Example Collection", - "creator_address": "0x770dbeb6101056eac5a19de9a73ad72fac512e0de909e7bcb13a9d9241d1d162", - "current_supply": 1, - "description": "Example description.", - "last_transaction_timestamp": "2023-11-29T21:26:04.521624", - "last_transaction_version": 8001117, - "max_supply": 18446744073709552000, - "mutable_description": true, - "mutable_uri": true, - "table_handle_v1": null, - "token_standard": "v2", - "total_minted_v2": 1, - "uri": "aptos.dev" - } - } -} - -=== Transfer the digital asset to Bob === - -Alice's digital assets balance: 0 -Bob's digital assets balance: 1 -``` - -
-This example demonstrates... -* Initializing the Aptos client. -* The creation of two accounts: Alice and Bob. -* The funding and creation of Alice and Bob's accounts. -* The creation of a collection and a token using Alice's account. -* Alice sending a token to Bob. -
- -
- - -The following output should appear after executing the `simple_aptos_token` example, though some values will be different: - -```bash filename="Terminal" -=== Addresses === -Alice: 0x391f8b07439768674023fb87ae5740e90fb8508600486d8ee9cc411b4365fe89 -Bob: 0xfbca055c91d12989dc6a2c1a5e41ae7ba69a35454b04c69f03094bbccd5210b4 - -=== Initial Coin Balances === -Alice: 100000000 -Bob: 100000000 - -=== Creating Collection and Token === - -Collection data: { - "address": "0x38f5310a8f6f3baef9a54daea8a356d807438d3cfe1880df563fb116731b671c", - "creator": "0x391f8b07439768674023fb87ae5740e90fb8508600486d8ee9cc411b4365fe89", - "name": "Alice's", - "description": "Alice's simple collection", - "uri": "https://aptos.dev" -} - -Token owner: Alice -Token data: { - "address": "0x57710a3887eaa7062f96967ebf966a83818017b8f3a8a613a09894d8465e7624", - "owner": "0x391f8b07439768674023fb87ae5740e90fb8508600486d8ee9cc411b4365fe89", - "collection": "0x38f5310a8f6f3baef9a54daea8a356d807438d3cfe1880df563fb116731b671c", - "description": "Alice's simple token", - "name": "Alice's first token", - "uri": "https://aptos.dev/img/nyan.jpeg", - "index": "1" -} - -=== Transferring the token to Bob === -Token owner: Bob - -=== Transferring the token back to Alice === -Token owner: Alice -``` - -
-This example demonstrates... -* Initializing the REST and faucet clients. -* The creation of two accounts: Alice and Bob. -* The funding and creation of Alice and Bob's accounts. -* The creation of a collection and a token using Alice's account. -* Alice sending a token to Bob. -* Bob sending the token back to Alice. -
- -
-
- ---- - -## Step 4: The SDK in depth - - - - - See [`simple_digital_asset`](https://github.com/aptos-labs/aptos-ts-sdk/blob/main/examples/typescript-esm/simple_digital_asset.ts) for the complete code as you follow the below steps. - - - - - See [`simple_aptos_token`](https://github.com/aptos-labs/aptos-python-sdk/blob/main/examples/simple_aptos_token.py) for the complete code as you follow the below steps. - - - - ---- - -### Step 4.1: Initializing the clients - - - - - -In the first step, the `simple_digital_asset` example initializes the Aptos client: - -```ts filename="example.ts" -const APTOS_NETWORK: Network = - NetworkToNetworkName[process.env.APTOS_NETWORK] || Network.DEVNET; -const config = new AptosConfig({ network: APTOS_NETWORK }); -const aptos = new Aptos(config); -``` - - -By default, the Aptos client points to Aptos devnet services. However, it can be configured with the `network` input argument - - - - - -In the first step, the example initializes both the API and faucet clients. - -- The API client interacts with the REST API. -- The faucet client interacts with the devnet Faucet service for creating and funding accounts. - -```python filename="example.py" -rest_client = RestClient(NODE_URL) -faucet_client = FaucetClient(FAUCET_URL, rest_client) -``` - -Using the API client we can create a `TokenClient` that we use for common token operations such as creating collections and tokens, transferring them, claiming them, and so on. - -```python filename="example.py" -token_client = AptosTokenClient(rest_client) -``` - -[`common.py`](https://github.com/aptos-labs/aptos-python-sdk/blob/main/examples/common.py) initializes these values as follows: - -```python filename="example.py" -NODE_URL = os.getenv("APTOS_NODE_URL", "https://api.devnet.aptoslabs.com/v1") -FAUCET_URL = os.getenv( - "APTOS_FAUCET_URL", - "https://faucet.devnet.aptoslabs.com", -) -``` - - - -By default, the URLs for both the services point to Aptos devnet services. However, they can be configured with the following environment variables: - -- `APTOS_NODE_URL` -- `APTOS_FAUCET_URL` - - - - ---- - -### Step 4.2: Creating local accounts - -The next step is to create two accounts locally. [Accounts](../../network/blockchain/accounts.mdx) consist of a public address and the public/private key pair used to authenticate ownership of the account. This step demonstrates how to generate an Account and store its key pair and address in a variable. - - - - -```ts filename="example.ts" -const alice = Account.generate(); -const bob = Account.generate(); -``` - - - - -```python filename="example.py" -alice = Account.generate() -bob = Account.generate() -``` - - - - - -Note that this only generates the local keypair. After generating the keypair and public address, the account still does not exist on-chain. - - ---- - -### Step 4.3: Creating blockchain accounts - -In order to actually instantiate the Account on-chain, it must be explicitly created somehow. On devnet, you can request free coins with the Faucet API to use for testing purposes. This example leverages the faucet to fund and inadvertently create Alice and Bob's accounts: - - - - -```ts filename="example.ts" -await aptos.fundAccount({ - accountAddress: alice.accountAddress, - amount: 100_000_000, -}); -await aptos.faucet.fundAccount({ - accountAddress: bob.accountAddress, - amount: 100_000_000, -}); -``` - - - - -```python filename="example.py" -bob_fund = faucet_client.fund_account(alice.address(), 100_000_000) -alice_fund = faucet_client.fund_account(bob.address(), 100_000_000) -``` - - - - ---- - -### Step 4.4: Creating a collection - -Now begins the process of creating the digital, non-fungible assets. First, as the creator, you must create a collection that groups the assets. A collection can contain zero, one, or many distinct fungible or non-fungible assets within it. The collection is simply a container, intended only to group assets for a creator. - - - - -Your application will call `createCollectionTransaction` and then `signAndSubmitTransaction` to chain: - -```ts filename="example.ts" -const createCollectionTransaction = await aptos.createCollectionTransaction({ - creator: alice, - description: collectionDescription, - name: collectionName, - uri: collectionURI, -}); - -const committedTxn = await aptos.signAndSubmitTransaction({ - signer: alice, - transaction: createCollectionTransaction, -}); -``` - -This is the function signature of `createCollectionTransaction`. It returns a `SingleSignerTransaction` that can be simulated or submitted to chain: - -```ts filename="example.ts" -export async function createCollectionTransaction( - args: { - creator: Account; - description: string; - name: string; - uri: string; - options?: InputGenerateTransactionOptions; - } & CreateCollectionOptions, -): Promise; -``` - - - - -Your application will call `create_collection`: - -```python filename="example.py" -txn_hash = await token_client.create_collection( - alice, - "Alice's simple collection", - 1, - collection_name, - "https://aptos.dev", - True, - True, - True, - True, - True, - True, - True, - True, - True, - 0, - 1, -) -``` - -This is the function signature of `create_collection`. It returns a transaction hash: - -```python filename="example.py" -async def create_collection( - self, - creator: Account, - description: str, - max_supply: int, - name: str, - uri: str, - 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: int, - royalty_denominator: int, -) -> str: -``` - - - - ---- - -### Step 4.5: Creating a token - -To create a token, the creator must specify an associated collection. A token must be associated with a collection, and that collection must have remaining tokens that can be minted. There are many attributes associated with a token, but the helper API exposes only the minimal amount required to create static content. - - - - -Your application will call `mintTokenTransaction`: - -```ts filename="example.ts" -const mintTokenTransaction = await aptos.mintTokenTransaction({ - creator: alice, - collection: collectionName, - description: tokenDescription, - name: tokenName, - uri: tokenURI, -}); - -const committedTxn = await aptos.signAndSubmitTransaction({ - signer: alice, - transaction: mintTokenTransaction, -}); -``` - -This is the function signature of `mintTokenTransaction`. It returns a `SingleSignerTransaction` that can be simulated or submitted to chain: - -```ts filename="example.ts" -async mintTokenTransaction(args: { - creator: Account; - collection: string; - description: string; - name: string; - uri: string; - options?: InputGenerateTransactionOptions; - }): Promise -``` - - - - -Your application will call `mint_token`: - {/* TODO: NEed to ensure image is still hosted */} -```python filename="example.py" -txn_hash = await token_client.mint_token( - alice, - collection_name, - "Alice's simple token", - token_name, - "https://aptos.dev/img/nyan.jpeg", - PropertyMap([]), -) -``` - -This is the function signature of `mint_token`. It returns a transaction hash: - -```python filename="example.py" -async def mint_token( - self, - creator: Account, - collection: str, - description: str, - name: str, - uri: str, - properties: PropertyMap, -) -> str: -``` - - - - ---- - -### Step 4.6: Reading token and collection metadata - -Both the collection and token assets are [Objects](../smart-contracts/objects.mdx) on-chain with unique addresses. Their metadata is stored at the object address. The SDKs provide convenience wrappers around querying this data: - - - - -To read a collection's metadata: - -```ts filename="example.ts" -const alicesCollection = await aptos.getCollectionData({ - creatorAddress: alice.accountAddress, - collectionName, - minimumLedgerVersion: BigInt(pendingTxn.version), -}); -console.log(`Alice's collection: ${JSON.stringify(alicesCollection, null, 4)}`); -``` - -To read an owned token's metadata: - -```ts filename="example.ts" -const alicesDigitalAsset = await aptos.getOwnedDigitalAssets({ - ownerAddress: alice.accountAddress, - minimumLedgerVersion: BigInt(pendingTxn.version), -}); - -console.log( - `Alice's digital asset: ${JSON.stringify(alicesDigitalAsset[0], null, 4)}`, -); -``` - - - - -To read a collection's metadata: - -```python filename="example.py" -async def get_collection_data( - token_client: AptosTokenClient, collection_addr: AccountAddress -) -> dict[str, str]: - collection = (await token_client.read_object(collection_addr)).resources[Collection] - return { - "creator": str(collection.creator), - "name": str(collection.name), - "description": str(collection.description), - "uri": str(collection.uri), - } -``` - -To read a token's metadata: - -```python filename="example.py" -async def get_token_data( - token_client: AptosTokenClient, token_addr: AccountAddress -) -> dict[str, str]: - token = (await token_client.read_object(token_addr)).resources[Token] - return { - "collection": str(token.collection), - "description": str(token.description), - "name": str(token.name), - "uri": str(token.uri), - "index": str(token.index), - } -``` - - - - ---- - -### Step 4.7: Reading an object's owner - -Each object created from the `aptos_token.move` contract is a distinct asset. The assets owned by a user are stored separately from the user's account. To check if a user owns an object, check the object's owner: - - - - -```ts filename="example.ts" -const alicesDigitalAsset = await aptos.getOwnedDigitalAssets({ - ownerAddress: alice.accountAddress, - minimumLedgerVersion: BigInt(pendingTxn.version), -}); - -console.log( - `Alice's digital asset: ${JSON.stringify(alicesDigitalAsset[0], null, 4)}`, -); -``` - -```ts filename="example.ts" -export async function getOwnedDigitalAssets(args: { - aptosConfig: AptosConfig; - ownerAddress: AccountAddressInput; - options?: PaginationArgs & OrderByArg; -}): Promise { - const { aptosConfig, ownerAddress, options } = args; - - const whereCondition: CurrentTokenOwnershipsV2BoolExp = { - owner_address: { _eq: AccountAddress.from(ownerAddress).toStringLong() }, - amount: { _gt: 0 }, - }; - - const graphqlQuery = { - query: GetCurrentTokenOwnership, - variables: { - where_condition: whereCondition, - offset: options?.offset, - limit: options?.limit, - order_by: options?.orderBy, - }, - }; - - const data = await queryIndexer({ - aptosConfig, - query: graphqlQuery, - originMethod: "getOwnedDigitalAssets", - }); - - return data.current_token_ownerships_v2; -} -``` - - - - -```python filename="resources.py" -obj_resources = await token_client.read_object(token_addr) -owner = str(get_owner(obj_resources)) -print(f"\nToken owner: {owners[owner]}") -``` - -```python filename="resources.py" -owners = {str(alice.address()): "Alice", str(bob.address()): "Bob"} -``` - - - - ---- - -### Step 4.8: Transfer the object back and forth - -Each object created from the `aptos_token.move` contract is a distinct asset. The assets owned by a user are stored separately from the user's account. To check if a user owns an object, check the object's owner: - - - - -```ts filename="example.ts" -const alicesDigitalAsset = await aptos.getOwnedDigitalAssets({ - ownerAddress: alice.accountAddress, - minimumLedgerVersion: BigInt(pendingTxn.version), -}); - -console.log( - `Alice's digital asset: ${JSON.stringify(alicesDigitalAsset[0], null, 4)}`, -); -``` - -```ts filename="example.ts" -const transferTransaction = await aptos.transferDigitalAsset({ - sender: alice, - digitalAssetAddress: alicesDigitalAsset[0].token_data_id, - recipient: bob.accountAddress, -}); -const committedTxn = await aptos.signAndSubmitTransaction({ - signer: alice, - transaction: transferTransaction, -}); -const pendingTxn = await aptos.waitForTransaction({ - transactionHash: committedTxn.hash, -}); -``` - -```ts filename="example.ts" -const alicesDigitalAssetsAfter = await aptos.getOwnedDigitalAssets({ - ownerAddress: alice.accountAddress, - minimumLedgerVersion: BigInt(pendingTxn.version), -}); -console.log( - `Alice's digital assets balance: ${alicesDigitalAssetsAfter.length}`, -); - -const bobDigitalAssetsAfter = await aptos.getOwnedDigitalAssets({ - ownerAddress: bob.accountAddress, - minimumLedgerVersion: BigInt(pendingTxn.version), -}); -console.log(`Bob's digital assets balance: ${bobDigitalAssetsAfter.length}`); -``` - - - - -```python filename="example.py" -print("\n=== Transferring the token to Bob ===") -txn_hash = await token_client.transfer_token( - alice, - token_addr, - bob.address(), -) -await rest_client.wait_for_transaction(txn_hash) -``` - -```python filename="example.py" -async def transfer_token( - self, owner: Account, token: AccountAddress, to: AccountAddress -) -> str: - return await self.client.transfer_object(owner, token, to) -``` - -```python filename="example.py" -obj_resources = await token_client.read_object(token_addr) -print(f"Token owner: {owners[str(get_owner(obj_resources))]}") -``` - -```python filename="example.py" -print("\n=== Transferring the token back to Alice ===") -txn_hash = await token_client.transfer_token( - bob, - token_addr, - alice.address(), -) -await rest_client.wait_for_transaction(txn_hash) -``` - -```python filename="example.py" -obj_resources = await token_client.read_object(token_addr) -print(f"Token owner: {owners[str(get_owner(obj_resources))]}\n") -``` - - - - ---- - -## Supporting documentation - -- [Account basics](../../network/blockchain/accounts.mdx) -- [TypeScript SDK](../sdks/ts-sdk.mdx) -- [Python SDK](../sdks/python-sdk.mdx) -- [REST API specification](../../network/nodes/aptos-api-spec.mdx) diff --git a/apps/nextra/pages/zh/build/smart-contracts.mdx b/apps/nextra/pages/zh/build/smart-contracts.mdx deleted file mode 100644 index 2cff9563a..000000000 --- a/apps/nextra/pages/zh/build/smart-contracts.mdx +++ /dev/null @@ -1,82 +0,0 @@ ---- -title: "Smart Contracts" ---- - -import { RemoteCodeblock, permalinkFetch, Card, Cards } from '@components/index' -import { Callout } from 'nextra/components' - -export async function getStaticProps() { - return await permalinkFetch([ - 'https://github.com/aptos-labs/aptos-core/blob/77e1d222ebc5e7294e115e0d090c001da1d0e072/aptos-move/move-examples/hello_blockchain/sources/hello_blockchain.move#L1-L59' - ]) -} - -# Smart Contracts on Aptos - -Aptos contracts are written using Move, a next generation language for secure, sandboxed, and formally verified programming which is used for multiple chains. -Move allows developers to write programs that flexibly manage and transfer assets while providing security and protections against attacks on those assets. - -## 📖 Learn Move - - - - Why Move? - Learn why Aptos uses the Move Language - - {/* - Move Prover - Formal specification and verification of Move contracts on Aptos - */} - - Create Package - Get started by learning how to create a Move package - - - Objects - Learn how to use the Object standard on Aptos to create composable and flexible primitives on chain - - - -## 👨‍💻 Move Examples - - - - Aptos Move Examples - 30+ examples on how to develop Move on Aptos - - - Move Tutorial - Covers the basics of programming with Move - - - Your first Move Module - A `hello_blockchain` example of how to publish your first move module - - - -Here is a `hello_blockchain` example of move - - - -## ⚒️ Developer Resources - -### FAQ and Discussions - -- [Aptos Dev Discussions](https://github.com/aptos-labs/aptos-developer-discussions/discussions) for Q&A about Move. - -### Move IDE plugins - -- [Aptos Move Analyzer](https://marketplace.visualstudio.com/items?itemName=MoveBit.aptos-move-analyzer) for Visual Studio. -- [Move language plugin for JetBrains IDEs](https://plugins.jetbrains.com/plugin/14721-move-language): Supports syntax highlighting, code navigation, renames, formatting, type checks and code generation. - -### External Resources - -- [Aptos Move by Example](https://move-developers-dao.gitbook.io/aptos-move-by-example) -- [Teach yourself Move on Aptos](https://github.com/econia-labs/teach-yourself-move). -- [Formal Verification, the Move Language, and the Move Prover](https://www.certik.com/resources/blog/2wSOZ3mC55AB6CYol6Q2rP-formal-verification-the-move-language-and-the-move-prover) -- [Pontem Move Playground](https://playground.pontem.network/) -- [Collection of nestable Move resources](https://github.com/taoheorg/taohe) - -A new Move compiler and language version is currently in early beta testing. If you are interested to play with it, check [this page](smart-contracts/compiler_v2.mdx). diff --git a/apps/nextra/pages/zh/build/smart-contracts/_meta.tsx b/apps/nextra/pages/zh/build/smart-contracts/_meta.tsx index 150397385..dbf8e2816 100644 --- a/apps/nextra/pages/zh/build/smart-contracts/_meta.tsx +++ b/apps/nextra/pages/zh/build/smart-contracts/_meta.tsx @@ -5,12 +5,15 @@ export default { }, "why-move": { title: "Why Move?", + href: "/en/build/smart-contracts/book/why-move", }, "create-package": { title: "Create Package", + href: "/en/build/smart-contracts/book/create-package", }, compiling: { title: "Compiling", + href: "/en/build/smart-contracts/book/compiling", }, testing: { title: "Testing", @@ -18,6 +21,7 @@ export default { }, deployment: { title: "Deployment", + href: "/en/build/smart-contracts/book/deployment", }, "---aptos-standards---": { type: "separator", @@ -25,35 +29,43 @@ export default { }, object: { title: "Object", + href: "/en/build/smart-contracts/book/objects", }, "digital-asset": { title: "Digital Asset (DA)", + href: "/en/build/smart-contracts/book/digital-assets", }, "fungible-asset": { title: "Fungible Asset (FA)", + href: "/en/build/smart-contracts/book/fungible-assets", }, "aptos-coin": { title: "Coin (legacy)", + href: "/en/build/smart-contracts/book/aptos-coin", }, "aptos-token": { title: "Token (legacy)", + href: "/en/build/smart-contracts/book/aptos-token", }, "---addressable-storage---": { title: "Addressable Storage", type: "separator", }, accounts: { - href: "/en/network/blockchain/accounts", title: "Accounts", + href: "/en/network/blockchain/accounts", }, table: { title: "Table", + href: "/en/build/smart-contracts/book/table", }, objects: { title: "Objects", + href: "/en/build/smart-contracts/book/objects", }, "resource-accounts": { title: "Resource Accounts", + href: "/en/build/smart-contracts/book/resource-accounts", }, "---data-structures---": { title: "Data Structures", @@ -65,12 +77,15 @@ export default { }, vector: { title: "Vector", + href: "/en/build/smart-contracts/book/vectors", }, "smart-vector": { title: "Smart Vector", + href: "/en/build/smart-contracts/book/smart-vectors", }, "smart-table": { title: "Smart Table", + href: "/en/build/smart-contracts/book/smart-tables", }, "---examples---": { type: "separator", @@ -83,6 +98,7 @@ export default { }, reference: { title: "Move Reference", + href: "/en/build/smart-contracts/book/reference", theme: { toc: false, layout: "full", @@ -90,9 +106,11 @@ export default { }, book: { title: "Move Book", + href: "/en/build/smart-contracts/book", }, "error-codes": { title: "Error Codes", + href: "/en/build/smart-contracts/book/error-codes", }, "---advanced-move---": { type: "separator", @@ -100,18 +118,23 @@ export default { }, scripts: { title: "Scripts", + href: "/en/build/smart-contracts/book/scripts", }, prover: { title: "Move Prover", + href: "/en/build/smart-contracts/book/move-prover", }, randomness: { title: "Randomness", + href: "/en/build/smart-contracts/book/randomness", }, cryptography: { title: "Cryptography", + href: "/en/build/smart-contracts/book/cryptography", }, "move-security-guidelines": { title: "Move Security Guidelines", + href: "/en/build/smart-contracts/book/move-security-guidelines", }, "gas-profiling": { title: "Gas Profiling", @@ -119,8 +142,10 @@ export default { }, compiler_v2: { title: "Compiler V2", + href: "/en/build/smart-contracts/book/compiler-v2", }, linter: { title: "Linter", + href: "/en/build/smart-contracts/book/linter", }, }; diff --git a/apps/nextra/pages/zh/build/smart-contracts/aptos-coin.mdx b/apps/nextra/pages/zh/build/smart-contracts/aptos-coin.mdx deleted file mode 100644 index ad08827ae..000000000 --- a/apps/nextra/pages/zh/build/smart-contracts/aptos-coin.mdx +++ /dev/null @@ -1,334 +0,0 @@ ---- -title: "Aptos Coin Standard (Legacy)" ---- - -import { Callout } from "nextra/components"; - -# Aptos Coin Standard (Legacy) - -[Coin](https://github.com/aptos-labs/aptos-core/blob/main/aptos-move/framework/aptos-framework/sources/coin.move) -provides a standard, type-safe framework for simple, fungible tokens or coins. - - - Coin is stored in `0x1::coin`. - - -## Structures - -### Reusability - -A coin is defined in Move as: - -```move -module 0x1::coin { - struct Coin has store { - /// Amount of coin this address has. - value: u64, - } -} -``` - -A Coin uses the `CoinType` to support re-usability of the Coin framework for -distinct Coins. For example, `Coin` and `Coin` are two distinct coins. - -### Global store - -Coin also supports a resource for storing coins in global store: - -```move -module 0x1::coin { - struct CoinStore has key { - coin: Coin, - frozen: bool, - deposit_events: EventHandle, - withdraw_events: EventHandle, - } -} -``` - -Coin information or metadata is stored in global store under the coin creators -account: - -```move -module 0x1::coin { - struct CoinInfo has key { - name: string::String, - /// Symbol of the coin, usually a shorter version of the name. - /// For example, Singapore Dollar is SGD. - symbol: string::String, - /// Number of decimals used to get its user representation. - /// For example, if `decimals` equals `2`, a balance of `505` coins should - /// be displayed to a user as `5.05` (`505 / 10 ** 2`). - decimals: u8, - /// Amount of this coin type in existence. - supply: Option, - } -} -``` - -## Primitives - -Coin provides the primitives for users creating and managing the coin and the -users who use it. - -### Creators - -Coin creators and managers can: - -- Initialize a coin and set its metadata and supply monitoring. -- Minting and burning Coin value. -- Burning coins from a `CoinStore`. -- Freezing mobility into and out of a `CoinStore`. - -### Users - -Coin users can: - -- Merging two Coin structs of the same type. -- Extracting value from a Coin struct into a new Coin struct. -- Ability to deposit and withdraw from a `CoinStore` and emit events as a result. -- Allows for users to register a `CoinStore` in their account to -handle coin. - -### Coin module key struct - -The following tables describe fields at the struct level. For the definitive list, -see the [Aptos Framework](https://github.com/aptos-labs/aptos-core/blob/main/aptos-move/framework/aptos-framework/doc/overview.md) -containing [`coin`](https://github.com/aptos-labs/aptos-core/blob/main/aptos-move/framework/aptos-framework/doc/coin.md). - -##### [`Coin`](https://github.com/aptos-labs/aptos-core/blob/744c2def47cddced878fda9bbd5633022fdb083a/aptos-move/framework/aptos-framework/sources/coin.move#L68) - -| Field | Type | Description | -| ------- | ---- | ---------------------------------- | -| `value` | u64 | Value of the token, eg: 1000000000 | - -##### [`CoinInfo`](https://github.com/aptos-labs/aptos-core/blob/744c2def47cddced878fda9bbd5633022fdb083a/aptos-move/framework/aptos-framework/sources/coin.move#L92) - -| Field | Type | Description | -| ---------- | -------------------------------- | -------------------------------------------------------------------------------------------------------------------------------- | -| `name` | String | Name of the token, eg: Aptos Coin | -| `symbol` | String | Symbol for the token, eg: APT | -| `decimals` | u8 | Determines how the value of coin is represented; for example APT’s decimal is 8, so a value of 100000000 is represented by 1 APT | -| `supply` | Option<OptionalAggregator> | option::some(optional_aggregator::new(MAX_U128, parallelizable)) | - -### Creating a new CoinType - -A coin creator can publish to an on-chain account a new module that defines a -struct to represent a new `CoinType`. The coin creator will then call -`coin:initialize` from that account to register this as a valid coin, -and in return receive back structs that enable calling the functions to burn and -mint coins and freeze `CoinStore`s. These will need to be stored in global -storage by the creator to manage the use of the coin. - -```move -module 0x1::coin { - public fun initialize( - account: &signer, - name: string::String, - symbol: string::String, - decimals: u8, - monitor_supply: bool, - ): (BurnCapability, FreezeCapability, MintCapability) { - // ... - } -} -``` - -The creator has the opportunity to define a name, symbol, decimals, and whether -the total supply for the coin is monitored. The following applies: - -- The first three of the above (`name`, `symbol`, `decimals`) are purely -metadata and have no impact for on-chain applications. Some applications may use -decimal to equate a single Coin from fractional coin. -- Monitoring supply (`monitor_supply`) helps track total coins in supply. -However, due to the way the parallel executor works, turning on this option will -prevent any parallel execution of mint and burn. If the coin will be regularly -minted or burned, consider disabling `monitor_supply`. - -### Minting Coins - -If the creator or manager would like to mint coins, they must retrieve a -reference to their `MintCapability`, which was produced in the `initialize`, -and call: - -```move -module 0x1::coin { - public fun mint( - amount: u64, - _cap: &MintCapability, - ): Coin acquires CoinInfo { - // ... - } -} -``` - -This will produce a new Coin struct containing a value as dictated by the -`amount`. If supply is tracked, then it will also be adjusted. - -### Burning Coins - -If the creator or manager would like to burn coins, they must retrieve a -reference to their `BurnCapability`, which was produced in the `initialize`, and -call: - -```move -module 0x1::coin { - public fun burn( - coin: Coin, - _cap: &BurnCapability, - ) acquires CoinInfo { - // ... - } -} -``` - -The creator or manager can also burn coins from a `CoinStore`: - -```move -module 0x1::coin { - public fun burn_from( - account_addr: address, - amount: u64, - burn_cap: &BurnCapability, - ) acquires CoinInfo, CoinStore { - // ... - } -} -``` - - - ### burn vs burn_from - The function `burn` eliminates the total value stored in the coin, while - `burn_from` only eliminates a given amount of value from a `CoinStore`. If - supply is tracked, then it will also be adjusted. - - Burning coins from an account does not emit a `WithdrawEvent` as the - `withdraw` - function does. - - -### Freezing Accounts - -If the creator or manager would like to freeze a `CoinStore` on a specific -account, they must retrieve a reference to their `FreezeCapability`, which was -produced in `initialize`, and call: - -```move -module 0x1::coin { - public entry fun freeze_coin_store( - account_addr: address, - _freeze_cap: &FreezeCapability, - ) acquires CoinStore { - // ... - } -} -``` - -### Merging Coins - -Two coins of the same type can be merged into a single Coin struct that -represents the accumulated value of the two coins independently by calling: - -```move -module 0x1::coin { - public fun merge( - dst_coin: &mut Coin, - source_coin: Coin, - ) { - // ... - } -} -``` - -### Extracting Coins - -A Coin can have value deducted to create another Coin by calling: - -```move -module 0x1::coin { - public fun extract( - coin: &mut Coin, - amount: u64, - ): Coin { - // ... - } -} -``` - -### Withdrawing Coins from CoinStore - -A holder of a `CoinStore` can extract a Coin of a specified value by calling: - -```move -module 0x1::coin { - public fun withdraw( - account: &signer, - amount: u64, - ): Coin acquires CoinStore { - // ... - } -} -``` - - - This function will emit a `WithdrawEvent`. - - -### Depositing Coins into CoinStore - -Any entity can deposit coins into an account’s `CoinStore` by calling: - -```move -module 0x1::coin { - public fun deposit( - account_addr: address, - coin: Coin, - ) acquires CoinStore { - // ... - } -} -``` - - - This function will emit a `DepositEvent`. - - -### Transferring Coins - -A holder of a `CoinStore` can directly transfer coins from their account to -another account’s `CoinStore` by calling: - -```move -module 0x1::coin { - public entry fun transfer( - from: &signer, - to: address, - amount: u64, - ) acquires CoinStore { - // ... - } -} -``` - - - This will emit both a `WithdrawEvent` and `DepositEvent` on the respective - `CoinStore`s. - - -## Events - -```move -module 0x1::coin { - struct DepositEvent has drop, store { - amount: u64, - } -} -``` - -```move -module 0x1::coin { - struct WithdrawEvent has drop, store { - amount: u64, - } -} -``` diff --git a/apps/nextra/pages/zh/build/smart-contracts/aptos-standards.mdx b/apps/nextra/pages/zh/build/smart-contracts/aptos-standards.mdx deleted file mode 100644 index cdceb9854..000000000 --- a/apps/nextra/pages/zh/build/smart-contracts/aptos-standards.mdx +++ /dev/null @@ -1,64 +0,0 @@ ---- -title: "Aptos Standards" ---- - -# Aptos Standards - -Standards define a common interoperable interface for all developers to build -upon. They consist of rules to ensure compatibility across applications and -wallets on the Aptos blockchain. See a [list of known coin resource addresses](https://github.com/hippospace/aptos-coin-list) -on Aptos provided by hippospace. - -## Move Standards - -### [Aptos Object](objects.mdx) - -The [Object model](https://github.com/aptos-labs/aptos-core/blob/main/aptos-move/framework/aptos-framework/sources/object.move) -allows Move to represent a complex type as a set of resources stored within a -single address and offers a rich capability model that allows for fine-grained -resource control and ownership management. - -## Asset Standards - -### [Digital Asset (DA)](digital-asset.mdx) - -The new [Aptos Digital Asset Standard](https://github.com/aptos-labs/aptos-core/blob/main/aptos-move/framework/aptos-token-objects/sources/token.move) -allows: - -- Rich, flexible assets and collectibles. -- Easy enhancement of base functionality to provide richer custom -functionalities. An example of this is the [aptos_token module](https://github.com/aptos-labs/aptos-core/blob/main/aptos-move/framework/aptos-token-objects/sources/aptos_token.move) - -Digital Asset (DA) is recommended for any new collections or protocols that want -to build NFT or semi-fungible tokens. - -### [Fungible Asset (FA)](fungible-asset.mdx) - -The new [Aptos Fungible Asset Standard](https://github.com/aptos-labs/aptos-core/blob/main/aptos-move/framework/aptos-framework/sources/fungible_asset.move) -is a standard meant for simple, type-safe, and fungible assets based on object -model intending to replace Aptos coin. Fungible Asset (FA) offers more features -and flexibility to Aptos move developers on creating and managing fungible assets. - -## Legacy Standards - -### [Aptos Token](aptos-token.mdx) - -The old -existing [Token module](https://github.com/aptos-labs/aptos-core/blob/main/aptos-move/framework/aptos-token/sources/token.move), -on the other hand: - -- Encapsulates rich, flexible assets and collectibles. These assets are discrete -(non-decimal) and can be fungible, semi-fungible, or non-fungible. -- The token standard is in its own `AptosToken` package at the Address `0x3` to -allow for rapid iteration based on feedback from the community. - -### [Aptos Coin](aptos-coin.mdx) - -The [Coin module](https://github.com/aptos-labs/aptos-core/blob/main/aptos-move/framework/aptos-framework/sources/coin.move) -is a lightweight standard meant for simple, type-safe, and fungible assets. The -coin standard is separated out into its own Move module to ensure that: - -- Applications and users can create and use simple tokens, with high performance -and low gas overhead. -- The Coin standard is part of the Aptos core framework, so it can be used for -currencies, including the gas currency. diff --git a/apps/nextra/pages/zh/build/smart-contracts/aptos-token.mdx b/apps/nextra/pages/zh/build/smart-contracts/aptos-token.mdx deleted file mode 100644 index a31548de5..000000000 --- a/apps/nextra/pages/zh/build/smart-contracts/aptos-token.mdx +++ /dev/null @@ -1,364 +0,0 @@ ---- -title: "Aptos Token Standard (Legacy)" ---- - -import { Callout } from "nextra/components"; -import { ThemedImage } from '@components/index' - -# Aptos Token Standard (Legacy) - -## Overview of NFT - -An [NFT](https://en.wikipedia.org/wiki/Non-fungible_token) is a non-fungible [token](https://github.com/aptos-labs/aptos-core/blob/main/aptos-move/framework/aptos-token/sources/token.move) or data stored on a blockchain that -uniquely defines ownership of an asset. NFTs were first defined in [EIP-721](https://eips.ethereum.org/EIPS/eip-721) -and later expanded upon in [EIP-1155](https://eips.ethereum.org/EIPS/eip-1155). -NFTs are typically defined using the following properties: - -- `name`: The name of the asset. It must be unique within a collection. -- `description`: The description of the asset. -- `uri`: A URL pointer to off-chain for more information about the asset. The -asset could be media such as an image or video or more metadata. -- `supply`: The total number of units of this NFT. Many NFTs have only a single -supply, while those that have more than one are referred to as editions. - -Additionally, most NFTs are part of a collection or a set of NFTs with a common -attribute, for example, a theme, creator, or minimally contract. Each collection -has a similar set of attributes: - -- `name`: The name of the collection. The name must be unique within the -creator's account. -- `description`: The description of the collection. -- `uri`: A URL pointer to off-chain for more information about the asset. The -asset could be media such as an image or video or more metadata. -- `supply`: The total number of NFTs in this collection. -- `maximum`: The maximum number of NFTs that this collection can have. If -`maximum` is set to 0, then supply is not tracked. - -## Design principles - -The [Aptos token standard](https://github.com/aptos-labs/aptos-core/blob/main/aptos-move/framework/aptos-token/sources/token.move) is developed with the following principles: - -- **Interoperability**: Provide a standard implementation to improve -interoperability across the ecosystem projects. Moreover, Move being a static -language without dynamic dispatch makes this principle even more imperative. -- **Liquidity**: Achieve maximal liquidity by defining the NFT, fungible -(non-decimal) and semi-fungible tokens in one contract. These different types of -tokens can be easily stored, transferred and transacted in the same way. As a -consequence, it becomes easier to achieve maximal interoperability across the -marketplaces, exchanges, and other methods of exchange. -- **Rich on-chain token properties**: Enable the customization of on-chain token -properties. Users can define their own properties and store them on-chain. This -can potentially eliminate the need for the off-chain metadata. -- **Reduced overhead**: Reduce the cost of creating large amounts of NFTs from -fungible tokens. This can lead to, for example, reduced overhead for similar -tokens by the reuse of on-chain metadata for certain fungible tokens. - - - **Fungible token → NFT**
- The Aptos token standard supports [mutation of a fungible token to an - NFT](#evolving-from-fungible-token-to-nft). -
- -### Storing customized token properties on-chain - -The Aptos token standard uses the [`PropertyMap`](https://github.com/aptos-labs/aptos-core/blob/main/aptos-move/framework/aptos-token/sources/property_map.move) module to store on-chain -properties of tokens. `PropertyMap` maps a string key to a property value -on-chain, which stores the value in Binary Canonical Serialization (BCS) format -and its type. Currently, only primitive types (bool, u8, u64, u128, address and -String) are supported in `PropertyMap`. Applications, such as [Aptos Names](https://www.aptosnames.com/), -define application specific properties that are read and written by the -applications smart contract. - -#### Default properties - -You can add your properties to [`default_properties`](https://github.com/aptos-labs/aptos-core/blob/e62fd09cb1c916d857fa655b3f174991ef8698b3/aptos-move/framework/aptos-token/sources/token.move#L98) in the TokenData. The -properties defined here are shared by all tokens by default. - -The `default_properties` field is a key-value store with type information. It -leverages the PropertyMap module which contain functions for serializing and -deserializing different primitive types to the property value. - -#### Token properties - -You can also use the `token_properties` defined in the token itself for -customization on-chain. You can create customized values for a property of this -specific token, thereby allowing a token to have a different property value from -its default. - -Note that limits exist to storing customized token properties on-chain, namely -1000 properties per token with field names limited to 128 characters. - -### Evolving from fungible token to NFT - -Fungible tokens share the same default property values. However, these property -values can evolve over time and become different from each other. To support -such evolution of token properties, the Aptos token standard provides the -`property_version` field. Here is how it works: - -- During the token creation (minting), all tokens initially have -`property_version` set to `0` and these tokens can be stacked together as -fungible token. -- When the creators mutate the default properties of a token, the mutated token -will be assigned a unique `property_version` to create a new [`token_id`](https://github.com/aptos-labs/aptos-core/blob/bba1690d7268759bd86ccd7459d7967172f1da24/aptos-move/framework/aptos-token/sources/token.move#L288) -to differentiate it from other fungible tokens. This unique `token_id` allows -the token to have its own property values, and all further mutation of this -token does **not** change the `property_version` again. This token essentially -becomes an NFT now. - -#### Configuring mutability - -To make mutability explicit for both the creator and owner, the Aptos token -standard provides [`mutability_config`](https://github.com/aptos-labs/aptos-core/blob/bba1690d7268759bd86ccd7459d7967172f1da24/aptos-move/framework/aptos-token/sources/token.move#L100) -at both the collection level and the token level to control which fields are -mutable. Configurable here means the creator can configure this field to be -mutable or immutable during creation. - -### Storing metadata off-chain - -Follow the standard below to ensure your NFT can be correctly displayed by -various wallets. - -You should store the metadata in a JSON file located in an onchain data solution like [Irys](https://docs.irys.xyz/), and provide the URL to the -JSON file in the `uri` field of the token or the collection. We recommend the -developers follow the [ERC-1155 off-chain data](https://eips.ethereum.org/EIPS/eip-1155) -schema to format their JSON files. - -```json -{ - "image": "https://gateway.irys.xyz/QH3rksVhbFg5L9vvjGzb4POUibCEG-TGPInmofp-O-o", - "animation_url": "https://gateway.irys.xyz/QH3rksVhbFg5L9vvjHzb4POUibCEG-TGPInmofp-O-o", - "external_url": "https://petra.app/", - "attributes": [ - { - "trait_type": "web", - "value": "yes" - }, - { - "trait_type": "mobile", - "value": "yes" - }, - { - "trait_type": "extension", - "value": "yes" - } - ], - "properties": { - "files": [ - { - "uri": "https://gateway.irys.xyz/QH3rksVhbFg5L9vvjGzb4POUibCEG-ENXUnmofp-O-o", - "type": "image/png" - }, - { - "uri": "https://gateway.irys.xyz/QH3rksVhbFg5L9vvjGzb4POUibCEG-UENCnmofp-O-o", - "type": "unknown", - "cdn": true - }, - { - "uri": "https://gateway.irys.xyz/QH3rksVhbFg5L9vvjGzb4POUibCEG-DJHUUnmofp-O-o", - "type": "video/mp4" - } - ], - "category": "video" - } -} -``` - -- `image`: URL to the image asset. You may use the `?ext={file_extension}` query -to provide information on the file type. -- `animation_url`: URL to the multimedia attachment of the asset. You may use -the same `file_extension` query to provide the file type. -- `external_url`: URL to an external website where the user can also view the -image. -- `attributes` - Object array, where an object should contain `trait_type` and -`value` fields. `value` can be a string or a number. -- `properties.files`: Object array, where an object should contain the URI and -type of the file that is part of the asset. The type should match the file -extension. The array should also include files specified in `image` and -`animation_url` fields, as well as any other files associated with the asset. -You may use the `?ext={file_extension}` query to provide information on the file -type. -- `properties.category`: Has supported categories: -- `image` - PNG, GIF, JPG -- `video` - MP4, MOV -- `audio` - MP3, FLAC, WAV -- `vr` - 3D models; GLB, GLTF -- `html` - HTML pages; scripts and relative paths within the HTML page are also -supported - -You can also host your files on CDN to provide faster loading time by using the -`cdn` flag in the file object. When the file exists, this should be the primary -location to read the media file (`video`, `audio`, `vr`) by wallet. If the file -is no longer available, the wallet can fall back to use the `animation_url` to -load the file. - -```json -{ - "properties": { - "files": [ - { - "uri": "https://watch.videodelivery.net/52a52c4a261c88f19d267931426c9be6", - "type": "unknown", - "cdn": true - } - ] - } -} -``` - -## Token data model - -The [following diagram](/docs/aptos-token-standard-flow.svg) depicts the -flow of token data through Aptos. - - - - -## Token resources - -As shown in the diagram above, token-related data are stored at both the -creator’s account and the owner’s account. - -### Struct-level resources - -The following tables describe fields at the struct level. For the definitive -list, see the [Aptos Token Framework](https://github.com/aptos-labs/aptos-core/blob/main/aptos-move/framework/aptos-token/doc/overview.md). - -#### Resource stored at the creator’s address - -| Field | Description | -| --------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| [`Collections`](https://github.com/aptos-labs/aptos-core/blob/main/aptos-move/framework/aptos-token/doc/token.md#resource-collections) | Maintains a table called `collection_data`, which maps the collection name to the `CollectionData`. It also stores all the `TokenData` that this creator creates. | -| [`CollectionData`](https://github.com/aptos-labs/aptos-core/blob/main/aptos-move/framework/aptos-token/doc/token.md#struct-collectiondata) | Stores the collection metadata. The supply is the number of tokens created for the current collection. The maximum is the upper bound of tokens in this collection. | -| [`CollectionMutabilityConfig`](https://github.com/aptos-labs/aptos-core/blob/main/aptos-move/framework/aptos-token/doc/token.md#0x3_token_CollectionMutabilityConfig) | Specifies which field is mutable. | -| [`TokenData`](https://github.com/aptos-labs/aptos-core/blob/main/aptos-move/framework/aptos-token/doc/token.md#0x3_token_TokenData) | Acts as the main struct for holding the token metadata. Properties is a where users can add their own properties that are not defined in the token data. Users can mint more tokens based on the `TokenData`, and those tokens share the same `TokenData`. | -| [`TokenMutabilityConfig`](https://github.com/aptos-labs/aptos-core/blob/main/aptos-move/framework/aptos-token/doc/token.md#0x3_token_TokenMutabilityConfig) | Controls which fields are mutable. | -| [`TokenDataId`](https://github.com/aptos-labs/aptos-core/blob/main/aptos-move/framework/aptos-token/doc/token.md#0x3_token_TokenDataId) | An ID used for representing and querying `TokenData` on-chain. This ID mainly contains three fields including creator address, collection name and token name. | -| [`Royalty`](https://github.com/aptos-labs/aptos-core/blob/main/aptos-move/framework/aptos-token/doc/token.md#0x3_token_Royalty) | Specifies the denominator and numerator for calculating the royalty fee. It also has the payee account address for depositing the royalty. | -| `PropertyValue` | Contains both value of a property and type of property. | - -#### Resource stored at the owner’s address - -| Field | Description | -| ------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| [`TokenStore`](https://github.com/aptos-labs/aptos-core/blob/main/aptos-move/framework/aptos-token/doc/token.md#0x3_token_TokenStore) | The main struct for storing the token owned by this address. It maps `TokenId` to the actual token. | -| [`Token`](https://github.com/aptos-labs/aptos-core/blob/main/aptos-move/framework/aptos-token/doc/token.md#0x3_token_Token) | The amount is the number of tokens. | -| [`TokenId`](https://github.com/aptos-labs/aptos-core/blob/main/aptos-move/framework/aptos-token/doc/token.md#0x3_token_TokenId) | `TokenDataId` points to the metadata of this token. The `property_version` represents a token with mutated `PropertyMap` from `default_properties` in the `TokenData`. | - -For more detailed descriptions, see [Aptos Token Framework](https://github.com/aptos-labs/aptos-core/blob/main/aptos-move/framework/aptos-token/doc/overview.md). - -## Token lifecycle - -### Token creation - -Every Aptos token belongs to a collection. The developer first needs to create a -collection through `create_collection_script` and then create the token -belonging to the collection `create_token_script`. To achieve parallel -`TokenData` and `Token` creation, a developer can create unlimited collection -and `TokenData` where the `maximum` of the collection and `TokenData` are set as -`0`. With this setting, the token contract won’t track the supply of types of -token (`TokenData` count) and supply of token within each token type. As the -result, the `TokenData` and token can be created in parallel. - -Aptos also enforces simple validation of the input size and prevents duplication: - -- Token name - unique within each collection -- Collection name - unique within each account -- Token and collection name length - smaller than 128 characters -- URI length - smaller than 512 characters -- Property map - can hold at most 1000 properties, and each key should be -smaller than 128 characters - -### Token mutation - -Our standard supports mutation with a principle that the mutable fields are -specified during the token creation. This allows the token owner to be informed -which fields are mutable when they get the token from the creator. Our contract -uses `CollectionMutabilityConfig` to check if a field is mutable. Our contract -uses `TokenMutabilityConfig` to check if a `TokenData` field is mutable. - -For mutation of properties, we have both - -- `default_properties` stored in `TokenData` shared by all tokens belonging to -the `TokenData` -- `token_properties` stored in the token itself - -To mutate `default_properties`, developers can use `mutate_tokendata_property` -to mutate the properties when `TokenMutabilityConfig` is set to `true`. - -> **CAUTION: Set the `TokenMutabilityConfig` field to `false` unless it is -absolutely necessary. Allowing `default_properties` to be mutable provides -creators too much power; creators can change the burnable config to provide -themselves the authority to burn tokens after token creation.** - -To mutate `token_properties` stored in the token, our standard uses the -`TOKEN_PROPERTY_MUTABLE` property stored in `default_properties`. When the -creator creates the `TokenData` with the `TOKEN_PROPERTY_MUTABLE` property -set to `true`, the creator can mutate `token_properties`. Note that if the -`mutate_tokendata_property` is set to `true`, creators can mutate the -`token_properties` anyway since they can overwrite the setting in -`default_properties`. - -### Token burn - -We provide `burn` and `burn_by_creator` functions for token owners and token -creators to burn (or destroy) tokens. However, these two functions are also -guarded by configs that are specified during the token creation so that both -creator and owner are clear on who can burn the token. Burn is allowed only when -the `BURNABLE_BY_OWNER` property is set to `true` in `default_properties`. Burn -by creator is allowed when `BURNABLE_BY_CREATOR` is `true` in -`default_properties`. Once all the tokens belonging to a `TokenData` are burned, -the `TokenData` will be removed from the creator’s account. Similarly, if all -`TokenData` belonging to a collection are removed, the `CollectionData` will be -removed from the creator’s account. - -### Token transfer - -We provide three different modes for transferring tokens between the sender and -receiver. - -#### Two-step transfer - -To protect users from receiving undesired NFTs, they must be first offered NFTs, -and then accept the offered NFTs. Then only the offered NFTs will be deposited -in the users' token stores. This is the default token transfer behavior. For -example: - -1. If Alice wants to send Bob an NFT, she must first offer Bob this NFT. This -NFT is still stored under Alice’s account. -2. Only when Bob claims the NFT, will the NFT be removed from Alice’s account -and stored in Bob’s token store. - - - **Token transfer module**
- The token transfer is implemented in the - [`token_transfers`](https://github.com/aptos-labs/aptos-core/blob/main/aptos-move/framework/aptos-token/sources/token_transfers.move) - module. -
- -#### Transfer with opt-in - -If a user wants to receive direct transfer of the NFT, skipping the initial -steps of offer and claim, then the user can call [`opt_in_direct_transfer`](https://github.com/aptos-labs/aptos-core/blob/main/aptos-move/framework/aptos-token/doc/token.md#0x3_token_opt_in_direct_transfer) -to allow other people to directly transfer the NFTs into the user's token store. -After opting into direct transfer, the user can call [`transfer`](https://github.com/aptos-labs/aptos-core/blob/main/aptos-move/framework/aptos-token/doc/token.md#0x3_token_transfer) -to transfer tokens directly. For example, Alice and anyone can directly send a -token to Bob's token store once Bob opts in. - - - **Turning off direct transfer**
- The user can also turn off this direct transfer behavior by calling the same - `opt_in_direct_transfer` function to reset to the default behavior. -
- -#### Multi-agent transfer - -The sender and receiver can both sign a transfer transaction to directly -transfer a token from the sender to receiver [`direct_transfer_script`](https://github.com/aptos-labs/aptos-core/blob/main/aptos-move/framework/aptos-token/doc/token.md#function-direct_transfer_script). -For example, once Alice and Bob both sign the transfer transaction, the token -will be directly transferred from Alice's account to Bob. diff --git a/apps/nextra/pages/zh/build/smart-contracts/book.mdx b/apps/nextra/pages/zh/build/smart-contracts/book.mdx deleted file mode 100644 index 93851f4ba..000000000 --- a/apps/nextra/pages/zh/build/smart-contracts/book.mdx +++ /dev/null @@ -1,27 +0,0 @@ -# The Move Book - -Welcome to Move, a next generation language for secure, sandboxed, and formally -verified programming. It has been used as the smart contract language for -several blockchains including Aptos. Move allows developers to write programs -that flexibly manage and transfer assets, while providing the security and -protections against attacks on those assets. However, Move has been developed -with use cases in mind outside a blockchain context as well. - -Move takes its cue from [Rust](https://www.rust-lang.org/) by using resource -types with move (hence the name) semantics as an explicit representation of -digital assets, such as currency. - -## Who is Aptos Move Book for? - -Move was designed and created as a secure, verified, yet flexible programming -language. The first use of Move is for the implementation of the Diem -blockchain, and it is currently being used on Aptos. - -This book is suitable for developers with some programming experience -and who want to begin understanding the core programming language and see -examples of its usage. - -## Where Do I Start? - -Begin with understanding [modules and scripts](book/modules-and-scripts.mdx) -and then work through the [Move Tutorial](book/move-tutorial.mdx). diff --git a/apps/nextra/pages/zh/build/smart-contracts/book/SUMMARY.mdx b/apps/nextra/pages/zh/build/smart-contracts/book/SUMMARY.mdx deleted file mode 100644 index 669d9e1e1..000000000 --- a/apps/nextra/pages/zh/build/smart-contracts/book/SUMMARY.mdx +++ /dev/null @@ -1,49 +0,0 @@ -# The Move Programming Language - -## Getting Started - -- [Modules and Scripts](modules-and-scripts.mdx) -- [Move Tutorial](move-tutorial.mdx) - -## Language Release Notes - -- [Move 2](move-2.mdx) - -## Primitive Types - -- [Integers](integers.mdx) -- [Bool](bool.mdx) -- [Address](address.mdx) -- [Vector](vector.mdx) -- [Signer](signer.mdx) -- [References](references.mdx) -- [Tuples and Unit](tuples.mdx) - -## Basic Concepts - -- [Local Variables and Scopes](variables.mdx) -- [Equality](equality.mdx) -- [Abort and Assert](abort-and-assert.mdx) -- [Conditionals](conditionals.mdx) -- [While, For, and Loop](loops.mdx) -- [Functions](functions.mdx) -- [Structs and Resources](structs-and-resources.mdx) -- [Enum Types](enums.mdx) -- [Constants](constants.mdx) -- [Generics](generics.mdx) -- [Type Abilities](abilities.mdx) -- [Uses and Aliases](uses.mdx) -- [Friends](friends.mdx) -- [Packages](packages.mdx) -- [Package Upgrades](package-upgrades.mdx) -- [Unit Tests](unit-testing.mdx) - -## Global Storage - -- [Global Storage Structure](global-storage-structure.mdx) -- [Global Storage Operators](global-storage-operators.mdx) - -## Reference - -- [Standard Library](standard-library.mdx) -- [Coding Conventions](coding-conventions.mdx) diff --git a/apps/nextra/pages/zh/build/smart-contracts/book/_meta.tsx b/apps/nextra/pages/zh/build/smart-contracts/book/_meta.tsx index f9f788e7a..875dcb783 100644 --- a/apps/nextra/pages/zh/build/smart-contracts/book/_meta.tsx +++ b/apps/nextra/pages/zh/build/smart-contracts/book/_meta.tsx @@ -1,9 +1,11 @@ export default { SUMMARY: { title: "Summary", + href: "/en/build/smart-contracts/book/summary", }, "move-2": { title: "Move 2 Release Notes", + href: "/en/build/smart-contracts/book/move-2", }, "---getting-started---": { type: "separator", @@ -11,9 +13,11 @@ export default { }, "modules-and-scripts": { title: "Modules and Scripts", + href: "/en/build/smart-contracts/book/modules-and-scripts", }, "move-tutorial": { title: "Move Tutorial", + href: "/en/build/smart-contracts/book/move-tutorial", }, "---primitive-types---": { type: "separator", @@ -21,24 +25,31 @@ export default { }, integers: { title: "Integers", + href: "/en/build/smart-contracts/book/integers", }, bool: { title: "Bool", + href: "/en/build/smart-contracts/book/bool", }, address: { title: "Address", + href: "/en/build/smart-contracts/book/address", }, vector: { title: "Vector", + href: "/en/build/smart-contracts/book/vector", }, signer: { title: "Signer", + href: "/en/build/smart-contracts/book/signer", }, references: { title: "References", + href: "/en/build/smart-contracts/book/references", }, tuples: { title: "Tuples and Unit", + href: "/en/build/smart-contracts/book/tuples", }, "---basic-concepts---": { type: "separator", @@ -46,51 +57,67 @@ export default { }, variables: { title: "Local Variables and Scopes", + href: "/en/build/smart-contracts/book/variables", }, equality: { title: "Equality", + href: "/en/build/smart-contracts/book/equality", }, "abort-and-assert": { title: "Abort and Assert", + href: "/en/build/smart-contracts/book/abort-and-assert", }, conditionals: { title: "Conditionals", + href: "/en/build/smart-contracts/book/conditionals", }, loops: { title: "While, For, and Loop", + href: "/en/build/smart-contracts/book/loops", }, functions: { title: "Functions", + href: "/en/build/smart-contracts/book/functions", }, "structs-and-resources": { title: "Structs and Resources", + href: "/en/build/smart-contracts/book/structs-and-resources", }, enums: { title: "Enum Types", + href: "/en/build/smart-contracts/book/enums", }, constants: { title: "Constants", + href: "/en/build/smart-contracts/book/constants", }, generics: { title: "Generics", + href: "/en/build/smart-contracts/book/generics", }, abilities: { title: "Type abilities", + href: "/en/build/smart-contracts/book/abilities", }, uses: { title: "Uses and Aliases", + href: "/en/build/smart-contracts/book/uses", }, friends: { title: "Friends", + href: "/en/build/smart-contracts/book/friends", }, packages: { title: "Packages", + href: "/en/build/smart-contracts/book/packages", }, "package-upgrades": { title: "Package Upgrades", + href: "/en/build/smart-contracts/book/package-upgrades", }, "unit-testing": { title: "Unit Tests", + href: "/en/build/smart-contracts/book/unit-testing", }, "---global-storage---": { type: "separator", @@ -98,9 +125,11 @@ export default { }, "global-storage-structure": { title: "Global Storage Structure", + href: "/en/build/smart-contracts/book/global-storage-structure", }, "global-storage-operators": { title: "Global Storage Operators", + href: "/en/build/smart-contracts/book/global-storage-operators", }, "---reference---": { type: "separator", @@ -108,8 +137,10 @@ export default { }, "standard-library": { title: "Standard Library", + href: "/en/build/smart-contracts/book/standard-library", }, "coding-conventions": { title: "Coding Conventions", + href: "/en/build/smart-contracts/book/coding-conventions", }, }; diff --git a/apps/nextra/pages/zh/build/smart-contracts/book/abilities.mdx b/apps/nextra/pages/zh/build/smart-contracts/book/abilities.mdx deleted file mode 100644 index 83ad99302..000000000 --- a/apps/nextra/pages/zh/build/smart-contracts/book/abilities.mdx +++ /dev/null @@ -1,269 +0,0 @@ -# Abilities - -Abilities are a typing feature in Move that control what actions are permissible for values of a given type. This system grants fine-grained control over the "linear" typing behavior of values, as well as if and how values are used in global storage. This is implemented by gating access to certain bytecode instructions so that for a value to be used with the bytecode instruction, it must have the ability required (if one is required at all—not every instruction is gated by an ability). - -{/* TODO future section on detailed walk through, maybe. We have some examples at the end, but it might be helpful to explain why we have precisely this set of abilities */} - -{/* If you are already somewhat familiar with abilities from writing Move programs, but are still confused as to what is going on, it might be helpful to skip to the [motivating walkthrough](#motivating-walkthrough) section to get an idea of what the system is set up in the way that it is. --> */} - -## The Four Abilities - -The four abilities are: - -- [`copy`](#copy) - - Allows values of types with this ability to be copied. -- [`drop`](#drop) - - Allows values of types with this ability to be popped/dropped. -- [`store`](#store) - - Allows values of types with this ability to exist inside a struct in global storage. -- [`key`](#key) - - Allows the type to serve as a key for global storage operations. - -### `copy` - -The `copy` ability allows values of types with that ability to be copied. It gates the ability to copy values out of local variables with the [`copy`](variables.mdx#move-and-copy) operator and to copy values via references with [dereference `*e`](references.mdx#reading-and-writing-through-references). - -If a value has `copy`, all values contained inside of that value have `copy`. - -### `drop` - -The `drop` ability allows values of types with that ability to be dropped. By dropped, we mean that value is not transferred and is effectively destroyed as the Move program executes. As such, this ability gates the ability to ignore values in a multitude of locations, including: - -- not using the value in a local variable or parameter -- not using the value in a [sequence via `;`](variables.mdx#expression-blocks) -- overwriting values in variables in [assignments](variables.mdx#assignments) -- overwriting values via references when [writing `*e1 = e2`](references.mdx#reading-and-writing-through-references). - -If a value has `drop`, all values contained inside of that value have `drop`. - -### `store` - -The `store` ability allows values of types with this ability to exist inside a struct (resource) in global storage, _but_ not necessarily as a top-level resource in global storage. This is the only ability that does not directly gate an operation. Instead, it gates the existence in global storage when used in tandem with `key`. - -If a value has `store`, all values contained inside of that value have `store` - -### `key` - -The `key` ability allows the type to serve as a key for [global storage operations](global-storage-operators.mdx). It gates all global storage operations, so in order for a type to be used with `move_to`, `borrow_global`, `move_from`, etc., the type must have the `key` ability. Note that the operations still must be used in the module where the `key` type is defined (in a sense, the operations are private to the defining module). - -If a value has `key`, all values contained inside of that value have `store`. This is the only ability with this sort of asymmetry. - -## Builtin Types - -Most primitive, builtin types have `copy`, `drop`, and `store` except for `signer`, which just has `drop` - -- `bool`, `u8`, `u16`, `u32`, `u64`, `u128`, `u256`, and `address` all have `copy`, `drop`, and `store`. -- `signer` has `drop` - - Cannot be copied and cannot be put into global storage -- `vector` may have `copy`, `drop`, and `store` depending on the abilities of `T`. - - See [Conditional Abilities and Generic Types](#conditional-abilities-and-generic-types) for more details. -- Immutable references `&` and mutable references `&mut` both have `copy` and `drop`. - - This refers to copying and dropping the reference itself, not what they refer to. - - References cannot appear in global storage, hence they do not have `store`. - -None of the primitive types have `key`, meaning none of them can be used directly with the [global storage operations](global-storage-operators.mdx). - -## Annotating Structs - -To declare that a `struct` has an ability, it is declared with `has ` after the struct name but before the fields. For example: - -```move -module 0x42::example { - struct Ignorable has drop { f: u64 } - - struct Pair has copy, drop, store { x: u64, y: u64 } -} -``` - -In this case: `Ignorable` has the `drop` ability. `Pair` has `copy`, `drop`, and `store`. - -All of these abilities have strong guarantees over these gated operations. The operation can be performed on the value only if it has that ability; even if the value is deeply nested inside some other collection! - -As such: when declaring a struct’s abilities, certain requirements are placed on the fields. All fields must satisfy these constraints. These rules are necessary so that structs satisfy the reachability rules for the abilities given above. If a struct is declared with the ability... - -- `copy`, all fields must have `copy`. -- `drop`, all fields must have `drop`. -- `store`, all fields must have `store`. -- `key`, all fields must have `store`. - - `key` is the only ability currently that doesn't require itself. - -For example: - -```move -module 0x42::example { - // A struct without any abilities - struct NoAbilities {} - - struct WantsCopy has copy { - f: NoAbilities, // ERROR 'NoAbilities' does not have 'copy' - } -} -``` - -and similarly: - -```move -module 0x42::example { - // A struct without any abilities - struct NoAbilities {} - - struct MyResource has key { - f: NoAbilities, // Error 'NoAbilities' does not have 'store' - } -} -``` - -## Conditional Abilities and Generic Types - -When abilities are annotated on a generic type, not all instances of that type are guaranteed to have that ability. Consider this struct declaration: - -```move -module 0x42::example { - struct Cup has copy, drop, store, key { item: T } -} -``` - -It might be very helpful if `Cup` could hold any type, regardless of its abilities. The type system can _see_ the type parameter, so it should be able to remove abilities from `Cup` if it _sees_ a type parameter that would violate the guarantees for that ability. - -This behavior might sound a bit confusing at first, but it might be more understandable if we think about collection types. We could consider the builtin type `vector` to have the following type declaration: - -```move -vector has copy, drop, store; -``` - -We want `vector`s to work with any type. We don't want separate `vector` types for different abilities. So what are the rules we would want? Precisely the same that we would want with the field rules above. So, it would be safe to copy a `vector` value only if the inner elements can be copied. It would be safe to ignore a `vector` value only if the inner elements can be ignored/dropped. And, it would be safe to put a `vector` in global storage only if the inner elements can be in global storage. - -To have this extra expressiveness, a type might not have all the abilities it was declared with depending on the instantiation of that type; instead, the abilities a type will have depends on both its declaration **and** its type arguments. For any type, type parameters are pessimistically assumed to be used inside the struct, so the abilities are only granted if the type parameters meet the requirements described above for fields. Taking `Cup` from above as an example: - -- `Cup` has the ability `copy` only if `T` has `copy`. -- It has `drop` only if `T` has `drop`. -- It has `store` only if `T` has `store`. -- It has `key` only if `T` has `store`. - -Here are examples for this conditional system for each ability: - -### Example: conditional `copy` - -```move -module 0x42::example { - struct NoAbilities {} - - struct S has copy, drop { f: bool } - - struct Cup has copy, drop, store { item: T } - - fun example(c_x: Cup, c_s: Cup) { - // Valid, 'Cup' has 'copy' because 'u64' has 'copy' - let c_x2 = copy c_x; - // Valid, 'Cup' has 'copy' because 'S' has 'copy' - let c_s2 = copy c_s; - } - - fun invalid(c_account: Cup, c_n: Cup) { - // Invalid, 'Cup' does not have 'copy'. - // Even though 'Cup' was declared with copy, the instance does not have 'copy' - // because 'signer' does not have 'copy' - let c_account2 = copy c_account; - // Invalid, 'Cup' does not have 'copy' - // because 'NoAbilities' does not have 'copy' - let c_n2 = copy c_n; - } -} -``` - -### Example: conditional `drop` - -```move -module 0x42::example { - struct NoAbilities {} - - struct S has copy, drop { f: bool } - - struct Cup has copy, drop, store { item: T } - - fun unused() { - Cup { item: true }; // Valid, 'Cup' has 'drop' - Cup { item: S { f: false } }; // Valid, 'Cup' has 'drop' - } - - fun left_in_local(c_account: Cup): u64 { - let c_b = Cup { item: true }; - let c_s = Cup { item: S { f: false } }; - // Valid return: 'c_account', 'c_b', and 'c_s' have values - // but 'Cup', 'Cup', and 'Cup' have 'drop' - 0 - } - - fun invalid_unused() { - // Invalid, Cannot ignore 'Cup' because it does not have 'drop'. - // Even though 'Cup' was declared with 'drop', the instance does not have 'drop' - // because 'NoAbilities' does not have 'drop' - Cup { item: NoAbilities {} }; - } - - fun invalid_left_in_local(): u64 { - let c_n = Cup { item: NoAbilities {} }; - // Invalid return: 'c_n' has a value - // and 'Cup' does not have 'drop' - 0 - } -} -``` - -### Example: conditional `store` - -```move -module 0x42::example { - struct Cup has copy, drop, store { item: T } - - // 'MyInnerResource' is declared with 'store' so all fields need 'store' - struct MyInnerResource has store { - yes: Cup, - // Valid, 'Cup' has 'store' - // no: Cup, Invalid, 'Cup' does not have 'store' - } - - // 'MyResource' is declared with 'key' so all fields need 'store' - struct MyResource has key { - yes: Cup, - // Valid, 'Cup' has 'store' - inner: Cup, - // Valid, 'Cup' has 'store' - // no: Cup, Invalid, 'Cup' does not have 'store' - } -} -``` - -### Example: conditional `key` - -```move -module 0x42::example { - struct NoAbilities {} - - struct MyResource has key { f: T } - - fun valid(account: &signer) acquires MyResource { - let addr = signer::address_of(account); - // Valid, 'MyResource' has 'key' - let has_resource = exists>(addr); - if (!has_resource) { - // Valid, 'MyResource' has 'key' - move_to(account, MyResource { f: 0 }) - }; - // Valid, 'MyResource' has 'key' - let r = borrow_global_mut>(addr) - r.f = r.f + 1; - } - - fun invalid(account: &signer) { - // Invalid, 'MyResource' does not have 'key' - let has_it = exists>(addr); - // Invalid, 'MyResource' does not have 'key' - let NoAbilities {} = move_from(addr); - // Invalid, 'MyResource' does not have 'key' - move_to(account, NoAbilities {}); - // Invalid, 'MyResource' does not have 'key' - borrow_global(addr); - } -} -``` diff --git a/apps/nextra/pages/zh/build/smart-contracts/book/abort-and-assert.mdx b/apps/nextra/pages/zh/build/smart-contracts/book/abort-and-assert.mdx deleted file mode 100644 index 152b3f754..000000000 --- a/apps/nextra/pages/zh/build/smart-contracts/book/abort-and-assert.mdx +++ /dev/null @@ -1,220 +0,0 @@ -# Abort and Assert - -[`return`](functions.mdx) and `abort` are two control flow constructs that end execution, one for -the current function and one for the entire transaction. - -More information on [`return` can be found in the linked section](functions.mdx) - -## `abort` - -`abort` is an expression that takes one argument: an **abort code** of type `u64`. For example: - -```move -abort 42 -``` - -The `abort` expression halts execution of the current function and reverts all changes made to global -state by the current transaction. There is no mechanism for "catching" or otherwise handling an -`abort`. - -Luckily, in Move transactions are all or nothing, meaning any changes to global storage are made all -at once only if the transaction succeeds. Because of this transactional commitment of changes, after -an abort there is no need to worry about backing out changes. While this approach is lacking in -flexibility, it is incredibly simple and predictable. - -Similar to [`return`](functions.mdx), `abort` is useful for exiting control flow when some -condition cannot be met. - -In this example, the function will pop two items off of the vector, but will abort early if the -vector does not have two items - -```move -script { - use std::vector; - fun pop_twice(v: &mut vector): (T, T) { - if (vector::length(v) < 2) abort 42; - - (vector::pop_back(v), vector::pop_back(v)) - } -} -``` - -This is even more useful deep inside a control-flow construct. For example, this function checks -that all numbers in the vector are less than the specified `bound`. And aborts otherwise - -```move -script { - use std::vector; - fun check_vec(v: &vector, bound: u64) { - let i = 0; - let n = vector::length(v); - while (i < n) { - let cur = *vector::borrow(v, i); - if (cur > bound) abort 42; - i = i + 1; - } - } -} -``` - -### `assert` - -`assert` is a builtin, macro-like operation provided by the Move compiler. It takes two arguments, a -condition of type `bool` and a code of type `u64` - -```move -assert!(condition: bool, code: u64) -assert!(condition: bool) // Since Move 2.0 -``` - -Since the operation is a macro, it must be invoked with the `!`. This is to convey that the -arguments to `assert` are call-by-expression. In other words, `assert` is not a normal function and -does not exist at the bytecode level. It is replaced inside the compiler with - -```move -if (condition) () else abort code -``` - -Since Move 2.0, `assert` without an error code is supported. If this assert is used, the -abort code `0xCA26CBD9BE0B0000` is generated. In terms of the `std::error` convention, this code has -category `std::error::INTERNAL` and reason `0`. - -`assert` is more commonly used than just `abort` by itself. The `abort` examples above can be -rewritten using `assert` - -```move -script { - use std::vector; - fun pop_twice(v: &mut vector): (T, T) { - assert!(vector::length(v) >= 2, 42); // Now uses 'assert' - - (vector::pop_back(v), vector::pop_back(v)) - } -} -``` - -and - -```move -script { - use std::vector; - fun check_vec(v: &vector, bound: u64) { - let i = 0; - let n = vector::length(v); - while (i < n) { - let cur = *vector::borrow(v, i); - assert!(cur <= bound, 42); // Now uses 'assert' - i = i + 1; - } - } -} -``` - -Note that because the operation is replaced with this `if-else`, the argument for the `code` is not -always evaluated. For example: - -```move -assert!(true, 1 / 0) -``` - -Will not result in an arithmetic error, it is equivalent to - -```move -if (true) () else (1 / 0) -``` - -So the arithmetic expression is never evaluated! - -### Abort codes in the Move VM - -When using `abort`, it is important to understand how the `u64` code will be used by the VM. - -Normally, after successful execution, the Move VM produces a change-set for the changes made to -global storage (added/removed resources, updates to existing resources, etc.). - -If an `abort` is reached, the VM will instead indicate an error. Included in that error will be two -pieces of information: - -- The module that produced the abort (address and name) -- The abort code. - -For example - -```move -module 0x42::example { - public fun aborts() { - abort 42 - } -} - -script { - fun always_aborts() { - 0x2::example::aborts() - } -} -``` - -If a transaction, such as the script `always_aborts` above, calls `0x2::example::aborts`, the VM -would produce an error that indicated the module `0x2::example` and the code `42`. - -This can be useful for having multiple aborts being grouped together inside a module. - -In this example, the module has two separate error codes used in multiple functions - -```move -module 0x42::example { - - use std::vector; - - const EMPTY_VECTOR: u64 = 0; - const INDEX_OUT_OF_BOUNDS: u64 = 1; - - // move i to j, move j to k, move k to i - public fun rotate_three(v: &mut vector, i: u64, j: u64, k: u64) { - let n = vector::length(v); - assert!(n > 0, EMPTY_VECTOR); - assert!(i < n, INDEX_OUT_OF_BOUNDS); - assert!(j < n, INDEX_OUT_OF_BOUNDS); - assert!(k < n, INDEX_OUT_OF_BOUNDS); - - vector::swap(v, i, k); - vector::swap(v, j, k); - } - - public fun remove_twice(v: &mut vector, i: u64, j: u64): (T, T) { - let n = vector::length(v); - assert!(n > 0, EMPTY_VECTOR); - assert!(i < n, INDEX_OUT_OF_BOUNDS); - assert!(j < n, INDEX_OUT_OF_BOUNDS); - assert!(i > j, INDEX_OUT_OF_BOUNDS); - - (vector::remove(v, i), vector::remove(v, j)) - } -} -``` - -## The type of `abort` - -The `abort i` expression can have any type! This is because both constructs break from the normal -control flow, so they never need to evaluate to the value of that type. - -The following are not useful, but they will type check - -```move -let y: address = abort 0; -``` - -This behavior can be helpful in situations where you have a branching instruction that produces a -value on some branches, but not all. For example: - -```move -script { - fun example() { - let b = - if (x == 0) false - else if (x == 1) true - else abort 42; - // ^^^^^^^^ `abort 42` has type `bool` - } -} -``` diff --git a/apps/nextra/pages/zh/build/smart-contracts/book/address.mdx b/apps/nextra/pages/zh/build/smart-contracts/book/address.mdx deleted file mode 100644 index ab2549fea..000000000 --- a/apps/nextra/pages/zh/build/smart-contracts/book/address.mdx +++ /dev/null @@ -1,77 +0,0 @@ -# Address - -`address` is a built-in type in Move that is used to represent locations (sometimes called accounts) in global storage. An `address` value is a 256-bit (32-byte) identifier. At a given address, two things can be stored: [Modules](modules-and-scripts.mdx) and [Resources](structs-and-resources.mdx). - -Although an `address` is a 256-bit integer under the hood, Move addresses are intentionally opaque---they cannot be created from integers, they do not support arithmetic operations, and they cannot be modified. Even though there might be interesting programs that would use such a feature (e.g., pointer arithmetic in C fills a similar niche), Move does not allow this dynamic behavior because it has been designed from the ground up to support static verification. - -You can use runtime address values (values of type `address`) to access resources at that address. You _cannot_ access modules at runtime via address values. - -## Addresses and Their Syntax - -Addresses come in two flavors, named or numerical. The syntax for a named address follows the -same rules for any named identifier in Move. The syntax of a numerical address is not restricted -to hex-encoded values, and any valid [`u256` numerical value](integers.mdx) can be used as an -address value, e.g., `42`, `0xCAFE`, and `2021` are all valid numerical address -literals. - -To distinguish when an address is being used in an expression context or not, the -syntax when using an address differs depending on the context where it's used: - -- When an address is used as an expression the address must be prefixed by the `@` character, i.e., [`@`](integers.mdx) or `@`. -- Outside of expression contexts, the address may be written without the leading `@` character, i.e., [``](integers.mdx) or ``. - -In general, you can think of `@` as an operator that takes an address from being a namespace item to being an expression item. - -## Named Addresses - -Named addresses are a feature that allow identifiers to be used in place of -numerical values in any spot where addresses are used, and not just at the -value level. Named addresses are declared and bound as top level elements -(outside of modules and scripts) in Move Packages, or passed as arguments -to the Move compiler. - -Named addresses only exist at the source language level and will be fully -substituted for their value at the bytecode level. Because of this, modules -and module members _must_ be accessed through the module's named address -and not through the numerical value assigned to the named address during -compilation, e.g., `use my_addr::foo` is _not_ equivalent to `use 0x2::foo` -even if the Move program is compiled with `my_addr` set to `0x2`. This -distinction is discussed in more detail in the section on [Modules and Scripts](modules-and-scripts.mdx). - -### Examples - -```move -script { - fun example() { - let a1: address = @0x1; // shorthand for 0x0000000000000000000000000000000000000000000000000000000000000001 - let a2: address = @0x42; // shorthand for 0x0000000000000000000000000000000000000000000000000000000000000042 - let a3: address = @0xDEADBEEF; // shorthand for 0x00000000000000000000000000000000000000000000000000000000DEADBEEF - let a4: address = @0x000000000000000000000000000000000000000000000000000000000000000A; - let a5: address = @std; // Assigns `a5` the value of the named address `std` - let a6: address = @66; - let a7: address = @0x42; - } -} - -module 66::some_module { // Not in expression context, so no @ needed - use 0x1::other_module; // Not in expression context so no @ needed - use std::vector; // Can use a named address as a namespace item when using other modules - ... -} - -module std::other_module { // Can use a named address as a namespace item to declare a module - ... -} -``` - -## Global Storage Operations - -The primary purpose of `address` values are to interact with the global storage operations. - -`address` values are used with the `exists`, `borrow_global`, `borrow_global_mut`, and `move_from` [operations](global-storage-operators.mdx). - -The only global storage operation that _does not_ use `address` is `move_to`, which uses [`signer`](signer.mdx). - -## Ownership - -As with the other scalar values built-in to the language, `address` values are implicitly copyable, meaning they can be copied without an explicit instruction such as [`copy`](variables.mdx#move-and-copy). diff --git a/apps/nextra/pages/zh/build/smart-contracts/book/bool.mdx b/apps/nextra/pages/zh/build/smart-contracts/book/bool.mdx deleted file mode 100644 index b25a489b0..000000000 --- a/apps/nextra/pages/zh/build/smart-contracts/book/bool.mdx +++ /dev/null @@ -1,33 +0,0 @@ -# Bool - -`bool` is Move's primitive type for boolean `true` and `false` values. - -## Literals - -Literals for `bool` are either `true` or `false`. - -## Operations - -### Logical - -`bool` supports three logical operations: - -| Syntax | Description | Equivalent Expression | -| ------------------------- | ---------------------------- | ------------------------------------------------------------------- | -| `&&` | short-circuiting logical and | `p && q` is equivalent to `if (p) q else false` | -| || | short-circuiting logical or | p || q is equivalent to `if (p) true else q` | -| `!` | logical negation | `!p` is equivalent to `if (p) false else true` | - -### Control Flow - -`bool` values are used in several of Move's control-flow constructs: - -- [`if (bool) { ... }`](conditionals.mdx) -- [`while (bool) { .. }`](loops.mdx) -- [`assert!(bool, u64)`](abort-and-assert.mdx) - -## Ownership - -As with the other scalar values built into the language, boolean values are implicitly copyable, -meaning they can be copied without an explicit instruction such as -[`copy`](variables.mdx#move-and-copy). diff --git a/apps/nextra/pages/zh/build/smart-contracts/book/coding-conventions.mdx b/apps/nextra/pages/zh/build/smart-contracts/book/coding-conventions.mdx deleted file mode 100644 index 477516c4b..000000000 --- a/apps/nextra/pages/zh/build/smart-contracts/book/coding-conventions.mdx +++ /dev/null @@ -1,117 +0,0 @@ -# Move Coding Conventions - -This section lays out some basic coding conventions for Move that the Move team has found helpful. These are only recommendations, and you should feel free to use other formatting guidelines and conventions if you have a preference for them. - -## Naming - -- **Module names**: should be lowercase snake case, e.g., `fixed_point32`, `vector`. -- **Type names**: should be camel case if they are not a native type, e.g., `Coin`, `RoleId`. -- **Function names**: should be lowercase snake case, e.g., `destroy_empty`. -- **Constant names**: should be upper camel case and begin with an `E` if they represent error codes (e.g., `EIndexOutOfBounds`) and upper snake case if they represent a non-error value (e.g., `MIN_STAKE`). -- -- **Generic type names**: should be descriptive, or anti-descriptive where appropriate, e.g., `T` or `Element` for the Vector generic type parameter. Most of the time the "main" type in a module should be the same name as the module e.g., `option::Option`, `fixed_point32::FixedPoint32`. -- **Module file names**: should be the same as the module name e.g., `option.move`. -- **Script file names**: should be lowercase snake case and should match the name of the "main" function in the script. -- **Mixed file names**: If the file contains multiple modules and/or scripts, the file name should be lowercase snake case, where the name does not match any particular module/script inside. - -## Imports - -- All module `use` statements should be at the top of the module. -- Functions should be imported and used fully qualified from the module in which they are declared, and not imported at the top level. -- Types should be imported at the top-level. Where there are name clashes, `as` should be used to rename the type locally as appropriate. - -For example, if there is a module: - -```move -module 0x1::foo { - struct Foo { } - const CONST_FOO: u64 = 0; - public fun do_foo(): Foo { Foo{} } - // ... -} -``` - -this would be imported and used as: - -```move -module 0x1::bar { - use 0x1::foo::{Self, Foo}; - - public fun do_bar(x: u64): Foo { - if (x == 10) { - foo::do_foo() - } else { - abort 0 - } - } - // ... -} -``` - -And, if there is a local name-clash when importing two modules: - -```move -module 0x1::other_foo { - struct Foo {} - // ... -} - -module 0x1::importer { - use 0x1::other_foo::Foo as OtherFoo; - use 0x1::foo::Foo; - // ... -} -``` - -## Comments - -- Each module, struct, and public function declaration should be commented. -- Move has doc comments `///`, regular single-line comments `//`, block comments `/* */`, and block doc comments `/** */`. - -### Comments Example - -Doc comments must be directly above the item they are commenting on. For example, the following is valid: -```move -/// My awesome module, doc comment can be used here -module 0x42::example { // double slash can be anywhere - - // Double slash can be anywhere - - /// My awesome constant - const MY_VALUE: u64 = 5; - - /// My awesome error message - const E_MY_ERROR: u64 = 10; - - #[view] - /// My awesome view function - fun show_me_the_money() { - // ... - } - - /* Similarly block comments can be anywhere */ -} -``` - -Below here are examples of doc comments `///` that will fail -```move -module 0x42::example { - - /// My awesome view function <- must be below the annotation, right above the thing commented - #[view] - fun show_me_the_money() { - // ... - /// Within a function - } - - /// Not attached to anything -} -``` - -## Formatting - -The Move team plans to write an auto-formatter to enforce formatting conventions. However, in the meantime: - -- Four space indentation should be used except for `script` and `address` blocks whose contents should not be indented. -- Lines should be broken if they are longer than 100 characters. -- Structs and constants should be declared before all functions in a module. diff --git a/apps/nextra/pages/zh/build/smart-contracts/book/conditionals.mdx b/apps/nextra/pages/zh/build/smart-contracts/book/conditionals.mdx deleted file mode 100644 index 0cceff0bc..000000000 --- a/apps/nextra/pages/zh/build/smart-contracts/book/conditionals.mdx +++ /dev/null @@ -1,87 +0,0 @@ -# Conditionals - -An `if` expression specifies that some code should only be evaluated if a certain condition is true. For example: - -```move -script { - fun example() { - if (x > 5) x = x - 5 - } -} -``` - -The condition must be an expression of type `bool`. - -An `if` expression can optionally include an `else` clause to specify another expression to evaluate when the condition is false. - -```move -script { - fun example() { - if (y <= 10) y = y + 1 else y = 10 - } -} -``` - -Either the "true" branch or the "false" branch will be evaluated, but not both. Either branch can be a single expression or an expression block. - -The conditional expressions may produce values so that the `if` expression has a result. - -```move -script { - fun example() { - let z = if (x < 100) x else 100; - } -} -``` - -The expressions in the true and false branches must have compatible types. For example: - -```move -script { - fun example() { - // x and y must be u64 integers - let maximum: u64 = if (x > y) x else y; - - // ERROR! branches different types - let z = if (maximum < 10) 10u8 else 100u64; - - // ERROR! branches different types, as default false-branch is () not u64 - if (maximum >= 10) maximum; - } -} -``` - -If the `else` clause is not specified, the false branch defaults to the unit value. The following are equivalent: - -```move -script { - fun example() { - if (condition) true_branch // implied default: else () - if (condition) true_branch else () - } -} -``` - -Commonly, [`if` expressions](conditionals.mdx) are used in conjunction with expression blocks. - -```move -script { - fun example() { - let maximum = if (x > y) x else y; - if (maximum < 10) { - x = x + 10; - y = y + 10; - } else if (x >= 10 && y >= 10) { - x = x - 10; - y = y - 10; - } - } -} - -``` - -## Grammar for Conditionals - -> _if-expression_ → **if (** _expression_ **)** _expression_ _else-clause__opt_ - -> _else-clause_ → **else** _expression_ diff --git a/apps/nextra/pages/zh/build/smart-contracts/book/constants.mdx b/apps/nextra/pages/zh/build/smart-contracts/book/constants.mdx deleted file mode 100644 index 50b46f128..000000000 --- a/apps/nextra/pages/zh/build/smart-contracts/book/constants.mdx +++ /dev/null @@ -1,114 +0,0 @@ -# Constants - -Constants are a way of giving a name to shared, static values inside of a `module` or `script`. - -The constant's must be known at compilation. The constant's value is stored in the compiled module -or script. And each time the constant is used, a new copy of that value is made. - -## Declaration - -Constant declarations begin with the `const` keyword, followed by a name, a type, and a value. They -can exist in either a script or module - -```text -const : = ; -``` - -For example - -```move -script { - const MY_ERROR_CODE: u64 = 0; - - fun main(input: u64) { - assert!(input > 0, MY_ERROR_CODE); - } -} - -module 0x42::example { - const MY_ADDRESS: address = @0x42; - - public fun permissioned(s: &signer) { - assert!(std::signer::address_of(s) == MY_ADDRESS, 0); - } -} -``` - -## Naming - -Constants must start with a capital letter `A` to `Z`. After the first letter, constant names can -contain underscores `_`, letters `a` to `z`, letters `A` to `Z`, or digits `0` to `9`. - -```move -script { - const FLAG: bool = false; - const MY_ERROR_CODE: u64 = 0; - const ADDRESS_42: address = @0x42; -} -``` - -Even though you can use letters `a` to `z` in a constant. The -[general style guidelines](coding-conventions.mdx) are to use just uppercase letters `A` to `Z`, -with underscores `_` between each word. - -This naming restriction of starting with `A` to `Z` is in place to give room for future language -features. It may or may not be removed later. - -## Visibility - -`public` constants are not currently supported. `const` values can be used only in the declaring -module. - -## Valid Expressions - -Currently, constants are limited to the primitive types `bool`, `u8`, `u16`, `u32`, `u64`, `u128`, `u256`, `address`, and -`vector`. Future support for other `vector` values (besides the "string"-style literals) will -come later. - -### Values - -Commonly, `const`s are assigned a simple value, or literal, of their type. For example - -```move -script { - const MY_BOOL: bool = false; - const MY_ADDRESS: address = @0x70DD; - const BYTES: vector = b"hello world"; - const HEX_BYTES: vector = x"DEADBEEF"; -} -``` - -### Complex Expressions - -In addition to literals, constants can include more complex expressions, as long as the compiler is -able to reduce the expression to a value at compile time. - -Currently, equality operations, all boolean operations, all bitwise operations, and all arithmetic -operations can be used. - -```move -script { - const RULE: bool = true && false; - const CAP: u64 = 10 * 100 + 1; - const SHIFTY: u8 = { - (1 << 1) * (1 << 2) * (1 << 3) * (1 << 4) - }; - const HALF_MAX: u128 = 340282366920938463463374607431768211455 / 2; - const REM: u256 = 57896044618658097711785492504343953926634992332820282019728792003956564819968 % 654321; - const EQUAL: bool = 1 == 1; -} -``` - -If the operation results in a runtime exception, the compiler will give an error that it is -unable to generate the constant's value - -```move -script { - const DIV_BY_ZERO: u64 = 1 / 0; // error! - const SHIFT_BY_A_LOT: u64 = 1 << 100; // error! - const NEGATIVE_U64: u64 = 0 - 1; // error! -} -``` - -Note that constants cannot currently refer to other constants. This feature, along with support for -other expressions, will be added in the future. diff --git a/apps/nextra/pages/zh/build/smart-contracts/book/enums.mdx b/apps/nextra/pages/zh/build/smart-contracts/book/enums.mdx deleted file mode 100644 index b241e0f28..000000000 --- a/apps/nextra/pages/zh/build/smart-contracts/book/enums.mdx +++ /dev/null @@ -1,237 +0,0 @@ -# Enums - -_Since language version 2.0_ - -Enum types are similar to struct types but support defining multiple *variants* of the data layout. Each variant has its distinct set of fields. Enum variants are supported in expressions, tools for testing, matching, and deconstructing. - - -## Declaration of Enum Types - -An enum type declaration lists the number of different variants, as seen in the example below: - -```move -enum Shape { - Circle{radius: u64}, - Rectangle{width: u64, height: u64} -} -``` - -There can be zero or more fields for an enum variant. If no arguments are given, the braces can also be omitted, declaring simple values: - -```move -enum Color { - Red, Blue, Green -} -``` - -Like struct types, enum types can have abilities. For example, the `Color` enum type would be appropriately declared as copyable, droppable, and storable, like primitive number types: - -```move -enum Color has copy, drop, store, key { Red, Blue, Green } -``` - -Enum types can also have the `key` ability and appear as roots of data in global storage. A common usage of enums in this context is versioning of data: - -```move -enum VersionedData has key { - V1{name: String} - V2{name: String, age: u64} -} -``` - -Similar to structs, enum types can be generic and take positional arguments. For example, the type below represents a generic result type, where the variant constructors use positional instead of named arguments (see also [positional structs](structs-and-resources.mdx#positional-structs)). - -```move -enum Result has copy, drop, store { - Err(u64), - Ok(T) -} -``` - -## Constructing Enum Values - -An enum value is constructed similarly to a struct value: - -```move -let s: String; -let data = VersionedData::V1{name: s}; -``` - -If the enum variant has no fields, the braces can also be omitted: - -```move -let color = Color::Blue; -``` - -## Name Resolution for Enum Variants - -The variant names for an enum need to be qualified by the enum type name, as in `VersionedData::V1`. - -> Note: Aliasing via the `use` clause is currently not supported for enum variants, but will be added in later language versions - -In certain cases (such as match expressions, below), the Move compiler can infer the enum type from the context, and the qualification by the type name may be omitted: - -```move -fun f(data: VersionedData) { - match (data) { V1{..} => .., ..} // simple variant name OK -} -``` - -## Matching Enum Values - -The value of an enum value can be inspected using a match expression. For example: - -```move -fun area(self: &Shape): u64 { - match (self) { - Circle{radius} => mul_with_pi(*radius * *radius), - Rectangle{width, height} => *width * *height - } -} -``` - -Notice above that the value matched is an immutable reference to an enum value. A match expression can also consume a value, or match over a mutable reference for interior updates: - -```move -fun scale_radius(self: &mut Shape, factor: u64) { - match (self) { - Circle{radius: r} => *r = *r * factor, - _ => {} // do nothing if not a Circle - } -} -``` - -The patterns provided in the match expression are evaluated sequentially, in order of textual occurrence, until a match is found. It is a compile time error if not all known patterns are covered. - -Patterns can be nested and contain conditions, as in the following example: - -```move -let r : Result> = Ok(Err(42)); -let v = match (r)) { - Ok(Err(c)) if c < 42 => 0, - Ok(Err(c)) if c >= 42 => 1, - Ok(_) => 2, - _ => 3 -}; -assert!(v == 1); -``` - -Notice that in the above example, the last match clause (`_`) covers both patterns `Ok(Err(_))` and `Err(_)`. Although at execution time, the earlier clauses match `Ok(Err(c))` for all values of `c`, the compiler cannot be sure all cases are covered due to the conditionals: conditions in match expressions are not considered when tracking coverage. Thus the first two clauses in the match expression above are not sufficient for match completeness, and an additional clause is required to avoid a compiler error. - -## Testing Enum Variants - -With the `is` operator, one can examine whether a given enum value is of a given variant: - -```move -let data: VersionedData; -if (data is VersionedData::V1) { .. } -``` - -The operator allows specifying a list of variants, separated by "`|`" characters. The variants need not be qualified by the enum name if the type of the expression being tested is known: - -```move -assert!(data is V1|V2); -``` - -## Selecting From Enum Values - -It is possible to directly select a field from an enum value. Recall the definition of versioned data: - -```move -enum VersionedData has key { - V1{name: String} - V2{name: String, age: u64} -} -``` - -One can write code as below to directly select the fields of variants: - -```move -let s: String; -let data1 = VersionedData::V1{name: s}; -let data2 = VersionedData::V2{name: s, age: 20}; -assert!(data1.name == data2.name) -assert!(data2.age == 20); -``` - -Notice that field selection aborts if the enum value has no variant with the given field. This is the case for `data1.age`. -The abort code used for this case is `0xCA26CBD9BE0B0001`. In terms of the `std::error` convention, this code has -category `std::error::INTERNAL` and reason `1`. - -Field selection is only possible if the field is uniquely named and typed throughout all variants. Thus, the following yields a compile time error: - -```move -enum VersionedData has key { - V1{name: String} - V2{name: u64} -} - -data.name - // ^^^^^ compile time error that `name` field selection is ambiguous -``` - -## Using Enums Patterns in Lets - -An enum variant pattern may be used in a `let` statement: - -```move -let data: VersionData; -let V1{name} = data; -``` - -Unpacking the enum value will abort if the variant is not the expected one. To ensure that all variants of an enum are handled, a `match` expression is recommended instead of a `let`. The `match` is checked at compile time, ensuring that all variants are covered. In some cases, tools like the Move Prover can be used to verify that unexpected aborts cannot happen with a `let`. - -## Destroying Enums via Pattern Matching - -Similar to struct values, enum values can be destroyed by explicitly unpacking them. Enums can be unpacked with pattern matching in a `match` expression, enum pattern in a `let` binding, or enum pattern in an assignment. - -```move -// Note: `Shape` has no `drop` ability, so must be destroyed with explicit unpacking. -enum Shape { - Circle{radius: u64}, - Rectangle{width: u64, height: u64} -} - -fun destroy_empty(self: Shape) { - match (self) { - Shape::Circle{radius} => assert!(radius == 0), - Shape::Rectangle{width, height: _} => assert!(width == 0), - } -} - -fun example_destroy_shapes() { - let c = Shape::Circle{radius: 0}; - let r = Shape::Rectangle{width: 0, height: 0}; - c.destroy_empty(); - r.destroy_empty(); -} -``` - -## Enum Type Upgrade Compatibility - -An enum type can be upgraded by another enum type if the new type only adds new variants at the end of the variant list. All variants present in the old enum type must also appear in the new type, in the same order and starting from the beginning. Consider the `VersionedData` type, which might have begun with a single version: - - -```move -enum VersionedData has key { - V1{name: String} -} -``` - -This type could be upgraded to the version we used so far in this text: - -```move -enum VersionedData has key { - V1{name: String} - V2{name: String, age: u64} -} -``` - -The following upgrade would not be allowed, since the order of variants must be preserved: - -```move -enum VersionedData has key { - V2{name: String, age: u64} // not a compatible upgrade - V1{name: String} -} -``` diff --git a/apps/nextra/pages/zh/build/smart-contracts/book/equality.mdx b/apps/nextra/pages/zh/build/smart-contracts/book/equality.mdx deleted file mode 100644 index 97bc9426e..000000000 --- a/apps/nextra/pages/zh/build/smart-contracts/book/equality.mdx +++ /dev/null @@ -1,186 +0,0 @@ -# Equality - -Move supports two equality operations `==` and `!=` - -## Operations - -| Syntax | Operation | Description | -| ------ | --------- | --------------------------------------------------------------------------- | -| `==` | equal | Returns `true` if the two operands have the same value, `false` otherwise | -| `!=` | not equal | Returns `true` if the two operands have different values, `false` otherwise | - -### Typing - -Both the equal (`==`) and not-equal (`!=`) operations only work if both operands are the same type - -```move -script { - fun example() { - 0 == 0; // `true` - 1u128 == 2u128; // `false` - b"hello" != x"00"; // `true` - } -} -``` - -Equality and non-equality also work over user defined types! - -```move -module 0x42::example { - struct S has copy, drop { f: u64, s: vector } - - fun always_true(): bool { - let s = S { f: 0, s: b"" }; - // parens are not needed but added for clarity in this example - (copy s) == s - } - - fun always_false(): bool { - let s = S { f: 0, s: b"" }; - // parens are not needed but added for clarity in this example - (copy s) != s - } -} -``` - -If the operands have different types, there is a type checking error - -```move -script { - fun example() { - 1u8 == 1u128; // ERROR! - // ^^^^^ expected an argument of type 'u8' - b"" != 0; // ERROR! - // ^ expected an argument of type 'vector' - } -} -``` - -### Typing with references - -When comparing [references](references.mdx), the type of the reference (immutable or mutable) does -not matter. This means that you can compare an immutable `&` reference with a mutable one `&mut` of -the same underlying type. - -```move -script { - fun example() { - let i = &0; - let m = &mut 1; - - i == m; // `false` - m == i; // `false` - m == m; // `true` - i == i; // `true` - } -} -``` - -The above is equivalent to applying an explicit freeze to each mutable reference where needed - -```move -script { - fun example() { - let i = &0; - let m = &mut 1; - - i == freeze(m); // `false` - freeze(m) == i; // `false` - m == m; // `true` - i == i; // `true` - } -} -``` - -But again, the underlying type must be the same type - -```move -script { - fun example() { - let i = &0; - let s = &b""; - - i == s; // ERROR! - // ^ expected an argument of type '&u64' - } -} -``` - -## Restrictions - -Both `==` and `!=` consume the value when comparing them. As a result, the type system enforces that -the type must have [`drop`](abilities.mdx). Recall that without the -[`drop` ability](abilities.mdx), ownership must be transferred by the end of the function, and such -values can only be explicitly destroyed within their declaring module. If these were used directly -with either equality `==` or non-equality `!=`, the value would be destroyed which would break -[`drop` ability](abilities.mdx) safety guarantees! - -```move -module 0x42::example { - struct Coin has store { value: u64 } - fun invalid(c1: Coin, c2: Coin) { - c1 == c2 // ERROR! -// ^^ ^^ These resources would be destroyed! - } -} -``` - -But, a programmer can _always_ borrow the value first instead of directly comparing the value, and -reference types have the [`drop` ability](abilities.mdx). For example - -```move -module 0x42::example { - struct Coin has store { value: u64 } - fun swap_if_equal(c1: Coin, c2: Coin): (Coin, Coin) { - let are_equal = &c1 == &c2; // valid - if (are_equal) (c2, c1) else (c1, c2) - } -} -``` - -## Avoid Extra Copies - -While a programmer _can_ compare any value whose type has [`drop`](abilities.mdx), a programmer -should often compare by reference to avoid expensive copies. - -```move -script { - fun example() { - let v1: vector = function_that_returns_vector(); - let v2: vector = function_that_returns_vector(); - assert!(copy v1 == copy v2, 42); - // ^^^^ ^^^^ - use_two_vectors(v1, v2); - - let s1: Foo = function_that_returns_large_struct(); - let s2: Foo = function_that_returns_large_struct(); - assert!(copy s1 == copy s2, 42); - // ^^^^ ^^^^ - use_two_foos(s1, s2); - } -} -``` - -This code is perfectly acceptable (assuming `Foo` has [`drop`](abilities.mdx)), just not efficient. -The highlighted copies can be removed and replaced with borrows - -```move -script { - fun example() { - let v1: vector = function_that_returns_vector(); - let v2: vector = function_that_returns_vector(); - assert!(&v1 == &v2, 42); - // ^ ^ - use_two_vectors(v1, v2); - - let s1: Foo = function_that_returns_large_struct(); - let s2: Foo = function_that_returns_large_struct(); - assert!(&s1 == &s2, 42); - // ^ ^ - use_two_foos(s1, s2); - } -} -``` - -The efficiency of the `==` itself remains the same, but the `copy`s are removed and thus the program -is more efficient. diff --git a/apps/nextra/pages/zh/build/smart-contracts/book/friends.mdx b/apps/nextra/pages/zh/build/smart-contracts/book/friends.mdx deleted file mode 100644 index b835cf638..000000000 --- a/apps/nextra/pages/zh/build/smart-contracts/book/friends.mdx +++ /dev/null @@ -1,126 +0,0 @@ -# Friends - -The `friend` syntax is used to declare modules that are trusted by the current module. -A trusted module is allowed to call any function defined in the current module that have the `public(friend)` visibility. -For details on function visibilities, please refer to the _Visibility_ section in [Functions](functions.mdx). - -## Friend declaration - -A module can declare other modules as friends via friend declaration statements, in the format of - -- `friend ` — friend declaration using fully qualified module name like the example below, or - - ```move - module 0x42::a { - friend 0x42::b; - } - ``` - -- `friend ` — friend declaration using a module name alias, where the module alias is introduced via the `use` statement. - - ```move - module 0x42::a { - use 0x42::b; - friend b; - } - ``` - -A module may have multiple friend declarations, and the union of all the friend modules forms the friend list. -In the example below, both `0x42::B` and `0x42::C` are considered as friends of `0x42::A`. - -```move -module 0x42::a { - friend 0x42::b; - friend 0x42::c; -} -``` - -Unlike `use` statements, `friend` can only be declared in the module scope and not in the expression block scope. -`friend` declarations may be located anywhere a top-level construct (e.g., `use`, `function`, `struct`, etc.) is allowed. -However, for readability, it is advised to place friend declarations near the beginning of the module definition. - -Note that the concept of friendship does not apply to Move scripts: - -- A Move script cannot declare `friend` modules as doing so is considered meaningless: there is no mechanism to call the function defined in a script. -- A Move module cannot declare `friend` scripts as well because scripts are ephemeral code snippets that are never published to global storage. - -### Friend declaration rules - -Friend declarations are subject to the following rules: - -- A module cannot declare itself as a friend. - - ```move - module 0x42::m { - friend Self; // ERROR! - // ^^^^ Cannot declare the module itself as a friend - } - - module 0x43::m { - friend 0x43::M; // ERROR - // ^^^^^^^ Cannot declare the module itself as a friend - } - ``` - -- Friend modules must be known by the compiler - - ```move - module 0x42::m { - friend 0x42::nonexistent; // ERROR! - // ^^^^^^^^^^^^^^^^^ Unbound module '0x42::nonexistent' - } - ``` - -- Friend modules must be within the same account address. (Note: this is not a technical requirement but rather a policy decision which _may_ be relaxed later.) - - ```move - module 0x42::m {} - - module 0x43::n { - friend 0x42::m; // ERROR! - // ^^^^^^^ Cannot declare modules out of the current address as a friend - } - ``` - -- Friends relationships cannot create cyclic module dependencies. - - Cycles are not allowed in the friend relationships, e.g., the relation `0x2::a` friends `0x2::b` friends `0x2::c` friends `0x2::a` is not allowed. - More generally, declaring a friend module adds a dependency upon the current module to the friend module (because the purpose is for the friend to call functions in the current module). - If that friend module is already used, either directly or transitively, a cycle of dependencies would be created. - - ```move - address 0x2 { - module a { - use 0x2::c; - friend 0x2::b; - - public fun a() { - c::c() - } - } - - module b { - friend 0x2::c; // ERROR! - // ^^^^^^ This friend relationship creates a dependency cycle: '0x2::b' is a friend of '0x2::a' uses '0x2::c' is a friend of '0x2::b' - } - - module c { - public fun c() {} - } - } - ``` - -- The friend list for a module cannot contain duplicates. - - ```move - address 0x42 { - module a {} - - module m { - use 0x42::a as aliased_a; - friend 0x42::A; - friend aliased_a; // ERROR! - // ^^^^^^^^^ Duplicate friend declaration '0x42::a'. Friend declarations in a module must be unique - } - } - ``` diff --git a/apps/nextra/pages/zh/build/smart-contracts/book/functions.mdx b/apps/nextra/pages/zh/build/smart-contracts/book/functions.mdx deleted file mode 100644 index 718846a3a..000000000 --- a/apps/nextra/pages/zh/build/smart-contracts/book/functions.mdx +++ /dev/null @@ -1,734 +0,0 @@ -import { Callout } from "nextra/components"; - -# Functions - -Function syntax in Move is shared between module functions and script functions. Functions inside of modules are reusable, whereas script functions are only used once to invoke a transaction. - -## Declaration - -Functions are declared with the `fun` keyword followed by the function name, type parameters, parameters, a return type, acquires annotations, and finally the function body. - -```text -fun <[type_parameters: constraint],*>([identifier: type],*): -``` - -For example - -```move -fun foo(x: u64, y: T1, z: T2): (T2, T1, u64) { (z, y, x) } -``` - -### Visibility - -Module functions, by default, can only be called within the same module. These internal (sometimes called private) functions cannot be called from other modules or from scripts. - -```move -address 0x42 { -module m { - fun foo(): u64 { 0 } - - fun calls_foo(): u64 { foo() } // valid -} - -module other { - fun calls_m_foo(): u64 { - 0x42::m::foo() // ERROR! - // ^^^^^ `foo` is internal to `0x42::m` - } -} -} - -script { - fun calls_m_foo(): u64 { - 0x42::m::foo() // ERROR! - // ^^^^^ `foo` is internal to `0x42::m` - } -} -``` - -To allow access from other modules or from scripts, the function must be declared `public` or `public(friend)`. - -#### `public` visibility - -A `public` function can be called by _any_ function defined in _any_ module or script. As shown in the following example, a `public` function can be called by: - -- other functions defined in the same module, -- functions defined in another module, or -- the function defined in a script. - -There are also no restrictions for what the argument types a public function can take and its return type. - -```move -address 0x42 { -module m { - public fun foo(): u64 { 0 } - - fun calls_foo(): u64 { foo() } // valid -} - -module other { - fun calls_m_foo(): u64 { - 0x42::m::foo() // valid - } -} -} - -script { - fun calls_m_foo(): u64 { - 0x42::m::foo() // valid - } -} -``` - -### `package` visibility - -_Since Language Version 2.0_ - -A `package` function can be only called within the same package. The notion of a package is -defined by the hosting environment of Move, and not explicit in the language. Typically, the package -is defined by a manifest file `Move.toml` which is processed by the build environment. - -The following works, provided the two modules belong to the same package and are at the same address: - -```move -module 0x42::m { - package fun foo(): u64 { 0 } -} - -module 0x42::other { - fun calls_m_foo(): u64 { - 0x42::m::foo() // valid - } -} -``` - -An attempt to access `0x42::m::foo` from another package will fail at compile time. - -In addition to the notation `package fun`, also the longer notation `public(package) fun` is supported. - -Notice that package visibility is a compile time concept which is reduced by the compiler to friend visibility (described [below](#friend-visibility)), which can be verified by the Move VM. The Move VM guarantees that friend functions -cannot be called across address boundaries, independent of what package system a compilation environment supports. - - -#### `public(friend)` visibility - -_Since Language Version 2.0_, `friend fun` replaces `public(friend) fun`. The old notation is still supported. - -The `public(friend)` visibility modifier is a more restricted form of the `public` modifier to give more control about where a function can be used. A `public(friend)` function can be called by: - -- other functions defined in the same module, or -- functions defined in modules which are explicitly specified in the **friend list** (see [Friends](friends.mdx) on how to specify the friend list), and which reside at the same address. - -Note that since we cannot declare a script to be a friend of a module, the functions defined in scripts can never call a `public(friend)` function. - -```move -address 0x42 { -module m { - friend 0x42::n; // friend declaration - public(friend) fun foo(): u64 { 0 } - friend fun foo2(): u64 { 0 } // Since Move 2.0 - - fun calls_foo(): u64 { foo() } // valid - fun calls_foo2(): u64 { foo2() } // valid, since Move 2.0 -} - -module n { - fun calls_m_foo(): u64 { - 0x42::m::foo() // valid - } - - fun calls_m_foo2(): u64 { - 0x42::m::foo2() // valid, since Move 2.0 - } -} - -module other { - fun calls_m_foo(): u64 { - 0x42::m::foo() // ERROR! - // ^^^^^ `foo` can only be called from a `friend` of module `0x42::m` - } - - fun calls_m_foo2(): u64 { - 0x42::m::foo2() // ERROR! - // ^^^^^^ `foo2` can only be called from a `friend` of module `0x42::m` - } -} -} - -script { - fun calls_m_foo(): u64 { - 0x42::m::foo() // ERROR! - // ^^^^^ `foo` can only be called from a `friend` of module `0x42::m` - } -} -``` - -### `entry` modifier - -The `entry` modifier is designed to allow module functions to be safely and directly invoked much like scripts. This allows module writers to specify which functions can be invoked to begin execution. The module writer then knows that any non-`entry` function will be called from a Move program already in execution. - -Essentially, `entry` functions are the "main" functions of a module, and they specify where Move programs start executing. - -Note though, an `entry` function _can_ still be called by other Move functions. So while they _can_ serve as the start of a Move program, they aren't restricted to that case. - -For example: - -```move -address 0x42 { -module m { - public entry fun foo() {} - - fun calls_foo() { foo(); } // valid! -} - -module n { - fun calls_m_foo() { - 0x42::m::foo(); // valid! - } -} - -module other { - public entry fun calls_m_foo() { - 0x42::m::foo(); // valid! - } -} -} - -script { - fun calls_m_foo() { - 0x42::m::foo(); // valid! - } -} -``` - -Even internal functions can be marked as `entry`! This lets you guarantee that the function is called only at the beginning of execution (assuming you do not call it elsewhere in your module) - -```move -address 0x42 { -module m { - entry fun foo() {} // valid! entry functions do not have to be public -} - -module n { - fun calls_m_foo() { - 0x42::m::foo(); // ERROR! - // ^^^^^ `foo` is internal to `0x42::m` - } -} - -module other { - public entry fun calls_m_foo() { - 0x42::m::foo(); // ERROR! - // ^^^^^ `foo` is internal to `0x42::m` - } -} -} - -script { - fun calls_m_foo() { - 0x42::m::foo(); // ERROR! - // ^^^^^ `foo` is internal to `0x42::m` - } -} -``` - -Entry functions can take primitive types, String, and vector arguments but cannot take Structs (e.g. Option). They also -must not have any return values. - -### Name - -Function names can start with letters `a` to `z` or letters `A` to `Z`. After the first character, function names can contain underscores `_`, letters `a` to `z`, letters `A` to `Z`, or digits `0` to `9`. - -```move -module 0x42::example { - // all valid - fun FOO() {} - - fun bar_42() {} - - fun bAZ19() {} - - // invalid - fun _bAZ19() {} // Function names cannot start with '_' -} -``` - -### Type Parameters - -After the name, functions can have type parameters - -```move -module 0x42::example { - fun id(x: T): T { x } - - fun example(x: T1, y: T2): (T1, T1, T2) { (copy x, x, y) } -} -``` - -For more details, see [Move generics](generics.mdx). - -### Parameters - -Functions parameters are declared with a local variable name followed by a type annotation - -```move -module 0x42::example { - fun add(x: u64, y: u64): u64 { x + y } -} -``` - -We read this as `x` has type `u64` - -A function does not have to have any parameters at all. - -```move -module 0x42::example { - fun useless() {} -} -``` - -This is very common for functions that create new or empty data structures - -```move -module 0x42::example { - struct Counter { count: u64 } - - fun new_counter(): Counter { - Counter { count: 0 } - } -} -``` - -### Acquires - -When a function accesses a resource using `move_from`, `borrow_global`, or `borrow_global_mut`, the function must indicate that it `acquires` that resource. This is then used by Move's type system to ensure the references into global storage are safe, specifically that there are no dangling references into global storage. - -```move -module 0x42::example { - - struct Balance has key { value: u64 } - - public fun add_balance(s: &signer, value: u64) { - move_to(s, Balance { value }) - } - - public fun extract_balance(addr: address): u64 acquires Balance { - let Balance { value } = move_from(addr); // acquires needed - value - } -} -``` - -`acquires` annotations must also be added for transitive calls within the module. Calls to these functions from another module do not need to annotated with these acquires because one module cannot access resources declared in another module--so the annotation is not needed to ensure reference safety. - -```move -module 0x42::example { - - struct Balance has key { value: u64 } - - public fun add_balance(s: &signer, value: u64) { - move_to(s, Balance { value }) - } - - public fun extract_balance(addr: address): u64 acquires Balance { - let Balance { value } = move_from(addr); // acquires needed - value - } - - public fun extract_and_add(sender: address, receiver: &signer) acquires Balance { - let value = extract_balance(sender); // acquires needed here - add_balance(receiver, value) - } -} - -module 0x42::other { - fun extract_balance(addr: address): u64 { - 0x42::example::extract_balance(addr) // no acquires needed - } -} -``` - -A function can `acquire` as many resources as it needs to - -```move -module 0x42::example { - use std::vector; - - struct Balance has key { value: u64 } - - struct Box has key { items: vector } - - public fun store_two( - addr: address, - item1: Item1, - item2: Item2, - ) acquires Balance, Box { - let balance = borrow_global_mut(addr); // acquires needed - balance.value = balance.value - 2; - let box1 = borrow_global_mut>(addr); // acquires needed - vector::push_back(&mut box1.items, item1); - let box2 = borrow_global_mut>(addr); // acquires needed - vector::push_back(&mut box2.items, item2); - } -} -``` - -### Return type - -After the parameters, a function specifies its return type. - -```move -module 0x42::example { - fun zero(): u64 { 0 } -} -``` - -Here `: u64` indicates that the function's return type is `u64`. - - -A function can return an immutable `&` or mutable `&mut` [reference](references.mdx) if derived from an input reference. Keep in mind, this means that a function [cannot return a reference to global storage](references.mdx#references-cannot-be-stored) unless it is an [inline function](#inline-functions). - - -Using tuples, a function can return multiple values: - -```move -module 0x42::example { - fun one_two_three(): (u64, u64, u64) { (0, 1, 2) } -} -``` - -If no return type is specified, the function has an implicit return type of unit `()`. These functions are equivalent: - -```move -module 0x42::example { - fun just_unit1(): () { () } - - fun just_unit2() { () } - - fun just_unit3() {} -} -``` - -`script` functions must have a return type of unit `()`: - -```move -script { - fun do_nothing() {} -} -``` - -As mentioned in the [tuples section](tuples.mdx), these tuple "values" are virtual and do not exist at runtime. So for a function that returns unit `()`, it will not be returning any value at all during execution. - -### Function body - -A function's body is an expression block. The return value of the function is the last value in the sequence - -```move -module 0x42::example { - fun example(): u64 { - let x = 0; - x = x + 1; - x // returns 'x' - } -} -``` - -See [the section below for more information on returns](#returning-values) - -For more information on expression blocks, see [Move variables](variables.mdx). - -### Native Functions - -Some functions do not have a body specified, and instead have the body provided by the VM. These functions are marked `native`. - -Without modifying the VM source code, a programmer cannot add new native functions. Furthermore, it is the intent that `native` functions are used for either standard library code or for functionality needed for the given Move environment. - -Most `native` functions you will likely see are in standard library code such as `vector` - -```move -module std::vector { - native public fun empty(): vector; - // ... -} -``` - -## Calling - -When calling a function, the name can be specified either through an alias or fully qualified - -```move -module 0x42::example { - public fun zero(): u64 { 0 } -} - -script { - use 0x42::example::{Self, zero}; - - fun call_zero() { - // With the `use` above all of these calls are equivalent - 0x42::example::zero(); - example::zero(); - zero(); - } -} -``` - -When calling a function, an argument must be given for every parameter. - -```move -module 0x42::example { - public fun takes_none(): u64 { 0 } - - public fun takes_one(x: u64): u64 { x } - - public fun takes_two(x: u64, y: u64): u64 { x + y } - - public fun takes_three(x: u64, y: u64, z: u64): u64 { x + y + z } -} - -script { - use 0x42::example; - - fun call_all() { - example::takes_none(); - example::takes_one(0); - example::takes_two(0, 1); - example::takes_three(0, 1, 2); - } -} -``` - -Type arguments can be either specified or inferred. Both calls are equivalent. - -```move -module 0x42::example { - public fun id(x: T): T { x } -} - -script { - use 0x42::example; - - fun call_all() { - example::id(0); - example::id(0); - } -} -``` - -For more details, see [Move generics](generics.mdx). - -## Returning values - -The result of a function, its "return value", is the final value of its function body. For example - -```move -module 0x42::example { - fun add(x: u64, y: u64): u64 { - x + y - } -} -``` - -[As mentioned above](#function-body), the function's body is an [expression block](variables.mdx). The expression block can be a sequence of various statements, and the final expression in the block will be the value of that block. - -```move -module 0x42::example { - fun double_and_add(x: u64, y: u64): u64 { - let double_x = x * 2; - let double_y = y * 2; - double_x + double_y - } -} -``` - -The return value here is `double_x + double_y` - -### `return` expression - -A function implicitly returns the value that its body evaluates to. However, functions can also use the explicit `return` expression: - -```move -module 0x42::example { - fun f1(): u64 { return 0 } - - fun f2(): u64 { 0 } -} -``` - -These two functions are equivalent. In this slightly more involved example, the function subtracts two `u64` values, but returns early with `0` if the second value is too large: - -```move -module 0x42::example { - fun safe_sub(x: u64, y: u64): u64 { - if (y > x) return 0; - x - y - } -} -``` - -Note that the body of this function could also have been written as `if (y > x) 0 else x - y`. - -However, where `return` really shines is in exiting deep within other control flow constructs. In this example, the function iterates through a vector to find the index of a given value: - -```move -module 0x42::example { - use std::vector; - use std::option::{Self, Option}; - - fun index_of(v: &vector, target: &T): Option { - let i = 0; - let n = vector::length(v); - while (i < n) { - if (vector::borrow(v, i) == target) return option::some(i); - i = i + 1 - }; - - option::none() - } -} -``` - -Using `return` without an argument is shorthand for `return ()`. That is, the following two functions are equivalent: - -```move -module 0x42::example { - fun foo1() { return } - - fun foo2() { return () } -} -``` - -## Inline Functions - -Inline functions are functions whose bodies are expanded in place at the caller location during compile time. -Thus, inline functions do not appear in Move bytecode as a separate functions: all calls to them are expanded away by the compiler. -In certain circumstances, they may lead to faster execution and save gas. -However, users should be aware that they could lead to larger bytecode size: excessive inlining potentially triggers various size restrictions. - -One can define an inline function by adding the `inline` keyword to a function declaration as shown below: - -```move -module 0x42::example { - inline fun percent(x: u64, y: u64): u64 { x * 100 / y } -} -``` - -If we call this inline function as `percent(2, 200)`, the compiler will replace this call with the inline function's body, as if the user has written `2 * 100 / 200`. - -### Function parameters and lambda expressions - -Inline functions support _function parameters_, which accept lambda expressions (i.e., anonymous functions) as arguments. -This feature allows writing several common programming patterns elegantly. -Similar to inline functions, lambda expressions are also expanded at call site. - -A lambda expression includes a list of parameter names (enclosed within `||`) followed by the body. -Some simple examples are: `|x| x + 1`, `|x, y| x + y`, `|| 1`, `|| { 1 }`. -A lambda's body can refer to variables available in the scope where the lambda is defined: this is also known as capturing. -Such variables can be read or written (if mutable) by the lambda expression. - -The type of function parameter is written as `|| `. -For example, when the function parameter type is `|u64, u64| bool`, any lambda expression that takes two `u64` parameters and returns a `bool` value can be provided as the argument. - -Below is an example that showcases many of these concepts in action (this example is taken from the `std::vector` module): - -```move -module 0x42::example { - /// Fold the function over the elements. - /// E.g, `fold(vector[1,2,3], 0, f)` is the same as `f(f(f(0, 1), 2), 3)`. - public inline fun fold( - v: vector, - init: Accumulator, - f: |Accumulator, Element|Accumulator - ): Accumulator { - let accu = init; - // Note: `for_each` is an inline function, but is not shown here. - for_each(v, |elem| accu = f(accu, elem)); - accu - } -} -``` - -The type signature of the elided public inline function `for_each` is `fun for_each(v: vector, f: |Element|)`. -Its second parameter `f` is a function parameter which accepts any lambda expression that consumes an `Element` and returns nothing. -In the code example, we use the lambda expression `|elem| accu = f(accu, elem)` as an argument to this function parameter. -Note that this lambda expression captures the variable `accu` from the outer scope. - -### Current restrictions - -There are plans to loosen some of these restrictions in the future, but for now, - -- Only inline functions can have function parameters. -- Only explicit lambda expressions can be passed as an argument to an inline function's function parameters. -- Inline functions and lambda expressions - - cannot have `return` expressions; or free `break` or `continue` expressions (occurring outside of a loop) - - cannot return lambda expressions. -- Cyclic recursion involving only inline functions is not allowed. -- Parameters in lambda expressions must not be type annotated (e.g., `|x: u64| x + 1` is not allowed): their types are inferred. - -### Additional considerations - -- Avoid using module-private constants/methods in public inline functions. - When such inline functions are called outside of that module, an in-place expansion at call site leads to invalid access of the private constants/methods. -- Avoid marking large functions that are called at different locations as inline. Also avoid inline functions calling lots of other inline functions transitively. - These may lead to excessive inlining and increase the bytecode size. -- Inline functions can be useful for returning references to global storage, which non-inline functions cannot do. - -### Inline functions and references - -As mentioned briefly [in a "tip" above](#return-type) `inline` functions can use references more freely than normal functions. - -For example, actual arguments to a call to a non-`inline` function may not be aliased unsafely -(multiple `&` parameters referring to the same object, with at least one of them `&mut`), -but calls to `inline` functions do not necessarily have that restriction, as long as no reference -usage conflicts remain after the function is inlined. - -```move -inline fun add(dest: &mut u64, a: &u64, b: &u64) { - *dest = *a + *b; -} - -fun user(...) { - ... - x = 3; - add(&mut x, &x, &x); // legal only because of inlining - ... -} -``` - -A reference-typed value returned from a non-inline function must be derived from a reference parameter -passed to the function, but this need not be the case for an inline function, as long as the referred -value is in the function scope after inlining. - -The exact details of reference safety and "borrow checking" are complex and documented elsewhere. -Advanced Move users find new expressiveness by understanding that -"borrow checking" happens only after all `inline` function calls are expanded. - -However, with this power comes new responsibility: documentation of a nontrivial `inline` function should -probably explain any underlying restrictions on reference parameters and results at a call site. - -## Dot (receiver) function call style - -_Since language version 2.0_ - -By using the well-known name `self` as the first parameter for a function declaration, one can enable calling this function with the `.` syntax -- often also called receiver style syntax. Example: - -```move -module 0x42::example { - struct S {} - - fun foo(self: &S, x: u64) { /* ... */ } - - //... - - fun example() { - let s = S {}; - s.foo(1); - } -} -``` - -The call `s.foo(1)` is syntactic sugar for `foo(&s, 1)`. Notice that the compiler automatically inserts the reference operator. The 2nd, old notation is still available for `foo`, so one can incrementally introduce the new call style without breaking existing code. - -The type of the `self` argument can be a struct or an immutable or mutable reference to a struct. The struct must be declared in the same module as the function. - -Notice that you do not need to `use` the modules which introduce receiver functions. The compiler will find those functions automatically based on the argument type of `s` in a call like `s.foo(1)`. This, in combination with the automatic insertion of reference operators, can make code using this syntax significantly more concise. diff --git a/apps/nextra/pages/zh/build/smart-contracts/book/generics.mdx b/apps/nextra/pages/zh/build/smart-contracts/book/generics.mdx deleted file mode 100644 index cd2e3eb73..000000000 --- a/apps/nextra/pages/zh/build/smart-contracts/book/generics.mdx +++ /dev/null @@ -1,500 +0,0 @@ -# Generics - -Generics can be used to define functions and structs over different input data types. This language feature is sometimes referred to as _parametric polymorphism_. In Move, we will often use the term generics interchangeably with type parameters and type arguments. - -Generics are commonly used in library code, such as in vector, to declare code that works over any possible instantiation (that satisfies the specified constraints). In other frameworks, generic code can sometimes be used to interact with global storage many different ways that all still share the same implementation. - -## Declaring Type Parameters - -Both functions and structs can take a list of type parameters in their signatures, enclosed by a pair of angle brackets `<...>`. - -### Generic Functions - -Type parameters for functions are placed after the function name and before the (value) parameter list. The following code defines a generic identity function that takes a value of any type and returns that value unchanged. - -```move -module 0x42::example { - fun id(x: T): T { - // this type annotation is unnecessary but valid - (x: T) - } -} -``` - -Once defined, the type parameter `T` can be used in parameter types, return types, and inside the function body. - -### Generic Structs - -Type parameters for structs are placed after the struct name, and can be used to name the types of the fields. - -```move -module 0x42::example { - struct Foo has copy, drop { x: T } - - struct Bar has copy, drop { - x: T1, - y: vector, - } -} -``` - -Note that [type parameters do not have to be used](#unused-type-parameters) - -## Type Arguments - -### Calling Generic Functions - -When calling a generic function, one can specify the type arguments for the function's type parameters in a list enclosed by a pair of angle brackets. - -```move -module 0x42::example { - fun foo() { - let x = id(true); - } -} -``` - -If you do not specify the type arguments, Move's [type inference](#type-inference) will supply them for you. - -### Using Generic Structs - -Similarly, one can attach a list of type arguments for the struct's type parameters when constructing or destructing values of generic types. - -```move -module 0x42::example { - fun foo() { - let foo = Foo { x: true }; - let Foo { x } = foo; - } -} -``` - -If you do not specify the type arguments, Move's [type inference](#type-inference) will supply them for you. - -### Type Argument Mismatch - -If you specify the type arguments, and they conflict with the actual values supplied, an error will be given: - -```move -module 0x42::example { - fun foo() { - let x = id(true); // error! true is not a u64 - } -} -``` - -and similarly: - -```move -module 0x42::example { - fun foo() { - let foo = Foo { x: 0 }; // error! 0 is not a bool - let Foo
{ x } = foo; // error! bool is incompatible with address - } -} -``` - -## Type Inference - -In most cases, the Move compiler will be able to infer the type arguments, so you don't have to write them down explicitly. Here's what the examples above would look like if we omit the type arguments: - -```move -module 0x42::example { - fun foo() { - let x = id(true); - // ^ is inferred - - let foo = Foo { x: true }; - // ^ is inferred - - let Foo { x } = foo; - // ^ is inferred - } -} -``` - -Note: when the compiler is unable to infer the types, you'll need annotate them manually. A common scenario is to call a function with type parameters appearing only at return positions. - -```move -module 0x2::m { - use std::vector; - - fun foo() { - // let v = vector::new(); - // ^ The compiler cannot figure out the element type. - - let v = vector::new(); - // ^~~~~ Must annotate manually. - } -} -``` - -However, the compiler will be able to infer the type if that return value is used later in that function: - -```move -module 0x2::m { - use std::vector; - - fun foo() { - let v = vector::new(); - // ^ is inferred - vector::push_back(&mut v, 42); - } -} -``` - -## Unused Type Parameters - -For a struct definition, -an unused type parameter is one that -does not appear in any field defined in the struct, -but is checked statically at compile time. -Move allows unused type parameters so the following struct definition is valid: - -```move -module 0x2::m { - struct Foo { - foo: u64 - } -} -``` - -This can be convenient when modeling certain concepts. Here is an example: - -```move -module 0x2::m { - // Currency Specifiers - struct Currency1 {} - struct Currency2 {} - - // A generic coin type that can be instantiated using a currency - // specifier type. - // e.g. Coin, Coin etc. - struct Coin has store { - value: u64 - } - - // Write code generically about all currencies - public fun mint_generic(value: u64): Coin { - Coin { value } - } - - // Write code concretely about one currency - public fun mint_concrete(value: u64): Coin { - Coin { value } - } -} -``` - -In this example, -`struct Coin` is generic on the `Currency` type parameter, -which specifies the currency of the coin and -allows code to be written either -generically on any currency or -concretely on a specific currency. -This genericity applies even when the `Currency` type parameter -does not appear in any of the fields defined in `Coin`. - -### Phantom Type Parameters - -In the example above, -although `struct Coin` asks for the `store` ability, -neither `Coin` nor `Coin` will have the `store` ability. -This is because of the rules for -[Conditional Abilities and Generic Types](abilities.mdx#conditional-abilities-and-generic-types) -and the fact that `Currency1` and `Currency2` don't have the `store` ability, -despite the fact that they are not even used in the body of `struct Coin`. -This might cause some unpleasant consequences. -For example, we are unable to put `Coin` into a wallet in the global storage. - -One possible solution would be to -add spurious ability annotations to `Currency1` and `Currency2` -(i.e., `struct Currency1 has store {}`). -But, this might lead to bugs or security vulnerabilities -because it weakens the types with unnecessary ability declarations. -For example, we would never expect a resource in the global storage to have a field in type `Currency1`, -but this would be possible with the spurious `store` ability. -Moreover, the spurious annotations would be infectious, -requiring many functions generic on the unused type parameter to also include the necessary constraints. - -Phantom type parameters solve this problem. -Unused type parameters can be marked as _phantom_ type parameters, -which do not participate in the ability derivation for structs. -In this way, -arguments to phantom type parameters are not considered when deriving the abilities for generic types, -thus avoiding the need for spurious ability annotations. -For this relaxed rule to be sound, -Move's type system guarantees that a parameter declared as `phantom` is either -not used at all in the struct definition, or -it is only used as an argument to type parameters also declared as `phantom`. - -#### Declaration - -In a struct definition -a type parameter can be declared as phantom by adding the `phantom` keyword before its declaration. -If a type parameter is declared as phantom we say it is a phantom type parameter. -When defining a struct, Move's type checker ensures that every phantom type parameter is either -not used inside the struct definition or -it is only used as an argument to a phantom type parameter. - -More formally, -if a type is used as an argument to a phantom type parameter -we say the type appears in _phantom position_. -With this definition in place, -the rule for the correct use of phantom parameters can be specified as follows: -**A phantom type parameter can only appear in phantom position**. - -The following two examples show valid uses of phantom parameters. -In the first one, -the parameter `T1` is not used at all inside the struct definition. -In the second one, the parameter `T1` is only used as an argument to a phantom type parameter. - -```move -module 0x2::m { - struct S1 { f: u64 } - // ^^ - // Ok: T1 does not appear inside the struct definition - - - struct S2 { f: S1 } - // ^^ - // Ok: T1 appears in phantom position -} -``` - -The following code shows examples of violations of the rule: - -```move -module 0x2::m { - struct S1 { f: T } - // ^ - // Error: Not a phantom position - - struct S2 { f: T } - - struct S3 { f: S2 } - // ^ - // Error: Not a phantom position -} -``` - -#### Instantiation - -When instantiating a struct, -the arguments to phantom parameters are excluded when deriving the struct abilities. -For example, consider the following code: - -```move -module 0x2::m { - struct S has copy { f: T1 } - struct NoCopy {} - struct HasCopy has copy {} -} -``` - -Consider now the type `S`. -Since `S` is defined with `copy` and all non-phantom arguments have `copy` -then `S` also has `copy`. - -#### Phantom Type Parameters with Ability Constraints - -Ability constraints and phantom type parameters are orthogonal features in the sense that -phantom parameters can be declared with ability constraints. -When instantiating a phantom type parameter with an ability constraint, -the type argument has to satisfy that constraint, -even though the parameter is phantom. -For example, the following definition is perfectly valid: - -```move -module 0x2::m { - struct S {} -} -``` - -The usual restrictions apply and `T` can only be instantiated with arguments having `copy`. - -## Constraints - -In the examples above, we have demonstrated how one can use type parameters to define "unknown" types that can be plugged in by callers at a later time. This however means the type system has little information about the type and has to perform checks in a very conservative way. In some sense, the type system must assume the worst case scenario for an unconstrained generic. Simply put, by default generic type parameters have no [abilities](abilities.mdx). - -This is where constraints come into play: they offer a way to specify what properties these unknown types have so the type system can allow operations that would otherwise be unsafe. - -### Declaring Constraints - -Constraints can be imposed on type parameters using the following syntax. - -```move -// T is the name of the type parameter -T: (+ )* -``` - -The `` can be any of the four [abilities](abilities.mdx), and a type parameter can be constrained with multiple abilities at once. So all the following would be valid type parameter declarations: - -```move -T: copy -T: copy + drop -T: copy + drop + store + key -``` - -### Verifying Constraints - -Constraints are checked at call sites so the following code won't compile. - -```move -module 0x2::m { - struct Foo { x: T } - - struct Bar { x: Foo } - // ^ error! u8 does not have 'key' - - struct Baz { x: Foo } - // ^ error! T does not have 'key' -} -``` - -```move -module 0x2::m { - struct R {} - - fun unsafe_consume(x: T) { - // error! x does not have 'drop' - } - - fun consume(x: T) { - // valid! - // x will be dropped automatically - } - - fun foo() { - let r = R {}; - consume(r); - // ^ error! R does not have 'drop' - } -} -``` - -```move -module 0x2::m { - struct R {} - - fun unsafe_double(x: T) { - (copy x, x) - // error! x does not have 'copy' - } - - fun double(x: T) { - (copy x, x) // valid! - } - - fun foo(): (R, R) { - let r = R {}; - double(r) - // ^ error! R does not have 'copy' - } -} -``` - -For more information, see the abilities section on [conditional abilities and generic types](abilities.mdx#conditional-abilities-and-generic-types). - -## Limitations on Recursions - -### Recursive Structs - -Generic structs can not contain fields of the same type, either directly or indirectly, even with different type arguments. All the following struct definitions are invalid: - -```move -module 0x2::m { - struct Foo { - x: Foo // error! 'Foo' containing 'Foo' - } - - struct Bar { - x: Bar // error! 'Bar' containing 'Bar' - } - - // error! 'A' and 'B' forming a cycle, which is not allowed either. - struct A { - x: B - } - - struct B { - x: A, - y: A - } -} -``` - -### Advanced Topic: Type-level Recursions - -Move allows generic functions to be called recursively. However, when used in combination with generic structs, this could create an infinite number of types in certain cases, and allowing this means adding unnecessary complexity to the compiler, vm and other language components. Therefore, such recursions are forbidden. - -Allowed: - -```move -module 0x2::m { - struct A {} - - // Finitely many types -- allowed. - // foo1 -> foo1 -> foo1 -> ... is valid - fun foo1() { - foo1(); - } - - // Finitely many types -- allowed. - // foo2 -> foo2> -> foo2> -> ... is valid - fun foo2() { - foo2>(); - } -} -``` - -Not allowed: - -```move -module 0x2::m { - struct A {} - - // Infinitely many types -- NOT allowed. - // error! - // foo -> foo> -> foo>> -> ... - fun foo() { - foo>(); - } -} -``` - -```move -module 0x2::n { - struct A {} - - // Infinitely many types -- NOT allowed. - // error! - // foo -> bar -> foo> - // -> bar, T2> -> foo, A> - // -> bar, A> -> foo, A>> - // -> ... - fun foo() { - bar(); - } - - fun bar() { - foo>(); - } -} -``` - -Note, the check for type level recursions is based on a conservative analysis on the call sites and does NOT take control flow or runtime values into account. - -```move -module 0x2::m { - struct A {} - - fun foo(n: u64) { - if (n > 0) { - foo>(n - 1); - }; - } -} -``` - -The function in the example above will technically terminate for any given input and therefore only creating finitely many types, but it is still considered invalid by Move's type system. diff --git a/apps/nextra/pages/zh/build/smart-contracts/book/global-storage-operators.mdx b/apps/nextra/pages/zh/build/smart-contracts/book/global-storage-operators.mdx deleted file mode 100644 index d034c616c..000000000 --- a/apps/nextra/pages/zh/build/smart-contracts/book/global-storage-operators.mdx +++ /dev/null @@ -1,289 +0,0 @@ -# Global Storage - Operators - -Move programs can create, delete, and update [resources](structs-and-resources.mdx) in global storage using the following five instructions: - -| Operation | Description | Aborts? | -| --------------------------------------- | --------------------------------------------------------------- | --------------------------------------- | -| `move_to(&signer,T)` | Publish `T` under `signer.address` | If `signer.address` already holds a `T` | -| `move_from(address): T` | Remove `T` from `address` and return it | If `address` does not hold a `T` | -| `borrow_global_mut(address): &mut T` | Return a mutable reference to the `T` stored under `address` | If `address` does not hold a `T` | -| `borrow_global(address): &T` | Return an immutable reference to the `T` stored under `address` | If `address` does not hold a `T` | -| `exists(address): bool` | Return `true` if a `T` is stored under `address` | Never | - -Each of these instructions is parameterized by a type `T` with the [`key` ability](abilities.mdx). However, each type `T` _must be declared in the current module_. This ensures that a resource can only be manipulated via the API exposed by its defining module. The instructions also take either an [`address`](address.mdx) or [`&signer`](signer.mdx) representing the account address where the resource of type `T` is stored. - -See also [index notation (`[]`)](#index-notation-for-storage-operators) for accessing global storage. - - -## References to resources - -References to global resources returned by `borrow_global` or `borrow_global_mut` mostly behave like references to local storage: they can be extended, read, and written using ordinary [reference operators](references.mdx) and passed as arguments to other function. However, there is one important difference between local and global references: **a function cannot return a reference that points into global storage**. For example, these two functions will each fail to compile: - -```move -module 0x42::example { - struct R has key { f: u64 } - // will not compile - fun ret_direct_resource_ref_bad(a: address): &R { - borrow_global(a) // error! - } - // also will not compile - fun ret_resource_field_ref_bad(a: address): &u64 { - &borrow_global(a).f // error! - } -} -``` - -Move must enforce this restriction to guarantee absence of dangling references to global -storage. [This section](#reference-safety-for-global-resources) contains much more detail for the interested reader. - -## Global storage operators with generics - -Global storage operations can be applied to generic resources with both instantiated and uninstantiated generic type parameters: - -```move -module 0x42::example { - struct Container has key { t: T } - - // Publish a Container storing a type T of the caller's choosing - fun publish_generic_container(account: &signer, t: T) { - move_to>(account, Container { t }) - } - - /// Publish a container storing a u64 - fun publish_instantiated_generic_container(account: &signer, t: u64) { - move_to>(account, Container { t }) - } -} -``` - -The ability to index into global storage via a type parameter chosen at runtime is a powerful Move feature known as _storage polymorphism_. For more on the design patterns enabled by this feature, see [Move generics](generics.mdx). - -## Example: `Counter` - -The simple `Counter` module below exercises each of the five global storage operators. The API exposed by this module allows: - -- Anyone to publish a `Counter` resource under their account -- Anyone to check if a `Counter` exists under any address -- Anyone to read or increment the value of a `Counter` resource under any address -- An account that stores a `Counter` resource to reset it to zero -- An account that stores a `Counter` resource to remove and delete it - -```move -module 0x42::counter { - use std::signer; - - /// Resource that wraps an integer counter - struct Counter has key { i: u64 } - - /// Publish a `Counter` resource with value `i` under the given `account` - public fun publish(account: &signer, i: u64) { - // "Pack" (create) a Counter resource. This is a privileged operation that - // can only be done inside the module that declares the `Counter` resource - move_to(account, Counter { i }) - } - - /// Read the value in the `Counter` resource stored at `addr` - public fun get_count(addr: address): u64 acquires Counter { - borrow_global(addr).i - } - - /// Increment the value of `addr`'s `Counter` resource - public fun increment(addr: address) acquires Counter { - let c_ref = &mut borrow_global_mut(addr).i; - *c_ref = *c_ref + 1 - } - - /// Reset the value of `account`'s `Counter` to 0 - public fun reset(account: &signer) acquires Counter { - let c_ref = &mut borrow_global_mut(signer::address_of(account)).i; - *c_ref = 0 - } - - /// Delete the `Counter` resource under `account` and return its value - public fun delete(account: &signer): u64 acquires Counter { - // remove the Counter resource - let c = move_from(signer::address_of(account)); - // "Unpack" the `Counter` resource into its fields. This is a - // privileged operation that can only be done inside the module - // that declares the `Counter` resource - let Counter { i } = c; - i - } - - /// Return `true` if `addr` contains a `Counter` resource - public fun exists_at(addr: address): bool { - exists(addr) - } -} -``` - -## Annotating functions with `acquires` - -In the `counter` example, you might have noticed that the `get_count`, `increment`, `reset`, and `delete` functions are annotated with `acquires Counter`. A Move function `m::f` must be annotated with `acquires T` if and only if: - -- The body of `m::f` contains a `move_from`, `borrow_global_mut`, or `borrow_global` instruction, or -- The body of `m::f` invokes a function `m::g` declared in the same module that is annotated with `acquires` - -For example, the following function inside `Counter` would need an `acquires` annotation: - -```move -module 0x42::example { - // Needs `acquires` because `increment` is annotated with `acquires` - fun call_increment(addr: address): u64 acquires Counter { - counter::increment(addr) - } -} -``` - -However, the same function _outside_ `Counter` would not need an annotation: - -```move -module 0x43::m { - use 0x42::counter; - - // Ok. Only need annotation when resource acquired by callee is declared - // in the same module - fun call_increment(addr: address): u64 { - counter::increment(addr) - } -} -``` - -If a function touches multiple resources, it needs multiple `acquires`: - -```move -module 0x42::two_resources { - struct R1 has key { f: u64 } - struct R2 has key { g: u64 } - - fun double_acquires(a: address): u64 acquires R1, R2 { - borrow_global(a).f + borrow_global(a).g - } -} -``` - -The `acquires` annotation does not take generic type parameters into account: - -```move -module 0x42::m { - struct R has key { t: T } - - // `acquires R`, not `acquires R` - fun acquire_generic_resource(a: address) acquires R { - let _ = borrow_global>(a); - } - - // `acquires R`, not `acquires R - fun acquire_instantiated_generic_resource(a: address) acquires R { - let _ = borrow_global>(a); - } -} -``` - -Finally: redundant `acquires` are not allowed. Adding this function inside `Counter` will result in a compilation error: - -```move -module 0x42::m { - // This code will not compile because the body of the function does not use a global - // storage instruction or invoke a function with `acquires` - fun redundant_acquires_bad() acquires Counter {} -} -``` - -For more information on `acquires`, see [Move functions](functions.mdx). - -## Reference Safety For Global Resources - -Move prohibits returning global references and requires the `acquires` annotation to prevent dangling references. This allows Move to live up to its promise of static reference safety (i.e., no dangling references, no `null` or `nil` dereferences) for all [reference](references.mdx) types. - -This example illustrates how the Move type system uses `acquires` to prevent a dangling reference: - -```move -module 0x42::dangling { - struct T has key { f: u64 } - - fun borrow_then_remove_bad(a: address) acquires T { - let t_ref: &mut T = borrow_global_mut(a); - let t = remove_t(a); // type system complains here - // t_ref now dangling! - let uh_oh = *&t_ref.f; - } - - fun remove_t(a: address): T acquires T { - move_from(a) - } -} -``` - -In this code, line 6 acquires a reference to the `T` stored at address `a` in global storage. The callee `remove_t` then removes the value, which makes `t_ref` a dangling reference. - -Fortunately, this cannot happen because the type system will reject this program. The `acquires` annotation on `remove_t` lets the type system know that line 7 is dangerous, without having to recheck or introspect the body of `remove_t` separately! - -The restriction on returning global references prevents a similar, but even more insidious problem: - -```move -address 0x42 { - module m1 { - struct T has key {} - - public fun ret_t_ref(a: address): &T acquires T { - borrow_global(a) // error! type system complains here - } - - public fun remove_t(a: address) acquires T { - let T {} = move_from(a); - } - } - - module m2 { - fun borrow_then_remove_bad(a: address) { - let t_ref = m1::ret_t_ref(a); - let t = m1::remove_t(a); // t_ref now dangling! - } - } -} -``` - -Line 16 acquires a reference to a global resource `m1::T`, then line 17 removes that same resource, which makes `t_ref` dangle. In this case, `acquires` annotations do not help us because the `borrow_then_remove_bad` function is outside the `m1` module that declares `T` (recall that `acquires` annotations can only be used for resources declared in the current module). Instead, the type system avoids this problem by preventing the return of a global reference at line 6. - -Fancier type systems that would allow returning global references without sacrificing reference safety are possible, and we may consider them in future iterations of Move. We chose the current design because it strikes a good balance between being expressive, annotation burden, and type system complexity. - - -## Index Notation for Storage Operators - -_Since language version 2.0_ - -Instead of the verbose `borrow_global` and `borrow_global_mut` functions, one -can also use index notations to access global storage. - -The table below gives an overview of index notations for storage: - -| Indexing Syntax | Storage Operation | -|-------------------------|--------------------------------------------| -| `&T[address]` | `borrow_global(address)` | -| `&mut T[address]` | `borrow_global_mut(address)` | -| `T[address]` | `*borrow_global(address)` | -| `T[address] = x` | `*borrow_global_mut(address) = x` | -| `&T[address].field` | `&borrow_global(address).field` | -| `&mut T[address].field` | `&mut borrow_global_mut(address).field` | -| `T[address].field` | `borrow_global(address).field` | -| `T[address].field = x` | `borrow_global_mut(address).field = x` | - -Here `T` represents a generic resource type that can take type parameters. - -Notice that `T[address].field` fetches a reference to the resource from storage and then makes a copy of the field value (which must -have the copy ability); it is a shortcut for `*&T[address].field`. - -Examples: - -```move -struct R has key, drop { value: bool } - -fun f1() acquires R { - let x = &mut R[@0x1]; - x.value = false; - assert!(R[@0x1].value == false); - R[@0x1].value = true; - assert!(R[@0x1].value == true); -} -``` diff --git a/apps/nextra/pages/zh/build/smart-contracts/book/global-storage-structure.mdx b/apps/nextra/pages/zh/build/smart-contracts/book/global-storage-structure.mdx deleted file mode 100644 index 724aa6cbd..000000000 --- a/apps/nextra/pages/zh/build/smart-contracts/book/global-storage-structure.mdx +++ /dev/null @@ -1,16 +0,0 @@ -# Global Storage - Structure - -The purpose of Move programs is to [read from and write to](global-storage-operators.mdx) tree-shaped persistent global storage. Programs cannot access the filesystem, network, or any other data outside of this tree. - -In pseudocode, the global storage looks something like: - -```move -module 0x42::example { - struct GlobalStorage { - resources: Map<(address, ResourceType), ResourceValue>, - modules: Map<(address, ModuleName), ModuleBytecode> - } -} -``` - -Structurally, global storage is a [forest](https://en.wikipedia.org/wiki/Tree_(graph_theory)) consisting of trees rooted at an account [`address`](address.mdx). Each address can store both [resource](structs-and-resources.mdx) data values and [module](modules-and-scripts.mdx) code values. As the pseudocode above indicates, each `address` can store at most one resource value of a given type and at most one module with a given name. diff --git a/apps/nextra/pages/zh/build/smart-contracts/book/integers.mdx b/apps/nextra/pages/zh/build/smart-contracts/book/integers.mdx deleted file mode 100644 index 29573df14..000000000 --- a/apps/nextra/pages/zh/build/smart-contracts/book/integers.mdx +++ /dev/null @@ -1,159 +0,0 @@ -# Integers - -Move supports six unsigned integer types: `u8`, `u16`, `u32`, `u64`, `u128`, and `u256`. Values of these types range from 0 to a maximum that depends on the size of the type. - -| Type | Value Range | -| -------------------------------- | ------------------------ | -| Unsigned 8-bit integer, `u8` | 0 to 28 - 1 | -| Unsigned 16-bit integer, `u16` | 0 to 216 - 1 | -| Unsigned 32-bit integer, `u32` | 0 to 232 - 1 | -| Unsigned 64-bit integer, `u64` | 0 to 264 - 1 | -| Unsigned 128-bit integer, `u128` | 0 to 2128 - 1 | -| Unsigned 256-bit integer, `u256` | 0 to 2256 - 1 | - -## Literals - -Literal values for these types are specified either as a sequence of digits (e.g.,`112`) or as hex literals, e.g., `0xFF`. The type of the literal can optionally be added as a suffix, e.g., `112u8`. If the type is not specified, the compiler will try to infer the type from the context where the literal is used. If the type cannot be inferred, it is assumed to be `u64`. - -Number literals can be separated by underscores for grouping and readability. (e.g.,`1_234_5678`, `1_000u128`, `0xAB_CD_12_35`). - -If a literal is too large for its specified (or inferred) size range, an error is reported. - -### Examples - -```move -script { - fun example() { - // literals with explicit annotations; - let explicit_u8 = 1u8; - let explicit_u16 = 1u16; - let explicit_u32 = 1u32; - let explicit_u64 = 2u64; - let explicit_u128 = 3u128; - let explicit_u256 = 1u256; - let explicit_u64_underscored = 154_322_973u64; - - // literals with simple inference - let simple_u8: u8 = 1; - let simple_u16: u16 = 1; - let simple_u32: u32 = 1; - let simple_u64: u64 = 2; - let simple_u128: u128 = 3; - let simple_u256: u256 = 1; - - // literals with more complex inference - let complex_u8 = 1; // inferred: u8 - // right hand argument to shift must be u8 - let _unused = 10 << complex_u8; - - let x: u8 = 38; - let complex_u8 = 2; // inferred: u8 - // arguments to `+` must have the same type - let _unused = x + complex_u8; - - let complex_u128 = 133_876; // inferred: u128 - // inferred from function argument type - function_that_takes_u128(complex_u128); - - // literals can be written in hex - let hex_u8: u8 = 0x1; - let hex_u16: u16 = 0x1BAE; - let hex_u32: u32 = 0xDEAD80; - let hex_u64: u64 = 0xCAFE; - let hex_u128: u128 = 0xDEADBEEF; - let hex_u256: u256 = 0x1123_456A_BCDE_F; - } -} -``` - -## Operations - -### Arithmetic - -Each of these types supports the same set of checked arithmetic operations. For all of these operations, both arguments (the left and right side operands) _must_ be of the same type. If you need to operate over values of different types, you will need to first perform a [cast](#casting). Similarly, if you expect the result of the operation to be too large for the integer type, perform a [cast](#casting) to a larger size before performing the operation. - -All arithmetic operations abort instead of behaving in a way that mathematical integers would not (e.g., overflow, underflow, divide-by-zero). - -| Syntax | Operation | Aborts If | -| ------ | ------------------- | ---------------------------------------- | -| `+` | addition | Result is too large for the integer type | -| `-` | subtraction | Result is less than zero | -| `*` | multiplication | Result is too large for the integer type | -| `%` | modular division | The divisor is `0` | -| `/` | truncating division | The divisor is `0` | - -### Bitwise - -The integer types support the following bitwise operations that treat each number as a series of individual bits, either 0 or 1, instead of as numerical integer values. - -Bitwise operations do not abort. - -| Syntax | Operation | Description | -| ------------------- | ----------- | ----------------------------------------------------- | -| `&` | bitwise and | Performs a boolean and for each bit pairwise | -| | | bitwise or | Performs a boolean or for each bit pairwise | -| `^` | bitwise xor | Performs a boolean exclusive or for each bit pairwise | - -### Bit Shifts - -Similar to the bitwise operations, each integer type supports bit shifts. But unlike the other operations, the right-hand side operand (how many bits to shift by) must _always_ be a `u8` and need not match the left side operand (the number you are shifting). - -Bit shifts can abort if the number of bits to shift by is greater than or equal to `8`, `16`, `32`, `64`, `128` or `256` for `u8`, `u16`, `u32`, `u64`, `u128` and `u256` respectively. - -| Syntax | Operation | Aborts if | -| ------ | ----------- | ----------------------------------------------------------------------- | -| `<<` | shift left | Number of bits to shift by is greater than the size of the integer type | -| `>>` | shift right | Number of bits to shift by is greater than the size of the integer type | - -### Comparisons - -Integer types are the _only_ types in Move that can use the comparison operators. Both arguments need to be of the same type. If you need to compare integers of different types, you will need to [cast](#casting) one of them first. - -Comparison operations do not abort. - -| Syntax | Operation | -| ------ | ------------------------ | -| `<` | less than | -| `>` | greater than | -| `<=` | less than or equal to | -| `>=` | greater than or equal to | - -### Equality - -Like all types with [`drop`](abilities.mdx) in Move, all integer types support the ["equal"](equality.mdx) and ["not equal"](equality.mdx) operations. Both arguments need to be of the same type. If you need to compare integers of different types, you will need to [cast](#casting) one of them first. - -Equality operations do not abort. - -| Syntax | Operation | -| ------ | --------- | -| `==` | equal | -| `!=` | not equal | - -For more details see the section on [equality](equality.mdx) - -## Casting - -Integer types of one size can be cast to integer types of another size. Integers are the only types in Move that support casting. - -Casts _do not_ truncate. Casting will abort if the result is too large for the specified type - -| Syntax | Operation | Aborts if | -| ---------- | ---------------------------------------------------- | -------------------------------------- | -| `(e as T)` | Cast integer expression `e` into an integer type `T` | `e` is too large to represent as a `T` | - -Here, the type of `e` must be `8`, `16`, `32`, `64`, `128` or `256` and `T` must be `u8`, `u16`, `u32`, `u64`, `u128` or `u256`. - -For example: - -- `(x as u8)` -- `(y as u16)` -- `(873u16 as u32)` -- `(2u8 as u64)` -- `(1 + 3 as u128)` -- `(4/2 + 12345 as u256)` - -Notice that since Language Version 2.0, casts don't always need to be in parentheses. Thus, `x as u8` is a valid expression. - -## Ownership - -As with the other scalar values built-in to the language, integer values are implicitly copyable, meaning they can be copied without an explicit instruction such as [`copy`](variables.mdx#move-and-copy). diff --git a/apps/nextra/pages/zh/build/smart-contracts/book/loops.mdx b/apps/nextra/pages/zh/build/smart-contracts/book/loops.mdx deleted file mode 100644 index 451db489a..000000000 --- a/apps/nextra/pages/zh/build/smart-contracts/book/loops.mdx +++ /dev/null @@ -1,286 +0,0 @@ -# While, For, and Loop - -Move offers three constructs for looping: `while`, `for`, and `loop`. - -## `while` loops - -The `while` construct repeats the body (an expression of type unit) until the condition (an expression of type `bool`) evaluates to `false`. - -Here is an example of simple `while` loop that computes the sum of the numbers from `1` to `n`: - -```move -script { - fun sum(n: u64): u64 { - let sum = 0; - let i = 1; - while (i <= n) { - sum = sum + i; - i = i + 1 - }; - - sum - } -} -``` - -Infinite loops are allowed: - -```move -script { - fun foo() { - while (true) { } - } -} -``` - -### `break` - -The `break` expression can be used to exit a loop before the condition evaluates to `false`. For example, this loop uses `break` to find the smallest factor of `n` that's greater than 1: - -```move -script { - fun smallest_factor(n: u64): u64 { - // assuming the input is not 0 or 1 - let i = 2; - while (i <= n) { - if (n % i == 0) break; - i = i + 1 - }; - - i - } -} -``` - -The `break` expression cannot be used outside of a loop. - -### `continue` - -The `continue` expression skips the rest of the loop and continues to the next iteration. This loop uses `continue` to compute the sum of `1, 2, ..., n`, except when the number is divisible by 10: - -```move -script { - fun sum_intermediate(n: u64): u64 { - let sum = 0; - let i = 0; - while (i < n) { - i = i + 1; - if (i % 10 == 0) continue; - sum = sum + i; - }; - - sum - } -} -``` - -The `continue` expression cannot be used outside of a loop. - -### The type of `break` and `continue` - -`break` and `continue`, much like `return` and `abort`, can have any type. The following examples illustrate where this flexible typing can be helpful: - -```move -script { - fun pop_smallest_while_not_equal( - v1: vector, - v2: vector, - ): vector { - let result = vector::empty(); - while (!vector::is_empty(&v1) && !vector::is_empty(&v2)) { - let u1 = *vector::borrow(&v1, vector::length(&v1) - 1); - let u2 = *vector::borrow(&v2, vector::length(&v2) - 1); - let popped = - if (u1 < u2) vector::pop_back(&mut v1) - else if (u2 < u1) vector::pop_back(&mut v2) - else break; // Here, `break` has type `u64` - vector::push_back(&mut result, popped); - }; - - result - } -} -``` - -```move -script { - fun pick( - indexes: vector, - v1: &vector
, - v2: &vector
- ): vector
{ - let len1 = vector::length(v1); - let len2 = vector::length(v2); - let result = vector::empty(); - while (!vector::is_empty(&indexes)) { - let index = vector::pop_back(&mut indexes); - let chosen_vector = - if (index < len1) v1 - else if (index < len2) v2 - else continue; // Here, `continue` has type `&vector
` - vector::push_back(&mut result, *vector::borrow(chosen_vector, index)) - }; - - result - } -} -``` - -## The `for` expression - -The `for` expression iterates over a range defined using integer-typed `lower_bound` (inclusive) and `upper_bound` (non-inclusive) expressions, executing its loop body for each element of the range. `for` is designed for scenarios where the number of iterations of a loop is determined by a specific range. - -Here is an example of a `for` loop that computes the sum of the elements in a range from `0` to `n-1`: - -```move -script { - fun sum(n: u64): u64 { - let sum = 0; - for (i in 0..n) { - sum = sum + i; - }; - - sum - } -} -``` - -The loop iterator variable (`i` in the above example) currently must be a numeric type (inferred from the bounds), and the bounds `0` and `n` here can be replaced by arbitrary numeric expressions. Each is only evaluated once at the start of the loop. The iterator variable `i` is assigned the `lower_bound` (in this case `0`) and incremented after each loop iteration; the loop exits when the iterator `i` reaches or exceeds `upper_bound` (in this case `n`). - -### `break` and `continue` in `for` loops - -Similar to `while` loops, the `break` expression can be used in `for` loops to exit prematurely. The `continue` expression can be used to skip the current iteration and move to the next. Here's an example that demonstrates the use of both `break` and `continue`. The loop will iterate through numbers from `0` to `n-1`, summing up them up. It will skip numbers that are divisible by `3` (using `continue`) and stop when it encounters a number greater than `10` (using `break`): - -```move -script { - fun sum_conditional(n: u64): u64 { - let sum = 0; - for (iter in 0..n) { - if (iter > 10) { - break; // Exit the loop if the number is greater than 10 - }; - if (iter % 3 == 0) { - continue; // Skip the current iteration if the number is divisible by 3 - }; - - sum = sum + iter; - }; - - sum - } -} -``` - -## The `loop` expression - -The `loop` expression repeats the loop body (an expression with type `()`) until it hits a `break` - -Without a `break`, the loop will continue forever - -```move -script { - fun foo() { - let i = 0; - loop { i = i + 1 } - } -} - -``` - -Here is an example that uses `loop` to write the `sum` function: - -```move -script { - fun sum(n: u64): u64 { - let sum = 0; - let i = 0; - loop { - i = i + 1; - if (i > n) break; - sum = sum + i - }; - - sum - } -} -``` - -As you might expect, `continue` can also be used inside a `loop`. Here is `sum_intermediate` from above rewritten using `loop` instead of `while` - -```move -script { - fun sum_intermediate(n: u64): u64 { - let sum = 0; - let i = 0; - loop { - i = i + 1; - if (i % 10 == 0) continue; - if (i > n) break; - sum = sum + i - }; - - sum - } -} -``` - -## The type of `while`, `loop`, and `for` expression - -Move loops are typed expressions. The `while` and `for` expression always has type `()`. - -```move -script { - fun example() { - let () = while (i < 10) { i = i + 1 }; - let () = for (i in 0..10) {}; - } -} -``` - -If a `loop` contains a `break`, the expression has type unit `()` - -```move -script { - fun example() { - (loop { if (i < 10) i = i + 1 else break }: ()); - let () = loop { if (i < 10) i = i + 1 else break }; - } -} -``` - -If `loop` does not have a `break`, `loop` can have any type much like `return`, `abort`, `break`, and `continue`. - -```move -script { - fun example() { - (loop (): u64); - (loop (): address); - (loop (): &vector>); - } -} -``` - -## Loop Labels - -_Since language version 2.1_ - -A `while` or `loop` statement can have a label which can be referred to by a `break` or `continue` statement. In the presence of nested loops, this allows to refer to outer loops. Example: - -```move -script { - fun example(x: u64): u64 { - 'label1: while (x > 10) { - loop { - if (x % 2 == 0) { - x -= 1; - continue 'label1; - } else if (x < 10) { - break 'label1 - } else - x -= 2 - } - }; - x - } -} -``` diff --git a/apps/nextra/pages/zh/build/smart-contracts/book/modules-and-scripts.mdx b/apps/nextra/pages/zh/build/smart-contracts/book/modules-and-scripts.mdx deleted file mode 100644 index a688a39ca..000000000 --- a/apps/nextra/pages/zh/build/smart-contracts/book/modules-and-scripts.mdx +++ /dev/null @@ -1,137 +0,0 @@ -import { Callout } from "nextra/components"; - -# Modules and Scripts - -Move has two different types of programs: **_Modules_** and **_Scripts_**. Modules are libraries that define struct types along with functions that operate on these types. Struct types define the schema of Move's [global storage](global-storage-structure.mdx), and module functions define the rules for updating storage. Modules themselves are also stored in global storage. A scripts is an executable entrypoint similar to a `main` function in a conventional language. A script typically calls functions of a published module that perform updates to global storage. Scripts are ephemeral code snippets that are not published in global storage. - -A Move source file (or **compilation unit**) may contain multiple modules and scripts. However, publishing a module or executing a script are separate VM operations. - -## Syntax - -### Scripts - - -To learn how to publish and execute a Move script, follow the [Move Scripts](../scripts/script-tutorial.mdx) example. - - -A script has the following structure: - -```text -script { - * - * - fun <[type parameters: constraint]*>([identifier: type]*) -} -``` - -A `script` block must start with all of its [`use`](uses.mdx) declarations, followed by any [constants](constants.mdx) and (finally) the main -[function](functions.mdx) declaration. -The main function can have any name (i.e., it need not be called `main`), is the only function in a script block, can have any number of -arguments, and must not return a value. Here is an example with each of these components: - -```move -script { - // Import the debug module published at the named account address std. - use std::debug; - - const ONE: u64 = 1; - - fun main(x: u64) { - let sum = x + ONE; - debug::print(&sum) - } -} -``` - -Scripts have very limited power—they cannot declare friends, struct types or access global storage. Their primary purpose is to invoke module functions. - -### Modules - -A module has the following syntax: - -```move -module
:: { - ( | | | | )* -} -``` - -where `
` is a valid [named or literal address](address.mdx). - -For example: - -```move -module 0x42::example { - struct Example has copy, drop { i: u64 } - - use std::debug; - friend 0x42::another_example; - - const ONE: u64 = 1; - - public fun print(x: u64) { - let sum = x + ONE; - let example = Example { i: sum }; - debug::print(&sum) - } -} -``` - -The `module 0x42::example` part specifies that the module `example` will be published under the [account address](address.mdx) `0x42` in [global storage](global-storage-structure.mdx). - -Modules can also be declared using [named addresses](address.mdx). For example: - -```move -module example_addr::example { - struct Example has copy, drop { a: address } - - use std::debug; - friend example_addr::another_example; - - public fun print() { - let example = Example { a: @example_addr }; - debug::print(&example) - } -} -``` - -Because named addresses only exist at the source language level and during compilation, -named addresses will be fully substituted for their value at the bytecode -level. For example if we had the following code: - -```move -script { - fun example() { - my_addr::m::foo(@my_addr); - } -} -``` - -and we compiled it with `my_addr` set to `0xC0FFEE`, then it would be equivalent -to the following operationally: - -```move -script { - fun example() { - 0xC0FFEE::m::foo(@0xC0FFEE); - } -} -``` - -However, at the source level, these _are not equivalent_—the function -`m::foo` _must_ be accessed through the `my_addr` named address, and not through -the numerical value assigned to that address. - -Module names can start with letters `a` to `z` or letters `A` to `Z`. After the first character, module names can contain underscores `_`, letters `a` to `z`, letters `A` to `Z`, or digits `0` to `9`. - -```move -module my_module {} -module foo_bar_42 {} -``` - -Typically, module names start with a lowercase letter. A module named `my_module` should be stored in a source file named `my_module.move`. - -All elements inside a `module` block can appear in any order. -Fundamentally, a module is a collection of [`types`](structs-and-resources.mdx) and [`functions`](functions.mdx). -The [`use`](uses.mdx) keyword is used to import types from other modules. -The [`friend`](friends.mdx) keyword specifies a list of trusted modules. -The [`const`](constants.mdx) keyword defines private constants that can be used in the functions of a module. diff --git a/apps/nextra/pages/zh/build/smart-contracts/book/move-2.mdx b/apps/nextra/pages/zh/build/smart-contracts/book/move-2.mdx deleted file mode 100644 index a1a6f1296..000000000 --- a/apps/nextra/pages/zh/build/smart-contracts/book/move-2.mdx +++ /dev/null @@ -1,39 +0,0 @@ -# Move 2 Release Notes - -The Move 2 language releases are described on this page. The reference documentation of the new features is integrated into the book, and marked in the text with "_Since language version 2.n_". - -## Move 2.1 - -The Move 2.1 language release adds the following features to Move: - -- **Compound Assignments** One can now use `x += n`, `x -= n`, etc. to combine assignments and arithmetic operations. See [reference doc here](variables.mdx#compound-assignments) for the supported operations. - -- **Loop Labels** One can now use labels for loops and have a `break` or `continue` expression refer to those labels. This allows to continue or break outer loops from within nested loops. See [reference doc here](loops.mdx#loop-labels). - -- **Underscore function parameters are wildcards, not symbols** Function parameters named `_` no longer act like variables: they do not bind a value, and multiple such parameters to a function does not cause a conflict. Using `_` in a value expression will yield an error, as it has no value. This makes the behavior of `_` more like the wildcard it is in patterns and let expressions, where it does not bind a value. - -## Move 2.0 - -The Move 2.0 language release adds the following features to Move: - -- **Enum Types** add the option to define different variants of data layout in one storable type. They are documented in the [Enum Type section](enums.mdx). - -- **Receiver Style Functions** add the ability to call functions in the familiar notation `value.func(arg)`. They are documented in [this section](functions.mdx#dot-receiver-function-call-style). - -- **Index Notation** allows access to [elements of vectors](vector.mdx#index-notation-for-vectors) and of [resource storage](global-storage-operators.mdx#index-notation-for-storage-operators) with notations like `&mut vector[index]`, or `&mut Resource[addr]`, respectively. - -- **Positional Structs** allow to define wrapper types such as `struct Wrapped(u64)`. Positional structs are described [here](structs-and-resources.mdx#positional-structs). Enum variants are also allowed to be positional. - -- **Dot-dot pattern wildcards** enable statements like `let Struct{x, ..} = value` to match selective parts of data. They are described [here](structs-and-resources.mdx#partial-patterns). Those patterns are also allowed for enum variants. - -- **Package visibility** allows to declare a function to be visible anywhere inside, but not outside a package. Friend functions continue to be supported, although package visibility is in many cases more suitable. As a more concise notation, package and friend functions can be simply declared as `package fun` or `friend fun`, respectively, instead of the longer `public(package) fun` and `public(friend) fun`. This feature is documented [here](functions.mdx#package-visibility). - -- **Assert abort code optional** The `assert!` macro can now be used with just one argument, omitting the abort code, in which case a default code will be chosen. See also [here](abort-and-assert.mdx#assert). - -- **New Cast Syntax** Until now, casts had to always be in parentheses, requiring code like `function((x as u256))`. This requirement is now dropped and casts can be top-level expressions without parenthesis, as in `function(x as u256)`. One still needs to write `(x as u64) + (y as u64)` in expressions. This similarly applies to the new enum variant test, `data is VersionedData::V1`. - -- **Well-defined evaluation order** The evaluation order in the cases below is now well-defined (these were previously unspecified): - - The (a) arguments to a function call, and the (b) operand expressions in a binary operation, are both evaluated from left-to-right. - - Given a "mutate" expression (see [mutating through a reference](variables.mdx#mutating-through-a-reference)) of the form `*lexp = rexp`, where `lexp` is an expression of type `&mut T` and `rexp` is an expression of type `T`, `rexp` is evaluated first, followed by `lexp`. - -- **Bug fix for acquires annotation** [A function should be annotated with `acquires`](functions.mdx#acquires) if and only if it accesses a resource using `move_from`, `borrow_global`, or `borrow_global_mut`, either directly or transitively through a call. Otherwise, it is an error. Previously, when the transitive call graph included a cycle, such errors were not reported: this was incorrect behavior. We have now corrected this behavior to report these errors even when the transitive call graph has cycles. diff --git a/apps/nextra/pages/zh/build/smart-contracts/book/move-tutorial.mdx b/apps/nextra/pages/zh/build/smart-contracts/book/move-tutorial.mdx deleted file mode 100644 index 692293897..000000000 --- a/apps/nextra/pages/zh/build/smart-contracts/book/move-tutorial.mdx +++ /dev/null @@ -1,3 +0,0 @@ -# Move Tutorial - -Please refer to the [Move Core Language Tutorial](https://github.com/aptos-labs/aptos-core/tree/main/aptos-move/move-examples/move-tutorial). diff --git a/apps/nextra/pages/zh/build/smart-contracts/book/package-upgrades.mdx b/apps/nextra/pages/zh/build/smart-contracts/book/package-upgrades.mdx deleted file mode 100644 index 39518d2d8..000000000 --- a/apps/nextra/pages/zh/build/smart-contracts/book/package-upgrades.mdx +++ /dev/null @@ -1,128 +0,0 @@ -import {Callout} from "nextra/components"; - -# Package Upgrades - -Move code (e.g., Move modules) on the Aptos blockchain can be upgraded. This -allows code owners and module developers to update and evolve their contracts -under a single, stable, well-known account address that doesn't change. If a -module upgrade happens, all consumers of that module will automatically receive -the latest version of the code (e.g., the next time they interact with it). - -The Aptos blockchain natively supports different _upgrade policies_, which allow -move developers to explicitly define the constraints around how their move code -can be upgraded. The default policy is _backwards compatible_. This means that -code upgrades are accepted only if they guarantee that no existing resource storage -or public APIs are broken by the upgrade (including public functions). -This compatibility checking is possible because of Move's strongly typed bytecode -semantics. - -We note, however, that even compatible upgrades can have hazardous effects on -applications and dependent Move code (for example, if the semantics of the underlying -module are modified). As a result, developers should be careful when depending on -third-party Move code that can be upgraded on-chain. See -[Security considerations for dependencies](#security-considerations-for-dependencies) -for more details. - -## How it works - -Move code upgrades on the Aptos blockchain happen at the [Move package](packages.mdx) -granularity. A package specifies an upgrade policy in the `Move.toml` manifest: - -```toml -[package] -name = "MyApp" -version = "0.0.1" -upgrade_policy = "compatible" -... -``` - - -Aptos checks compatibility at the time a Move package is published via an Aptos transaction. This transaction will abort if deemed incompatible. - - -## How to upgrade - -To upgrade already published Move code, simply attempt to republish the code at -the same address that it was previously published. This can be done by following the -instructions for code compilation and publishing using the -[Aptos CLI](../../cli/working-with-move-contracts.mdx). For an example, -see the [Your First Move Module](../../guides/first-move-module.mdx) tutorial. - -## Upgrade policies - -There are two different upgrade policies currently supported by Aptos: - -- `compatible`: these upgrades must be backwards compatible, specifically: - - For storage, all old struct declarations must be the same in - the new code. This ensures that the existing state of storage is - correctly interpreted by the new code. However, new struct declarations - can be added. - - For APIs, all existing public functions must have the same signature as - before. New functions, including public and entry functions, can be added. -- `immutable`: the code is not upgradeable and is guaranteed to stay the same - forever. - -Those policies are ordered regarding strength such that `compatible < immutable`, -i.e., compatible is weaker than immutable. The policy of a package on-chain can -only get stronger, not weaker. Moreover, the policy of all dependencies of a -package must be stronger or equal to the policy of the given package. For example, -an `immutable` package cannot refer directly or indirectly to a `compatible` package. -This gives users the guarantee that no unexpected updates can happen under the hood. - -Note that there is one exception to the above rule: framework packages -installed at addresses `0x1` to `0xa` are exempted from the dependency check. -This is necessary so one can define an `immutable` package based on the standard -libraries, which have the `compatible` policy to allow critical upgrades and fixes. - -## Compatibility rules - -When using `compatible` upgrade policy, a module package can be upgraded. However, updates to existing modules already -published previously need to be compatible and follow the rules below: - -- All existing structs' fields cannot be updated. This means no new fields can be added and existing fields cannot be - modified. -- All public and entry functions cannot change their signature (argument types, type argument, return types). However, - argument names can change. -- `public(friend)` functions are treated as private and thus their signature can arbitrarily change. This is safe as - only modules in the same package can call friend functions anyway, and they need to be updated if the signature changes. -- [Enum type upgrade compatibility rules](enums.mdx#enum-type-upgrade-compatibility). -- Existing abilities on a struct/enum type cannot be removed (but abilities can be added). - -When updating your modules, if you see an incompatible error, make sure to check the above rules and fix any violations. - -## Security considerations for dependencies - -As mentioned above, even compatible upgrades can have disastrous effects for -applications that depend on the upgraded code. These effects can come from bugs, -but they can also be the result of malicious upgrades. For example, -an upgraded dependency can suddenly make all functions abort, breaking the -operation of your Move code. Alternatively, an upgraded dependency can make -all functions suddenly cost much more gas to execute then before the upgrade. -As result, dependencies to upgradeable packages need to be handled with care: - -- The safest dependency is, of course, an `immutable` package. This guarantees - that the dependency will never change, including its transitive dependencies. - In order to update an immutable package, the owner would have to introduce a - new major version, which is practically like deploying a new, separate - and independent package. This is because major versioning can be expressed - only by name (e.g. `module feature_v1` and `module feature_v2`). However, - not all package owners like to publish their code as `immutable`, because this - takes away the ability to fix bugs and update the code in place. -- If you have a dependency to a `compatible` package, it is highly - recommended you know and understand the entity publishing the package. - The highest level of assurance is when the package is governed by a - Decentralized Autonomous Organization (DAO) where no single user can initiate - an upgrade; a vote or similar has to be taken. This is the case for the Aptos - framework. - -## Programmatic upgrade - -In general, Aptos offers, via the Move module `aptos_framework::code`, -ways to publish code from anywhere in your smart contracts. However, -notice that code published in the current transaction can be executed -only after that transaction ends. - -The Aptos framework itself, including all the on-chain administration logic, is -an example for programmatic upgrade. The framework is marked as `compatible`. -Upgrades happen via specific generated governance scripts. For more details, -see [Aptos Governance](../../../network/blockchain/governance.mdx). diff --git a/apps/nextra/pages/zh/build/smart-contracts/book/packages.mdx b/apps/nextra/pages/zh/build/smart-contracts/book/packages.mdx deleted file mode 100644 index 541e6554d..000000000 --- a/apps/nextra/pages/zh/build/smart-contracts/book/packages.mdx +++ /dev/null @@ -1,415 +0,0 @@ -import { FileTree } from "nextra/components"; - -# Packages - -Packages allow Move programmers to more easily re-use code and share it -across projects. The Move package system allows programmers to easily do the following: - -- Define a package containing Move code; -- Parameterize a package by [named addresses](address.mdx); -- Import and use packages in other Move code and instantiate named addresses; -- Build packages and generate associated compilation artifacts from packages; and -- Work with a common interface around compiled Move artifacts. - -## Package Layout and Manifest Syntax - -A Move package source directory contains a `Move.toml` package manifest -file along with a set of subdirectories: - - - - - - - - - - - - - - - -The directories marked `required` _must_ be present in order for the directory -to be considered a Move package and to be compiled. Optional directories can -be present, and if so will be included in the compilation process. Depending on -the mode that the package is built with (`test` or `dev`), the `tests` and -`examples` directories will be included as well. - -The `sources` directory can contain both Move modules and Move scripts (both -Move scripts and modules containing script functions). The `examples` -directory can hold additional code to be used only for development and/or -tutorial purposes that will not be included when compiled outside `test` or -`dev` mode. - -A `scripts` directory is supported so Move scripts can be separated -from modules if that is desired by the package author. The `scripts` -directory will always be included for compilation if it is present. -Documentation will be built using any documentation templates present in -the `doc_templates` directory. - -### Move.toml - -The Move package manifest is defined within the `Move.toml` file and has the -following syntax. Optional fields are marked with `*`, `+` denotes -one or more elements: - -```toml filename="Move.toml" -[package] -name = # e.g., "MoveStdlib" -version = ".." # e.g., "0.1.1" -license* = # e.g., "MIT", "GPL", "Apache 2.0" -authors* = [] # e.g., ["Joe Smith (joesmith@noemail.com)", "Jane Smith (janesmith@noemail.com)"] - -[addresses] # (Optional section) Declares named addresses in this package and instantiates named addresses in the package graph -# One or more lines declaring named addresses in the following format - = "_" | "" # e.g., std = "_" or my_addr = "0xC0FFEECAFE" - -[dependencies] # (Optional section) Paths to dependencies and instantiations or renamings of named addresses from each dependency -# One or more lines declaring dependencies in the following format - = { local = , addr_subst* = { ( = ( | ""))+ } } # local dependencies - = { git = , subdir=, rev=, addr_subst* = { ( = ( | ""))+ } } # git dependencies - -[dev-addresses] # (Optional section) Same as [addresses] section, but only included in "dev" and "test" modes -# One or more lines declaring dev named addresses in the following format - = "_" | "" # e.g., std = "_" or my_addr = "0xC0FFEECAFE" - -[dev-dependencies] # (Optional section) Same as [dependencies] section, but only included in "dev" and "test" modes -# One or more lines declaring dev dependencies in the following format - = { local = , addr_subst* = { ( = ( |
))+ } } -``` - -An example of a minimal package manifest with one local dependency and one git dependency: - -```toml -[package] -name = "AName" -version = "0.0.0" -``` - -An example of a more standard package manifest that also includes the Move -standard library and instantiates the named address `Std` from it with the -address value `0x1`: - -```toml -[package] -name = "AName" -version = "0.0.0" -license = "Apache 2.0" - -[addresses] -address_to_be_filled_in = "_" -specified_address = "0xB0B" - -[dependencies] -# Local dependency -LocalDep = { local = "projects/move-awesomeness", addr_subst = { "std" = "0x1" } } -# Git dependency -MoveStdlib = { git = "https://github.com/aptos-labs/aptos-framework", subdir="move-stdlib", rev = "mainnet" } - -[dev-addresses] # For use when developing this module -address_to_be_filled_in = "0x101010101" -``` - -Most of the sections in the package manifest are self-explanatory, but named -addresses can be a bit difficult to understand, so it's worth examining them in -a bit more detail. - -## Named Addresses During Compilation - -Recall that Move has [named addresses](address.mdx) and that -named addresses cannot be declared in Move. Because of this, until now -named addresses and their values needed to be passed to the compiler on the -command line. With the Move package system this is no longer needed, and -you can declare named addresses in the package, instantiate other named -addresses in scope, and rename named addresses from other packages within -the Move package system manifest file. Let's go through each of these -individually: - -### Declaration - -Let's say we have a Move module in `example_pkg/sources/A.move` as follows: - -```move -module named_addr::A { - public fun x(): address { @named_addr } -} -``` - -We could in `example_pkg/Move.toml` declare the named address `named_addr` in -two different ways. The first: - -```toml -[package] -name = "ExamplePkg" -# ... -[addresses] -named_addr = "_" -``` - -Declares `named_addr` as a named address in the package `ExamplePkg` and -that _this address can be any valid address value_. Therefore, an importing -package can pick the value of the named address `named_addr` to be any address -it wishes. Intuitively you can think of this as parameterizing the package -`ExamplePkg` by the named address `named_addr`, and the package can then be -instantiated later on by an importing package. - -`named_addr` can also be declared as: - -```toml -[package] -name = "ExamplePkg" -# ... -[addresses] -named_addr = "0xCAFE" -``` - -which states that the named address `named_addr` is exactly `0xCAFE` and cannot be -changed. This is useful so other importing packages can use this named -address without needing to worry about the exact value assigned to it. - -With these two different declaration methods, there are two ways that -information about named addresses can flow in the package graph: - -- The former ("unassigned named addresses") allows named address values to flow -from the importation site to the declaration site. -- The latter ("assigned named addresses") allows named address values to flow -from the declaration site upwards in the package graph to usage sites. - -With these two methods for flowing named address information throughout the -package graph the rules around scoping and renaming become important to -understand. - -## Scoping and Renaming of Named Addresses - -A named address `N` in a package `P` is in scope if: - -1. It declares a named address `N`; or -2. A package in one of `P`'s transitive dependencies declares the named address -`N` and there is a dependency path in the package graph between `P` and the -declaring package of `N` with no renaming of `N`. - -Additionally, every named address in a package is exported. Because of this and -the above scoping rules each package can be viewed as coming with a set of -named addresses that will be brought into scope when the package is imported, -e.g., if the `ExamplePkg` package was imported, that importation would bring -into scope the `named_addr` named address. Because of this, if `P` imports two -packages `P1` and `P2` both of which declare a named address `N` an issue -arises in `P`: which "`N`" is meant when `N` is referred to in `P`? The one -from `P1` or `P2`? To prevent this ambiguity around which package a named -address is coming from, we enforce that the sets of scopes introduced by all -dependencies in a package are disjoint, and provide a way to _rename named -addresses_ when the package that brings them into scope is imported. - -Renaming a named address when importing can be done as follows in our `P`, -`P1`, and `P2` example above: - -```toml -[package] -name = "P" -# ... -[dependencies] -P1 = { local = "some_path_to_P1", addr_subst = { "P1N" = "N" } } -P2 = { local = "some_path_to_P2" } -``` - -With this renaming `N` refers to the `N` from `P2` and `P1N` will refer to `N` -coming from `P1`: - -```move -module N::A { - public fun x(): address { @P1N } -} -``` - -It is important to note that _renaming is not local_: once a named address `N` -has been renamed to `N2` in a package `P` all packages that import `P` will not -see `N` but only `N2` unless `N` is reintroduced from outside of `P`. This is -why rule (2) in the scoping rules at the start of this section specifies a -"dependency path in the package graph between `P` and the declaring -package of `N` with no renaming of `N`." - -### Instantiation - -Named addresses can be instantiated multiple times across the package graph as -long as it is always with the same value. It is an error if the same named -address (regardless of renaming) is instantiated with differing values across -the package graph. - -A Move package can only be compiled if all named addresses resolve to a value. -This presents issues if the package wishes to expose an uninstantiated named -address. This is what the `[dev-addresses]` section solves. This section can -set values for named addresses, but cannot introduce any named addresses. -Additionally, only the `[dev-addresses]` in the root package are included in -`dev` mode. For example a root package with the following manifest would not compile -outside of `dev` mode since `named_addr` would be uninstantiated: - -```toml -[package] -name = "ExamplePkg" -# ... -[addresses] -named_addr = "_" - -[dev-addresses] -named_addr = "0xC0FFEE" -``` - -## Usage, Artifacts, and Data Structures - -The Move package system comes with a command line option as part of the Move -CLI `move `. Unless a -particular path is provided, all package commands will run in the current working -directory. The full list of commands and flags for the Move CLI can be found by -running `move --help`. - -### Usage - -A package can be compiled either through the Move CLI commands, or as a library -command in Rust with the function `compile_package`. This will create a -`CompiledPackage` that holds the compiled bytecode along with other compilation -artifacts (source maps, documentation, ABIs) in memory. This `CompiledPackage` -can be converted to an `OnDiskPackage` and vice versa -- the latter being the data of -the `CompiledPackage` laid out in the file system in the following format: - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -See the `move-package` crate for more information on these data structures and -how to use the Move package system as a Rust library. - -## Using Bytecode for Dependencies - -Move bytecode can be used as dependencies when the Move source code for those dependencies are not available locally. To use this feature, you will need co-locate the files in directories at the same level and then specify their paths in the corresponding `Move.toml` files. - -## Requirements and limitations - -Using local bytecode as dependencies requires bytecode files to be downloaded locally, and the actual address for each named address must be specified in either `Move.toml` or through `--named-addresses`. - -Note, both `aptos move prove` and `aptos move test` commands, currently, do not support bytecode as dependencies. - -## Recommended structure - -We use an example to illustrate the development flow of using this feature. Suppose we want to compile the package `A`. The package layout is: - - - - - - - - - - - -`A.move` is defined below, depending on the modules `Bar` and `Foo`: - -```move filename="A/AModule.move" -module A::AModule { - use B::Bar; - use C::Foo; - public fun foo(): u64 { - Bar::foo() + Foo::bar() - } -} -``` - -Suppose the source of `Bar` and `Foo` are not available but the corresponding bytecode `Bar.mv` and `Foo.mv` are available locally. To use them as dependencies, we would: - -Specify `Move.toml` for `Bar` and `Foo`. Note that named addresses are already instantiated with the actual address in the bytecode. In our example, the actual address for `C` is already bound to `0x3`. As a result, `[addresses]` must be specified `C` as `0x3`, as shown below: - -```toml filename="workspace/C/Move.toml" -[package] -name = "Foo" -version = "0.0.0" - -[addresses] -C = "0x3" -``` - -Place the bytecode file and the corresponding `Move.toml` file in the same directory with the bytecode in a `build` subdirectory. Note an empty `sources` directory is **required**. For instance, the layout of the folder `B` (for the package `Bar`) and `C` (for the package `Foo`) would resemble: - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -Specify `[dependencies]` in the `Move.toml` of the target (first) package with the location of the dependent (secondary) packages. For instance, assuming all three package directories are at the same level, `Move.toml` of `A` would resemble: - -```toml filename="workspace/A/Move.toml" -[package] -name = "A" -version = "0.0.0" - -[addresses] -A = "0x2" - -[dependencies] -Bar = { local = "../B" } -Foo = { local = "../C" } -``` - -Note that if both the bytecode and the source code of the same package exist in the search paths, the compiler will complain that the declaration is duplicated. diff --git a/apps/nextra/pages/zh/build/smart-contracts/book/references.mdx b/apps/nextra/pages/zh/build/smart-contracts/book/references.mdx deleted file mode 100644 index 47406127c..000000000 --- a/apps/nextra/pages/zh/build/smart-contracts/book/references.mdx +++ /dev/null @@ -1,260 +0,0 @@ -# References - -Move has two types of references: immutable `&` and mutable `&mut`. Immutable references are read -only, and cannot modify the underlying value (or any of its fields). Mutable references allow for -modifications via a write through that reference. Move's type system enforces an ownership -discipline that prevents reference errors. - -For more details on the rules of references, see [Structs and Resources](structs-and-resources.mdx) - -## Reference Operators - -Move provides operators for creating and extending references as well as converting a mutable -reference to an immutable one. Here and elsewhere, we use the notation `e: T` for "expression `e` -has type `T`". - -| Syntax | Type | Description | -| ----------- | ----------------------------------------------------- | -------------------------------------------------------------- | -| `&e` | `&T` where `e: T` and `T` is a non-reference type | Create an immutable reference to `e` | -| `&mut e` | `&mut T` where `e: T` and `T` is a non-reference type | Create a mutable reference to `e`. | -| `&e.f` | `&T` where `e.f: T` | Create an immutable reference to field `f` of struct `e`. | -| `&mut e.f` | `&mut T` where `e.f: T` | Create a mutable reference to field `f` of struct`e`. | -| `freeze(e)` | `&T` where `e: &mut T` | Convert the mutable reference `e` into an immutable reference. | - -The `&e.f` and `&mut e.f` operators can be used both to create a new reference into a struct or to -extend an existing reference: - -```move -script { - fun example() { - let s = S { f: 10 }; - let f_ref1: &u64 = &s.f; // works - let s_ref: &S = &s; - let f_ref2: &u64 = &s_ref.f; // also works - } -} -``` - -A reference expression with multiple fields works as long as both structs are in the same module: - -```move -module 0x42::example { - struct A { b: B } - struct B { c : u64 } - - fun f(a: &A): &u64 { - &a.b.c - } -} -``` - -Finally, note that references to references are not allowed: - -```move -script { - fun example() { - let x = 7; - let y: &u64 = &x; - let z: &&u64 = &y; // will not compile - } -} -``` - -## Reading and Writing Through References - -Both mutable and immutable references can be read to produce a copy of the referenced value. - -Only mutable references can be written. A write `*x = v` discards the value previously stored in `x` -and updates it with `v`. - -Both operations use the C-like `*` syntax. However, note that a read is an expression, whereas a -write is a mutation that must occur on the left hand side of an equals. - -| Syntax | Type | Description | -| ---------- | ----------------------------------- | ----------------------------------- | -| `*e` | `T` where `e` is `&T` or `&mut T` | Read the value pointed to by `e` | -| `*e1 = e2` | `()` where `e1: &mut T` and `e2: T` | Update the value in `e1` with `e2`. | - -In order for a reference to be read, the underlying type must have the -[`copy` ability](abilities.mdx) as reading the reference creates a new copy of the value. This rule -prevents the copying of resource values: - -```move -module 0x42::coin { - struct Coin {} // Note does not have copy - - fun copy_resource_via_ref_bad(c: Coin) { - let c_ref = &c; - let counterfeit: Coin = *c_ref; // not allowed! - pay(c); - pay(counterfeit); - } -} -``` - -Dually: in order for a reference to be written to, the underlying type must have the -[`drop` ability](abilities.mdx) as writing to the reference will discard (or "drop") the old value. -This rule prevents the destruction of resource values: - -```move -module 0x42::coin { - struct Coin {} // Note does not have drop - - fun destroy_resource_via_ref_bad(ten_coins: Coin, c: Coin) { - let ref = &mut ten_coins; - *ref = c; // not allowed--would destroy 10 coins! - } -} -``` - -## `freeze` inference - -A mutable reference can be used in a context where an immutable reference is expected: - -```move -script { - fun example() { - let x = 7; - let y: &u64 = &mut x; - } -} -``` - -This works because the under the hood, the compiler inserts `freeze` instructions where they are -needed. Here are a few more examples of `freeze` inference in action: - -```move -module 0x42::example { - fun takes_immut_returns_immut(x: &u64): &u64 { x } - - // freeze inference on return value - fun takes_mut_returns_immut(x: &mut u64): &u64 { x } - - fun expression_examples() { - let x = 0; - let y = 0; - takes_immut_returns_immut(&x); // no inference - takes_immut_returns_immut(&mut x); // inferred freeze(&mut x) - takes_mut_returns_immut(&mut x); // no inference - - assert!(&x == &mut y, 42); // inferred freeze(&mut y) - } - - fun assignment_examples() { - let x = 0; - let y = 0; - let imm_ref: &u64 = &x; - - imm_ref = &x; // no inference - imm_ref = &mut y; // inferred freeze(&mut y) - } -} -``` - -### Subtyping - -With this `freeze` inference, the Move type checker can view `&mut T` as a subtype of `&T`. As shown -above, this means that anywhere for any expression where a `&T` value is used, a `&mut T` value can -also be used. This terminology is used in error messages to concisely indicate that a `&mut T` was -needed where a `&T` was supplied. For example - -```move -module 0x42::example { - fun read_and_assign(store: &mut u64, new_value: &u64) { - *store = *new_value - } - - fun subtype_examples() { - let x: &u64 = &0; - let y: &mut u64 = &mut 1; - - x = &mut 1; // valid - y = &2; // invalid! - - read_and_assign(y, x); // valid - read_and_assign(x, y); // invalid! - } -} -``` - -will yield the following error messages - -```bash -error: - - ┌── example.move:12:9 ─── - │ - 12 │ y = &2; // invalid! - │ ^ Invalid assignment to local 'y' - · - 12 │ y = &2; // invalid! - │ -- The type: '&{integer}' - · - 9 │ let y: &mut u64 = &mut 1; - │ -------- Is not a subtype of: '&mut u64' - │ - -error: - - ┌── example.move:15:9 ─── - │ - 15 │ read_and_assign(x, y); // invalid! - │ ^^^^^^^^^^^^^^^^^^^^^ Invalid call of '0x42::example::read_and_assign'. Invalid argument for parameter 'store' - · - 8 │ let x: &u64 = &0; - │ ---- The type: '&u64' - · - 3 │ fun read_and_assign(store: &mut u64, new_value: &u64) { - │ -------- Is not a subtype of: '&mut u64' - │ -``` - -The only other types currently that has subtyping are [tuples](tuples.mdx) - -## Ownership - -Both mutable and immutable references can always be copied and extended _even if there are existing -copies or extensions of the same reference_: - -```move -script { - fun reference_copies(s: &mut S) { - let s_copy1 = s; // ok - let s_extension = &mut s.f; // also ok - let s_copy2 = s; // still ok - // ... - } -} -``` - -This might be surprising for programmers familiar with Rust's ownership system, which would reject -the code above. Move's type system is more permissive in its treatment of -[copies](variables.mdx#move-and-copy), but equally strict in ensuring unique ownership of mutable -references before writes. - -### References Cannot Be Stored - -References and tuples are the _only_ types that cannot be stored as a field value of structs, which -also means that they cannot exist in global storage. All references created during program execution -will be destroyed when a Move program terminates; they are entirely ephemeral. This invariant is -also true for values of types without the `store` [ability](abilities.mdx), but note that -references and tuples go a step further by never being allowed in structs in the first place. - -This is another difference between Move and Rust, which allows references to be stored inside of -structs. - -Currently, Move cannot support this because references cannot be -[serialized](https://en.wikipedia.org/wiki/Serialization), but _every Move value must be -serializable_. This requirement comes from Move's -[persistent global storage](global-storage-structure.mdx), which needs to serialize values to -persist them across program executions. Structs can be written to global storage, and thus they must -be serializable. - -One could imagine a fancier, more expressive, type system that would allow references to be stored -in structs _and_ ban those structs from existing in global storage. We could perhaps allow -references inside of structs that do not have the `store` [ability](abilities.mdx), but that would -not completely solve the problem: Move has a fairly complex system for tracking static reference -safety, and this aspect of the type system would also have to be extended to support storing -references inside of structs. In short, Move's type system (particularly the aspects around -reference safety) would have to expand to support stored references. But it is something we are -keeping an eye on as the language evolves. diff --git a/apps/nextra/pages/zh/build/smart-contracts/book/signer.mdx b/apps/nextra/pages/zh/build/smart-contracts/book/signer.mdx deleted file mode 100644 index 4e365e588..000000000 --- a/apps/nextra/pages/zh/build/smart-contracts/book/signer.mdx +++ /dev/null @@ -1,79 +0,0 @@ -# Signer - -`signer` is a built-in Move resource type. A `signer` is a -[capability](https://en.wikipedia.org/wiki/Object-capability_model) that allows the holder to act on -behalf of a particular `address`. You can think of the native implementation as being: - -```move -module 0x1::signer { - struct signer has drop { a: address } -} -``` - -A `signer` is somewhat similar to a Unix [UID](https://en.wikipedia.org/wiki/User_identifier) in -that it represents a user authenticated by code _outside_ of Move (e.g., by checking a cryptographic -signature or password). - -## Comparison to `address` - -A Move program can create any `address` value without special permission using address literals: - -```move -script { - fun example() { - let a1 = @0x1; - let a2 = @0x2; - // ... and so on for every other possible address - } -} -``` - -However, `signer` values are special because they cannot be created via literals or -instructions--only by the Move VM. Before the VM runs a script with parameters of type `signer`, it -will automatically create `signer` values and pass them into the script: - -```move -script { - use std::signer; - fun main(s: signer) { - assert!(signer::address_of(&s) == @0x42, 0); - } -} -``` - -This script will abort with code `0` if the script is sent from any address other than `0x42`. - -A Move script can have an arbitrary number of `signer`s as long as the `signer`s are a prefix -to any other arguments. In other words, all of the `signer` arguments must come first: - -```move -script { - use std::signer; - fun main(s1: signer, s2: signer, x: u64, y: u8) { - // ... - } -} -``` - -This is useful for implementing _multi-signer scripts_ that atomically act with the authority of -multiple parties. For example, an extension of the script above could perform an atomic currency -swap between `s1` and `s2`. - -## `signer` Operators - -The `std::signer` standard library module provides two utility functions over `signer` values: - -| Function | Description | -| ------------------------------------------- | -------------------------------------------------------------- | -| `signer::address_of(&signer): address` | Return the `address` wrapped by this `&signer`. | -| `signer::borrow_address(&signer): &address` | Return a reference to the `address` wrapped by this `&signer`. | - -In addition, the `move_to(&signer, T)` [global storage operator](global-storage-operators.mdx) -requires a `&signer` argument to publish a resource `T` under `signer.address`'s account. This -ensures that only an authenticated user can elect to publish a resource under their `address`. - -## Ownership - -Unlike simple scalar values, `signer` values are not copyable, meaning they cannot be copied from -any operation whether it be through an explicit [`copy`](variables.mdx#move-and-copy) instruction -or through a [dereference `*`](references.mdx#reading-and-writing-through-references). diff --git a/apps/nextra/pages/zh/build/smart-contracts/book/standard-library.mdx b/apps/nextra/pages/zh/build/smart-contracts/book/standard-library.mdx deleted file mode 100644 index 3a23d67f6..000000000 --- a/apps/nextra/pages/zh/build/smart-contracts/book/standard-library.mdx +++ /dev/null @@ -1,4 +0,0 @@ -# Libraries - -Aptos provides multiple useful libraries for developers. The complete up-to-date -docs can be found [here](../../../network/blockchain/move.mdx). diff --git a/apps/nextra/pages/zh/build/smart-contracts/book/structs-and-resources.mdx b/apps/nextra/pages/zh/build/smart-contracts/book/structs-and-resources.mdx deleted file mode 100644 index a238e0c91..000000000 --- a/apps/nextra/pages/zh/build/smart-contracts/book/structs-and-resources.mdx +++ /dev/null @@ -1,621 +0,0 @@ -# Structs and Resources - -A _struct_ is a user-defined data structure containing typed fields. Structs can store any -non-reference type, including other structs. - -We often refer to struct values as _resources_ if they cannot be copied and cannot be dropped. In -this case, resource values must have ownership transferred by the end of the function. This property -makes resources particularly well served for defining global storage schemas or for representing -important values (such as a token). - -By default, structs are linear and ephemeral. By this we mean that they: cannot be copied, cannot be -dropped, and cannot be stored in global storage. This means that all values have to have ownership -transferred (linear) and the values must be dealt with by the end of the program's execution -(ephemeral). We can relax this behavior by giving the struct [abilities](abilities.mdx) which allow -values to be copied or dropped and also to be stored in global storage or to define global storage -schemas. - -## Defining Structs - -Structs must be defined inside a module: - -```move -module 0x2::m { - struct Foo { x: u64, y: bool } - struct Bar {} - struct Baz { foo: Foo, } - // ^ note: it is fine to have a trailing comma -} -``` - -Structs cannot be recursive, so the following definition is invalid: - -```move -module 0x2::m { - struct Foo { x: Foo } - // ^ error! Foo cannot contain Foo -} -``` -For positional structs which used numbered instead of named fields, see -the [positional structs](#positional-structs) section. - -As mentioned above: by default, a struct declaration is linear and ephemeral. So to allow the value -to be used with certain operations (that copy it, drop it, store it in global storage, or use it as -a storage schema), structs can be granted [abilities](abilities.mdx) by annotating them with -`has `: - -```move -module 0x2::m { - struct Foo has copy, drop { x: u64, y: bool } -} -``` - -For more details, see the [annotating structs](abilities.mdx#annotating-structs) section. - - -### Naming - -Structs must start with a capital letter `A` to `Z`. After the first letter, struct names can -contain underscores `_`, letters `a` to `z`, letters `A` to `Z`, or digits `0` to `9`. - -```move -module 0x2::m { - struct Foo {} - struct BAR {} - struct B_a_z_4_2 {} -} -``` - -This naming restriction of starting with `A` to `Z` is in place to give room for future language -features. It may or may not be removed later. - -## Using Structs - -### Creating Structs - -Values of a struct type can be created (or "packed") by indicating the struct name, followed by -value for each field: - -```move -module 0x2::m { - struct Foo has drop { x: u64, y: bool } - struct Baz has drop { foo: Foo } - - fun example() { - let foo = Foo { x: 0, y: false }; - let baz = Baz { foo }; - } -} -``` - -If you initialize a struct field with a local variable whose name is the same as the field, you can -use the following shorthand: - -```move -module 0x2::m { - fun example() { - let baz = Baz { foo: foo }; - // is equivalent to - let baz = Baz { foo }; - } -} -``` - -This is sometimes called "field name punning". - -### Destroying Structs via Pattern Matching - -Struct values can be destroyed by binding or assigning them patterns. - -```move -module 0x2::m { - struct Foo { x: u64, y: bool } - struct Bar { foo: Foo } - struct Baz {} - - fun example_destroy_foo() { - let foo = Foo { x: 3, y: false }; - let Foo { x, y: foo_y } = foo; - // ^ shorthand for `x: x` - - // two new bindings - // x: u64 = 3 - // foo_y: bool = false - } - - fun example_destroy_foo_wildcard() { - let foo = Foo { x: 3, y: false }; - let Foo { x, y: _ } = foo; - - // only one new binding since y was bound to a wildcard - // x: u64 = 3 - } - - fun example_destroy_foo_assignment() { - let x: u64; - let y: bool; - Foo { x, y } = Foo { x: 3, y: false }; - - // mutating existing variables x & y - // x = 3, y = false - } - - fun example_foo_ref() { - let foo = Foo { x: 3, y: false }; - let Foo { x, y } = &foo; - - // two new bindings - // x: &u64 - // y: &bool - } - - fun example_foo_ref_mut() { - let foo = Foo { x: 3, y: false }; - let Foo { x, y } = &mut foo; - - // two new bindings - // x: &mut u64 - // y: &mut bool - } - - fun example_destroy_bar() { - let bar = Bar { foo: Foo { x: 3, y: false } }; - let Bar { foo: Foo { x, y } } = bar; - // ^ nested pattern - - // two new bindings - // x: u64 = 3 - // y: bool = false - } - - fun example_destroy_baz() { - let baz = Baz {}; - let Baz {} = baz; - } -} -``` - -### Borrowing Structs and Fields - -The `&` and `&mut` operator can be used to create references to structs or fields. These examples -include some optional type annotations (e.g., `: &Foo`) to demonstrate the type of operations. - -```move -module 0x2::m { - fun example() { - let foo = Foo { x: 3, y: true }; - let foo_ref: &Foo = &foo; - let y: bool = foo_ref.y; // reading a field via a reference to the struct - let x_ref: &u64 = &foo.x; - - let x_ref_mut: &mut u64 = &mut foo.x; - *x_ref_mut = 42; // modifying a field via a mutable reference - } -} -``` - -It is possible to borrow inner fields of nested structs: - -```move -module 0x2::m { - fun example() { - let foo = Foo { x: 3, y: true }; - let bar = Bar { foo }; - - let x_ref = &bar.foo.x; - } -} -``` - -You can also borrow a field via a reference to a struct: - -```move -module 0x2::m { - fun example() { - let foo = Foo { x: 3, y: true }; - let foo_ref = &foo; - let x_ref = &foo_ref.x; - // this has the same effect as let x_ref = &foo.x - } -} -``` - -### Reading and Writing Fields - -If a field is copyable, you can read and copy a field's value by dereferencing the borrowed field: - -```move -module 0x2::m { - fun example() { - let foo = Foo { x: 3, y: true }; - let bar = Bar { foo: copy foo }; - let x: u64 = *&foo.x; - let y: bool = *&foo.y; - let foo2: Foo = *&bar.foo; - } -} -``` - -The dot operator can be used to read and copy any copyable field of a struct without explicit -borrowing and dereferencing: - -```move -module 0x2::m { - fun example() { - let foo = Foo { x: 3, y: true }; - let x = foo.x; // x == 3 - let y = foo.y; // y == true - - let bar = Bar { foo }; - let foo2: Foo = *&bar.foo; // `Foo` must be copyable - let foo3: Foo = bar.foo; // same as the statement above - } -} -``` - -Dot operators can be chained to access nested fields: - -```move -module 0x2::m { - fun example() { - let baz = Baz { foo: Foo { x: 3, y: true } }; - let x = baz.foo.x; // x = 3; - } -} -``` - -Furthermore, the dot syntax can be used to modify fields. - -```move -module 0x2::m { - fun example() { - let foo = Foo { x: 3, y: true }; - foo.x = 42; // foo = Foo { x: 42, y: true } - foo.y = !foo.y; // foo = Foo { x: 42, y: false } - let bar = Bar { foo }; // bar = Bar { foo: Foo { x: 42, y: false } } - bar.foo.x = 52; // bar = Bar { foo: Foo { x: 52, y: false } } - bar.foo = Foo { x: 62, y: true }; // bar = Bar { foo: Foo { x: 62, y: true } } - } -} -``` - -The dot syntax also works via a reference to a struct: - -```move -module 0x2::m { - fun example() { - let foo = Foo { x: 3, y: true }; - let foo_ref = &mut foo; - foo_ref.x = foo_ref.x + 1; - } -} -``` - -## Privileged Struct Operations - -Most struct operations on a struct type `T` can only be performed inside the module that declares -`T`: - -- Struct types can only be created ("packed"), destroyed ("unpacked") inside the module that defines - the struct. -- The fields of a struct are only accessible inside the module that defines the struct. - -Following these rules, if you want to modify your struct outside the module, you will need to -provide public APIs for them. The end of the chapter contains some examples of this. - -However, struct _types_ are always visible to another module or script: - -```move -// m.move -module 0x2::m { - struct Foo has drop { x: u64 } - - public fun new_foo(): Foo { - Foo { x: 42 } - } -} -``` - -```move -// n.move -module 0x2::n { - use 0x2::m; - - struct Wrapper has drop { - foo: m::Foo - } - - fun f1(foo: m::Foo) { - let x = foo.x; - // ^ error! cannot access fields of `foo` here - } - - fun f2() { - let foo_wrapper = Wrapper { foo: m::new_foo() }; - } -} -``` - -Note that structs do not have visibility modifiers (e.g., `public` or `private`). - -## Ownership - -As mentioned above in [Defining Structs](#defining-structs), structs are by default linear and -ephemeral. This means they cannot be copied or dropped. This property can be very useful when -modeling real world resources like money, as you do not want money to be duplicated or get lost in -circulation. - -```move -module 0x2::m { - struct Foo { x: u64 } - - public fun copying_resource() { - let foo = Foo { x: 100 }; - let foo_copy = copy foo; // error! 'copy'-ing requires the 'copy' ability - let foo_ref = &foo; - let another_copy = *foo_ref // error! dereference requires the 'copy' ability - } - - public fun destroying_resource1() { - let foo = Foo { x: 100 }; - - // error! when the function returns, foo still contains a value. - // This destruction requires the 'drop' ability - } - - public fun destroying_resource2(f: &mut Foo) { - *f = Foo { x: 100 } // error! - // destroying the old value via a write requires the 'drop' ability - } -} -``` - -To fix the second example (`fun destroying_resource1`), you would need to manually "unpack" the -resource: - -```move -module 0x2::m { - struct Foo { x: u64 } - - public fun destroying_resource1_fixed() { - let foo = Foo { x: 100 }; - let Foo { x: _ } = foo; - } -} -``` - -Recall that you are only able to deconstruct a resource within the module in which it is defined. -This can be leveraged to enforce certain invariants in a system, for example, conservation of money. - -If on the other hand, your struct does not represent something valuable, you can add the abilities -`copy` and `drop` to get a struct value that might feel more familiar from other programming -languages: - -```move -module 0x2::m { - struct Foo has copy, drop { x: u64 } - - public fun run() { - let foo = Foo { x: 100 }; - let foo_copy = copy foo; - // ^ this code copies foo, whereas `let x = foo` or - // `let x = move foo` both move foo - - let x = foo.x; // x = 100 - let x_copy = foo_copy.x; // x = 100 - - // both foo and foo_copy are implicitly discarded when the function returns - } -} -``` - -## Positional Structs - -_Since language version 2.0_ - -A struct can be declared to have _positional fields_, fields which are not named -but numbered. Positional structs behave similar to regular structs, -except providing a different syntax which may be more suitable for use cases with -only a few fields. - -Fields of positional structs are assigned in the order they appear. In the below -example, field `0` is of type `u64` and field `1` of type `u8`: - -```move -module 0x2::m { - struct Pair(u64, u8); -} -``` - -Abilities for positional structs are declared _after_ and not before the field list, - -```move -module 0x2::m { - struct Pair(u64, u8) has copy, drop; -} -``` - -For pure type tags, often used for phantom types in Move code, the list of arguments -can be also completely omitted: - -```move -module 0x2::m { - struct TypeTag has copy, drop; -} -``` - -Values of positional structs are created and deconstructed as shown below: -using `PositionalStructs(arguments)`: - -```move -module 0x2::m { - fun work() { - let value = Pair(1, true); - let Pair(number, boolean) = value; - assert!(number == 1 && boolean == true); - } -} -``` - -Fields of positional structs can be accessed using the position as a field selector. For example, in the above code example, `value.0` and `value.1` can be used to access the two fields without deconstructing the `value`. -`positional_struct.0`. - -## Partial Patterns - -_Since language version 2.0_ - -Patterns can use the `..` notation to match any remaining, non-listed fields in structs or variants with named fields, and omitted fields at either the beginning or end of a struct or variant with positional fields. Here are -some examples: - -```move -module 0x2::m { - struct Foo{ x: u8, y: u16, z: u32 } - struct Bar(u8, u16, u32); - - fun foo_get_x(self: &Foo): u16 { - let Foo{y, ..} = self; - x - } - - fun bar_get_0(self: &Foo): u8 { - let Bar(x, ..) = self; - x - } - - fun bar_get_2(self: &Foo): u52 { - // For positional structs, one can also put the - // .. at the beginning. - let Bar(.., z) = self; - z - } -} -``` - -Notice that partial patterns can currently not be used as the left-hand side of assignment. -While one can use `let Bar(x, ..) = v`, we do not yet support `let x; Bar(x, ..) = v`. - - -## Storing Resources in Global Storage - -Structs with the `key` ability can be saved directly in -[persistent global storage](global-storage-operators.mdx). All values stored within those `key` -structs must have the `store` ability. See the [ability](abilities.mdx) and -[global storage](global-storage-operators.mdx) chapters for more detail. - -## Examples - -Here are two short examples of how you might use structs to represent valuable data (in the case of -`Coin`) or more classical data (in the case of `Point` and `Circle`). - -### Example 1: Coin - -{/* */} - -```move -module 0x2::m { - // We do not want the Coin to be copied because that would be duplicating this "money", - // so we do not give the struct the 'copy' ability. - // Similarly, we do not want programmers to destroy coins, so we do not give the struct the - // 'drop' ability. - // However, we *want* users of the modules to be able to store this coin in persistent global - // storage, so we grant the struct the 'store' ability. This struct will only be inside of - // other resources inside of global storage, so we do not give the struct the 'key' ability. - struct Coin has store { - value: u64, - } - - public fun mint(value: u64): Coin { - // You would want to gate this function with some form of access control to prevent - // anyone using this module from minting an infinite amount of coins. - Coin { value } - } - - public fun withdraw(coin: &mut Coin, amount: u64): Coin { - assert!(coin.balance >= amount, 1000); - coin.value = coin.value - amount; - Coin { value: amount } - } - - public fun deposit(coin: &mut Coin, other: Coin) { - let Coin { value } = other; - coin.value = coin.value + value; - } - - public fun split(coin: Coin, amount: u64): (Coin, Coin) { - let other = withdraw(&mut coin, amount); - (coin, other) - } - - public fun merge(coin1: Coin, coin2: Coin): Coin { - deposit(&mut coin1, coin2); - coin1 - } - - public fun destroy_zero(coin: Coin) { - let Coin { value } = coin; - assert!(value == 0, 1001); - } -} -``` - -### Example 2: Geometry - -```move -module 0x2::point { - struct Point has copy, drop, store { - x: u64, - y: u64, - } - - public fun new(x: u64, y: u64): Point { - Point { - x, y - } - } - - public fun x(p: &Point): u64 { - p.x - } - - public fun y(p: &Point): u64 { - p.y - } - - fun abs_sub(a: u64, b: u64): u64 { - if (a < b) { - b - a - } - else { - a - b - } - } - - public fun dist_squared(p1: &Point, p2: &Point): u64 { - let dx = abs_sub(p1.x, p2.x); - let dy = abs_sub(p1.y, p2.y); - dx*dx + dy*dy - } -} -``` - -```move -module 0x2::circle { - use 0x2::point::{Self, Point}; - - struct Circle has copy, drop, store { - center: Point, - radius: u64, - } - - public fun new(center: Point, radius: u64): Circle { - Circle { center, radius } - } - - public fun overlaps(c1: &Circle, c2: &Circle): bool { - let dist_squared_value = point::dist_squared(&c1.center, &c2.center); - let r1 = c1.radius; - let r2 = c2.radius; - dist_squared_value <= r1*r1 + 2*r1*r2 + r2*r2 - } -} -``` diff --git a/apps/nextra/pages/zh/build/smart-contracts/book/tuples.mdx b/apps/nextra/pages/zh/build/smart-contracts/book/tuples.mdx deleted file mode 100644 index 81f6109a3..000000000 --- a/apps/nextra/pages/zh/build/smart-contracts/book/tuples.mdx +++ /dev/null @@ -1,137 +0,0 @@ -# Tuples and Unit - -Move does not fully support tuples as one might expect coming from another language with them as a -[first-class value](https://en.wikipedia.org/wiki/First-class_citizen). However, in order to support multiple return values, Move has tuple-like -expressions. These expressions do not result in a concrete value at runtime (there are no tuples in -the bytecode), and as a result they are very limited: they can only appear in expressions (usually -in the return position for a function); they cannot be bound to local variables; they cannot be -stored in structs; and tuple types cannot be used to instantiate generics. - -Similarly, [unit `()`](https://en.wikipedia.org/wiki/Unit_type) is a type created by the Move source language in order to be expression based. -The unit value `()` does not result in any runtime value. We can consider unit`()` to be an empty -tuple, and any restrictions that apply to tuples also apply to unit. - -It might feel weird to have tuples in the language at all given these restrictions. But one of the -most common use cases for tuples in other languages is for functions to allow functions to return -multiple values. Some languages work around this by forcing the users to write structs that contain -the multiple return values. However, in Move, you cannot put references inside of -[structs](structs-and-resources.mdx). This required Move to support multiple return values. These -multiple return values are all pushed on the stack at the bytecode level. At the source level, these -multiple return values are represented using tuples. - -## Literals - -Tuples are created by a comma separated list of expressions inside of parentheses. - -| Syntax | Type | Description | -| --------------- | ---------------------------------------------------------------------------- | ------------------------------------------------------------ | -| `()` | `(): ()` | Unit, the empty tuple, or the tuple of arity 0 | -| `(e1, ..., en)` | `(e1, ..., en): (T1, ..., Tn)` where `e_i: Ti` s.t. `0 < i <= n` and `n > 0` | A `n`-tuple, a tuple of arity `n`, a tuple with `n` elements | - -Note that `(e)` does not have type `(e): (t)`, in other words there is no tuple with one element. If -there is only a single element inside the parentheses, the parentheses are only used for -disambiguation and do not carry any other special meaning. - -Sometimes, tuples with two elements are called "pairs" and tuples with three elements are called -"triples." - -### Examples - -```move -module 0x42::example { - // all 3 of these functions are equivalent - - // when no return type is provided, it is assumed to be `()` - fun returns_unit_1() { } - - // there is an implicit () value in empty expression blocks - fun returns_unit_2(): () { } - - // explicit version of `returns_unit_1` and `returns_unit_2` - fun returns_unit_3(): () { () } - - - fun returns_3_values(): (u64, bool, address) { - (0, false, @0x42) - } - fun returns_4_values(x: &u64): (&u64, u8, u128, vector) { - (x, 0, 1, b"foobar") - } -} -``` - -## Operations - -The only operation that can be done on tuples currently is destructuring. - -### Destructuring - -For tuples of any size, they can be destructured in either a `let` binding or in an assignment. - -For example: - -```move -module 0x42::example { - // all 3 of these functions are equivalent - fun returns_unit() {} - fun returns_2_values(): (bool, bool) { (true, false) } - fun returns_4_values(x: &u64): (&u64, u8, u128, vector) { (x, 0, 1, b"foobar") } - - fun examples(cond: bool) { - let () = (); - let (x, y): (u8, u64) = (0, 1); - let (a, b, c, d) = (@0x0, 0, false, b""); - - () = (); - (x, y) = if (cond) (1, 2) else (3, 4); - (a, b, c, d) = (@0x1, 1, true, b"1"); - } - - fun examples_with_function_calls() { - let () = returns_unit(); - let (x, y): (bool, bool) = returns_2_values(); - let (a, b, c, d) = returns_4_values(&0); - - () = returns_unit(); - (x, y) = returns_2_values(); - (a, b, c, d) = returns_4_values(&1); - } -} -``` - -For more details, see [Move Variables](variables.mdx). - -## Subtyping - -Along with references, tuples are the only other type that have [subtyping](https://en.wikipedia.org/wiki/Subtyping) in Move. Tuples have -subtyping only in the sense that they subtype with references (in a covariant way). - -For example: - -```move -script { - fun example() { - let x: &u64 = &0; - let y: &mut u64 = &mut 1; - - // (&u64, &mut u64) is a subtype of (&u64, &u64) - // since &mut u64 is a subtype of &u64 - let (a, b): (&u64, &u64) = (x, y); - - // (&mut u64, &mut u64) is a subtype of (&u64, &u64) - // since &mut u64 is a subtype of &u64 - let (c, d): (&u64, &u64) = (y, y); - - // error! (&u64, &mut u64) is NOT a subtype of (&mut u64, &mut u64) - // since &u64 is NOT a subtype of &mut u64 - let (e, f): (&mut u64, &mut u64) = (x, y); - } -} -``` - -## Ownership - -As mentioned above, tuple values don't really exist at runtime. And currently they cannot be stored -into local variables because of this (but it is likely that this feature will come soon). As such, -tuples can only be moved currently, as copying them would require putting them into a local variable -first. diff --git a/apps/nextra/pages/zh/build/smart-contracts/book/unit-testing.mdx b/apps/nextra/pages/zh/build/smart-contracts/book/unit-testing.mdx deleted file mode 100644 index af5e42b64..000000000 --- a/apps/nextra/pages/zh/build/smart-contracts/book/unit-testing.mdx +++ /dev/null @@ -1,263 +0,0 @@ -# Unit Tests - -Unit testing for Move adds three new annotations to the Move source language: - -- `#[test]` -- `#[test_only]`, and -- `#[expected_failure]`. - -They respectively mark a function as a test, mark a module or module member (`use`, function, or struct) as code to be included for testing only, and mark that a test is expected to fail. These annotations can be placed on a function with any visibility. Whenever a module or module member is annotated as `#[test_only]` or `#[test]`, it will not be included in the compiled bytecode unless it is compiled for testing. - -## Testing Annotations: Their Meaning and Usage - -Both the `#[test]` and `#[expected_failure]` annotations can be used either with or without arguments. - -Without arguments, the `#[test]` annotation can only be placed on a function with no parameters. This annotation simply marks this function as a test to be run by the unit testing harness. - -```move -module 0x42::example { - #[test] // OK - fun this_is_a_test() { /* ... */ } - - #[test] // Will fail to compile since the test takes an argument - fun this_is_not_correct(arg: signer) { /* ... */ } -} -``` - -### Expected Failure - -A test can also be annotated as an `#[expected_failure]`. This -annotation marks that the test should is expected to raise an error. - -You can ensure that a test is aborting with a specific abort `` -by annotating it with `#[expected_failure(abort_code = )]`, -corresponding to the parameter to an `abort` statement (or -failing `assert!` macro). - -Instead of an `abort_code`, an `expected_failure` may specify program -execution errors, such as `arithmetic_error`, `major_status`, -`vector_error`, and `out_of_gas`. For more specificity, a -`minor_status` may optionally be specified. - -If the error is expected from a specific location, that may also be specified: -`#[expected_failure(abort_code = , location = )]`. -If the test then fails with the right error but in a different module, the test will also fail. -Note that `` can be `Self`(in the current module) or a qualified name, e.g. `vector::std`. - -Only functions that have the `#[test]` annotation can also be annotated as an #`[expected_failure]`. - -```move -module 0x42::example { - #[test] - #[expected_failure] - public fun this_test_will_abort_and_pass() { abort 1 } - - #[test] - #[expected_failure] - public fun test_will_error_and_pass() { 1/0; } - - #[test] - #[expected_failure(abort_code = 0, location = Self)] - public fun test_will_error_and_fail() { 1/0; } - - #[test, expected_failure] // Can have multiple in one attribute. This test will pass. - public fun this_other_test_will_abort_and_pass() { abort 1 } - - #[test] - #[expected_failure(vector_error, minor_status = 1, location = Self)] - fun borrow_out_of_range() { /* ... */ } - #[test] - #[expected_failure(abort_code = 26113, location = extensions::table)] - fun test_destroy_fails() { /* ... */ } -} -``` - -### Test parameters - -With arguments, a test annotation takes the form `#[test( =
, ..., =
)]`. If a function is annotated in such a manner, the function's parameters must be a permutation of the parameters `, ..., `, i.e., the order of these parameters as they occur in the function and their order in the test annotation do not have to be the same, but they must be able to be matched up with each other by name. - -Only parameters with a type of `signer` are supported as test parameters. If a parameter other than `signer` is supplied, the test will result in an error when run. - -```move -module 0x42::example { - #[test(arg = @0xC0FFEE)] // OK - fun this_is_correct_now(arg: signer) { /* ... */ } - - #[test(wrong_arg_name = @0xC0FFEE)] // Not correct: arg name doesn't match - fun this_is_incorrect(arg: signer) { /* ... */ } - - #[test(a = @0xC0FFEE, b = @0xCAFE)] // OK. We support multiple signer arguments, but you must always provide a value for that argument - fun this_works(a: signer, b: signer) { /* ... */ } - - // somewhere a named address is declared - #[test_only] // test-only named addresses are supported - address TEST_NAMED_ADDR = @0x1; - ... - #[test(arg = @TEST_NAMED_ADDR)] // Named addresses are supported! - fun this_is_correct_now(arg: signer) { /* ... */ } -} -``` - -### Arbitrary code to support tests - -A module and any of its members can be declared as test only. In such a case the item will only be included in the compiled Move bytecode when compiled in test mode. Additionally, when compiled outside of test mode, any non-test `use`s of a `#[test_only]` module will raise an error during compilation. - -```move -#[test_only] // test only attributes can be attached to modules -module 0x42::abc { /*... */ } - -module 0x42::other { - #[test_only] // test only attributes can be attached to named addresses - address ADDR = @0x1; - - #[test_only] // .. to uses - use 0x1::some_other_module; - - #[test_only] // .. to structs - struct SomeStruct { /* ... */ } - - #[test_only] // .. and functions. Can only be called from test code, but not a test - fun test_only_function(/* ... */) { /* ... */ } -} -``` - -## Running Unit Tests - -Unit tests for a Move package can be run with the `aptos move test` command. See [package](packages.mdx) for more info. - -When running tests, every test will either `PASS`, `FAIL`, or `TIMEOUT`. If a test case fails, the location of the failure along with the function name that caused the failure will be reported if possible. You can see an example of this below. - -A test will be marked as timing out if it exceeds the maximum number of instructions that can be executed for any single test. This bound can be changed using the options below, and its default value is set to 100000 instructions. Additionally, while the result of a test is always deterministic, tests are run in parallel by default, so the ordering of test results in a test run is non-deterministic unless running with only one thread (see `OPTIONS` below). - -There are also a number of options that can be passed to the unit testing binary to fine-tune testing and to help debug failing tests. These can be found using the help flag: - -```bash filename="Terminal" -$ aptos move test -h -``` - -## Example - -A simple module using some of the unit testing features is shown in the following example: - -First create an empty package inside an empty directory: - -```bash filename="Terminal" -$ aptos move init --name TestExample -``` - -Next add the following to the `Move.toml`: - -```toml -[dependencies] -MoveStdlib = { git = "https://github.com/aptos-labs/aptos-framework.git", subdir="aptos-move/framework/move-stdlib", rev = "main", addr_subst = { "std" = "0x1" } } -``` - -Next add the following module under the `sources` directory: - -```move -// filename: sources/my_module.move -module 0x1::my_module { - - struct MyCoin has key { value: u64 } - - public fun make_sure_non_zero_coin(coin: MyCoin): MyCoin { - assert!(coin.value > 0, 0); - coin - } - - public fun has_coin(addr: address): bool { - exists(addr) - } - - #[test] - fun make_sure_non_zero_coin_passes() { - let coin = MyCoin { value: 1 }; - let MyCoin { value: _ } = make_sure_non_zero_coin(coin); - } - - #[test] - // Or #[expected_failure] if we don't care about the abort code - #[expected_failure(abort_code = 0, location = Self)] - fun make_sure_zero_coin_fails() { - let coin = MyCoin { value: 0 }; - let MyCoin { value: _ } = make_sure_non_zero_coin(coin); - } - - #[test_only] // test only helper function - fun publish_coin(account: &signer) { - move_to(account, MyCoin { value: 1 }) - } - - #[test(a = @0x1, b = @0x2)] - fun test_has_coin(a: signer, b: signer) { - publish_coin(&a); - publish_coin(&b); - assert!(has_coin(@0x1), 0); - assert!(has_coin(@0x2), 1); - assert!(!has_coin(@0x3), 1); - } -} -``` - -### Running Tests - -You can then run these tests with the `aptos move test` command: - -```bash filename="Terminal" -$ aptos move test -BUILDING MoveStdlib -BUILDING TestExample -Running Move unit tests -[ PASS ] 0x1::my_module::make_sure_non_zero_coin_passes -[ PASS ] 0x1::my_module::make_sure_zero_coin_fails -[ PASS ] 0x1::my_module::test_has_coin -Test result: OK. Total tests: 3; passed: 3; failed: 0 -``` - -### Using Test Flags - -#### `-f ` or `--filter ` - -This will only run tests whose fully qualified name contains ``. For example if we wanted to only run tests with `"zero_coin"` in their name: - -```bash filename="Terminal" -$ aptos move test -f zero_coin -CACHED MoveStdlib -BUILDING TestExample -Running Move unit tests -[ PASS ] 0x1::my_module::make_sure_non_zero_coin_passes -[ PASS ] 0x1::my_module::make_sure_zero_coin_fails -Test result: OK. Total tests: 2; passed: 2; failed: 0 -``` - -#### `--coverage` - -This will compute code being covered by test cases and generate coverage summary. - -```bash filename="Terminal" -$ aptos move test --coverage -INCLUDING DEPENDENCY AptosFramework -INCLUDING DEPENDENCY AptosStdlib -INCLUDING DEPENDENCY MoveStdlib -BUILDING TestExample -Running Move unit tests -[ PASS ] 0x1::my_module::make_sure_non_zero_coin_passes -[ PASS ] 0x1::my_module::make_sure_zero_coin_fails -[ PASS ] 0x1::my_module::test_has_coin -Test result: OK. Total tests: 3; passed: 3; failed: 0 -+-------------------------+ -| Move Coverage Summary | -+-------------------------+ -Module 0000000000000000000000000000000000000000000000000000000000000001::my_module ->>> % Module coverage: 100.00 -+-------------------------+ -| % Move Coverage: 100.00 | -+-------------------------+ -Please use `aptos move coverage -h` for more detailed source or bytecode test coverage of this package -``` - -Then by running `aptos move coverage`, we can get more detailed coverage information. These can be found using the help flag: - -```bash filename="Terminal" -$ aptos move coverage -h -``` diff --git a/apps/nextra/pages/zh/build/smart-contracts/book/uses.mdx b/apps/nextra/pages/zh/build/smart-contracts/book/uses.mdx deleted file mode 100644 index 337884499..000000000 --- a/apps/nextra/pages/zh/build/smart-contracts/book/uses.mdx +++ /dev/null @@ -1,366 +0,0 @@ -# Uses and Aliases - -The `use` syntax can be used to create aliases to members in other modules. `use` can be used to -create aliases that last either for the entire module, or for a given expression block scope. - -## Syntax - -There are several different syntax cases for `use`. Starting with the most simple, we have the -following for creating aliases to other modules - -```move -use
::; -use
:: as ; -``` - -For example - -```move -script { - use std::vector; - use std::vector as V; -} -``` - -`use std::vector;` introduces an alias `vector` for `std::vector`. This means that anywhere you -would want to use the module name `std::vector` (assuming this `use` is in scope), you could use -`vector` instead. `use std::vector;` is equivalent to `use std::vector as vector;` - -Similarly `use std::vector as V;` would let you use `V` instead of `std::vector` - -```move -module 0x42::example { - use std::vector; - use std::vector as V; - - fun new_vecs(): (vector, vector, vector) { - let v1 = std::vector::empty(); - let v2 = vector::empty(); - let v3 = V::empty(); - (v1, v2, v3) - } -} -``` - -If you want to import a specific module member (such as a function, struct, or constant). You can -use the following syntax. - -```move -use
::::; -use
:::: as ; -``` - -For example - -```move -script { - use std::vector::empty; - use std::vector::empty as empty_vec; -} -``` - -This would let you use the function `std::vector::empty` without full qualification. Instead, you -could use `empty` and `empty_vec` respectively. Again, `use std::vector::empty;` is equivalent to -`use std::vector::empty as empty;` - -```move -module 0x42::example { - use std::vector::empty; - use std::vector::empty as empty_vec; - - fun new_vecs(): (vector, vector, vector) { - let v1 = std::vector::empty(); - let v2 = empty(); - let v3 = empty_vec(); - (v1, v2, v3) - } -} -``` - -If you want to add aliases for multiple module members at once, you can do so with the following -syntax - -```move -use
::::{, as ... }; -``` - -For example - -```move -module 0x42::example { - use std::vector::{push_back, length as len, pop_back}; - - fun swap_last_two(v: &mut vector) { - assert!(len(v) >= 2, 42); - let last = pop_back(v); - let second_to_last = pop_back(v); - push_back(v, last); - push_back(v, second_to_last) - } -} -``` - -If you need to add an alias to the Module itself in addition to module members, you can do that in a -single `use` using `Self`. `Self` is a member of sorts that refers to the module. - -```move -script { - use std::vector::{Self, empty}; -} -``` - -For clarity, all the following are equivalent: - -```move -script { - use std::vector; - use std::vector as vector; - use std::vector::Self; - use std::vector::Self as vector; - use std::vector::{Self}; - use std::vector::{Self as vector}; -} -``` - -If needed, you can have as many aliases for any item as you like - -```move -module 0x42::example { - use std::vector::{ - Self, - Self as V, - length, - length as len, - }; - - fun pop_twice(v: &mut vector): (T, T) { - // all options available given the `use` above - assert!(vector::length(v) > 1, 42); - assert!(V::length(v) > 1, 42); - assert!(length(v) > 1, 42); - assert!(len(v) > 1, 42); - - (vector::pop_back(v), vector::pop_back(v)) - } -} -``` - -## Inside a `module` - -Inside a `module` all `use` declarations are usable regardless of the order of declaration. - -```move -module 0x42::example { - use std::vector; - - fun example(): vector { - let v = empty(); - vector::push_back(&mut v, 0); - vector::push_back(&mut v, 10); - v - } - - use std::vector::empty; -} -``` - -The aliases declared by `use` in the module are usable within that module. - -Additionally, the aliases introduced cannot conflict with other module members. See -[Uniqueness](#uniqueness) for more details - -## Inside an expression - -You can add `use` declarations to the beginning of any expression block - -```move -module 0x42::example { - - fun example(): vector { - use std::vector::{empty, push_back}; - - let v = empty(); - push_back(&mut v, 0); - push_back(&mut v, 10); - v - } -} -``` - -As with `let`, the aliases introduced by `use` in an expression block are removed at the end of that -block. - -```move -module 0x42::example { - - fun example(): vector { - let result = { - use std::vector::{empty, push_back}; - let v = empty(); - push_back(&mut v, 0); - push_back(&mut v, 10); - v - }; - result - } -} -``` - -Attempting to use the alias after the block ends will result in an error - -```move -module 0x42::example { - fun example(): vector { - let result = { - use std::vector::{empty, push_back}; - let v = empty(); - push_back(&mut v, 0); - push_back(&mut v, 10); - v - }; - let v2 = empty(); // ERROR! -// ^^^^^ unbound function 'empty' - result - } -} -``` - -Any `use` must be the first item in the block. If the `use` comes after any expression or `let`, it -will result in a parsing error - -```move -script { - fun example() { - { - let x = 0; - use std::vector; // ERROR! - let v = vector::empty(); - } - } -} - -``` - -## Naming rules - -Aliases must follow the same rules as other module members. This means that aliases to structs or -constants must start with `A` to `Z` - -```move -address 0x42 { - module data { - struct S {} - const FLAG: bool = false; - fun foo() {} - } - module example { - use 0x42::data::{ - S as s, // ERROR! - FLAG as fLAG, // ERROR! - foo as FOO, // valid - foo as bar, // valid - }; - } -} -``` - -## Uniqueness - -Inside a given scope, all aliases introduced by `use` declarations must be unique. - -For a module, this means aliases introduced by `use` cannot overlap - -```move -module 0x42::example { - use std::vector::{empty as foo, length as foo}; // ERROR! - // ^^^ duplicate 'foo' - - use std::vector::empty as bar; - use std::vector::length as bar; // ERROR! - // ^^^ duplicate 'bar' -} -``` - -And, they cannot overlap with any of the module's other members - -```move -address 0x42 { - module data { - struct S {} - } - module example { - use 0x42::data::S; - - struct S { value: u64 } // ERROR! - // ^ conflicts with alias 'S' above - } -} -``` - -Inside an expression block, they cannot overlap with each other, but they can -[shadow](#shadowing) other aliases or names from an outer scope - -## Shadowing - -`use` aliases inside of an expression block can shadow names (module members or aliases) from the -outer scope. As with shadowing of locals, the shadowing ends at the end of the expression block; - -```move -module 0x42::example { - - struct WrappedVector { vec: vector } - - fun empty(): WrappedVector { - WrappedVector { vec: std::vector::empty() } - } - - fun example1(): (WrappedVector, WrappedVector) { - let vec = { - use std::vector::{empty, push_back}; - // 'empty' now refers to std::vector::empty - - let v = empty(); - push_back(&mut v, 0); - push_back(&mut v, 1); - push_back(&mut v, 10); - v - }; - // 'empty' now refers to Self::empty - - (empty(), WrappedVector { vec }) - } - - fun example2(): (WrappedVector, WrappedVector) { - use std::vector::{empty, push_back}; - let w: WrappedVector = { - use 0x42::example::empty; - empty() - }; - push_back(&mut w.vec, 0); - push_back(&mut w.vec, 1); - push_back(&mut w.vec, 10); - - let vec = empty(); - push_back(&mut vec, 0); - push_back(&mut vec, 1); - push_back(&mut vec, 10); - - (w, WrappedVector { vec }) - } -} -``` - -## Unused Use or Alias - -An unused `use` will result in an error - -```move -module 0x42::example { - use std::vector::{empty, push_back}; // ERROR! - // ^^^^^^^^^ unused alias 'push_back' - - fun example(): vector { - empty() - } -} -``` diff --git a/apps/nextra/pages/zh/build/smart-contracts/book/variables.mdx b/apps/nextra/pages/zh/build/smart-contracts/book/variables.mdx deleted file mode 100644 index 721cd7e98..000000000 --- a/apps/nextra/pages/zh/build/smart-contracts/book/variables.mdx +++ /dev/null @@ -1,1012 +0,0 @@ -# Local Variables and Scope - -Local variables in Move are lexically (statically) scoped. New variables are introduced with the -keyword `let`, which will shadow any previous local with the same name. Locals are mutable and can -be updated both directly and via a mutable reference. - -## Declaring Local Variables - -### `let` bindings - -Move programs use `let` to bind variable names to values: - -```move -script { - fun example() { - let x = 1; - let y = x + x; - } -} -``` - -`let` can also be used without binding a value to the local. - -```move -script { - fun example() { - let x; - } -} -``` - -The local can then be assigned a value later. - -```move -script { - fun example() { - let x; - if (cond) { - x = 1 - } else { - x = 0 - } - } -} -``` - -This can be very helpful when trying to extract a value from a loop when a default value cannot be -provided. - -```move -script { - fun example() { - let x; - let cond = true; - let i = 0; - loop { - (x, cond) = foo(i); - if (!cond) break; - i = i + 1; - } - } -} -``` - -### Variables must be assigned before use - -Move's type system prevents a local variable from being used before it has been assigned. - -```move -script { - fun example() { - let x; - x + x; // ERROR! - } -} -``` - -```move -script { - fun example() { - let x; - if (cond) x = 0; - x + x; // ERROR! - } -} -``` - -```move -script { - fun example() { - let x; - while (cond) x = 0; - x + x; // ERROR! - } -} -``` - -### Valid variable names - -Variable names can contain underscores `_`, letters `a` to `z`, letters `A` to `Z`, and digits `0` -to `9`. Variable names must start with either an underscore `_` or a letter `a` through `z`. They -_cannot_ start with uppercase letters. - -```move -script { - fun example() { - // all valid - let x = e; - let _x = e; - let _A = e; - let x0 = e; - let xA = e; - let foobar_123 = e; - - // all invalid - let X = e; // ERROR! - let Foo = e; // ERROR! - } -} -``` - -### Type annotations - -The type of local variable can almost always be inferred by Move's type system. However, Move -allows explicit type annotations that can be useful for readability, clarity, or debuggability. The -syntax for adding a type annotation is: - -```move -script { - fun example() { - let x: T = e; // "Variable x of type T is initialized to expression e" - } -} -``` - -Some examples of explicit type annotations: - -```move -module 0x42::example { - - struct S { f: u64, g: u64 } - - fun annotated() { - let u: u8 = 0; - let b: vector = b"hello"; - let a: address = @0x0; - let (x, y): (&u64, &mut u64) = (&0, &mut 1); - let S { f, g: f2 }: S = S { f: 0, g: 1 }; - } -} -``` - -Note that the type annotations must always be to the right of the pattern: - -```move -script { - fun example() { - let (x: &u64, y: &mut u64) = (&0, &mut 1); // ERROR! should be let (x, y): ... = - } -} -``` - -### When annotations are necessary - -In some cases, a local type annotation is required if the type system cannot infer the type. This -commonly occurs when the type argument for a generic type cannot be inferred. For example: - -```move -script { - fun example() { - let _v1 = vector::empty(); // ERROR! - // ^^^^^^^^^^^^^^^ Could not infer this type. Try adding an annotation - let v2: vector = vector::empty(); // no error - } -} -``` - -In a rarer case, the type system might not be able to infer a type for divergent code (where all the -following code is unreachable). Both `return` and [`abort`](abort-and-assert.mdx) are expressions -and can have any type. A [`loop`](loops.mdx) has type `()` if it has a `break`, but if there is no -break out of the `loop`, it could have any type. If these types cannot be inferred, a type -annotation is required. For example, this code: - -```move -script { - fun example() { - let a: u8 = return (); - let b: bool = abort 0; - let c: signer = loop (); - - let x = return (); // ERROR! - // ^ Could not infer this type. Try adding an annotation - let y = abort 0; // ERROR! - // ^ Could not infer this type. Try adding an annotation - let z = loop (); // ERROR! - // ^ Could not infer this type. Try adding an annotation - } -} -``` - -Adding type annotations to this code will expose other errors about dead code or unused local -variables, but the example is still helpful for understanding this problem. - -### Multiple declarations with tuples - -`let` can introduce more than one local at a time using tuples. The locals declared inside the -parenthesis are initialized to the corresponding values from the tuple. - -```move -script { - fun example() { - let () = (); - let (x0, x1) = (0, 1); - let (y0, y1, y2) = (0, 1, 2); - let (z0, z1, z2, z3) = (0, 1, 2, 3); - } -} -``` - -The type of the expression must match the arity of the tuple pattern exactly. - -```move -script { - fun example() { - let (x, y) = (0, 1, 2); // ERROR! - let (x, y, z, q) = (0, 1, 2); // ERROR! - } -} -``` - -You cannot declare more than one local with the same name in a single `let`. - -```move -script { - fun example() { - let (x, x) = 0; // ERROR! - } -} -``` - -### Multiple declarations with structs - -`let` can also introduce more than one local at a time when destructuring (or matching against) a -struct. In this form, the `let` creates a set of local variables that are initialized to the values -of the fields from a struct. The syntax looks like this: - -```move -script { - fun example() { - struct T { f1: u64, f2: u64 } - } -} -``` - -```move -script { - fun example() { - let T { f1: local1, f2: local2 } = T { f1: 1, f2: 2 }; - // local1: u64 - // local2: u64 - } -} -``` - -Here is a more complicated example: - -```move -module 0x42::example { - struct X { f: u64 } - struct Y { x1: X, x2: X } - - fun new_x(): X { - X { f: 1 } - } - - fun example() { - let Y { x1: X { f }, x2 } = Y { x1: new_x(), x2: new_x() }; - assert!(f + x2.f == 2, 42); - - let Y { x1: X { f: f1 }, x2: X { f: f2 } } = Y { x1: new_x(), x2: new_x() }; - assert!(f1 + f2 == 2, 42); - } -} -``` - -Fields of structs can serve double duty, identifying the field to bind _and_ the name of the -variable. This is sometimes referred to as punning. - -```move -script { - fun example() { - let X { f } = e; - } -} -``` - -is equivalent to: - -```move -script { - fun example() { - let X { f: f } = e; - } -} -``` - -As shown with tuples, you cannot declare more than one local with the same name in a single `let`. - -```move -script { - fun example() { - let Y { x1: x, x2: x } = e; // ERROR! - } -} -``` - -### Destructuring against references - -In the examples above for structs, the bound value in the let was moved, destroying the struct value -and binding its fields. - -```move -script { - fun example() { - struct T { f1: u64, f2: u64 } - } -} -``` - -```move -script { - fun example() { - let T { f1: local1, f2: local2 } = T { f1: 1, f2: 2 }; - // local1: u64 - // local2: u64 - } -} -``` - -In this scenario the struct value `T { f1: 1, f2: 2 }` no longer exists after the `let`. - -If you wish instead to not move and destroy the struct value, you can borrow each of its fields. For -example: - -```move -script { - fun example() { - let t = T { f1: 1, f2: 2 }; - let T { f1: local1, f2: local2 } = &t; - // local1: &u64 - // local2: &u64 - } -} -``` - -And similarly with mutable references: - -```move -script { - fun example() { - let t = T { f1: 1, f2: 2 }; - let T { f1: local1, f2: local2 } = &mut t; - // local1: &mut u64 - // local2: &mut u64 - } -} -``` - -This behavior can also work with nested structs. - -```move -module 0x42::example { - struct X { f: u64 } - struct Y { x1: X, x2: X } - - fun new_x(): X { - X { f: 1 } - } - - fun example() { - let y = Y { x1: new_x(), x2: new_x() }; - - let Y { x1: X { f }, x2 } = &y; - assert!(*f + x2.f == 2, 42); - - let Y { x1: X { f: f1 }, x2: X { f: f2 } } = &mut y; - *f1 = *f1 + 1; - *f2 = *f2 + 1; - assert!(*f1 + *f2 == 4, 42); - } -} -``` - -### Ignoring Values - -In `let` bindings, it is often helpful to ignore some values. Local variables that start with `_` -will be ignored and not introduce a new variable - -```move -module 0x42::example { - fun three(): (u64, u64, u64) { - (0, 1, 2) - } - - fun example() { - let (x1, _, z1) = three(); - let (x2, _y, z2) = three(); - assert!(x1 + z1 == x2 + z2, 42); - } -} -``` - -This can be necessary at times as the compiler will error on unused local variables - -```move -module 0x42::example { - fun example() { - let (x1, y, z1) = three(); // ERROR! - // ^ unused local 'y' - } -} -``` - -### General `let` grammar - -All the different structures in `let` can be combined! With that we arrive at this general -grammar for `let` statements: - -> _let-binding_ → **let** _pattern-or-list_ _type-annotation__opt_ _initializer__opt_ - -> _pattern-or-list_ → _pattern_ | **(** _pattern-list_ **)** - -> _pattern-list_ → _pattern_ **,**_opt_ | _pattern_ **,** _pattern-list_ - -> _type-annotation_ → **:** _type_ - -> _initializer_ → **=** _expression_ - -The general term for the item that introduces the bindings is a _pattern_. The pattern serves to -both destructure data (possibly recursively) and introduce the bindings. The pattern grammar is as -follows: - -> _pattern_ → _local-variable_ | _struct-type_ **\{** _field-binding-list_ **}** - -> _field-binding-list_ → _field-binding_ **,**_opt_ | _field-binding_ **,** _field-binding-list_ - -> _field-binding_ → _field_ | _field_ **:** _pattern_ - -A few concrete examples with this grammar applied: - -```move -script { - fun example() { - let (x, y): (u64, u64) = (0, 1); - // ^ local-variable - // ^ pattern - // ^ local-variable - // ^ pattern - // ^ pattern-list - // ^^^^ pattern-list - // ^^^^^^ pattern-or-list - // ^^^^^^^^^^^^ type-annotation - // ^^^^^^^^ initializer - // ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ let-binding - - let Foo { f, g: x } = Foo { f: 0, g: 1 }; - // ^^^ struct-type - // ^ field - // ^ field-binding - // ^ field - // ^ local-variable - // ^ pattern - // ^^^^ field-binding - // ^^^^^^^ field-binding-list - // ^^^^^^^^^^^^^^^ pattern - // ^^^^^^^^^^^^^^^ pattern-or-list - // ^^^^^^^^^^^^^^^^^^^^ initializer - // ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ let-binding - } -} -``` - -## Mutations - -### Assignments - -After the local is introduced (either by `let` or as a function parameter), the local can be -modified via an assignment: - -```move -script { - fun example(e: u8) { - let x = 0; - x = e - } -} -``` - -Unlike `let` bindings, assignments are expressions. In some languages, assignments return the value -that was assigned, but in Move, the type of any assignment is always `()`. - -```move - -script { - fun example(e: u8) { - let x = 0; - (x = e) == () - } -} -``` - -Practically, assignments being expressions means that they can be used without adding a new -expression block with braces (`{`...`}`). - -```move -script { - fun example(e: u8) { - let x = 0; - if (cond) x = 1 else x = 2; - } -} -``` - -The assignment uses the same pattern syntax scheme as `let` bindings: - -```move -module 0x42::example { - struct X { f: u64 } - - fun new_x(): X { - X { f: 1 } - } - - // This example will complain about unused variables and assignments. - fun example() { - let (x, _, z) = (0, 1, 3); - let (x, y, f, g); - - (X { f }, X { f: x }) = (new_x(), new_x()); - assert!(f + x == 2, 42); - - (x, y, z, f, _, g) = (0, 0, 0, 0, 0, 0); - } -} -``` - -Note that a local variable can only have one type, so the type of the local cannot change between -assignments. - -```move -script { - fun example() { - let x; - x = 0; - x = false; // ERROR! - } -} -``` - -### Mutating through a reference - -In addition to directly modifying a local with assignment, a local can be modified via a mutable -reference `&mut`. - -```move -script { - fun example() { - let x = 0; - let r = &mut x; - *r = 1; - assert!(x == 1, 42); - } -} -``` - -This is particularly useful if either: - -(1) You want to modify different variables depending on some condition. - -```move -script { - fun example() { - let x = 0; - let y = 1; - let r = if (cond) { - &mut x - } else { - &mut y - }; - *r = *r + 1; - } -} -``` - -(2) You want another function to modify your local value. - -```move -script { - fun example() { - let x = 0; - modify_ref(&mut x); - } -} -``` - -This sort of modification is how you modify structs and vectors! - -```move -script { - use 0x1::vector; - - fun example() { - let v = vector::empty(); - vector::push_back(&mut v, 100); - assert!(*vector::borrow(&v, 0) == 100, 42); - } -} -``` - -For more details, see [Move references](references.mdx). - -### Compound Assignments - -_Since language version 2.1_ - -Move also supports compound assignment operators. These are like an assignment to a variable, -or a mutation through a reference, except that the assigned location must already have a value, -which is read and operated on before being stored back into the location. -Currently these are only applicable to numeric values. - -| Syntax | Description | -| ------ | ------------------------------------------------------------ | -| `+=` | Performs addition and updates the left-hand value | -| `-=` | Performs subtraction and updates the left-hand value | -| `*=` | Performs multiplication and updates the left-hand value | -| `%=` | Performs modular division and updates the left-hand value | -| `/=` | Performs truncating division and updates the left-hand value | -| `&=` | Performs bitwise and and updates the left-hand value | -| `\|=` | Performs bitwise or and updates the left-hand value | -| `^=` | Performs bitwise xor and updates the left-hand value | -| `<<=` | Performs shift left and updates the left-hand value | -| `>>=` | Performs shift right and updates the left-hand value | - -For `e1 += e2`, the **modifying operand** `e2` is evaluated first, followed by the **assigned operand** `e1`. -The result of performing `+` on the operand values is then stored in the left-hand side location. -The assigned operand is only evaluated once. Similarly for all other operations listed in the table above. - -```move -module 0x42::example { - struct S { f: u64 } - - fun example() { - let x = 41; - x += 1; - assert!(x == 42); - - let y = 41; - let p = &mut y; - *p += 1; - assert!(*p == 42); - - let z = S { f: 41 }; - z.f += 1; - assert!(z.f == 42); - } -} -``` - -## Scopes - -Any local declared with `let` is available for any subsequent expression, _within that scope_. -Scopes are declared with expression blocks, `{`...`}`. - -Locals cannot be used outside the declared scope. - -```move -script { - fun example() { - let x = 0; - { - let y = 1; - }; - x + y // ERROR! - // ^ unbound local 'y' - } -} -``` - -But, locals from an outer scope _can_ be used in a nested scope. - -```move -script { - fun example() { - { - let x = 0; - { - let y = x + 1; // valid - } - } - } -} -``` - -Locals can be mutated in any scope where they are accessible. That mutation survives with the local, -regardless of the scope that performed the mutation. - -```move -script { - fun example() { - let x = 0; - x = x + 1; - assert!(x == 1, 42); - { - x = x + 1; - assert!(x == 2, 42); - }; - assert!(x == 2, 42); - } -} -``` - -### Expression Blocks - -An expression block is a series of statements separated by semicolons (`;`). The resulting value of -an expression block is the value of the last expression in the block. - -```move -script { - fun example() { - { let x = 1; let y = 1; x + y } - } -} -``` - -In this example, the result of the block is `x + y`. - -A statement can be either a `let` declaration or an expression. Remember that assignments (`x = e`) -are expressions of type `()`. - -```move -script { - fun example() { - { let x; let y = 1; x = 1; x + y } - } -} -``` - -Function calls are another common expression of type `()`. Function calls that modify data are -commonly used as statements. - -```move -script { - fun example() { - { let v = vector::empty(); vector::push_back(&mut v, 1); v } - } -} -``` - -This is not just limited to `()` types---any expression can be used as a statement in a sequence! - -```move -script { - fun example() { - { - let x = 0; - x + 1; // value is discarded - x + 2; // value is discarded - b"hello"; // value is discarded - } - } -``` - -But! If the expression contains a resource (a value without the `drop` [ability](abilities.mdx)), -you will get an error. This is because Move's type system guarantees that any value that is dropped -has the `drop` [ability](abilities.mdx). (Ownership must be transferred or the value must be -explicitly destroyed within its declaring module.) - -```move -script { - fun example() { - { - let x = 0; - Coin { value: x }; // ERROR! - // ^^^^^^^^^^^^^^^^^ unused value without the `drop` ability - x - } - } -} -``` - -If a final expression is not present in a block---that is, if there is a trailing semicolon `;`, -there is an implicit [unit `()` value](https://en.wikipedia.org/wiki/Unit_type). Similarly, if the expression block is empty, there is an -implicit unit `()` value. - -```move -script { - fun example() { - // Both are equivalent - { x = x + 1; 1 / x; }; - { x = x + 1; 1 / x; () }; - } -} -``` - -```move -script { - fun example() { - // Both are equivalent - {} - { () } - } -} -``` - -An expression block is itself an expression and can be used anyplace an expression is used. (Note: -The body of a function is also an expression block, but the function body cannot be replaced by -another expression.) - -```move -script { - fun example() { - let my_vector: vector> = { - let v = vector::empty(); - vector::push_back(&mut v, b"hello"); - vector::push_back(&mut v, b"goodbye"); - v - }; - } -} -``` - -(The type annotation is not needed in this example and only added for clarity.) - -### Shadowing - -If a `let` introduces a local variable with a name already in scope, that previous variable can no -longer be accessed for the rest of this scope. This is called _shadowing_. - -```move -script { - fun example() { - let x = 0; - assert!(x == 0, 42); - - let x = 1; // x is shadowed - assert!(x == 1, 42); - } -} -``` - -When a local is shadowed, it does not need to retain the same type as before. - -```move -script { - fun example() { - let x = 0; - assert!(x == 0, 42); - - let x = b"hello"; // x is shadowed - assert!(x == b"hello", 42); - } -} -``` - -After a local is shadowed, the value stored in the local still exists, but will no longer be -accessible. This is important to keep in mind with values of types without the -[`drop` ability](abilities.mdx), as ownership of the value must be transferred by the end of the -function. - -```move -module 0x42::example { - struct Coin has store { value: u64 } - - fun unused_resource(): Coin { - let x = Coin { value: 0 }; // ERROR! - // ^ This local still contains a value without the `drop` ability - x.value = 1; - let x = Coin { value: 10 }; - x - // ^ Invalid return - } -} -``` - -When a local is shadowed inside a scope, the shadowing only remains for that scope. The shadowing is -gone once that scope ends. - -```move -script { - fun example() { - let x = 0; - { - let x = 1; - assert!(x == 1, 42); - }; - assert!(x == 0, 42); - } -} - -``` - -Remember, locals can change type when they are shadowed. - -```move -script { - fun example() { - let x = 0; - { - let x = b"hello"; - assert!(x = b"hello", 42); - }; - assert!(x == 0, 42); - } -} -``` - -## Move and Copy - -All local variables in Move can be used in two ways, either by `move` or `copy`. If one or the other -is not specified, the Move compiler is able to infer whether a `copy` or a `move` should be used. -This means that in all the examples above, a `move` or a `copy` would be inserted by the -compiler. A local variable cannot be used without the use of `move` or `copy`. - -`copy` will likely feel the most familiar coming from other programming languages, as it creates a -new copy of the value inside the variable to use in that expression. With `copy`, the local -variable can be used more than once. - -```move -script { - fun example() { - let x = 0; - let y = copy x + 1; - let z = copy x + 2; - } -} -``` - -Any value with the `copy` [ability](abilities.mdx) can be copied in this way. - -`move` takes the value out of the local variable _without_ copying the data. After a `move` occurs, -the local variable is unavailable. - -```move -script { - fun example() { - let x = 1; - let y = move x + 1; - // ------ Local was moved here - let z = move x + 2; // Error! - // ^^^^^^ Invalid usage of local 'x' - y + z; - } -} -``` - -### Safety - -Move's type system will prevent a value from being used after it is moved. This is the same safety -check described in [`let` declaration](#let-bindings) that prevents local variables from being used -before it is assigned a value. - -{/* For more information, see TODO future section on ownership and move semantics. */} - -### Inference - -As mentioned above, the Move compiler will infer a `copy` or `move` if one is not indicated. The -algorithm for doing so is quite simple: - -- Any value with the `copy` [ability](abilities.mdx) is given a `copy`. -- Any reference (both mutable `&mut` and immutable `&`) is given a `copy`. - - Except under special circumstances where it is made a `move` for predictable borrow checker - errors. -- Any other value is given a `move`. -- If the compiler can prove that the source value with copy ability is not used after the - assignment, then a move may be used instead of a copy for performance, but this will be invisible - to the programmer (except in possible decreased time or gas cost). - -For example: - -```move -module 0x42::example { - struct Foo { - f: u64 - } - - struct Coin has copy { - value: u64 - } - - fun example() { - let s = b"hello"; - let foo = Foo { f: 0 }; - let coin = Coin { value: 0 }; - - let s2 = s; // copy - let foo2 = foo; // move - let coin2 = coin; // copy - - let x = 0; - let b = false; - let addr = @0x42; - let x_ref = &x; - let coin_ref = &mut coin2; - - let x2 = x; // copy - let b2 = b; // copy - let addr2 = @0x42; // copy - let x_ref2 = x_ref; // copy - let coin_ref2 = coin_ref; // copy - } -} -``` diff --git a/apps/nextra/pages/zh/build/smart-contracts/book/vector.mdx b/apps/nextra/pages/zh/build/smart-contracts/book/vector.mdx deleted file mode 100644 index e399325c4..000000000 --- a/apps/nextra/pages/zh/build/smart-contracts/book/vector.mdx +++ /dev/null @@ -1,224 +0,0 @@ -# Vector - -`vector` is the only primitive collection type provided by Move. A `vector` is a homogenous -collection of `T`'s that can grow or shrink by pushing/popping values off the "end". - -A `vector` can be instantiated with any type `T`. For example, `vector`, `vector
`, -`vector<0x42::MyModule::MyResource>`, and `vector>` are all valid vector types. - -## Literals - -### General `vector` Literals - -Vectors of any type can be created with `vector` literals. - -| Syntax | Type | Description | -| --------------------- | ----------------------------------------------------------------------------- | ------------------------------------------ | -| `vector[]` | `vector[]: vector` where `T` is any single, non-reference type | An empty vector | -| `vector[e1, ..., en]` | `vector[e1, ..., en]: vector` where `e_i: T` s.t. `0 < i <= n` and `n > 0` | A vector with `n` elements (of length `n`) | - -In these cases, the type of the `vector` is inferred, either from the element type or from the -vector's usage. If the type cannot be inferred, or simply for added clarity, the type can be -specified explicitly: - -```move -vector[]: vector -vector[e1, ..., en]: vector -``` - -#### Example Vector Literals - -```move -script { - fun example() { - (vector[]: vector); - (vector[0u8, 1u8, 2u8]: vector); - (vector[]: vector); - (vector
[@0x42, @0x100]: vector
); - } -} -``` - -### `vector` literals - -A common use-case for vectors in Move is to represent "byte arrays", which are represented with -`vector`. These values are often used for cryptographic purposes, such as a public key or a hash -result. These values are so common that specific syntax is provided to make the values more -readable, as opposed to having to use `vector[]` where each individual `u8` value is specified in -numeric form. - -There are currently two supported types of `vector` literals, _byte strings_ and _hex strings_. - -#### Byte Strings - -Byte strings are quoted string literals prefixed by a `b`, e.g. `b"Hello!\n"`. - -These are ASCII encoded strings that allow for escape sequences. Currently, the supported escape -sequences are: - -| Escape Sequence | Description | -| --------------- | ---------------------------------------------- | -| `\n` | New line (or Line feed) | -| `\r` | Carriage return | -| `\t` | Tab | -| `\\` | Backslash | -| `\0` | Null | -| `\"` | Quote | -| `\xHH` | Hex escape, inserts the hex byte sequence `HH` | - -#### Hex Strings - -Hex strings are quoted string literals prefixed by a `x`, e.g. `x"48656C6C6F210A"`. - -Each byte pair, ranging from `00` to `FF`, is interpreted as hex encoded `u8` value. So each byte -pair corresponds to a single entry in the resulting `vector`. - -#### Example String Literals - -```move -script { - fun byte_and_hex_strings() { - assert!(b"" == x"", 0); - assert!(b"Hello!\n" == x"48656C6C6F210A", 1); - assert!(b"\x48\x65\x6C\x6C\x6F\x21\x0A" == x"48656C6C6F210A", 2); - assert!( - b"\"Hello\tworld!\"\n \r \\Null=\0" == - x"2248656C6C6F09776F726C6421220A200D205C4E756C6C3D00", - 3 - ); - } -} -``` - -## Operations - -`vector` provides several operations via the `std::vector` module in the Move standard -library, as shown below. More operations may be added over time. -Up-to-date document on `vector` can be found [here](https://github.com/aptos-labs/aptos-core/blob/main/aptos-move/framework/move-stdlib/doc/vector.md). - -| Function | Description | Aborts? | -| ------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------ | -------------------------------------------------------- | -| `vector::empty(): vector` | Create an empty vector that can store values of type `T` | Never | -| `vector::is_empty(self: &vector): bool` | Return `true` if the vector `self` has no elements and `false` otherwise. | Never | -| `vector::singleton(t: T): vector` | Create a vector of size 1 containing `t` | Never | -| `vector::length(self: &vector): u64` | Return the length of the vector `self` | Never | -| `vector::push_back(self: &mut vector, t: T)` | Add `t` to the end of `self` | Never | -| `vector::pop_back(self: &mut vector): T` | Remove and return the last element in `self` | If `self` is empty | -| `vector::borrow(self: &vector, i: u64): &T` | Return an immutable reference to the element at index `i` | If `i` is not in bounds | -| `vector::borrow_mut(self: &mut vector, i: u64): &mut T` | Return a mutable reference to the element at index `i` | If `i` is not in bounds | -| `vector::destroy_empty(self: vector)` | Delete `self` | If `self` is not empty | -| `vector::append(self: &mut vector, other: vector)` | Add the elements in `other` to the end of `self` | Never | -| `vector::reverse_append(self: &mut vector, other: vector)` | Pushes all of the elements of the `other` vector into the `self` vector, in the reverse order as they occurred in `other` | Never | -| `vector::contains(self: &vector, e: &T): bool` | Return true if `e` is in the vector `self`. Otherwise, returns `false` | Never | -| `vector::swap(self: &mut vector, i: u64, j: u64)` | Swaps the elements at the `i`th and `j`th indices in the vector `self` | If `i` or `j` is out of bounds | -| `vector::reverse(self: &mut vector)` | Reverses the order of the elements in the vector `self` in place | Never | -| `vector::reverse_slice(self: &mut vector, l: u64, r: u64)` | Reverses the order of the elements `[l, r)` in the vector `self` in place | If `l > r` or if `l` or `r` is out of bounds | -| `vector::index_of(self: &vector, e: &T): (bool, u64)` | Return `(true, i)` if `e` is in the vector `self` at index `i`. Otherwise, returns `(false, 0)` | Never | -| `vector::insert(self: &mut vector, i: u64, e: T)` | Insert a new element `e` at position `0 <= i <= length`, using `O(length - i)` time | If `i` is out of bounds | -| `vector::remove(self: &mut vector, i: u64): T` | Remove the `i`th element of the vector `self`, shifting all subsequent elements. This is O(n) and preserves ordering of elements in the vector | If `i` is out of bounds | -| `vector::swap_remove(self: &mut vector, i: u64): T` | Swap the `i`th element of the vector `self` with the last element and then pop the element, This is O(1), but does not preserve ordering of elements in the vector | If `i` is out of bounds | -| `vector::trim(self: &mut vector, new_len: u64): vector` | Trim the vector `self` to the smaller size `new_len` and return the evicted elements in order | If `new_len > self.length()` | -| `vector::trim_reverse(self: &mut vector, new_len: u64): vector` | Trim the vector `self` to the smaller size `new_len` and return the evicted elements in the reverse order | If `new_len > self.length()` | -| `vector::rotate(self: &mut vector, rot: u64): u64` | `rotate(&mut [1, 2, 3, 4, 5], 2) -> [3, 4, 5, 1, 2]` in place, returns the split point i.e., `3` in this example | If `rot <= self.length()` does not hold | -| `vector::rotate_slice(self: &mut vector, left: u64, rot: u64, right: u64): u64` | rotate a slice `[left, right)` with `left <= rot <= right` in place, returns the split point | If `left <= rot <= right <= self.length()` does not hold | - -Example: - -```move -script { - use std::vector; - - fun example() { - let v = vector::empty(); - vector::push_back(&mut v, 5); - vector::push_back(&mut v, 6); - - assert!(*vector::borrow(&v, 0) == 5, 42); - assert!(*vector::borrow(&v, 1) == 6, 42); - assert!(vector::pop_back(&mut v) == 6, 42); - assert!(vector::pop_back(&mut v) == 5, 42); - } -} -``` - -## Index Notation for Vectors - -_Since language version 2.0_ - -Index notation using square brackets (`[]`) is available for vector operations, simplifying syntax -and making programs easier to understand. The index notation is simply syntactic sugar which -is reduced to existing operations by the compiler; the named operations are also still supported. - -The table below gives an overview of index notations for vectors: - - -| Indexing Syntax | Vector Operation | -|-------------------|--------------------------------------------| -| `&v[i]` | `vector::borrow(&v, i)` | -| `&mut v[i]` | `vector::borrow_mut(&mut v, i)` | -| `v[i]` | `*vector::borrow(&v, i)` | -| `v[i] = x` | `*vector::borrow_mut(&mut v, i) = x` | -| `&v[i].field` | `&vector::borrow(&v, i).field` | -| `&mut v[i].field` | `&mut vector::borrow_mut(&mut v, i).field` | -| `v[i].field` | `vector::borrow(&v, i).field` | -| `v[i].field = x` | `vector::borrow_mut(&mut v, i).field = x` | - -As an example, here is a bubble sort algorithm for vectors using index notation: - -```move -fun bubble_sort(v: vector) { - use std::vector; - let n = vector::length(&v); - let i = 0; - - while (i < n) { - let j = 0; - while (j < n - i - 1) { - if (v[j] > v[j + 1]) { - let t = v[j]; - v[j] = v[j + 1]; - v[j + 1] = t; - }; - j = j + 1; - }; - i = i + 1; - }; -} -``` - -## Destroying and copying vectors - -Some behaviors of `vector` depend on the abilities of the element type, `T`. For example, vectors -containing elements that do not have `drop` cannot be implicitly discarded like `v` in the example -above--they must be explicitly destroyed with `vector::destroy_empty`. - -Note that `vector::destroy_empty` will abort at runtime unless `vec` contains zero elements: - -```move -script { - fun destroy_any_vector(vec: vector) { - vector::destroy_empty(vec) // deleting this line will cause a compiler error - } -} -``` - -But no error would occur for dropping a vector that contains elements with `drop`: - -```move -script { - fun destroy_droppable_vector(vec: vector) { - // valid! - // nothing needs to be done explicitly to destroy the vector - } -} -``` - -Similarly, vectors cannot be copied unless the element type has `copy`. In other words, a -`vector` has `copy` if and only if `T` has `copy`. - -For more details see the sections on [type abilities](abilities.mdx) and [generics](generics.mdx). - -## Ownership - -As mentioned [above](#destroying-and-copying-vectors), `vector` values can be copied only if the -elements can be copied. diff --git a/apps/nextra/pages/zh/build/smart-contracts/compiler_v2.mdx b/apps/nextra/pages/zh/build/smart-contracts/compiler_v2.mdx deleted file mode 100644 index 854ceb358..000000000 --- a/apps/nextra/pages/zh/build/smart-contracts/compiler_v2.mdx +++ /dev/null @@ -1,51 +0,0 @@ ---- -title: "Move On Aptos Compiler (beta)" ---- - -# Move On Aptos Compiler - -The Move on Aptos compiler (codename 'compiler v2') is a new tool which translates Move source code into Move bytecode. It unifies the architectures of the Move compiler and the Move Prover, enabling faster innovation in the Move language. It also offers new tools for defining code optimizations which can be leveraged to generate more gas efficient code for Move programs. - -The new compiler supports Move 2, the latest revision of the Move language. Head over to the [release page in the book](book/move-2.mdx) to learn more about the new features in Move 2. Starting at Aptos CLI v6.0.0, this language version and the new compiler are the default. - -## Compiler v2 Release State - -Compiler v2 is now the default. - -## Bug Bounty - -We award a bounty for each unique semantic bug identified in the compiler. Bugs need to surface as a difference of execution outcome of the v1 and v2 compilers, as demonstrated by a Move unit test. Not all differences in the compiler behavior count for the program, [head over here](https://hackenproof.com/programs/move-on-aptos-compiler) for details of the bounty program. Even if a difference in behavior does not qualify for a bounty, we encourage you to open an issue using the link below. - -## Reporting an Issue - -If you run into issues, please use [this link to create a github issue][bug]. If you are able to provide a small piece of Move code which reproduces the issue, debugging and fixing it will be easier for us. - -[bug]: https://github.com/aptos-labs/aptos-core/issues/new?title=[compiler-v2]%20%3CPLEASE%20NAME%20IT%3E&body=%3CPLEASE%20DESCRIBE%20IT%3E&labels=compiler-v2&projects=aptos-labs/16 - -## Using Compiler v2 - -Ensure to have installed the latest version of the Aptos CLI: - -```bash filename="Terminal" -aptos update aptos # on supported OS -brew upgrade aptos # on MacOS -``` - -Compiler v2 and Move 2 are now the default, requiring no changes to your usage. Examples: - -```bash filename="Terminal" -aptos move compile -aptos move test -aptos move prove -``` - -## Using Compiler v1 - -Compiler v1 (i.e., the old compiler) is expected to be sunset soon, as it does not support any of the Move 2 features, and is thus not recommended for use. However, if you still want to use compiler v1 before it is sunset, on your project that does not use any Move 2 features, you can do so with the `--move-1` flag (eg., `aptos move compile --move-1` or `aptos move test --move-1`). You may need to specify the `aptos-release-v1.25` version of the Aptos framework in the dependencies (this is the last version of the Aptos framework that does not use any Move 2 features), as shown below. - -```toml filename="Move.toml" -[dependencies.AptosFramework] -git = "https://github.com/aptos-labs/aptos-framework.git" -rev = "aptos-release-v1.25" -subdir = "aptos-framework" -``` diff --git a/apps/nextra/pages/zh/build/smart-contracts/compiling.mdx b/apps/nextra/pages/zh/build/smart-contracts/compiling.mdx deleted file mode 100644 index 41da431bf..000000000 --- a/apps/nextra/pages/zh/build/smart-contracts/compiling.mdx +++ /dev/null @@ -1,80 +0,0 @@ ---- -title: "Compiling (Move)" ---- - -import { Steps, Callout, FileTree } from "nextra/components"; -import { RemoteCodeblock, permalinkFetch } from '@components/index'; - -# Compiling - - -Ensure that your [CLI](../cli) is up to date before compiling. - - -## `aptos move compile` - -Once you have a package set up, you can compile your Move code by doing: - -```bash filename="Terminal" -aptos move compile -``` - -If run successfully, you should receive a Terminal output like so - -```bash filename="Terminal" -{ - "Result": [ - "::" - ] -} -``` - - - You may need to add named addresses, especially for examples. For example, with the Hello Blockchain Move example, you will need to add the `hello_blockchain` named address: - - ```bash filename="Terminal" - aptos move compile --named-addresses hello_blockchain=default - ``` - - -## Unpacking Build - -Compiled Move packages contain a folder structure that resembles the one below. - - - - - - - - - - - - - - - - - - - - - - - - - - - -### `bytecode_modules` - -The bytecode modules folder contains the compiled Move bytecode for your module(s) (such as `module_name.mv`). -To learn more about the bytecode and its security features, see [why move?](./why-move) - -### `source_maps` - -The source maps folder contains source maps (such as `module_name.mvsm`) which allow -users to map the compiled bytecode back to the source code and relevant dependencies. - - diff --git a/apps/nextra/pages/zh/build/smart-contracts/create-package.mdx b/apps/nextra/pages/zh/build/smart-contracts/create-package.mdx deleted file mode 100644 index 99fd9bc68..000000000 --- a/apps/nextra/pages/zh/build/smart-contracts/create-package.mdx +++ /dev/null @@ -1,77 +0,0 @@ ---- -title: "Create Package (Move)" ---- - -import { Steps, Callout, FileTree } from "nextra/components"; -import { RemoteCodeblock, permalinkFetch } from '@components/index'; - -export async function getStaticProps() { - return await permalinkFetch([ - 'https://github.com/aptos-labs/aptos-core/blob/afd3706c17bcccfb39a9d6059aecbfa648ed295d/aptos-move/move-examples/hello_blockchain/sources/hello_blockchain.move#L1-L64' - ]) -} - -# Create Package - - -We recommend installing the Aptos CLI before beginning. -If you haven't already installed the Aptos CLI, see the [CLI section](../cli) - - - - -### `aptos move init` - -In a new project directory, initialize a Move package by running: - -```bash filename="Terminal" -aptos move init --name -``` - -You should now have a Move project that looks like so: - - - - - - - - - - You can also create a Move package from a [template](../cli/start-from-template). - - -### Update `Move.toml` - -In `Move.toml`, fill in the following key information: - -1. `name`: name of your package -2. `version`: package version (default is `"0.0.0"`) -3. `addresses`: Describes which address the module will be deployed to. These are named addresses that can be used as aliases. In the below example, we will use `hello_blockchain` as the named address. -4. `dependencies`: You will likely want to use `AptosFramework` and other [Third Party Dependencies](./third-party-dependencies) - -Below is an example - -```toml filename="Move.toml" -[package] -name = "Examples" -version = "0.0.0" - -[addresses] -hello_blockchain = "_" - -[dependencies.AptosFramework] -git = "https://github.com/aptos-labs/aptos-framework.git" -rev = "mainnet" -subdir = "aptos-framework" -``` - -### Add to `sources` directory - -Add your code in the `sources` directory. Here we have a `hello_blockchain.move` example. - - - - diff --git a/apps/nextra/pages/zh/build/smart-contracts/cryptography.mdx b/apps/nextra/pages/zh/build/smart-contracts/cryptography.mdx deleted file mode 100644 index 3b467648d..000000000 --- a/apps/nextra/pages/zh/build/smart-contracts/cryptography.mdx +++ /dev/null @@ -1,247 +0,0 @@ ---- -title: "Cryptography" ---- -import {Callout} from "nextra/components"; - -# Cryptography in Move - -Cryptography plays an integral role in ensuring the security, integrity, confidentiality, and immutability of data in blockchain systems. The Aptos adapter for Move provides developers with an array of cryptographic primitives to cater to this need. This document delves into the cryptographic functionalities offered by Move on Aptos and elucidates the principles that drive their design. - -## Cryptographic primitives - -Move, through the Aptos adapter, encompasses several fundamental cryptographic tools: - -1. [Cryptographic Hash Functions](#cryptographic-hash-functions) – Algorithms that produce a fixed-size output (hash) from variable-sized input data. Supported functions include SHA2-256, SHA3-256, Keccak256, and Blake2b-256. -2. [Digital Signature Verification](#digital-signature-verification) – Algorithms for signing a message to ensure its integrity, authenticate its sender, ensure non-repudiation, or any combination thereof. Supported signature schemes include Ed25519, ECDSA, and BLS. -3. [Elliptic Curve Arithmetic](#elliptic-curve-arithmetic) – Elliptic curves are one of the building blocks of advanced cryptographic primitives, such as digital signatures, public-key encryption or verifiable secret sharing. Supported curves include Ristretto255 and BLS12-381. -4. [Zero-Knowledge Proofs (ZKP)](#building-powerful-cryptographic-applications) – These cryptographic techniques enable a party to prove that a relation $R(x; w)$ is satisfied on a public statement $x$ without leaking the secret witness $w$ that makes it hold. Currently, we support Groth16 ZKP verification and Bulletproofs ZK range proof verification. - -Three fundamental principles guide the design and integration of the Aptos cryptographic extensions into Move: - -1. **Economic Gas Usage** – Striving to minimize gas costs for Move developers by implementing key primitives as [Move native functions](book/functions.mdx#native-functions). For example, see the module for [BLS signatures over BLS12-381 elliptic curves](https://github.com/aptos-labs/aptos-core/blob/main/aptos-move/framework/aptos-stdlib/sources/cryptography/bls12381.move). -2. **Type-Safe APIs** – Ensuring that APIs are resistant to common mistakes, type-safety enhances code reliability and promotes an efficient development process. For an example, see the [Ed25519 signature module](https://github.com/aptos-labs/aptos-core/blob/main/aptos-move/framework/aptos-stdlib/sources/cryptography/ed25519.move). -3. **Empowerment of Developers** – In instances where native functions are unavailable, we empower developers to build their own cryptographic primitives on top of abstract cryptographic building blocks such as _finite fields_ and _Abelian groups_. Refer to the [`aptos_std::crypto_algebra`](https://github.com/aptos-labs/aptos-core/blob/main/aptos-move/framework/aptos-stdlib/sources/cryptography/crypto_algebra.move) module for more insights. - -Continue reading to delve a bit deeper and uncover some of the intricacies behind these extensions, as well as the range of applications they empower. For the most comprehensive understanding of this subject, refer to the [cryptography Move modules code](https://github.com/aptos-labs/aptos-core/tree/main/aptos-move/framework/aptos-stdlib/sources/cryptography). - -## Cryptographic hash functions - -Developers can now use more cryptographic hash functions in Move via the [`aptos_std::aptos_hash`](https://github.com/aptos-labs/aptos-core/blob/main/aptos-move/framework/aptos-stdlib/sources/hash.move) module: - -| Hash function | Hash size (bits) | Cost for hashing 1KiB (in internal gas units) | Collision-resistance security (bits) | -| ------------- | ---------------- | --------------------------------------------- | ------------------------------------ | -| Keccak256 | 256 | 1,001,600 | 128 | -| SHA2-256 | 256 | 1,084,000 | 128 | -| SHA2-512 | 512 | 1,293,600 | 256 | -| SHA3-256 | 256 | 1,001,600 | 128 | -| SHA3-512 | 512 | 1,114,000 | 256 | -| RIPEMD160 | 160 | 1,084,000 | 80 (**weak**) | -| Blake2b-256 | 256 | 342,200 | 128 | - -All hash functions have the same security properties (e.g., one-wayness, collision resistance, etc.), but their security levels are different. - - -RIPEMD160 should be avoided as a collision-resistant function due to its 80-bit security level. It is mainly supported for backward-compatibility reasons: e.g., Bitcoin address derivation relies on RIPEMD160. - - -Some of these functions can be used for interoperability with other chains (e.g., verifying Ethereum Merkle proofs via [`aptos_std::aptos_hash::keccak256`](https://github.com/aptos-labs/aptos-core/blob/137acee4c6dddb1c86398dce25b041d78a3028d3/aptos-move/framework/aptos-stdlib/sources/hash.move#L35)). -Others, have lower gas costs, such as [`aptos_std::aptos_hash::blake2b_256`](https://github.com/aptos-labs/aptos-core/blob/137acee4c6dddb1c86398dce25b041d78a3028d3/aptos-move/framework/aptos-stdlib/sources/hash.move#L69). -In general, a wider variety of hash functions give developers additional freedom in terms of both security and interoperability with other off-chain cryptographic systems. - -## Digital signature verification - -Developers can now use a _type-safe_ API for verifying many kinds of digital signatures in Move: - -| Signature scheme | Curve | Sig. size (bytes) | PK size (bytes) | Malleability | Assumptions | Pros | Cons | -| ---------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------- | ----------------- | --------------- | ------------ | ----------- | ------------- | ------------------- | -| [ECDSA](https://github.com/aptos-labs/aptos-core/blob/main/aptos-move/framework/aptos-stdlib/sources/cryptography/secp256k1.move) | secp256k1 | 64 | 64 | Yes | GGM | Wide adoption | Security proof | -| [Ed25519](https://github.com/aptos-labs/aptos-core/blob/main/aptos-move/framework/aptos-stdlib/sources/cryptography/ed25519.move) | Edwards 25519 | 64 | 32 | No | DLA, ROM | Fast | Subtleties | -| [MultiEd25519](https://github.com/aptos-labs/aptos-core/blob/main/aptos-move/framework/aptos-stdlib/sources/cryptography/multi_ed25519.move) | Edwards 25519 | $4 + t \cdot 64$ | $n \cdot 32$ | No | DLA, ROM | Easy-to-adopt | Large sig. size | -| [MinPK BLS](https://github.com/aptos-labs/aptos-core/blob/main/aptos-move/framework/aptos-stdlib/sources/cryptography/bls12381.move) | BLS12-381 | 96 | 48 | No | CDH, ROM | Versatile | Slower verification | -| [MinSig BLS](https://github.com/aptos-labs/aptos-core/blob/7d4fb98c6604c67e526a96f55668e7add7aaebf6/aptos-move/move-examples/drand/sources/drand.move#L57) | BLS12-381 | 48 | 96 | No | CDH, ROM | Versatile | Slower verification | - - - -- CDH stands for the _"Computational Diffie-Hellman Assumption"_ -- DLA stands for the _"Discrete Log Assumption"_ -- GGM stands for the _"Generic Group Model"_ -- ROM stands for the _"Random Oracle Model"_ - - -The digital signature modules above can be used to build smart contract-based wallets, secure claiming mechanisms for airdrops, or any digital-signature-based access-control mechanism for dapps. - -The right choice of a signature scheme in your dapp could depend on many factors: - -1. **Backwards-compatibility** - - If your dapp's user base predominantly uses a particular signing mechanism, it would be prudent to support that mechanism for ease of transition and adoption. - - Example: If users mainly sign using Ed25519, it becomes a logical choice. -2. **Ease-of-implementation** - - While theoretically sound, complex protocols may be challenging to implement in practice. - - Example: Even though $t$-out-of-$n$ threshold protocols for Ed25519 exist, their intricacy on the signer's side might push developers toward MultiEd25519 due to its more straightforward signing implementation. -3. **Efficiency** - - Depending on the dapp's requirements, you might prioritize one aspect of efficiency over another. - - Signature size vs. public key size: Some applications might prioritize a smaller signature footprint, while others might emphasize a compact PK. - - Signing time vs. verification time: For certain dapps, the signing speed might be more crucial, while for others, rapid signature verification could be the priority. -4. **Security analysis** - - It is essential to consider the underlying assumptions and potential vulnerabilities of a signature scheme. - - Example: ECDSA's security is proven under strong assumptions such as the Generic Group Model (GGM). - - Malleability concerns: Some signature schemes are susceptible to malleability, where a valid signature, $\sigma$, can be mauled into a different yet still valid signature, $\sigma$, for the same message $m$. -5. **Versatility** - - The adaptability and flexibility of signature schemes are important to consider, so you may properly accommodate the cryptographic needs of your dapp. - - Example: $t$-out-of-$n$ threshold BLS signatures are very simple to implement. - - -Despite its careful, principled design[^ed25519], Ed25519 has known implementation subtleties. For example, different implementations could easily disagree on the validity of signatures, especially when batch verification is employed[^devalence]$^,$[^eddsa]. - - - -Our [`aptos_std::bls12381`](https://github.com/aptos-labs/aptos-core/blob/main/aptos-move/framework/aptos-stdlib/sources/cryptography/bls12381.move) module for [MinPK BLS](https://datatracker.ietf.org/doc/html/draft-irtf-cfrg-bls-signature-05#name-variants) supports verification of individual signatures, **multi**-signatures, **aggregate** signatures and **threshold** signatures. - - -## Elliptic curve arithmetic - -While the [hash function](#cryptographic-hash-functions) and [digital signature](#digital-signature-verification) modules should provide enough functionality for most applications, some applications will require more powerful cryptography. -Normally, developers of such applications would have to wait until their desired cryptographic functionality is implemented efficiently as a [Move native function](book/functions.mdx#native-functions) in the [Aptos Move framework](../../network/blockchain/move.mdx). -Instead, we expose basic building blocks that developers can use to implement their own cryptographic primitives directly in the Move language and do so **efficiently**. - -Specifically, we currently expose low-level arithmetic operations on two popular elliptic curve groups and their associated finite fields: - -1. Ristretto255, via [`aptos_std::ristretto255`](https://github.com/aptos-labs/aptos-core/blob/main/aptos-move/framework/aptos-stdlib/sources/cryptography/ristretto255.move) -2. BLS12-381, via [`aptos_std::crypto_algebra`](https://github.com/aptos-labs/aptos-core/blob/main/aptos-move/framework/aptos-stdlib/sources/cryptography/crypto_algebra.move) - and [`aptos_std::bls12381_algebra`](https://github.com/aptos-labs/aptos-core/blob/main/aptos-move/framework/aptos-stdlib/sources/cryptography/bls12381_algebra.move) - -These modules support low-level operations such as: - -- scalar multiplication of elliptic curve points -- multi-scalar multiplications (MSMs) -- pairings -- scalar addition, multiplication, inversion -- hashing to a scalar or to a point -- and many more - -Examples of powerful applications that can be built on top include: - -1. **Validity rollups** – See the [`groth16` zkSNARK verifier example](#groth16-zksnark-verifier). -2. **Randomness-based games** – See the [`drand` verifier example](#verifying-randomness-from-the-drand-beacon). -3. **Privacy-preserving applications** – See the [`veiled_coin` example](#veiled-coins). - -### Ristretto255 arithmetic - -The [`aptos_std::ristretto255`](https://github.com/aptos-labs/aptos-core/blob/main/aptos-move/framework/aptos-stdlib/sources/cryptography/ristretto255.move) module provides support for elliptic curve arithmetic on the popular [Ristretto255 curve](https://ristretto.group/). -One of the main advantages of Ristretto255 is that it is a prime order group (unlike the Edwards 25519 curve), which obviates small-subgroup attacks on higher-level cryptosystems built on top of it. -Furthermore, Ristretto255 serialization is canonical and deserialization only accepts canonical encodings, which obviates malleability issues in higher-level protocols. - -This module has proven useful for implementing several cryptographic primitives: - -1. **Zero-knowledge $\Sigma$-protocols** – See the [`veiled_coin` example](#veiled-coins). -2. **ElGamal** encryption – See [`aptos_std::ristretto255_elgamal`](https://github.com/aptos-labs/aptos-core/blob/main/aptos-move/framework/aptos-stdlib/sources/cryptography/ristretto255_elgamal.move) -3. **Pedersen** commitments – See [`aptos_std::ristretto255_pedersen`](https://github.com/aptos-labs/aptos-core/blob/main/aptos-move/framework/aptos-stdlib/sources/cryptography/ristretto255_pedersen.move) -4. **Bulletproofs** ZK range proofs[^bulletproofs] – See [`aptos_std::ristretto255_bulletproofs`](https://github.com/aptos-labs/aptos-core/blob/main/aptos-move/framework/aptos-stdlib/sources/cryptography/ristretto255_bulletproofs.move) - -Need ideas for a cryptosystem to build on top of `ristretto255`? -A popular primitive that you could easily build would be the [schnorrkel](https://github.com/w3f/schnorrkel) signature scheme, which is a hardened version of Schnorr signatures over Ristretto255 groups. - -### Generic elliptic curve arithmetic - -What is better than one curve? More curves! - -The [`aptos_std::crypto_algebra`](https://github.com/aptos-labs/aptos-core/blob/main/aptos-move/framework/aptos-stdlib/sources/cryptography/crypto_algebra.move) provides elliptic curve arithmetic operations for **any** supported elliptic curve, including pairing-friendly curves. -As a consequence, Move developers can implement a cryptosystem generically over **any** curve that is or will be supported in the future. -Compared to fixing a particular curve in the code (e.g., by implementing against the [Ristretto255 module](#ristretto255-arithmetic)), this approach provides more flexibility and lowers development time when migrating to a different curve. - -Although currently the `crypto_algebra` module only supports arithmetic over BLS12-381 curves (via the marker types declared in [`aptos_std::bls12381_algebra`](https://github.com/aptos-labs/aptos-core/blob/main/aptos-move/framework/aptos-stdlib/sources/cryptography/bls12381_algebra.move)), more curves will be supported into the future (e.g., BN254, Ristretto255, BLS12-377, BW6-761, secp256k1, secp256r1). - -As an example, a Move developer can implement the popular Boneh-Lynn-Shacham (BLS) signature scheme generically over **any** curve by using [type arguments](book/functions.mdx#type-parameters) for the curve type in their implementation: - -```rust -use std::option; -use aptos_std::crypto_algebra::{eq, pairing, one, deserialize, hash_to}; - -/// Example of a BLS signature verification function that works over any pairing-friendly -/// group triple `Gr1`, `Gr2`, `GrT` where signatures are in `Gr1` and PKs in `Gr2`. -/// Points are serialized using the format in `FormatG1` and `FormatG2` and the hashing -/// method is `HashMethod`. -/// -/// WARNING: This example is type-unsafe and probably not a great fit for production code. -public fun bls_verify_sig( - dst: vector, - signature: vector, - message: vector, - public_key: vector): bool -{ - let sig = option::extract(&mut deserialize(&signature)); - let pk = option::extract(&mut deserialize(&public_key)); - let hash = hash_to(&dst, &message); - - // Checks if $e(H(m), pk) = e(sig, g_2)$, where $g_2$ generates $\mathbb{G}_2$ - eq( - &pairing(&hash, &pk), - &pairing(&sig, &one()) - ) -} -``` - -Using the `bls_verify_sig` _generic_ function from above, developers can verify BLS signatures over **any** of the supported (pairing-friendly) curves. -For example, one can verify [MinSig BLS](https://datatracker.ietf.org/doc/html/draft-irtf-cfrg-bls-signature-05#name-variants) signatures over BLS12-381 curves by calling the function above with the right BLS12-381 marker types as its type arguments: - -```rust -use aptos_std::bls12381_algebra::{ - G1, G2, Gt, FormatG1Compr, FormatG2Compr, HashG1XmdSha256SswuRo -}; - -// Aborts with code 1 if the MinSig BLS signature over the BLS12-381 curve fails to verify. -assert( - bls_verify_sig( - dst, signature, message, public_key - ), - 1 -); -``` - -For more use cases of the `crypto_algebra` module, check out some Move examples: - -1. [Verifying Groth16 zkSNARK proofs](#groth16-zksnark-verifier) over **any** curve -2. [Verifying randomness from the `drand` beacon](#verifying-randomness-from-the-drand-beacon) - -## Building powerful cryptographic applications - -### Veiled coins - -The [`veiled_coin` example](https://github.com/aptos-labs/aptos-core/tree/main/aptos-move/framework-experimental/veiled_coin) demonstrates how to use [the Ristretto255 modules from above](#ristretto255-arithmetic) to add a reasonable layer of confidentiality to coin balances and transactions. - -Specifically, users can **veil** their balance, keeping it hidden from everyone, including validators. -Furthermore, a user can send a **veiled transaction** that hides the transaction amount from everybody, including validators. -An important caveat is that veiled transactions do **not** hide the identities of the sender or the recipient. - - -This module is educational. It is **not** production-ready. Using it could lead to loss of funds. - - -### Groth16 zkSNARK verifier - -The [`groth16` example](https://github.com/aptos-labs/aptos-core/blob/main/aptos-move/move-examples/groth16_example/sources/groth16.move) demonstrates how to verify Groth16 zkSNARK proofs[^groth16], which are the shortest, fastest-to-verify, general-purpose zero-knowledge proofs. -Importantly, as explained [above](#generic-elliptic-curve-arithmetic), this implementation is _generic_ over **any** curve, making it very easy for Move developers to use it with their favorite (supported) curves. - - -This code has not been audited by a third-party organization. If using it in a production system, proceed at your own risk. - - -### Verifying randomness from the `drand` beacon - -The [`drand` example](https://github.com/aptos-labs/aptos-core/tree/main/aptos-move/move-examples/drand/sources) shows how to verify public randomness from the [drand](https://drand.love) randomness beacon. -This randomness can be used in games or any other chance-based smart contract. -We give a simple example of a lottery implemented on top of `drand` randomness in [`lottery.move`](https://github.com/aptos-labs/aptos-core/tree/main/aptos-move/move-examples/drand/sources/lottery.move). - - -This code has not been audited by a third-party organization. If using it in a production system, proceed at your own risk. - - -Another application that can be built on top of `drand` is time-lock encryption[^tlock], which allows users to encrypt information such that it can only be decrypted in a future block. -We do not currently have an implementation but the reader is encouraged to write one! - -[^bulletproofs]: _bulletproofs:_ **Bulletproofs: Short Proofs for Confidential Transactions and More**; by B. Bünz and J. Bootle and D. Boneh and A. Poelstra and P. Wuille and G. Maxwell; in 2018 IEEE Symposium on Security and Privacy -[^devalence]: _devalence:_ **It’s 255:19AM. Do you know what your validation criteria are?**, by Henry de Valence, [https://hdevalence.ca/blog/2020-10-04-its-25519am](https://hdevalence.ca/blog/2020-10-04-its-25519am) -[^ed25519]: _ed25519:_ **Ed25519: high-speed high-security signatures**, by Daniel J. Bernstein, Niels Duif, Tanja Lange, Peter Schwabe, Bo-Yin Yang, [https://ed25519.cr.yp.to/](https://ed25519.cr.yp.to/) -[^eddsa]: _eddsa:_ **Taming the Many EdDSAs**, by Konstantinos Chalkias, François Garillot, Valeria Nikolaenko, in SSR 2020, [https://dl.acm.org/doi/abs/10.1007/978-3-030-64357-7_4](https://dl.acm.org/doi/abs/10.1007/978-3-030-64357-7_4) -[^groth16]: _groth16:_ **On the Size of Pairing-Based Non-interactive Arguments**; by Groth, Jens; in EUROCRYPT 2016 -[^tlock]: _tlock:_ **tlock: Practical Timelock Encryption from Threshold BLS**; by Nicolas Gailly and Kelsey Melissaris and Yolan Romailler; [https://eprint.iacr.org/2023/189](https://eprint.iacr.org/2023/189) diff --git a/apps/nextra/pages/zh/build/smart-contracts/deployment.mdx b/apps/nextra/pages/zh/build/smart-contracts/deployment.mdx deleted file mode 100644 index e627da1d8..000000000 --- a/apps/nextra/pages/zh/build/smart-contracts/deployment.mdx +++ /dev/null @@ -1,119 +0,0 @@ ---- -title: "Object Code Deployment" ---- - -# Object Code Deployment - -This document goes through how you can deploy code to [Objects](objects.mdx). This is the recommended way to deploy code to the blockchain, as this reduces deployment complexity, -and safely manages access control policies for the code owner. Note that in this context, code refers to [packages](book/packages.mdx). - -Deploying code to objects will guarantee the following: -- Each deployment publishes to a new address. -- Only the **owner of the code object** can upgrade and freeze the code. - -This means you can transfer the object to a new owner, and they will have full ownership of the code. You are granting them the rights to upgrade and freeze the code. -There is no need to manage seeds, or deploy to a new address on each deployment. Object code deployment greatly simplifies the deployment process. - -## Instructions - -Below are the instructions on how to compile, deploy and upgrade code to objects. - -import { Steps } from 'nextra/components' - - - -### Compile code - -Make sure `` is left as a placeholder `_`. This is needed as the CLI command will override the address. `` value represents the owner of the code, or the owner of the object to deploy the code to. -Here is an example as `` with the value `my_address`. - -```toml filename="Move.toml" -[addresses] -my_address = "_" -``` - -Compile your move code running the below command. -- Replace `` with the named address. -- Replace `` with the address of your account. - -```bash filename="Terminal" -aptos move compile --named-addresses = -``` - -### Deploy code to an object - -Deploy the compiled code to an object via the command: -- Replace `` with the named address. - -```bash filename="Terminal" -aptos move deploy-object --address-name -``` - -**An example can be found below:** - -```bash filename="Terminal" -aptos move deploy-object --address-name my_address -``` - -This will ask if you want to publish the code under the specified object address. - -**Example output:** - -```bash filename="Terminal" -Do you want to publish this package at object address 0x8d6eb306bcf6c61dbaa0dbf8daa8252e121b63e95991afcab3b12d3be7f001ab [yes/no] > -``` - -**Congrats, you have deployed your code to a new object with a unique address!** - -Take note of the object address as you will need it later for upgrades. - -### Transfer and upgrade code in an existing package - -First, you may want to transfer the object from the deployer account to an admin account. The admin account will have rights to upgrade the code. - -Here's how you can do it via CLI, here your `deployer_account` should be the current owner of the object. -```bash -aptos move run —-function-id 0x1::object::transfer --type-args 0x1::object::ObjectCore -—args address: address: —-profile -``` - -Here's how you can do it via the typescript SDK: -```typescript -const transaction = await aptos.transaction.build.simple({ - sender: deployerAccount.accountAddress, - data: { - function: "0x1::object::transfer", - typeArguments: [`0x1::object::ObjectCore`], - functionArguments: [object_address, new_owner_address], - }, -}); -``` -Now you can upgrade the code with the designated admin account, as shown below. - -If you wish to upgrade the code in the object deployed, run the following: -- Replace `` with the existing named address. -- Replace `` with the address of the object hosting the code. - -Note: the value for the account name should now be the object address, as the package containing the module(s) is now deployed to that address. - -```bash filename="Terminal" -aptos move upgrade-object --address-name --object-address -``` - -Example of the command above: - -```bash filename="Terminal" -aptos move upgrade-object --address-name my_address --object-address 0x8d6eb306bcf6c61dbaa0dbf8daa8252e121b63e95991afcab3b12d3be7f001ab -``` - -This will ask if you want to upgrade the existing code deployed at the object address. - -**Example output:** - -```bash filename="Terminal" -Do you want to upgrade the package 'MyPackage' at object address 0x8d6eb306bcf6c61dbaa0dbf8daa8252e121b63e95991afcab3b12d3be7f001ab [yes/no] -``` - -**Congrats, you have upgraded your code in the existing object!** - - - diff --git a/apps/nextra/pages/zh/build/smart-contracts/digital-asset.mdx b/apps/nextra/pages/zh/build/smart-contracts/digital-asset.mdx deleted file mode 100644 index 8ea344d66..000000000 --- a/apps/nextra/pages/zh/build/smart-contracts/digital-asset.mdx +++ /dev/null @@ -1,429 +0,0 @@ ---- -title: "Aptos Digital Asset Standard" ---- - -import { Callout } from 'nextra/components' -import { ThemedImage, GraphQLEditor } from "@components/index"; - -# Aptos Digital Asset (DA) Standard - -The Digital Asset (DA) standard is a modern Non-Fungible Token (NFT) standard for Aptos. NFTs represent unique assets on-chain, and are stored in collections. These NFTs can be customized to later be transferred, soulbound, burned, mutated, or customized via your own smart contracts. - -This standard replaces the legacy [Aptos Token Standard](aptos-token.mdx). The most important improvements to note are: - -| **Improvement** | **Description** | -|-----------------|-----------------| -| **Token Extension** | Tokens can be easily extended since they are implemented using Move [Objects](objects.mdx). | -| **Direct NFT Transfer** | You can now directly transfer NFTs without the recipient “opting-in” on-chain. | -| **NFT Composability** | NFTs can own other NFTs for easy composability. | - -If you want a simple way to mint NFTs without the ability to customize or extend their functionality, you can use the `aptos_token` module which implements the DA standard (see the section on how to use it below). - - -Note that all Digital Asset modules are deployed at the reserved address `0x4`. - - -## Using the Digital Asset Standard - -This standard is implemented with two Objects: - -1. `Collection`s - A set of NFTs with a name and a bit of context for the group. -2. `Token`s - Digital assets which represent unique assets. These are often used to represent NFTs and usually use a `uri` link to point to more info about the asset (ex. a link to an image, video, etc.). - - - -All `Token`s are required to have a reference to a parent `Collection`, but the parent `Collection` does **not** own the `Token`. Newly minted `Token`s are usually owned by the creator. From there, they can be transferred to other accounts. - -## Collections - -| **Field** | **Description** | -|-----------|-----------------| -| **Description** | An optional string smaller than 2048 characters (modifiable with a `MutatorRef`). | -| **Name** | A required string to identify the `Collection`. The name must be unique within each account. That means a single creator account cannot create more than one `Collection` with the same name. | -| **Royalty** | An optional [`Royalty`](https://aptos.dev/reference/move/?branch=mainnet&page=aptos-token-objects/doc/royalty.md#0x4_royalty_Royalty) struct indicating what % of the sale price goes to the creator of the `Collection`. This can be changed with a `MutatorRef` generated by the [Royalty module](https://github.com/aptos-labs/aptos-core/blob/main/aptos-move/framework/aptos-token-objects/sources/royalty.move). The Royalty module is an extension for the DA standard. See example usage in [`aptos_token.move`](https://github.com/aptos-labs/aptos-core/blob/main/aptos-move/framework/aptos-token-objects/sources/aptos_token.move). | -| **URI length** | An optional string that is smaller than 512 characters which links to relevant content for the `Collection` (modifiable with a `MutatorRef`). | - -### Creating a `Collection` - -There are two ways to create a `Collection` depending on whether you want there to be a maximum supply of `Token`s it can hold. - -#### Fixed Maximum Supply - -To make a `Collection` with a fixed supply you can use `collection::create_fixed_collection` like so: - -```move filename="example.move" -use aptos_token_objects::collection; -use std::option::{Self, Option}; - -public entry fun create_collection(creator: &signer) { - let max_supply = 1000; - let royalty = option::none(); - - // Maximum supply cannot be changed after collection creation - collection::create_fixed_collection( - creator, - "My Collection Description", - max_supply, - "My Collection", - royalty, - "https://mycollection.com", - ); -} -``` -#### Unlimited Supply - -To create a `Collection` with unlimited supply you can use `collection::create_unlimited_collection`: - -```move filename="example.move" -use std::option::{Self, Option}; - -public entry fun create_collection(creator: &signer) { - let royalty = option::none(); - - collection::create_unlimited_collection( - creator, - "My Collection Description", - "My Collection", - royalty, - "https://mycollection.com", - ); -} -``` - - -A `Collection`'s maximum supply cannot be changed after creation. - - -### Customizing a `Collection` - -Since each `Collection` is a [Move Object](objects.mdx), you can customize it by generating permissions called `Ref`s. Each `Ref` allows you to modify an aspect of the Object later on. Beyond the normal [Object Refs](object/creating-objects.mdx), `Collection`s can also get a `MutatorRef` by calling `get_mutator_ref` like so: - -```move filename="example.move" -use std::option::{Self, Option}; - -public entry fun create_collection(creator: &signer) { - let royalty = option::none(); - let collection_constructor_ref = &collection::create_unlimited_collection( - creator, - "My Collection Description", - "My Collection", - royalty, - "https://mycollection.com", - ); - let mutator_ref = collection::get_mutator_ref(collection_constructor_ref); - // Store the mutator ref somewhere safe -} -``` - - -Refs **must** be generated at creation time of an Object. The `ConstructorRef` used to generate other `Ref`s expires as soon as the transaction to create the Object is finished. - - -You can further customize your `Collection` by adding more resources or functionalities via smart contract. For example, a `Collection` can track when it was created in order to limit when `Token`s can be minted like so: - -```move filename="example.move" -use std::option::{Self, Option}; - -struct MyCollectionMetadata has key { - creation_timestamp_secs: u64, -} - -public entry fun create_collection(creator: &signer) { - let royalty = option::none(); - // Constructor ref is a non-storable struct returned when creating a new object. - // It can generate an object signer to add resources to the collection object. - let collection_constructor_ref = &collection::create_unlimited_collection( - creator, - "My Collection Description", - "My Collection", - royalty, - "https://mycollection.com", - ); - // Constructor ref can be exchanged for signer to add resources to the collection object. - let collection_signer = &object::generate_signer(collection_constructor_ref); - move_to(collection_signer, MyCollectionMetadata { creation_timestamp_secs: timestamp::now_seconds() } }) -} -``` - -## Tokens - -| **Field** | **Description** | -|-----------|-----------------| -| **Description** | An optional string smaller than 2048 characters (modifiable with a `MutatorRef`). | -| **Name** | A required string to identify the `Collection` that is unique within each `Collection`. This means a single `Collection` account cannot have more than one `Token` with the same name. | -| **Royalty** | An optional [`Royalty`](https://aptos.dev/reference/move/?branch=mainnet&page=aptos-token-objects/doc/royalty.md#0x4_royalty_Royalty) struct indicating what % of the sale price goes to the creator of the `Collection`. This can be changed with a `MutatorRef` generated by the [Royalty module](https://github.com/aptos-labs/aptos-core/blob/main/aptos-move/framework/aptos-token-objects/sources/royalty.move) (an extension for the DA standard. See example usage in [`aptos_token.move`](https://github.com/aptos-labs/aptos-core/blob/main/aptos-move/framework/aptos-token-objects/sources/aptos_token.move)). Usually royalty is set on collections, but setting it on `Token`s allows the individual `Token` to have a custom royalty amount. | -| **URI length** | An optional string that is smaller than 512 characters which links to relevant content for the `Collection` (modifiable with a `MutatorRef`). | - -### Creating Tokens - -There are a few ways to create a `Token`: - -1. Named tokens. These use the name of the `Token` to generate a named Object. This makes it easy to find the address for the token if you know the token and `Collection` name, but named Objects are not deletable. Trying to delete the a named token will only delete the data, not the Object itself. - -```move filename="example.move" -use aptos_token_objects::token; -use std::option::{Self, Option}; - -public entry fun mint_token(creator: &signer) { - let royalty = option::none(); - token::create_named_token( - creator, - "Collection Name", - "Description", - "Token Name", - royalty, - "https://mycollection.com/my-named-token.jpeg", - ); -} -``` - - -You can derive the address for named tokens by: -1. Concatenating the creator address, collection name and token name. -2. Doing a sha256 hash of that new string. - - -2. “Unnamed” tokens. These create unnamed *Objects* (which **are** deletable) but still have a `Token` name. Because the Object address is not deterministic, you must use an Indexer to find the address for them. - -```move filename="example.move" -use aptos_token_objects::token; -use std::option::{Self, Option}; - -public entry fun mint_token(creator: &signer) { - let royalty = option::none(); - token::create( - creator, - "Collection Name", - "Description", - "Token Name", - royalty, - "https://mycollection.com/my-named-token.jpeg", - ); -} -``` - -### Finding Unnamed Token Addresses via Indexer - -You can find the addresses of your recently created “unnamed” `Token`s by using the [Aptos Indexer](../indexer/aptos-hosted.mdx) with queries like the following: - -1. Looking up the collection id by using your account address and the name of the `Collection`. - - - -2. Then look up the address (`token_data_id`) of the `Token` by using the `collection_id` (from above) and the name of the token: - - - - -In general, using unnamed tokens give you the most flexibility because the Object can be deleted later, but named tokens simplify looking up addresses. - - -### Using Tokens - -#### Transfer Tokens - -Transferring a `Token` can be done by calling [`object::transfer`](https://aptos.dev/reference/move/?branch=mainnet&page=aptos-framework/doc/object.md#0x1_object_transfer). - -```move filename="example.move" -public entry fun transfer(owner: &signer, object: object::Object, to: address) -``` - -#### Burning Tokens - -Burning / deleting a `Token` requires storing a `BurnRef` with `token::generate_burn_ref`, then calling `token::burn`. - -```move filename="example.move" -module 0x42::example { - use std::option; - use aptos_token_objects::token::{Self, BurnRef, Token}; - use std::string::utf8; - use aptos_framework::object::{Self, Object}; - - struct CustomData has key, drop { - burn_ref: BurnRef, - } - - public entry fun mint_token(creator: &signer) { - let token_constructor_ref = &token::create( - creator, - utf8(b"My Collection"), - utf8(b"My named Token description"), - utf8(b"My named token"), - option::none(), - utf8(b"https://mycollection.com/my-named-token.jpeg"), - ); - - let token_signer = &object::generate_signer(token_constructor_ref); - - let burn_ref = token::generate_burn_ref(token_constructor_ref); - - // Store the burn ref somewhere safe - move_to(token_signer, CustomData { - burn_ref, - }); - } - - public entry fun burn_token(token: Object) acquires CustomData { - let token_address = object::object_address(&token); - // Remove all custom data from the token object. - // Retrieve the burn ref from storage - let CustomData { burn_ref } = move_from(token_address); - // Burn the token - token::burn(burn_ref); - } -} -``` - - -If any custom resources were moved onto the Token, those must be removed / deleted first before`token::burn` can delete the Token. For named tokens which cannot be deleted, `token::burn` will For named Tokens `token::burn` will remove all Token content instead. - - -#### Modifying Tokens After Creation - -Mutating a `Token`’s `URI` or `description` requires a `MutatorRef` (which must be generated when creating the `Token`, then stored for later). - -```move filename="example.move" -module 0x42::example { - use std::option; - use aptos_token_objects::token::{Self, MutatorRef, Token}; - use std::string::utf8; - use aptos_framework::object::{Self, Object}; - - struct CustomData has key, drop { - mutator_ref: MutatorRef, - } - - public entry fun mint_token(creator: &signer) { - // Constructor ref is a non-storable struct returned when creating a new object. - // It can be exchanged for signer to add resources to the token object. - let token_constructor_ref = &token::create( - creator, - utf8(b"My Collection"), - utf8(b"My named Token description"), - utf8(b"My named Token"), - option::none(), - utf8(b"https://mycollection.com/my-named-token.jpeg"), - ); - - let token_signer = &object::generate_signer(token_constructor_ref); - - let mutator_ref = token::generate_mutator_ref(token_constructor_ref); - - // Store the mutator ref somewhere safe - move_to(token_signer, CustomData { - mutator_ref, - }); - } - - public entry fun mutate_token(token: Object) acquires CustomData { - let token_address = object::object_address(&token); - // Retrieve the mutator ref from storage - let CustomData { mutator_ref } = move_from(token_address); - // Change token description - token::set_description(&mutator_ref, utf8(b"This is my named Token description")); - } -} -``` - - -Changing the royalty requires generating a *separate* `MutatorRef` from the [Royalty module](https://github.com/aptos-labs/aptos-core/blob/main/aptos-move/framework/aptos-token-objects/sources/royalty.move). - - -#### Extending Tokens - -`Token`s can be extended either by adding additional resources (since they are an Object) or using `Ref`s to modify the Object. - -## Aptos Token - -For NFT creators who want to avoid writing their own logic for how your NFT should work, you can use the [`aptos_token`](https://github.com/aptos-labs/aptos-core/blob/main/aptos-move/framework/aptos-token-objects/sources/aptos_token.move) module to mint an NFT. This module is already deployed at `0x4` and allows you to: - -1. Mint a `Token` you can transfer with royalties. -2. Mint a soulbound `Token`. -3. Manage the resources your NFT has. - -See the `aptos_token` [reference docs](https://aptos.dev/reference/move/?branch=mainnet&page=aptos-token-objects/doc/aptos_token.md) for all the helper functions you can use. - - -The main drawback of using the `aptos_token` module is that the Tokens are not extensible (the `mint` function does not return a `ConstructorRef`). - - -### Minting with `aptos_token` - -Minting a `Token` using `aptos_token` requires the same parameters as any token that implements the DA standard. In addition though, the `aptos_token` module allows you to specify a property map of key/value pairs for any other properties your specific NFT may require. - -You can mint your `Token` by calling `aptos_token::mint` like so: - -```move filename="example.move" -public entry fun mint( - creator: &signer, - collection: String, - description: String, - name: String, - uri: String, - property_keys: vector, - property_types: vector, - property_values: vector>, -) acquires AptosCollection, AptosToken -``` - -#### Soulbound Tokens - -To mint a soul bound `Token`, you can call [`aptos_token::mint_soul_bound`](https://aptos.dev/reference/move/?branch=mainnet&page=aptos-token-objects/doc/aptos_token.md#0x4_aptos_token_mint_soul_bound) instead: - -```move filename="example.move" -public entry fun mint_soul_bound( - creator: &signer, - collection: String, - description: String, - name: String, - uri: String, - property_keys: vector, - property_types: vector, - property_values: vector>, - soul_bound_to: address, -) acquires AptosCollection -``` - - -In the near future, a new module `TokenMinter` will be released to replace `aptos_token`. You can follow the status of that proposal [here](https://github.com/aptos-foundation/AIPs/blob/main/aips/aip-72.md). - - -## Examples and Useful Links - -- [Digital Asset Examples](https://github.com/aptos-labs/aptos-core/tree/main/aptos-move/move-examples/token_objects) -- [Digital Asset Marketplace Example](https://github.com/aptos-labs/aptos-core/tree/main/aptos-move/move-examples/marketplace) -- [Source code](https://github.com/aptos-labs/aptos-core/blob/main/aptos-move/framework/aptos-token-objects/sources/token.move) -- [`aptos_token` source code](https://github.com/aptos-labs/aptos-core/blob/main/aptos-move/framework/aptos-token-objects/sources/aptos_token.move) diff --git a/apps/nextra/pages/zh/build/smart-contracts/error-codes.mdx b/apps/nextra/pages/zh/build/smart-contracts/error-codes.mdx deleted file mode 100644 index 6d72c0b3b..000000000 --- a/apps/nextra/pages/zh/build/smart-contracts/error-codes.mdx +++ /dev/null @@ -1,200 +0,0 @@ ---- -title: "Aptos Error Codes" ---- - -# Error Codes in Aptos - -This page catalogs common errors encountered in the Aptos blockchain and -explains how to resolve them wherever possible. As with all software, the code -itself is the source of truth for error handling and will always contain entries -not found here. Instead, this matrix aims to help you address those errors most -typically found, misunderstood, or both. - -For the sources of these errors, see: - -- [vm_status.rs](https://github.com/aptos-labs/aptos-core/blob/main/third_party/move/move-core/types/src/vm_status.rs) -- [error.move](https://github.com/aptos-labs/aptos-core/blob/main/aptos-move/framework/move-stdlib/sources/error.move) -- [account.move](https://github.com/aptos-labs/aptos-core/blob/main/aptos-move/framework/aptos-framework/sources/account/account.move) -- [coin.move](https://github.com/aptos-labs/aptos-core/blob/main/aptos-move/framework/aptos-framework/sources/coin.move) -- [token.move](https://github.com/aptos-labs/aptos-core/blob/main/aptos-move/framework/aptos-token/sources/token.move) -- [token_transfers.move](https://github.com/aptos-labs/aptos-core/blob/main/aptos-move/framework/aptos-token/sources/token_transfers.move) - -Help us update this list by sending pull requests containing the errors you -encounter. If you don't know how to resolve the error, as described int the -_Action_ column, simply leave it blank. - -## Frequent Errors - -### INSUFFICIENT_BALANCE_FOR_TRANSACTION_FEE - -This means that the highest possible gas used for the transaction is greater than -the balance of APT in the transaction sender's (or fee payer's) account. To resolve, -please submit with a lower max gas amount, and try again. - -For example, if the max gas amount is 1000 gas units, and the gas unit price is `100` octas, the total -APT required in the account would be `0.00100000` APT (`1000 * 100 / 100000000`). The default is often `200000` gas units which -would end up requiring `0.20000000` APT. If you are having issues with this, please -reach out to your wallet provider. - -### OUT_OF_GAS - -This means that the transaction used more gas than the sender specified as the max -gas amount for the transaction, and aborted as a result. To resolve, please -try to increase the max gas amount, and submit the transaction again. - -### SEQUENCE_NUMBER_TOO_OLD - -This means that the transaction's sequence number in the sender's account has already -been used and committed to the blockchain. In order to submit a new transaction -to the blockchain, please try and submit it again with a new sequence number. - -### SEQUENCE_NUMBER_TOO_NEW - -This only occurs in simulation, but means that the sequence number being submitted -is greater than the next sequence number for the account. Please reduce the -sequence number and try again. - -## Move Virtual Machine (VM) - -{/* TODO improve formatting on table */} -| Error | Meaning | Possible Resolution | -|----------------------------------------------------------|:-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------:|-------------------------------------------------------------------------------------------------------------------------------------------------------| -| LOOKUP_FAILED | A function that is being called isn't present on the network being used | Check that your dependencies on-chain have the same version | -| UNKNOWN_VALIDATION_STATUS | We don't want the default value to be valid. | N/A | -| INVALID_SIGNATURE | The transaction has a bad signature. | Submit a new transaction with a new signature | -| INVALID_AUTH_KEY | Bad account authentication key. | Submit a new transaction with a new signature, check that the account matches the authentication key and hasn't been rotated | -| SEQUENCE_NUMBER_TOO_OLD | Sequence number is too old. | Submit a new transaction with a newer sequence number from the account | -| SEQUENCE_NUMBER_TOO_NEW | Sequence number is too new. | Submit a new transaction with a new signature | -| INSUFFICIENT_BALANCE_FOR_TRANSACTION_FEE | Insufficient balance to pay for max_gas specified in the transaction. Balance needs to be above max_gas_amount * gas_unit_price to proceed. | Fund the account with more APT to pay for the gas fee | -| TRANSACTION_EXPIRED | The transaction has expired. | Submit a new transaction with an expiration time further in the future | -| SENDING_ACCOUNT_DOES_NOT_EXIST | The sending account does not exist. | Create the account prior to sending the transaction again | -| REJECTED_WRITE_SET | This write set transaction was rejected because it did not meet the requirements for one. | N/A | -| INVALID_WRITE_SET | This write set transaction cannot be applied to the current state. | N/A | -| EXCEEDED_MAX_TRANSACTION_SIZE | Length of program field in raw transaction exceeded max length. | The transaction is too large for a single transaction; if this is a package publish, try to break it into multiple packages | -| UNKNOWN_SCRIPT | This script is not in our allowlist of scripts. | N/A | -| UNKNOWN_MODULE | Transaction is trying to publish a new module. | N/A | -| MAX_GAS_UNITS_EXCEEDS_MAX_GAS_UNITS_BOUND | Max gas units submitted with transaction exceeds max gas units bound in VM. | Decrease the max gas amount in the transaction below the maximum value in the gas schedule | -| MAX_GAS_UNITS_BELOW_MIN_TRANSACTION_GAS_UNITS | Max gas units submitted with transaction not enough to cover the intrinsic cost of the transaction. | Increase the max gas amount above the minimum value in the gas schedule | -| GAS_UNIT_PRICE_BELOW_MIN_BOUND | Gas unit price submitted with transaction is below minimum gas price set in the VM. | Increase the gas unit price below the minimum gas unit price in the gas schedule | -| GAS_UNIT_PRICE_ABOVE_MAX_BOUND | Gas unit price submitted with the transaction is above the maximum gas price set in the VM. | Decrease the gas unit price below the maximum gas unit price in the gas schedule | -| INVALID_GAS_SPECIFIER | Gas specifier submitted is either malformed (not a valid identifier), or does not refer to an accepted gas specifier. | N/A | -| SENDING_ACCOUNT_FROZEN | The sending account is frozen. | N/A | -| UNABLE_TO_DESERIALIZE_ACCOUNT | Unable to deserialize the account blob. | N/A | -| CURRENCY_INFO_DOES_NOT_EXIST | The currency info was unable to be found. | N/A | -| INVALID_MODULE_PUBLISHER | The account sender doesn't have permissions to publish modules. | N/A | -| NO_ACCOUNT_ROLE | The sending account has no role. | N/A | -| BAD_CHAIN_ID | The transaction's chain_id does not match the one published on-chain. | Verify that your chain ID matches the chain ID for your network | -| SEQUENCE_NUMBER_TOO_BIG | The sequence number is too large and would overflow if the transaction were executed. | N/A | -| BAD_TRANSACTION_FEE_CURRENCY | The gas currency is not registered as a TransactionFee currency. | N/A | -| FEATURE_UNDER_GATING | The feature requested is intended for a future Aptos version instead of the current one. | N/A | -| SECONDARY_KEYS_ADDRESSES_COUNT_MISMATCH | The number of secondary signer addresses is different from the number of secondary public keys provided. | Verify the multi-agent or multi-ed25519 secondary signer addresses match the secondary public keys | -| SIGNERS_CONTAIN_DUPLICATES | There are duplicates among signers, including the sender and all the secondary signers. | Remove any duplicate signers | -| SEQUENCE_NONCE_INVALID | The sequence nonce in the transaction is invalid (too new, too old, or already used). | N/A | -| CHAIN_ACCOUNT_INFO_DOES_NOT_EXIST | There was an error when accessing chain-specific account information. | N/A | -| MODULE_ADDRESS_DOES_NOT_MATCH_SENDER | the module publisher is not the account that will eventually hold the module. | Confirm the module address in the move contract matches the sender of the transaction | -| ZERO_SIZED_STRUCT | Reported when a struct has zero fields. | N/A | -| DUPLICATE_MODULE_NAME | The sender is trying to publish two modules with the same name in one transaction. | Confirm every module has a unique name | -| BACKWARD_INCOMPATIBLE_MODULE_UPDATE | The sender is trying to publish a module that breaks the compatibility checks. | Confirm your new modules being published don't break backwards compatibility | -| CYCLIC_MODULE_DEPENDENCY | The updated module introduces a cyclic dependency (i.e., A uses B and B also uses A). | Check for loops in your module dependencies in the modules being published | -| INVALID_FRIEND_DECL_WITH_SELF | Cannot mark the module itself as a friend. | Confirm no module has itself marked as a friend in the modules being published | -| INVALID_FRIEND_DECL_WITH_MODULES_OUTSIDE_ACCOUNT_ADDRESS | Cannot declare modules outside of account address as friends. | Confirm all friends are in the same account address in the modules being published | -| INVALID_FRIEND_DECL_WITH_MODULES_IN_DEPENDENCIES | Cannot declare modules that this module depends on as friends. | Check friend declarations of the modules being published | -| CYCLIC_MODULE_FRIENDSHIP | The updated module introduces a cyclic friendship (i.e., A friends B and B also friends A). | Check friend declarations of the modules being published | -| INVALID_PHANTOM_TYPE_PARAM_POSITION | A phantom type parameter was used in a non-phantom position. | Confirm phantom types are used only with generics | -| LOOP_MAX_DEPTH_REACHED | Loops are too deeply nested. | Check for many nested loops | -| TYPE_RESOLUTION_FAILURE | Failed to resolve type due to linking being broken after verification. | N/A | -| RESOURCE_DOES_NOT_EXIST | We tried to access a resource that does not exist under the account. | Check the contract and possibly change it to handle resources that don't exist | -| RESOURCE_ALREADY_EXISTS | We tried to create a resource under an account where that resource already exists. | Check the contract and possibly change it to handle resources that already exist | -| UNKNOWN_STATUS | A reserved status to represent an unknown vm status. This is std::u64::MAX, but we can't pattern match on that, so put the hardcoded value in. | N/A | -| LINKER_ERROR | This may be due to the function has not been published on chain or by trying to call an invalid function as the result of either an incorrect account address, module name, or function name. This might not happen locally if the sources are available locally but have yet to be published on-chain. | There are many reasons, but you should check your account addresses, module names, and function names to determine that they're correct and published | -| FAILED_TO_DESERIALIZE_ARGUMENT | This error in deserializing argument is triggered by one of the following validation checks. 1) It exceeds the limit on the number of nested or unpacked structs (including in a vector). The maximum overall args equals to depth * number of args. The max depth is currently 10. 2) The nested struct exceeds the aforementioned max depth. 3) The serialized arguments to constructor contained extra data. 4) It was derializing utf8 but struct_constructors are disabled. 5) The string argument is too long. 6) BCS deserialization fails for utf8. | N/A | - -## Move Standard Library (stdlib) - -| Error | Meaning | -|--------------------|:-----------------------------------------------------------------------------------------------:| -| INVALID_ARGUMENT | Caller specified an invalid argument (HTTP: 400). | -| OUT_OF_RANGE | An input or result of a computation is out of range (HTTP: 400). | -| INVALID_STATE | The system is not in a state where the operation can be performed (HTTP: 400). | -| UNAUTHENTICATED | Request not authenticated due to missing, invalid, or expired authentication token (HTTP: 401). | -| PERMISSION_DENIED | The client does not have sufficient permission (HTTP: 403). | -| NOT_FOUND | A specified resource is not found (HTTP: 404). | -| ABORTED | Concurrency conflict, such as read-modify-write conflict (HTTP: 409). | -| ALREADY_EXISTS | The resource that a client tried to create already exists (HTTP: 409). | -| RESOURCE_EXHAUSTED | Out of gas or other forms of quota (HTTP: 429). | -| CANCELLED | Request cancelled by the client (HTTP: 499). | -| INTERNAL | Internal error (HTTP: 500). | -| NOT_IMPLEMENTED | Feature not implemented (HTTP: 501). | -| UNAVAILABLE | The service is currently unavailable. Indicates that a retry could solve the issue (HTTP: 503). | - -## Aptos accounts - -| Error | Meaning | Possible Resolution | -|--------------------------------------|:----------------------------------------------------------------------------------------------------------------------------------:|----------------------------------------------------------------------------------| -| EACCOUNT_ALREADY_EXISTS | Account already exists. | N/A | -| EACCOUNT_DOES_NOT_EXIST | Account does not exist. | Create the account first | -| ESEQUENCE_NUMBER_TOO_BIG | Sequence number exceeds the maximum value for a u64. | Provide a smaller sequence number | -| EMALFORMED_AUTHENTICATION_KEY | The provided authentication key has an invalid length. | Check your authentication key; it should be a 32-byte vector | -| ECANNOT_RESERVED_ADDRESS | Cannot create account because address is reserved. | N/A | -| EOUT_OF_GAS | Transaction exceeded its allocated max gas. | Increase the max gas amount | -| EWRONG_CURRENT_PUBLIC_KEY | Specified current public key is not correct. | Confirm the public key matches the account | -| EINVALID_PROOF_OF_KNOWLEDGE | Specified proof of knowledge required to prove ownership of a public key is invalid. | Check your proof of knowledge in key rotation to ensure it has proper signatures | -| ENO_CAPABILITY | The caller does not have a digital-signature-based capability to call this function. | Confirm you have the capability for the called functions | -| EINVALID_ACCEPT_ROTATION_CAPABILITY | The caller does not have a valid rotation capability offer from the other account. | Confirm the account being rotated is correct | -| ENO_VALID_FRAMEWORK_RESERVED_ADDRESS | Address to create is not a valid reserved address for Aptos framework. | N/A | -| EINVALID_SCHEME | Specified scheme required to proceed with the smart contract operation - can only be ED25519_SCHEME(0) OR MULTI_ED25519_SCHEME(1). | Confirm the transaction was signed correctly when creating the account | -| EINVALID_ORIGINATING_ADDRESS | Abort the transaction if the expected originating address is different from the originating address on-chain. | Confirm you are rotating the correct account's key | -| ENO_SUCH_SIGNER_CAPABILITY | The signer capability doesn't exist at the given address. | Confirm the address is correct | - -## Aptos coins - -| Error | Meaning | Possible Resolution | -|------------------------------------|:------------------------------------------------------------------------------------------------------:|---------------------------------------------------------------------------| -| ECOIN_INFO_ADDRESS_MISMATCH | Address of account which is used to initialize a coin `CoinType` doesn't match the deployer of module. | Create the coin using a `CoinType` in the same account creating the coin. | -| ECOIN_INFO_ALREADY_PUBLISHED | `CoinType` is already initialized as a coin. | N/A | -| ECOIN_INFO_NOT_PUBLISHED | `CoinType` hasn't been initialized as a coin. | Create the coin with `CoinType` first before using it | -| ECOIN_STORE_ALREADY_PUBLISHED | Account already has `CoinStore` registered for `CoinType`. | N/A | -| ECOIN_STORE_NOT_PUBLISHED | Account hasn't registered `CoinStore` for `CoinType`. | Register the account for the `CoinType` | -| EINSUFFICIENT_BALANCE | Not enough coins to complete transaction. | Transfer less coins, or acquire more coins prior to the transfer | -| EDESTRUCTION_OF_NONZERO_TOKEN | Cannot destroy non-zero coins. | N/A | -| EZERO_COIN_AMOUNT | Coin amount cannot be zero. | Don't burn coins or conduct other actions with zero coins | -| EFROZEN | CoinStore is frozen. Coins cannot be deposited or withdrawn. | Account is frozen for this token; talk to the coin owner | -| ECOIN_SUPPLY_UPGRADE_NOT_SUPPORTED | Cannot upgrade the total supply of coins to different implementation. | N/A | -| ECOIN_NAME_TOO_LONG | Name of the coin is too long. | Coin name must be less than or equal to 32 characters | -| ECOIN_SYMBOL_TOO_LONG | Symbol of the coin is too long. | Coin symbol must be less than or equal to 10 characters | - -## Aptos tokens - -| Error | Meaning | -|----------------------------------------------|:--------------------------------------------------------------:| -| EALREADY_HAS_BALANCE | The token has balance and cannot be initialized. | -| ECOLLECTIONS_NOT_PUBLISHED | There isn't any collection under this account. | -| ECOLLECTION_NOT_PUBLISHED | Cannot find collection in creator's account. | -| ECOLLECTION_ALREADY_EXISTS | The collection already exists. | -| ECREATE_WOULD_EXCEED_COLLECTION_MAXIMUM | Exceeds the collection's maximal number of token_data. | -| EINSUFFICIENT_BALANCE | Insufficient token balance. | -| EINVALID_TOKEN_MERGE | Cannot merge the two tokens with different token IDs. | -| EMINT_WOULD_EXCEED_TOKEN_MAXIMUM | Exceed the token data maximal allowed. | -| ENO_BURN_CAPABILITY | No burn capability. | -| ETOKEN_DATA_ALREADY_EXISTS | TokenData already exists. | -| ETOKEN_DATA_NOT_PUBLISHED | TokenData not published. | -| ETOKEN_STORE_NOT_PUBLISHED | TokenStore doesn't exist. | -| ETOKEN_SPLIT_AMOUNT_LARGER_THAN_TOKEN_AMOUNT | Cannot split token to an amount larger than its amount. | -| EFIELD_NOT_MUTABLE | The field is not mutable. | -| ENO_MUTATE_CAPABILITY | Not authorized to mutate. | -| ENO_TOKEN_IN_TOKEN_STORE | Token not in the token store. | -| EUSER_NOT_OPT_IN_DIRECT_TRANSFER | User didn't opt-in direct transfer. | -| EWITHDRAW_ZERO | Cannot withdraw 0 token. | -| ENFT_NOT_SPLITABLE | Cannot split a token that only has 1 amount. | -| ENO_MINT_CAPABILITY | No mint capability | -| ECOLLECTION_NAME_TOO_LONG | The collection name is too long. | -| ENFT_NAME_TOO_LONG | The NFT name is too long. | -| EURI_TOO_LONG | The URI is too long. | -| ENO_DEPOSIT_TOKEN_WITH_ZERO_AMOUNT | Cannot deposit a token with 0 amount. | -| ENO_BURN_TOKEN_WITH_ZERO_AMOUNT | Cannot burn 0 token. | -| EWITHDRAW_PROOF_EXPIRES | Withdraw proof expires. | -| EOWNER_CANNOT_BURN_TOKEN | Token is not burnable by owner. | -| ECREATOR_CANNOT_BURN_TOKEN | Token is not burnable by creator. | -| ECANNOT_UPDATE_RESERVED_PROPERTY | Reserved fields for token contract. Cannot be updated by user. | -| EURI_TOO_SHORT | URI too short. | -| ETOKEN_OFFER_NOT_EXIST | Token offer doesn't exist. | diff --git a/apps/nextra/pages/zh/build/smart-contracts/fungible-asset.mdx b/apps/nextra/pages/zh/build/smart-contracts/fungible-asset.mdx deleted file mode 100644 index 53c6faa92..000000000 --- a/apps/nextra/pages/zh/build/smart-contracts/fungible-asset.mdx +++ /dev/null @@ -1,435 +0,0 @@ ---- -title: "Aptos Fungible Asset (FA) Standard" -id: "fungible-asset" ---- - -import { Callout } from "nextra/components"; -import { ThemedImage } from "@components/index"; - -# Aptos Fungible Asset (FA) Standard - -The Aptos Fungible Asset Standard (also known as “Fungible Asset” or “FA”) provides a standard, type-safe way to define fungible assets in the Move ecosystem. It is a modern replacement for the `coin` module that allows for seamless minting, transfer, and customization of fungible assets for any use case. - -This standard is important because it allows fungible assets on Aptos (such as Currencies and Real World Assets (RWAs)) to represent and transfer ownership in a consistent way dApps can recognize. This standard also allows for more extensive customization than the `coin` module did by leveraging [Move Objects](objects.mdx) to represent fungible asset data. - -The FA standard provides all the functionality you need to create, mint, transfer, and burn fungible assets (as well as automatically allowing recipients of the fungible asset to store and manage any fungible assets they receive). - -It does so by using two Move Objects: - -1. `Object` - This represents details about the fungible asset itself, including information such as the `name`, `symbol`, and `decimals`. -2. `Object` - This stores a count of fungible asset units owned by this account. Fungible assets are interchangeable with any other fungible asset that has the same metadata. An account *may* own more than one `FungibleStore` for a single Fungible Asset, but that is only for advanced use cases. - -The diagram below shows the relationship between these Objects. The `Metadata` Object is owned by the Fungible Asset creator, then referenced in FA holders' `FungibleStore`s to indicate which FA is being tracked: - - - -[This implementation](https://github.com/aptos-labs/aptos-core/blob/main/aptos-move/framework/aptos-framework/sources/fungible_asset.move) is an improvement on the `coin` Standard because Move Objects are more customizable and extensible via smart contract. See the advanced guides on writing [Move Objects](objects.mdx) for more details. -The FA standard also automatically handles tracking how much of a fungible asset an account owns, as opposed to requiring the recipient to register a `CoinStore` resource separate from the transfer. - -## Creating a new Fungible Asset (FA) - -At a high level, this is done by: - -1. Creating a non-deletable Object to own the newly created Fungible Asset `Metadata`. -2. Generating `Ref`s to enable any desired permissions. -3. Minting Fungible Assets and transferring them to any account you want to. - -To start with, the Fungible Asset standard is implemented using Move Objects. Objects by default are transferable, can own multiple resources, and can be customized via smart contract. For full details on Objects and how they work, please read [this guide](objects.mdx). - -To create an FA, first you need to create a **non-deletable Object** since destroying the metadata for a Fungible Asset while there are active balances would not make sense. You can do that by either calling `object::create_named_object(caller_address, NAME)` or `object::create_sticky_object(caller_address)` to create the Object on-chain. - -When you call these functions, they will return a `ConstructorRef`. `Ref`s allow Objects to be customized immediately after they are created. You can use the `ConstructorRef` to generate other permissions that may be needed based on your use case. - - -Note that the `ConstructorRef` cannot be stored and is destroyed by the end of the transaction used to create this Object, so any `Ref`s must be generated during Object creation. - - -One use for the `ConstructorRef` is to generate the FA `Metadata` Object. The standard provides a generator function called `primary_fungible_store::create_primary_store_enabled_fungible_asset` which will allow your fungible asset to be transferred to any account. This method makes it so the primary `FungibleStore` for recipients is automatically created or re-used so you don’t need to create or index the store directly. - -This is what `create_primary_store_enabled_fungible_asset` looks like: - -```move filename="example.move" -public fun create_primary_store_enabled_fungible_asset( - constructor_ref: &ConstructorRef, - // This ensures total supply does not surpass this limit - however, - // Setting this will prevent any parallel execution of mint and burn. - maximum_supply: Option, - // The fields below here are purely metadata and have no impact on-chain. - name: String, - symbol: String, - decimals: u8, - icon_uri: String, - project_uri: String, -) -``` - - -Alternatively, you can use `add_fungibility` which uses the same parameters, but requires recipients to keep track of their `FungibleStore` addresses to keep track of how many units of your FA they have. - - -Once you have created the Metadata, you can also use the `ConstructorRef` to generate additional `Ref`s. In addition to the usual [Object Refs](object/creating-objects.mdx), FAs define three additional permissions you can generate: - -1. `MintRef` offers the capability to mint new FA units. -2. `TransferRef` offers the capability to freeze accounts from transferring this FA, or to bypass an existing freeze. (This can be important when trying to be compliant with some regulations). -3. `BurnRef` offers the capability to burn or delete FA units. - - -Note: All `Ref`s must be generated when the Object is created as that is the only time you can generate an Object’s `ConstructorRef`. - - -To generate an Object with all FA permissions, you could deploy a contract like this: - -```move filename="example.move" -module my_addr::fungible_asset_example { - use aptos_framework::fungible_asset::{Self, MintRef, TransferRef, BurnRef, Metadata, FungibleAsset}; - use aptos_framework::object::{Self, Object}; - use aptos_framework::primary_fungible_store; - use std::error; - use std::signer; - use std::string::utf8; - use std::option; - - const ASSET_SYMBOL: vector = b"FA"; - - // Make sure the `signer` you pass in is an address you own. - // Otherwise you will lose access to the Fungible Asset after creation. - entry fun init_module(admin: &signer) { - // Creates a non-deletable object with a named address based on our ASSET_SYMBOL - let constructor_ref = &object::create_named_object(admin, ASSET_SYMBOL); - - // Create the FA's Metadata with your name, symbol, icon, etc. - primary_fungible_store::create_primary_store_enabled_fungible_asset( - constructor_ref, - option::none(), - utf8(b"FA Coin"), /* name */ - utf8(ASSET_SYMBOL), /* symbol */ - 8, /* decimals */ - utf8(b"http://example.com/favicon.ico"), /* icon */ - utf8(b"http://example.com"), /* project */ - ); - - // Generate the MintRef for this object - // Used by fungible_asset::mint() and fungible_asset::mint_to() - let mint_ref = fungible_asset::generate_mint_ref(constructor_ref); - - // Generate the TransferRef for this object - // Used by fungible_asset::set_frozen_flag(), fungible_asset::withdraw_with_ref(), - // fungible_asset::deposit_with_ref(), and fungible_asset::transfer_with_ref(). - let transfer_ref = fungible_asset::generate_transfer_ref(constructor_ref); - - // Generate the BurnRef for this object - // Used by fungible_asset::burn() and fungible_asset::burn_from() - let burn_ref = fungible_asset::generate_burn_ref(constructor_ref); - - // Add any other logic required for your use case. - // ... - } -} -``` - - -For a full example of how to create your own Fungible Asset module, please see [fa_coin.move](https://github.com/aptos-labs/aptos-core/blob/main/aptos-move/move-examples/fungible_asset/fa_coin/sources/FACoin.move). -Alternatively, you can explore the collection of [FA example code here](https://github.com/aptos-labs/aptos-core/tree/main/aptos-move/move-examples/fungible_asset). - - -Now you can use the `MintRef` to mint via: - -```move filename="example.move" -public fun mint(ref: &MintRef, amount:u64): FungibleAsset -``` - -Or burn FA units using the `BurnRef` like so: - -```move filename="example.move" -public fun burn(ref: &BurnRef, fa: FungibleAsset) -``` - -At this point, you can mint, transfer, and burn Fungible Assets using the `Ref`s you generated. See the above example Move scripts for how to implement those entry functions. - -## Reference Docs - -You can find the complete set of functions that the Fungible Asset Standard provides [here](https://aptos.dev/reference/move/?branch=mainnet&page=aptos-framework/doc/fungible_asset.md). {/* TODO: Update the Move Reference link once we migrate that page */} - -The basic features owners of Fungible Assets can use include the following. - -### Withdraw - -An owner can withdraw FA from their primary store by calling: - -```move filename="withdraw" -public fun primary_fungible_store::withdraw(owner: &signer, metadata: Object, amount:u64): FungibleAsset -``` -This function will emit a `WithdrawEvent`. - -### Deposit - -An owner can deposit FA to their primary store by calling: - -```move filename="deposit" -public fun primary_fungible_store::deposit(owner: address, fa: FungibleAsset) -``` - -This function will emit a `DepositEvent`. - -### Transfer -An owner can deposit FA from their primary store to that of another account by calling: - -```move filename="transfer" -public entry fun primary_fungible_store::transfer(sender: &signer, metadata: Object, recipient: address, amount:u64) -``` - -This will emit both `WithdrawEvent` and `DepositEvent` on the respective `FungibleStore`s. - -### Check Balance - -To check the balance of a primary store, call: - -```move filename="check_balances" -public fun primary_fungible_store::balance(account: address, metadata: Object): u64 -``` - -### Check Frozen Status - -To check whether the given account's primary store is frozen, call: - -```move filename="is_frozen" -public primary_fungible_store::fun is_frozen(account: address, metadata: Object): bool -``` - -### Events - -FAs have three events emitted from the above basic functions: - -1. `Deposit`: Emitted when FAs are deposited into a store. - -```move filename="deposit_event" -struct Deposit has drop, store { - store: address, - amount: u64, -} -``` - -2. `Withdraw`: Emitted when FAs are withdrawn from a store. - -```move filename="withdraw_event" -struct Withdraw has drop, store { - store: address, - amount: u64, -} -``` - -3. `Frozen`: Emitted when the frozen status of a fungible store is updated. - -```move filename="frozen_event" -struct Frozen has drop, store { - store: address, - frozen: bool, -} -``` - -## Dispatchable Fungible Asset (Advanced) - -Aside from the default managed fungible asset functionality provided by the Aptos Framework, fungible asset issuers can implement their own deposit/withdraw logic using the dispatchable fungible asset standard. This is done by registering custom hook functions to be invoked at withdrawal/deposit time. These hook functions are stored in the metadata of a fungible asset class, and the Aptos Framework will automatically invoke them instead of the default logic. This allows issuers to implement complex logic, such as customized access control. For more details, refer to the corresponding [AIP](https://github.com/aptos-foundation/AIPs/blob/main/aips/aip-73.md). - -### Implementing the Hook Function - -To implement a custom hook function, build a module with functions that have the following signature: - -```move filename="example.move" -module my_addr::my_fungible_asset_example { - // ... - public fun withdraw( - store: Object, - amount: u64, - transfer_ref: &TransferRef, - ): FungibleAsset { - // Your custom logic here - } - - public fun deposit( - store: Object, - fa: FungibleAsset, - transfer_ref: &TransferRef, - ) { - // Your custom logic here - } - // ... -} -``` - -### Limitations - -- **Reentrancy Prevention**: Only call `with_ref` APIs in your custom hooks for deposit/withdraw operations. - - Use `fungible_asset::deposit_with_ref` instead of `fungible_asset::deposit`. - - Use `fungible_asset::withdraw_with_ref` instead of `fungible_asset::withdraw`. -- Avoid calling functions defined in `dispatchable_fungible_asset` and `primary_fungible_store`, _except_ for inline functions, to prevent errors during transfers. -- Note that calling `fungible_asset::withdraw` and `fungible_asset::deposit` will NOT work for assets with registered hooks. See more information in Interacting with dispatchable fungible asset. - -For more details on these limitations, refer to the [AIP](https://github.com/aptos-foundation/AIPs/blob/main/aips/aip-73.md). - -### Registering the Hook Function - -Once the functions are implemented, use the API defined in `dispatchable_fungible_asset::register_dispatch_functions` to bind the functions with your fungible asset. - -```move filename="register_dispatch" -module 0x1::dispatchable_fungible_asset { - public fun register_dispatch_functions( - constructor_ref: &ConstructorRef, - withdraw_function: Option, - deposit_function: Option, - derived_balance_function: Option, - ) -} -``` - - -The `register_dispatch_functions` function takes `Option` as input to specify whether to use custom or default logic for withdraw/deposit/balance operations. If `option::none()` is passed, the default logic is used. -The `constructor_ref` is a reference for the metadata object of your fungible asset. - - -To construct `FunctionInfo`, use: - -```move filename="new_function_info" -module 0x1::dispatchable_fungible_asset { - public fun new_function_info(module_signer: &signer, module_name: String, function_name: String): FunctionInfo -} -``` - -For security reasons, you need the signer of the module (the deployer or code owner) to create a FunctionInfo. - -```move filename="example.move" -module my_addr::my_fungible_asset_example { - use aptos_framework::string; - use aptos_framework::object; - use aptos_framework::primary_fungible_store; - use aptos_framework::dispatchable_fungible_asset; - - fun create_fungible_asset(module_signer: &signer, /* ... */) { - // Make the deposit override function info - let deposit_override = dispatchable_fungible_asset::new_function_info( - module_signer, - string::utf8(b"example"), - string::utf8("deposit") - ); - - // Create the fungible asset - let constructor_ref = object::create_sticky_object( /* ... */); - - primary_fungible_store::create_primary_store_enabled_fungible_asset(&constructor_ref, ...); - // or below if not having primary stores - // fungible_asset::add_fungibility(&constructor_ref, /* ... */); - - // Override default functionality for deposit - dispatchable_fungible_asset::register_dispatch_functions( - &constructor_ref, - option::none(), - option::some(deposit_override), - option::none() - ); - - // ... - } - - // ... -} -``` - -### Interacting with dispatchable fungible asset -For users using `primary_fungible_store` to manage assets, no changes are required to interact with assets with dispatchable hooks. The Aptos Framework automatically invokes the dispatch logic if a hook is set up. - -For users using secondary `FungibleStore` to interact with assets, use `dispatchable_fungible_asset::withdraw/deposit` instead of `fungible_asset::withdraw/deposit` to handle assets with registered hooks. - -The `dispatchable_fungible_asset::withdraw/deposit` functions are replacements, and also work with functions that do not have hooks registered. - -## Managing Stores (Advanced) - -Behind the scenes, the Fungible Asset Standard needs to manage how the asset balances are stored on each account. In the vast majority of circumstances, users will store all FA balances in their Primary `FungibleStore`. Sometimes though, additional “Secondary Stores” can be created for advanced DeFi applications. - -- Each account owns only one non-deletable primary store for each type of FA, the address of which is derived in a deterministic manner from the account address and metadata object address. If primary store does not exist, it will be created if FA is going to be deposited by calling functions defined in `primary_fungible_store.move` -- Secondary stores do not have deterministic addresses and are deletable when empty. Users are able to create as many secondary stores as they want using the provided functions but there is a caveat that addressing secondary stores on-chain may be more complex. - -You can look up a primary store via the following function: - -```move filename="primary_store" -public fun primary_store(owner: address, metadata: Object): Object -``` - -And if you want to create a primary store manually you can use this function: - -```move filename="create_primary_store" -public fun create_primary_store(owner_addr: address, metadata: Object): Object -``` - -The primary reason to use a secondary store is for assets managed by a smart contract. For example, an asset pool may have to manage multiple fungible stores for one or more types of FA. - -To create a secondary store, you must: - -1. Create an Object to get its `ConstructorRef`. -2. Call: - -```move filename="create_store" -public fun create_store(constructor_ref: &ConstructorRef, metadata: Object): Object -``` - - -This will create a secondary store owned by the newly created Object. Sometimes an object can be reused as a store. For example, a metadata object can also be a store to hold some FA of its own type or a liquidity pool object can be a store of the issued liquidity pool's coin. - -It is crucial to set the correct owner of a `FungibleStore` object for managing the FA stored inside. By default, the owner of a newly created object is the creator whose `signer` is passed into the creation function. For `FungibleStore` objects managed by smart contract itself, the owner should usually be an Object address controlled by the contract. For those cases, those objects should keep their `ExtendRef` at the proper place to create `signer` as needed to modify the `FungibleStore` via contract logic. - -## Migration from `Coin` to the Fungible Asset Standard - -### Smart Contract Migration - -**Projects utilizing the `coin` module do not need to modify their contracts.** The `coin` module has been enhanced to handle migration automatically. Whenever a paired FA is required for a `coin`, it will be automatically created if it doesn't already exist. The mapping between coins and FAs is immutable and stored in an on-chain table: - -```move filename="coin_conversion" -struct CoinConversionMap has key { - coin_to_fungible_asset_map: Table, -} -``` - -A `#[view]` function is available to retrieve metadata for the paired FA if it exists: - -```move filename="paired_metadata" -#[view] -public fun paired_metadata(): Option> -``` - -Similarly, a function exists for reverse lookup: - -```move filename="paired_coin" -#[view] -public fun paired_coin(metadata: Object): Option -``` - -### Off-chain Migration - -There are two changes needed for off-chain services: - -1. Balances should reflect that a user may have **both** a `coin` balance and a paired FA balance. -2. Event listeners should listen for both `coin` and FA events. - -Since a user may possess **both** a `coin` balance and a paired FA balance, off-chain applications should be updated to reflect the **sum** of both the `coin` balance and its paired FA balance. - -- For Aptos Indexer users, you may utilize a new table called `current_unified_fungible_asset_balances` to obtain the latest sum of coin balance and FA balance representing the same asset type. -- For users employing Node API or other customized indexing, they should add the balance of the paired FA in users' `FungibleStore` and `ConcurrentFungibleBalance` if any of them exist to the coin balance. - -To retrieve the balance of the `PrimaryFungibleStore` for a paired FA to an existing `coin` of type `CoinType`: - -1. Call `paired_metadata()` to obtain the paired FA metadata object address (the address is immutable). -2. Retrieve the balance of the paired FA: - - Call [getCurrentFungibleAssetBalances](https://github.com/aptos-labs/aptos-ts-sdk/blob/c01a26ff899235fac1c31c6cc3fe504b764e5b91/src/api/fungibleAsset.ts#L115). - - Alternatively, determine the address of the primary `FungibleStore`, which can be deterministically calculated with the following formula: - - `sha3_256(32-byte account address | 32-byte metadata object address | 0xFC)` - - Then use that address to obtain the `FungibleStore` resource to fetch the balance. - - If the balance is non-zero, this is the final balance of this FA. - - Otherwise, try to get `ConcurrentFungibleBalance` resource at the same address and get the balance there instead. - - If neither exist, the FA balance for this account is 0. - -**Post-migration, both coin events and FA events could be emitted for an activity, depending on whether the user has migrated or not.** Dapps relying on events should update their business logic accordingly. diff --git a/apps/nextra/pages/zh/build/smart-contracts/linter.mdx b/apps/nextra/pages/zh/build/smart-contracts/linter.mdx deleted file mode 100644 index 4f62cf6b4..000000000 --- a/apps/nextra/pages/zh/build/smart-contracts/linter.mdx +++ /dev/null @@ -1,122 +0,0 @@ ---- -title: "Aptos Move Lint" ---- - -# Aptos Move Lint - -The "Aptos Move Lint" tool runs on a Move package to find and warn about common issues in Move programs, helping improve your Move code. - -You can run it with the aptos CLI command: `aptos move lint`. - -This tool is currently in beta, so please try it out and submit [bugs and feedback](https://github.com/aptos-labs/aptos-core/issues/new?title=%5Blinter%5D%20%3CDescriptive%20Title%3E&body=%3CDetailed%20description%20of%20the%20issue%20or%20feature%20request%3E&labels=move-linter&projects=aptos-labs/16). Also, we are tracking ideas and prioritization requests for new lint rules [here](https://github.com/aptos-labs/aptos-core/issues/15221), we welcome your contributions. - - -## Lint Checks -### `avoid_copy_on_identity_comparison` - -Checks for identity comparisons (`==` or `!=`) between copied values of type `vector` or `struct` (i.e., types for which copy can be expensive). It instead suggests to use reference-based identity comparison instead (i.e., use `&x == &y` instead of `x == y`, when the above mentioned conditions meet). - -[This recommendation](book/equality.mdx#avoid-extra-copies) is also given in the Move book. Due to automatic copy inference, it may not be obvious when a copy is being made while using `==` or `!=` on values with types that have the `copy` ability. This lint identifies cases where extra copies on vectors or structs could be skipped by using reference-based identity comparisons. - -### `blocks_in_conditions` - -Checks for use of blocks in conditions (e.g., in `if`, `match`, and `while` conditions), which can make code hard to read. An example coding pattern caught by this lint is: - -```move -if ({let x = foo(); !x}) { // uses a block in condition - bar(); -} -``` - -Such code can usually be rewritten to hoist the block out and above the condition, usually making it more readable. - -It is a common Move pattern to provide inline specifications in conditions, especially loop invariants, which requires creating blocks in conditions. We exclude this pattern in the lint check to continue to allow for this specification pattern. - -Note that an `assert!` is translated to a conditional abort, so blocks in `assert!` condition also are reported by this lint. - -### `needless_bool` - -Checks for patterns of the form (where `x` is any arbitrary boolean expression): - -- `if (x) true else false`, which can be replaced with just `x`. -- `if (x) false else true`, which can be replaced with just `!x`. -- `if (x) { return true } else { return false }`, which can be replaced with just `return x`. -- `if (x) { return false } else { return true }`, which can be replaced with just `return !x`. -- `if (x) true else true` or `if (x) false else false`, which should be rewritten to remove the redundant branch. - -### `needless_deref_ref` - -Checks for patterns where references taken are immediately dereferenced, and suggests removing the pair of dereference-reference operators: - -- `*&x.f` can be simplified to `x.f` -- `*&mut x.f` can be simplified to `x.f` -- `*&mut x.f = 5;` can be simplified to `x.f = 5;` - -### `needless_mutable_reference` - -Checks for mutable references or borrows (currently: mutable reference parameters, mutable borrow of locals, `borrow_global_mut`) that are not used mutably, and suggests to use the immutable reference or borrow instead. - -For example, in the function `foo` below, `&mut` can be replaced by `&` because the reference is not mutably used. - -```move -fun foo(x: u64): u64 { - let y = &mut x; - *y -} - -``` - -### `needless_ref_deref` - -Checks for patterns where immutable reference are taken for a dereference, and suggests removing the pair of reference-dereference operators: `&*x` can be simplified to `x`. - -### `needless_ref_in_field_access` - -Checks for patterns where there are needless references taken when accessing a field of a struct or an enum, and suggests removing the explicit reference taken: - -- `(&s).f` can be simplified to `s.f` -- `(&mut s).f = 42;` can be simplified to `s.f = 42;` - -### `simpler_numeric_expression` - -Checks for various patterns where a simpler numeric expression can be used instead. In all these cases, the code must already type check, and `x` can be any numeric expression. - -- `x & 0`, `x * 0`, `0 & x`, `0 * x`, `0 << x`, `0 >> x`, `x % 1` can all be replaced with just `0`. -- `x | 0`, `x ^ 0`, `x >> 0`, `x << 0`, `x + 0`, `x - 0`, `x / 1`, `x * 1`, `0 | x`, `0 ^ x`, `0 + x`, `1 * x` can all be replaced with just `x`. - -### `unnecessary_boolean_identity_comparison` - -Checks for boolean identity comparisons of the form: - -- `x == true`, `true == x`, which can be replaced with just `x`. -- `x == false`, `false == x`, which can be replaced with just `!x`. - -In all these cases, `x` can be any arbitrary boolean expression. - -### `unnecessary_numerical_extreme_comparison` - -Checks if there are any numerical comparisons with extreme values (i.e., min and max value representable by that numeric type) that are unnecessary or can be made more precise and clear. Depending on the comparison, various recommendations are made. - -Consider the following example expressions that are caught by the lint, and the corresponding recommendations made (in all these cases, `x` is a place holder for a numerical expression of type `u8`, `u16`, `u32`, `u64`, `u128`, or `u256`, and `MAX` is a place holder for the max value representable for that numeric type): - -- `x < 0`, `0 > x`, `x > MAX`, `MAX < x`, are always false, rewrite code to remove this comparison -- `x >= 0`, `0 <= x`, `x <= MAX`, `MAX >= x`, are always true, rewrite code to remove this comparison -- `x <= 0`, `0 >= x`, `x >= MAX`, `MAX <= x`, can all be simplified to use `==` instead -- `x > 0`, `0 < x`, `x < MAX`, `MAX > x`, can all be clarified to use `!=` instead - -### `while_true` - -Checks for `while (true) { .... }` patterns and suggests using the more explicit `loop { .... }` construct instead. - -## Suppressing Lint Warnings - -To suppress one or more lint checks named `check1`, `check2`, ... (and so on), you can add the attribute `#[lint::skip(check1, check2, ...)]` to a function or a module. The linter will then not perform the checks named `check1`, `check2`, ... (and so on) for that function or module. - -For example, the function below would usually get a warning from the linter about a `needless_bool`, but due to the attribute on the function, the linter does not emit a warning. - -```move -#[lint::skip(needless_bool)] -fun violation(): bool { - if (foo()) true else false -} -``` diff --git a/apps/nextra/pages/zh/build/smart-contracts/modules-on-aptos.mdx b/apps/nextra/pages/zh/build/smart-contracts/modules-on-aptos.mdx deleted file mode 100644 index dbb75ee6f..000000000 --- a/apps/nextra/pages/zh/build/smart-contracts/modules-on-aptos.mdx +++ /dev/null @@ -1,26 +0,0 @@ ---- -title: "Modules on Aptos" ---- -import { Callout } from "nextra/components"; - -# Modules on Aptos - -Aptos allows for permissionless publishing of [modules](book/modules-and-scripts.mdx) -within a [package](book/packages.mdx) as well as [upgrading](book/package-upgrades.mdx) -those that have appropriate compatibility policy set. - -A module contains several structs and functions, much like Rust. - -During package publishing time, a few constraints are maintained: - -- Both Structs and public function signatures are published as immutable. -- Only when a module is being published for the first time, and not during an -upgrade, will the VM search for and execute an `init_module(account: &signer)` -function. The signer of the account that is publishing the module is passed into -the `init_module` function of the contract. **This function must be private and -not return any value.** - - -`init_module` is optional -It is only necessary if you want to initialize data when publishing a module for the first time. - diff --git a/apps/nextra/pages/zh/build/smart-contracts/move-reference.mdx b/apps/nextra/pages/zh/build/smart-contracts/move-reference.mdx deleted file mode 100644 index aca8b08b4..000000000 --- a/apps/nextra/pages/zh/build/smart-contracts/move-reference.mdx +++ /dev/null @@ -1,5 +0,0 @@ -import { MoveReference, AptosFrameworkReference } from '@components/index'; - -# Move Reference - - diff --git a/apps/nextra/pages/zh/build/smart-contracts/move-security-guidelines.mdx b/apps/nextra/pages/zh/build/smart-contracts/move-security-guidelines.mdx deleted file mode 100644 index 7ab55b4fb..000000000 --- a/apps/nextra/pages/zh/build/smart-contracts/move-security-guidelines.mdx +++ /dev/null @@ -1,832 +0,0 @@ ---- -title: "Move Security Guidelines" ---- - -# Move Security Guidelines - -The Move language is designed with security and inherently offers several features including a type system and a linear logic. Despite this, its novelty and the intricacies of some business logic mean that developers might not always be familiar with Move's secure coding patterns, potentially leading to bugs. - -This guide addresses this gap by detailing common anti-patterns and their secure alternatives. It provides practical examples to illustrate how security issues can arise and recommends best practices for secure coding. This guide aims to sharpen developers' understanding of Move's security mechanisms and ensure the robust development of smart contracts. - -## Access Control - -### Object Ownership Check - -Every `Object` can be accessed by anyone, which means any `Object` can be passed to any function, even if the caller doesn't own it. -It's important to verify that the `signer` is the rightful owner of the object. - -#### Example Insecure Code - -In this module, a user must purchase a subscription before performing certain actions. The user invokes the registration function to acquire an `Object`, which they can later use to execute operations. - -```move filename="object_example.move" -module 0x42::example { - - struct Subscription has key { - end_subscription: u64 - } - - entry fun registration(user: &signer, end_subscription: u64) { - let price = calculate_subscription_price(end_subscription); - payment(user,price); - - let user_address = address_of(user); - let constructor_ref = object::create_object(user_address); - let subscription_signer = object::generate_signer(&constructor_ref); - move_to(&subscription_signer, Subscription { end_subscription }); - } - - entry fun execute_action_with_valid_subscription( - user: &signer, obj: Object - ) acquires Subscription { - let object_address = object::object_address(&obj); - let subscription = borrow_global(object_address); - assert!(subscription.end_subscription >= aptos_framework::timestamp::now_seconds(),1); - // Use the subscription - [...] - } -} -``` - -In this insecure example, `execute_action_with_valid_subscription` does not verify if the user owns the `obj` passed to it. Consequently, anyone can use another person's subscription, bypassing the payment requirement. - -#### Example Secure Code - -Ensure that the signer owns the object. - -```move filename="object_example.move" -module 0x42::example { - - struct Subscription has key { - end_subscription: u64 - } - - entry fun registration(user: &signer, end_subscription: u64) { - let price = calculate_subscription_price(end_subscription); - payment(user,price); - - let user_address = address_of(user); - let constructor_ref = object::create_object(user_address); - let subscription_signer = object::generate_signer(&constructor_ref); - move_to(&subscription_signer, Subscription { end_subscription }); - } - - entry fun execute_action_with_valid_subscription( - user: &signer, obj: Object - ) acquires Subscription { - //ensure that the signer owns the object. - assert!(object::owner(&obj)==address_of(user),ENOT_OWNWER); - let object_address = object::object_address(&obj); - let subscription = borrow_global(object_address); - assert!(subscription.end_subscription >= aptos_framework::timestamp::now_seconds(),1); - // Use the subscription - [...] - } -} -``` - -### Global Storage Access Control - -Accepting a `&signer` is not always sufficient for access control purposes. Be sure to assert that the signer is the expected account, especially when performing sensitive operations. - -Users without proper authorization can execute privileged actions. - -#### Example Insecure Code - -This code snippet allows any user invoking the `delete` function to remove an `Object`, without verifying that the caller has the necessary permissions. - -```move -module 0x42::example { - struct Object has key{ - data: vector - } - - public fun delete(user: &signer, obj: Object) { - let Object { data } = obj; - } -} -``` - -#### Example Secure Code - -A better alternative is to use the global storage provided by Move, by directly borrowing data off of `signer::address_of(signer)`. This approach ensures robust access control, as it exclusively accesses data contained within the address of the signer of the transaction. This method minimizes the risk of access control errors, ensuring that only the data owned by the `signer` can be manipulated. - -```move -module 0x42::example { - struct Object has key{ - data: vector - } - - public fun delete(user: &signer) { - let Object { data } = move_from(signer::address_of(user)); - } -} -``` - -### Function visibility - -Adhere to the principle of least privilege: - -- Always start with private functions, change their visibility as it is needed by the business logic. -- Utilize `entry` for functions intended for use solely from the Aptos CLI or SDK. -- Utilize `friend` for functions that can only be accessible by specific modules. -- Utilize the `#[view]` decorator with functions that read data from storage without altering state. #[view] functions can be invoked indirectly and in this case they might change the storage. - -Function visibility determines who can call a function. It's a way to enforce access control and is critical for smart contract security: - -- private functions are only callable within the module they are defined in. They're not accessible from other modules or from the CLI/SDK, which prevents unintended interactions with contract internals. - -```move -module 0x42::example { - fun sample_function() { /* ... */ } -} -``` - -- `public(friend)` functions expand on this by allowing specified _friends_ modules to call the function, enabling controlled interaction between different contracts while still restricting general access. - -```move -module 0x42::example { - friend package::mod; - - public(friend) fun sample_function() { /* ... */ } -} -``` - -- `public` functions are callable by any published module or script. - -```move -module 0x42::example { - public fun sample_function() { /* ... */ } -} -``` - -- `#[view]` decorated functions cannot alter storage; they only read data, providing a safe way to access information without risking state modification. - -```move -module 0x42::example { - #[view] - public fun read_only() { /* ... */ } -} -``` - -- The `entry` modifier in Move is used to indicate entry points for transactions. Functions with the `entry` modifier serve as the starting point of execution when a transaction is submitted to the blockchain. - -```move -module 0x42::example { - entry fun f(){} -} -``` - -To summarize: - -| | Module itself | Other Modules | Aptos CLI/SDK | -| -------------- | ------------- | --------------------------------- | ------------- | -| private | ✅ | ⛔ | ⛔ | -| public(friend) | ✅ | ✅ if friend

⛔ otherwise | ⛔ | -| public | ✅ | ✅ | ⛔ | -| entry | ✅ | ⛔ | ✅ | - -This layered visibility ensures that only authorized entities can execute certain functions, greatly reducing the risk of bugs or attacks that compromise contract integrity. - -Note that it’s possible to combine `entry` with `public` or `public(friend)` - -```move -module 0x42::example { - public(friend) entry fun sample_function() { /* ... */ } -} -``` - -In this case `sample_function` can be called by both the Aptos CLI/SDK by any module declared as a friend. - -#### Impact - -Adhering to this principle ensures that functions are not over-exposed, restricting the scope of function access to only what is necessary for the business logic. - -## Types and Data Structures - -### Generics type check - -Generics can be used to define functions and structs over different input data types. When using them, ensure that the generic types are valid and what’s expected. [Read more](book/generics.mdx) about generics. - -Unchecked generics can lead to unauthorized actions or transaction aborts, potentially compromising the integrity of the protocol. - -#### Example Insecure Code - -The code below outlines a simplified version of a flash loan. - -In the `flash_loan` function, a user can borrow a given amount of coins type **`T`** along with a `Receipt` that records the borrowed amount plus a fee that should be returned to the protocol before the end of the transaction. - -The `repay_flash_loan` function accepts a `Receipt` and a `Coin` as parameters. The function extracts the repayment amount from the `Receipt` and asserts that the value of the returned `Coin` is greater than or equal to the amount specified in the `Receipt`, however there’s no check to ensure that the `Coin` returned is the same as the `Coin`that was initially loaned out, giving the ability to repay the loan with a coin of lesser value. - -```move -module 0x42::example { - struct Coin { - amount: u64 - } - - struct Receipt { - amount: u64 - } - - public fun flash_loan(user: &signer, amount: u64): (Coin, Receipt) { - let (coin, fee) = withdraw(user, amount); - ( coin, Receipt {amount: amount + fee} ) - } - - public fun repay_flash_loan(rec: Receipt, coins: Coin) { - let Receipt{ amount } = rec; - assert!(coin::value(&coin) >= rec.amount, 0); - deposit(coin); - } -} -``` - -#### Example Secure Code - -The Aptos Framework sample below creates a key-value table consisting of two generic types `K` and `V` . Its related `add` functions accepts as parameters a `Table` object, a `key`, and a `value` of types `K` and `V` . The `phantom` syntax ensures that the key and value types cannot be different than those in the table, preventing type mismatches. [Read more](book/generics.mdx#phantom-type-parameters) about `phantom` type parameters. - -```move -module 0x42::example { - struct Table has store { - handle: address, - } - - public fun add(table: &mut Table, key: K, val: V) { - add_box>(table, key, Box { val }) - } -} -``` - -Given the by-design type checking provided by the Move language, we can refine the code of our flash loan protocol. The code below ensures that the coins passed to `repay_flash_loan` match the originally-loaned coins. - -```move -module 0x42::example { - struct Coin { - amount: u64 - } - struct Receipt { - amount: u64 - } - - public fun flash_loan(_user: &signer, amount:u64): (Coin, Receipt) { - let (coin, fee) = withdraw(user, amount); - (coin,Receipt { amount: amount + fee}) - } - - public fun repay_flash_loan(rec: Receipt, coins: Coin) { - let Receipt{ amount } = rec; - assert!(coin::value(&coin) >= rec.amount, 0); - deposit(coin); - } -} -``` - -### Resource management and Unbounded Execution - -Effective resource management and unbounded execution prevention are important for maintaining security and gas efficiency in protocol. It's essential to consider these aspects in contract design: - -1. Avoid iterating over a publicly accessible structure that allows for unlimited entries, where any number of users can contribute without constraints. -2. Store user-specific assets, such as coins and NFTs, within individual user accounts. -3. Keep module or package-related information within Objects, separate from user data. -4. Instead of combining all user operations in a single shared global space, separating them by individual users. - -#### Impact - -The negligence of these aspects allowing an attacker to deplete the gas and abort the transaction. This can block application functionalities. - -#### Example Insecure Code - -The code below shows a loop iterating over every open order and could potentially be blocked by registering many orders: - -```move -module 0x42::example { - public fun get_order_by_id(order_id: u64): Option acquires OrderStore { - let order_store = borrow_global_mut(@admin); - let i = 0; - let len = vector::length(&order_store.orders); - while (i < len) { - let order = vector::borrow(&order_store.orders, i); - if (order.id == order_id) { - return option::some(*order) - }; - i = i + 1; - }; - return option::none() - } - //O(1) in time and gas operation. - public entry fun create_order(buyer: &signer) { /* ... */ } -} -``` - -#### Example Secure Code - -It's recommended to structure the order management system in a way that each user's orders are stored in their respective account rather than in a single global order store. This approach not only enhances security by isolating user data but also improves scalability by distributing the data load. Instead of using **`borrow_global_mut(@admin)`** which accesses a global store, the orders should be accessed through the individual user's account. - -```move -module 0x42::example { - public fun get_order_by_id(user: &signer, order_id: u64): Option acquires OrderStore { - let order_store = borrow_global_mut(signer::address_of(user)); - if (smart_table::contains(&order_store.orders, order_id)) { - let order = smart_table::borrow(&order_store.orders, order_id); - option::some(*order) - } else { - option::none() - } - } -} -``` - -It is also advisable to utilize efficient data structures tailored to the specific needs of the operations being performed. For instance, a **`SmartTable`** can be particularly effective in this context. - -### Move Abilities - -Move's abilities are a set of permissions that control the possible actions on data structures within the language. Smart contract developers must handle these capabilities with care, ensuring they're only assigned where necessary and understanding their implications to prevent security vulnerabilities. - -| Ability | Description | -| ------- | ---------------------------------------------------------------------------------------------------------------------- | -| copy | Permits the duplication of values, allowing them to be used multiple times within the contract. | -| drop | Allows values to be discarded from memory, which is necessary for controlling resources and preventing leaks. | -| store | Enables data to be saved in the global storage, critical to persist data across transactions. | -| key | Grants data the ability to serve as a key in global storage operations, important for data retrieval and manipulation. | - -[Read more](book/abilities.mdx) about abilities. - -Incorrect usage of abilities can lead to security issues such as unauthorized copying of sensitive data (`copy`), resource leaks (`drop`), and global storage mishandling (`store`). - -#### Example Insecure Code - -```move -module 0x42::example { - struct Token has copy { } - struct FlashLoan has drop { } -} -``` - -- `copy` capability for a `Token` allows tokens to be replicated, potentially enabling double-spending and inflation of the token supply, which could devalue the currency. -- Allowing the `drop` capability in a `FlashLoan` struct could permit borrowers to get out of their loan by destroying it before repayment. - -## Arithmetic Operations - ---- - -### Division Precision - -Arithmetic operations that decrease precision by rounding down could lead protocols to underreport the outcome of these computations. - -Move includes six unsigned integer data types: `u8`, `u16`, `u32`, `u64`, `u128`, and `u256`. Division operations in Move truncate any fractional part, effectively rounding down to the nearest whole number, potentially causing protocols to underrepresent the result of such calculations. - -Rounding errors in calculations can have wide-ranging impacts, potentially causing financial imbalances, data inaccuracies, and flawed decision-making processes. These errors can result in a loss of revenue, give undue benefits, or even pose safety risks, depending on the context. Accurate and precise computation is essential to maintain system reliability and user confidence. - -#### Example Insecure Code - -```move -module 0x42::example { - public fun calculate_protocol_fees(size: u64): (u64) { - return size * PROTOCOL_FEE_BPS / 10000 - } -} -``` - -If `size` is less than `10000 / PROTOCOL_FEE_BPS`, the fee will round down to 0, effectively enabling a user to interact with the protocol without incurring any fees. - -#### Example Secure Code - -The following examples outlines two distinct strategies to mitigate the issue in the code: - -- Set a minimum order size threshold that is greater than `10000 / PROTOCOL_FEE_BPS`, ensuring that the fee will never round down to zero. - -```move -module 0x42::example { - const MIN_ORDER_SIZE: u64 = 10000 / PROTOCOL_FEE_BPS + 1; - - public fun calculate_protocol_fees(size: u64): (u64) { - assert!(size >= MIN_ORDER_SIZE, 0); - return size * PROTOCOL_FEE_BPS / 10000 - } -} -``` - -- Check that fees are non-zero and handle the situation specifically, for example by set a minimum fee or rejecting the transaction. - -```move -module 0x42::example { - public fun calculate_protocol_fees(size: u64): (u64) { - let fee = size * PROTOCOL_FEE_BPS / 10000; - assert!(fee > 0, 0); - return fee; - } -} -``` - -### Integer Considerations - -In Move, the security around integer operations is designed to prevent overflow and underflow which can cause unexpected behavior or vulnerabilities. Specifically: - -- Additions (`+`) and multiplications (`*`) cause the program to abort if the result is too large for the integer type. An abort in this context means that the program will terminate immediately. -- Subtractions (`-`) abort if the result is less than zero. -- Division (`/`) abort if the divisor is zero. -- Left Shift (`<<`), uniquely, does not abort in the event of an overflow. This means if the shifted bits exceed the storage capacity of the integer type, the program will not terminate, resulting in incorrect values or unpredictable behavior. - -[Read more](book/integers.mdx#operations) about operations. - -Bad operations could unexpectedly alter the correct execution of the smart contract, either by causing an unwanted abort or by calculating inaccurate data. - -## Aptos Objects - ---- - -### ConstructorRef leak - -When creating objects ensure to never expose the object’s `ConstructorRef` as it allows adding resources to an object. A `ConstructorRef` can also be used to generate other capabilities (or "Refs") that are used to alter or transfer the ownership the object. [Read more](object/creating-objects.mdx) about Objects capabilities. - -#### Example Vulnerable code - -For example, if a `mint` function returns the `ConstructorRef` for an NFT, it can be transformed to a `TransferRef` , stored in global storage, and can allow the original owner to transfer the NFT back after it’s being sold. - -```move -module 0x42::example { - use std::string::utf8; - - public fun mint(creator: &signer): ConstructorRef { - let constructor_ref = token::create_named_token( - creator, - string::utf8(b"Collection Name"), - string::utf8(b"Collection Description"), - string::utf8(b"Token"), - option::none(), - string::utf8(b"https://mycollection/token.jpeg"), - ); - constructor_ref - } -} -``` - -#### Example Secure Code - -Don’t return `CostructorRef` in the `mint` function: - -```move -module 0x42::example { - use std::string::utf8; - - public fun mint(creator: &signer) { - let constructor_ref = token::create_named_token( - creator, - string::utf8(b"Collection Name"), - string::utf8(b"Collection Description"), - string::utf8(b"Token"), - option::none(), - string::utf8(b"https://mycollection/token.jpeg"), - ); - } -} -``` - -### Object Accounts - -In the Aptos Framework, multiple `key`-able resources can be stored at a single object account. - -However, objects should be isolated to different account, otherwise modifications to one object within an account can influence the entire collection. - -For example, transferring one resource implies the transfer of all group members, since the transfer function operates on `ObjectCore`, which is essentially a general tag for all resources at the account. - -[Read more](objects.mdx) about Aptos Objects. - -#### Example Insecure Code - -The `mint_two` function lets `sender` create a `Monkey` for themselves and send a `Toad` to `recipient` . - -As `Monkey` and `Toad` belong to the same object account the result is that both objects’ are now owned by the `recipient` . - -```move -module 0x42::example { - #[resource_group(scope = global)] - struct ObjectGroup { } - - #[resource_group_member(group = 0x42::example::ObjectGroup)] - struct Monkey has store, key { } - - #[resource_group_member(group = 0x42::example::ObjectGroup)] - struct Toad has store, key { } - - fun mint_two(sender: &signer, recipient: &signer) { - let constructor_ref = &object::create_object_from_account(sender); - let sender_object_signer = object::generate_signer(constructor_ref); - let sender_object_addr = object::address_from_constructor_ref(constructor_ref); - - move_to(sender_object_signer, Monkey{}); - move_to(sender_object_signer, Toad{}); - let monkey_object: Object = object::address_to_object(sender_object_addr); - object::transfer(sender, monkey_object, signer::address_of(recipient)); - } -} -``` - -#### Example Secure Code - -In this example, objects should be stored at separate object accounts: - -```move -module 0x42::example { - #[resource_group(scope = global)] - struct ObjectGroup { } - - #[resource_group_member(group = 0x42::example::ObjectGroup)] - struct Monkey has store, key { } - - #[resource_group_member(group = 0x42::example::ObjectGroup)] - struct Toad has store, key { } - - fun mint_two(sender: &signer, recipient: &signer) { - let sender_address = signer::address_of(sender); - - let constructor_ref_monkey = &object::create_object(sender_address); - let constructor_ref_toad = &object::create_object(sender_address); - let object_signer_monkey = object::generate_signer(&constructor_ref_monkey); - let object_signer_toad = object::generate_signer(&constructor_ref_toad); - - move_to(object_signer_monkey, Monkey{}); - move_to(object_signer_toad, Toad{}); - - let object_address_monkey = signer::address_of(&object_signer_monkey); - - let monkey_object: Object = object::address_to_object(object_address_monkey); - object::transfer(sender, monkey_object, signer::address_of(recipient)); - } -} -``` - -## Business logic - -### Front-running - -Front-running involves executing transactions ahead of others by exploiting knowledge of future actions already made by others. This tactic gives front-runners an unfair advantage, as they can anticipate and benefit from the outcomes of these pending transactions. - -Front-running can undermine the fairness and integrity of a decentralized application. It can lead to loss of funds, unfair advantages in games, manipulation of market prices, and a general loss of trust in the platform - -#### Example Insecure Code - -In a lottery scenario, users participate by selecting a number from 1 to 100. At a certain point, the game administrator invokes the function `set_winner_number` to set the winning number. Subsequently, in a separate transaction, the administrator reviews all player bets to determine the winner via `evaluate_bets_and_determine_winners`. - -A front-runner observing the winning number set by `set_winner_number` could attempt to submit a late bet or modify an existing bet to match the winning number before `evaluate_bets_and_determine_winners` executes. - -```move -module 0x42::example { - struct LotteryInfo { - winning_number: u8, - is_winner_set: bool, - } - - struct Bets { } - - public fun set_winning_number(admin: &signer, winning_number: u8) { - assert!(signer::address_of(admin) == @admin, 0); - assert!(winning_number < 10,0); - let lottery_info = LotteryInfo { winning_number, is_winner_set: true }; - move_to(admin, lottery_info); - } - - public fun evaluate_bets_and_determine_winners(admin: &signer) acquires LotteryInfo, Bets { - assert!(signer::address_of(admin) == @admin, 0); - let lottery_info = borrow_global(admin); - assert(lottery_info.is_winner_set, 1); - - let bets = borrow_global(admin); - let winners: vector
= vector::empty(); - - let winning_bets_option = smart_table::borrow_with_default(&bets.bets, lottery_info.winning_number, &vector::empty()); - - vector::iter(winning_bets_option, |bet| { - vector::push_back(&mut winners, bet.player); - }); - distribute_rewards(&winners); - } -} -``` - -#### Example Secure Code - -An effective strategy to avoid front-running could be implementing a `finalize_lottery` function that reveals the answer and concludes the game within a single transaction, and making the other functions private. This approach guarantees that as soon as the answer is disclosed, the system no longer accepts any new answers, thereby eliminating the chance for front-running. - -```move -module 0x42::example { - public fun finalize_lottery(admin: &signer, winning_number: u64) { - set_winner_number(admin, winning_number); - evaluate_bets_and_determine_winners(admin); - } - - fun set_winning_number(admin: &signer, winning_number: u64) { } - - fun evaluate_bets_and_determine_winners(admin: &signer) acquires LotteryInfo, Bets { } -} -``` - -### Price Oracle Manipulation - -In Defi applications, price oracles that utilize the liquidity ratio of tokens in a pair to determine prices for transactions can be vulnerable to manipulation. This susceptibility arises from the fact that the liquidity ratio can be influenced by market participants who hold a significant amount of tokens. When these participants strategically increase or decrease their token holdings, it can impact the liquidity ratio and consequently affect the prices determined by the price oracle, potentially draining the pool. - -We recommend to use multiple oracles to determine prices. - -#### Secure Code Example - -Thala, for example, utilizes a tiered-oracle design. The system has a primary and a secondary oracle. Should one of the oracles fail, the other one serves as a backup based on a sophisticated switching logic. The system is designed with adversarial situations in mind, and strives to provide highly accurate price feeds with minimal governance interaction all the time. - -For more in-depth information, refer to [Thala's documentation](https://docs.thala.fi/thala-protocol-design/move-dollar-mod/oracles). - -### Token Identifier Collision - -When dealing with tokens, ensure that the method for comparing token structs to establish a deterministic ordering does not lead to collisions. Concatenating the address, module, and struct names into a vector is insufficient, as it does not differentiate between similar names that should be treated as unique. - -As a consequence, the protocol may erroneously reject legitimate swap pairs due to collisions in token struct comparisons. This oversight could compromise the integrity of swap operations, leading to a loss of funds. - -#### Example Insecure Code - -The `get_pool_address` function creates a unique address for a liquidity pool associated with trading pairs of fungible assets. It generates and returns an address that serves as a distinct identifier for the liquidity pool of the specified two tokens. - -However, users have the freedom to create an `Object` with any symbol they choose. This flexibility could lead to the creation of `Object` instances that mimic other existing instances. This issue might result in a seed collision, which in turn could cause a collision in the generation of the pool address. - -```move -module 0x42::example { - public fun get_pool_address(token_1: Object, token_2: Object): address { - let token_symbol = string::utf8(b"LP-"); - string::append(&mut token_symbol, fungible_asset::symbol(token_1)); - string::append_utf8(&mut token_symbol, b"-"); - string::append(&mut token_symbol, fungible_asset::symbol(token_2)); - let seed = *string::bytes(&token_symbol); - object::create_object_address(&@swap, seed) - } -} -``` - -#### Example Secure Code - -`object::object_address` returns an unique identifier for each `Object` - -```move -module 0x42::example { - public fun get_pool_address(token_1: Object, token_2: Object): address { - let seeds = vector[]; - vector::append(&mut seeds, bcs::to_bytes(&object::object_address(&token_1))); - vector::append(&mut seeds, bcs::to_bytes(&object::object_address(&token_2))); - object::create_object_address(&@swap, seed) - } -} -``` - -## Operations - ---- - -### Pausing functionality - -Protocols should have the ability to pause operations effectively. For immutable protocols, a built-in pause functionality is necessary. Upgradable protocols can achieve pausing either through smart contract functionality or via protocol upgrades. Teams should be equipped with automation for the quick and efficient execution of this process. - -The absence of a pausing mechanism can lead to prolonged exposure to vulnerabilities, potentially resulting in significant losses. An efficient pausing functionality allows for prompt response to security threats, bugs, or other critical issues, minimizing the risk of exploitation and ensuring the safety of user assets and protocol integrity. - -#### Example Secure Code - -Example of how to integrate a pause functionality - -```move -module 0x42::example { - struct State { - is_paused: bool, - } - - public entry fun pause_protocol(admin: &signer) { - assert!(signer::address_of(admin)==@protocol_address, ERR_NOT_ADMIN); - let state = borrow_global_mut(@protocol_address); - state.is_paused = true; - } - - public entry fun resume_protocol(admin: &signer) { - assert!(signer::address_of(admin)==@protocol_address, ERR_NOT_ADMIN); - let state = borrow_global_mut(@protocol_address); - state.is_paused = false; - } - - public fun main(user: &signer) { - let state = borrow_global(@protocol_address); - assert!(!state.is_paused, 0); - // ... - } -} -``` - -### Smart contract publishing key management - -Using the same account for testnet and mainnet poses a security risk, as testnet private keys, often stored in less secure environments (ex. laptops), can be more easily exposed or leaked. An attacker that can obtain the private key for the testnet smart contract would be able to upgrade the mainnet one. - -## Randomness - -For more information on randomness and why it is crucial for preventing the predictability of random numbers, please refer to this page: [Randomness Guide](https://aptos.dev/guides/randomness/). - ---- - -### Randomness - test-and-abort - -> At Aptos, We are always security-first. During compilation, we ensure that no randomness API is invoked from a public function. However, we still allow users to make this choice by adding the attribute `#[lint::allow_unsafe_randomness]` to the public function. - -If a `public` function directly or indirectly invokes the randomness API, a malicious user can abuse the composability of this function and abort the transaction if the result is not as desired. This allows the user to keep trying until they achieve a beneficial outcome, undermining the randomness. - -#### Example Vulnerable code - -```move filename="randomness_example.move" -module user::lottery { - fun mint_to_user(user: &signer) { - move_to(user, WIN {}); - } - - #[lint::allow_unsafe_randomness] - public entry fun play(user: &signer) { - let random_value = aptos_framework::randomness::u64_range(0, 100); - if (random_value == 42) { - mint_to_user(user); - } - } -} -``` - -In this example, the `play` function is `public`, allowing it to be composed with other modules. A malicious user can invoke this function and then check if they have won. If they have not won, they can abort the transaction and try again. - -```move filename="randomness_example.move" -module attacker::exploit { - entry fun exploit(attacker: &signer) { - @user::lottery::play(attacker); - assert!(exists<@user::lottery::WIN>(address_of(attacker))); - } -} -``` - -To resolve the possible issue, is sufficient to set the visibility of all functions that invoke the randomness API, either directly or indirectly, to `entry` rather than `public` or `public entry`. - -#### Example Secure Code - -```move filename="randomness_example.move" -module user::lottery { - fun mint_to_user(user: &signer) { - move_to(user, WIN {}); - } - - #[lint::allow_unsafe_randomness] - entry fun play(user: &signer) { - let random_value = aptos_framework::randomness::u64_range(0, 100); - if (random_value == 42) { - mint_to_user(user); - } - } -} -``` - -### Randomness - undergasing - -When different code paths in a function consume different amounts of gas, an attacker can manipulate the gas limit to bias the outcome. Let's look at an example of how different paths can consume different amounts of gas. - -#### Example Vulnerable code - -```move filename="randomness_example.move" -module user::lottery { - - //transfer 10 aptos from admin to user - fun win(user: &signer) { - let admin_signer = &get_admin_signer(); - let aptos_metadata = get_aptos_metadata(); - primary_fungible_store::transfer(admin_signer, aptos_metadata, address_of(user),10); - } - - //transfer 10 aptos from user to admin, then 1 aptos from admin to fee_admin - fun lose(user: &signer) { - - //user to admin - let aptos_metadata = get_aptos_metadata(); - primary_fungible_store::transfer(user, aptos_metadata, @admin, 10); - - //admin to fee_admin - let admin_signer = &get_admin_signer(); - primary_fungible_store::transfer(admin_signer, aptos_metadata, @fee_admin, 1); - } - - #[randomness] - entry fun play(user: &signer) { - let random_value = aptos_framework::randomness::u64_range(0, 100); - if (random_value == 42) { - win(user); - } else { - lose(user); - } - } -} -``` - -In this lottery-example, `win` and `lose` consume different amounts of gas. -The `lose` function consumes more gas than the `win` function. An attacker can set the max gas limit that is sufficient for `win` but not for `lose`. This forces the transaction to abort when the `lose` path is taken, ensuring that the user will never execute the `lose` path. Then, the user can call the function repeatedly until they win. - -#### Example Secure Code - -There are different ways to secure the code: - -1. Ensure better outcomes use more or the same gas as worse outcomes. -2. Allow only admin addresses to invoke the randomness API. -3. Ensure entry functions work regardless of random outcomes. This can be handled by committing the random result, then using the random result to provide the action in a different transaction. Avoid immediate actions based on randomness for consistent gas use. - -> We will be providing more functionality in the future, to allow for more complex code to be able to be safe against undergasing attacks. diff --git a/apps/nextra/pages/zh/build/smart-contracts/object/_meta.tsx b/apps/nextra/pages/zh/build/smart-contracts/object/_meta.tsx index c492cbaad..e71dcf280 100644 --- a/apps/nextra/pages/zh/build/smart-contracts/object/_meta.tsx +++ b/apps/nextra/pages/zh/build/smart-contracts/object/_meta.tsx @@ -1,8 +1,10 @@ export default { "creating-objects": { title: "Creating Objects", + href: "/en/build/smart-contracts/object/creating-objects", }, "using-objects": { title: "Using Objects", + href: "/en/build/smart-contracts/object/using-objects", }, }; diff --git a/apps/nextra/pages/zh/build/smart-contracts/object/creating-objects.mdx b/apps/nextra/pages/zh/build/smart-contracts/object/creating-objects.mdx deleted file mode 100644 index 9ab5c97a7..000000000 --- a/apps/nextra/pages/zh/build/smart-contracts/object/creating-objects.mdx +++ /dev/null @@ -1,408 +0,0 @@ ---- -title: "Creating objects" ---- - -import { Callout } from 'nextra/components' - -# Creating and Configuring Objects - -Creating an Object involves two steps: - -1. Creating the `ObjectCore` resource group (which has an address you can use to refer to the Object later). -2. Customizing how the Object will behave using permissions called `Ref`s. - - - Configuring an Object by generating `Ref`s has to happen in the same - transaction you create it. Later on it is impossible to change those settings. - - -## Creating an Object - -There are three types of Object you can create: - -1. A **normal Object.** This type is deletable and has a random address. You can create it using: `0x1::object::create_object(owner_address: address)`. For example: -```move filename="object_playground.move" -module my_addr::object_playground { - use std::signer; - use aptos_framework::object; - - entry fun create_my_object(caller: &signer) { - let caller_address = signer::address_of(caller); - let constructor_ref = object::create_object(caller_address); - // ... - } -} -``` - -2. A **named Object.** This type is **not** deletable and has a deterministic address. You can create it by using: `0x1::object::create_named_object(creator: &signer, seed: vector)`. For example: -```move filename="object_playground.move" -module my_addr::object_playground { - use std::signer; - use aptos_framework::object; - - /// Seed for my named object, must be globally unique to the creating account - const NAME: vector = b"MyAwesomeObject"; - - entry fun create_my_object(caller: &signer) { - let caller_address = signer::address_of(caller); - let constructor_ref = object::create_named_object(caller, NAME); - // ... - } - - #[view] - fun has_object(creator: address): bool { - let object_address = object::create_object_address(&creator, NAME); - object::object_exists<0x1::object::ObjectCore>(object_address) - } -} -``` - -3. A **sticky Object.** This type is also **not** deletable and has a random address. You can create it by using `0x1::object::create_sticky_object(owner_address: address)`. For example: -```move filename="object_playground.move" -module my_addr::object_playground { - use std::signer; - use aptos_framework::object; - - entry fun create_my_object(caller: &signer) { - let caller_address = signer::address_of(caller); - let constructor_ref = object::create_sticky_object(caller_address); - // ... - } -} -``` - -## Customizing Object Features - -Once you create your object, you will receive a `ConstructorRef` you can use to generate additional `Ref`s. `Ref`s can be used in future to enable / disable / execute certain Object functions such as transferring resources, transferring the object itself, or deleting the Object. - -The following sections will walk through commonly used `Ref`s and the features they enable. - - - Note: The `ConstructorRef` cannot be stored. It is destroyed at the end of the - transaction used to create the Object, so any other `Ref`s **must** be - generated during Object creation. - - -### Adding Resources - -You can use the `ConstructorRef` with `object::generate_signer` to create a signer that allows you to transfer resources onto the Object. This uses `move_to`, the same function as for adding resources to an account. - -```move filename="Example.move" -module my_addr::object_playground { - use std::signer; - use aptos_framework::object; - - #[resource_group_member(group = aptos_framework::object::ObjectGroup)] - struct MyStruct has key { - num: u8 - } - - entry fun create_my_object(caller: &signer) { - let caller_address = signer::address_of(caller); - - // Creates the object - let constructor_ref = object::create_object(caller_address); - - // Retrieves a signer for the object - let object_signer = object::generate_signer(&constructor_ref); - - // Moves the MyStruct resource into the object - move_to(&object_signer, MyStruct { num: 0 }); - - // ... - } -} -``` - -### Adding Extensibility (`ExtendRef`) - -Sometimes you want an Object to be editable later on. In that case, you can generate an `ExtendRef` with `object::generate_extend_ref`. This ref can be used to generate a signer for the object. - -You can control who has permission to use the `ExtendRef` via smart contract logic like in the below example. - -```move filename="Example.move" -module my_addr::object_playground { - use std::signer; - use std::string::{Self, String}; - use aptos_framework::object::{Self, Object}; - - /// Caller is not the owner of the object - const E_NOT_OWNER: u64 = 1; - /// Caller is not the publisher of the contract - const E_NOT_PUBLISHER: u64 = 2; - - #[resource_group_member(group = aptos_framework::object::ObjectGroup)] - struct MyStruct has key { - num: u8 - } - - #[resource_group_member(group = aptos_framework::object::ObjectGroup)] - struct Message has key { - message: string::String - } - - #[resource_group_member(group = aptos_framework::object::ObjectGroup)] - struct ObjectController has key { - extend_ref: object::ExtendRef, - } - - entry fun create_my_object(caller: &signer) { - let caller_address = signer::address_of(caller); - - // Creates the object - let constructor_ref = object::create_object(caller_address); - - // Retrieves a signer for the object - let object_signer = object::generate_signer(&constructor_ref); - - // Moves the MyStruct resource into the object - move_to(&object_signer, MyStruct { num: 0 }); - - // Creates an extend ref, and moves it to the object - let extend_ref = object::generate_extend_ref(&constructor_ref); - move_to(&object_signer, ObjectController { extend_ref }); - // ... - } - - entry fun add_message( - caller: &signer, - object: Object, - message: String - ) acquires ObjectController { - let caller_address = signer::address_of(caller); - // There are a couple ways to go about permissions - - // Allow only the owner of the object - assert!(object::is_owner(object, caller_address), E_NOT_OWNER); - // Allow only the publisher of the contract - assert!(caller_address == @my_addr, E_NOT_PUBLISHER); - // Or any other permission scheme you can think of, the possibilities are endless! - - // Use the extend ref to get a signer - let object_address = object::object_address(&object); - let extend_ref = &borrow_global(object_address).extend_ref; - let object_signer = object::generate_signer_for_extending(extend_ref); - - // Extend the object to have a message - move_to(&object_signer, Message { message }); - } -} -``` - -### Disabling / Toggling Transfers (`TransferRef`) - -By default, all Objects are transferable. This can be changed via a `TransferRef` which you can generate with `object::generate_transfer_ref`. - -The example below shows how you could generate and manage permissions for determining whether an Object is transferrable. - -```move filename="Example.move" -module my_addr::object_playground { - use std::signer; - use aptos_framework::object::{Self, Object}; - - /// Caller is not the publisher of the contract - const E_NOT_PUBLISHER: u64 = 1; - - #[resource_group_member(group = aptos_framework::object::ObjectGroup)] - struct ObjectController has key { - transfer_ref: object::TransferRef, - } - - entry fun create_my_object( - caller: &signer, - transferrable: bool, - controllable: bool - ) { - let caller_address = signer::address_of(caller); - - // Creates the object - let constructor_ref = object::create_object(caller_address); - - // Retrieves a signer for the object - let object_signer = object::generate_signer(&constructor_ref); - - // Creates a transfer ref for controlling transfers - let transfer_ref = object::generate_transfer_ref(&constructor_ref); - - // We now have a choice, we can make it so the object can be transferred - // and we can decide if we want to allow it to change later. By default, it - // is transferrable - if (!transferrable) { - object::disable_ungated_transfer(&transfer_ref); - }; - - // If we want it to be controllable, we must store the transfer ref for later - if (controllable) { - move_to(&object_signer, ObjectController { transfer_ref }); - } - // ... - } - - /// In this example, we'll only let the publisher of the contract change the - /// permissions of transferring - entry fun toggle_transfer( - caller: &signer, - object: Object - ) acquires ObjectController { - // Only let the publisher toggle transfers - let caller_address = signer::address_of(caller); - assert!(caller_address == @my_addr, E_NOT_PUBLISHER); - - // Retrieve the transfer ref - let object_address = object::object_address(&object); - let transfer_ref = &borrow_global( - object_address - ).transfer_ref; - - // Toggle it based on its current state - if (object::ungated_transfer_allowed(object)) { - object::disable_ungated_transfer(transfer_ref); - } else { - object::enable_ungated_transfer(transfer_ref); - } - } -} -``` - -### One-Time Transfers (`LinearTransferRef`) - -Additionally, if the creator wants to control all transfers, a `LinearTransferRef` can be created from the `TransferRef` to provide a one time use transfer functionality. This can be used to create “soulbound” objects by having a one-time transfer from the Object creator to the recipient. The `LinearTransferRef` must be used by the owner of the Object. - -```move filename="Example.move" -module my_addr::object_playground { - use std::signer; - use aptos_framework::object::{Self, Object}; - - /// Caller is not the publisher of the contract - const E_NOT_PUBLISHER: u64 = 1; - - #[resource_group_member(group = aptos_framework::object::ObjectGroup)] - struct ObjectController has key { - transfer_ref: object::TransferRef, - } - - entry fun create_my_object( - caller: &signer, - ) { - let caller_address = signer::address_of(caller); - - // Creates the object - let constructor_ref = object::create_object(caller_address); - - // Retrieves a signer for the object - let object_signer = object::generate_signer(&constructor_ref); - - // Creates a transfer ref for controlling transfers - let transfer_ref = object::generate_transfer_ref(&constructor_ref); - - // Disable ungated transfer - object::disable_ungated_transfer(&transfer_ref); - move_to(&object_signer, ObjectController { - transfer_ref, - }); - // ... - } - - /// In this example, we'll only let the publisher of the contract change the - /// permissions of transferring - /// Now only owner can transfer exactly once - entry fun transfer( - caller: &signer, - object: Object, - new_owner: address - ) acquires ObjectController { - // Only let the publisher toggle transfers - let caller_address = signer::address_of(caller); - assert!(caller_address == @my_addr, E_NOT_PUBLISHER); - - let object_address = object::object_address(&object); - - // Retrieve the transfer ref - let transfer_ref = &borrow_global( - object_address - ).transfer_ref; - - // Generate a one time use `LinearTransferRef` - let linear_transfer_ref = object::generate_linear_transfer_ref( - transfer_ref - ); - - object::transfer_with_ref(linear_transfer_ref, new_owner); - } -} -``` - -## Allowing Deletion of an Object (`DeleteRef`) - -For Objects created with the default method (allowing deletion) you can generate a `DeleteRef` which can be used later. This can help remove clutter as well as receive a storage refund. - -You cannot create a `DeleteRef` for a non-deletable Object. - -```move filename="Example.move" -module my_addr::object_playground { - use std::signer; - use aptos_framework::object::{Self, Object}; - - /// Caller is not the owner of the object - const E_NOT_OWNER: u64 = 1; - - #[resource_group_member(group = aptos_framework::object::ObjectGroup)] - struct ObjectController has key { - delete_ref: object::DeleteRef, - } - - entry fun create_my_object( - caller: &signer, - _transferrable: bool, - _controllable: bool - ) { - let caller_address = signer::address_of(caller); - - // Creates the object - let constructor_ref = object::create_object(caller_address); - - // Retrieves a signer for the object - let object_signer = object::generate_signer(&constructor_ref); - - // Creates and store the delete ref - let delete_ref = object::generate_delete_ref(&constructor_ref); - move_to(&object_signer, ObjectController { - delete_ref - }); - // ... - } - - /// Now only let the owner delete the object - entry fun delete( - caller: &signer, - object: Object, - ) acquires ObjectController { - // Only let caller delete - let caller_address = signer::address_of(caller); - assert!(object::is_owner(object, caller_address), E_NOT_OWNER); - - let object_address = object::object_address(&object); - - // Retrieve the delete ref, it is consumed so it must be extracted - // from the resource - let ObjectController { - delete_ref - } = move_from( - object_address - ); - - // Delete the object forever! - object::delete(delete_ref); - } -} -``` - -## Making an Object Immutable - -An object can be made immutable by making the contract associated immutable, and removing any ability to extend or mutate the object. By default, contracts are not immutable, and objects can be extended with an `ExtendRef`, and resources can be mutated if the contract allows for it. - -## Further Reading - -You can find documentation for all possible `Refs` by looking at the Move reference docs for `0x1::object` [here](https://aptos.dev/reference/move?branch=mainnet&page=aptos-framework/doc/object.md). - -You can also explore how to use Objects once they are constructed [here](using-objects.mdx). diff --git a/apps/nextra/pages/zh/build/smart-contracts/object/using-objects.mdx b/apps/nextra/pages/zh/build/smart-contracts/object/using-objects.mdx deleted file mode 100644 index 976f0c927..000000000 --- a/apps/nextra/pages/zh/build/smart-contracts/object/using-objects.mdx +++ /dev/null @@ -1,285 +0,0 @@ ---- -title: "Using objects" ---- - -import { Callout } from 'nextra/components' - -# Using Objects - -Once you've created your Object, you can use it in Move entry functions, structs, transfer it, and modify it using any refs you generated during [Object construction](creating-objects.mdx). Below are various ways to utilize, manage, and interact with Objects in Move. - -## Using an Object as an entry function argument - -Objects in move functions have the type `Object`, where `T` is the type of a resource owned by the Object. All Objects have an `ObjectCore` type which contains the metadata for the Object. - -To use an Object parameter, users can pass in the Object address or a reference to the Object. At runtime the contract will verify that the Object exists at that address, and has a resource of type T before executing the function. - -```move filename="Example.move" -module my_addr::object_playground { - use aptos_framework::object::{Object, ObjectCore}; - - struct MyAwesomeStruct has key {} - - /// This will fail if the object doesn't have MyAwesomeStruct stored - entry fun do_something(object: Object) { - // ... - } - - /// All Objects have ObjectCore, so this will only fail if the - /// address is not an object - entry fun do_something_to_object_core(object: Object) { - // ... - } -} -``` - -To let the user of the entry function specify the type of resource, you can keep the generic type `T` like so: - -```move filename="Example.move" -module my_addr::object_playground { - use aptos_framework::object::Object; - - /// This will fail if the object doesn't have the generic `T` stored - entry fun do_something(object: Object) { - // ... - } -} -``` - -### Object types - -You can refer to an Object by any type of resource that is owned by the Object. For convenience, you can convert an address to an Object, or convert an Object between types as long as the resources are available using `address_to_object` and `convert` like so: - -```move filename="Example.move" -module my_addr::object_playground { - use aptos_framework::object::{Self, Object, ObjectCore}; - - struct MyAwesomeStruct has key {} - - fun convert_type(object: Object): Object { - object::convert(object) - } - - fun address_to_type(object_address: address): Object { - object::address_to_object(object_address) - } -} -``` - - -Objects can be owned by any address, including Objects, Accounts, and Resource accounts. This allows composability between objects and complex relationships between them. - - -## Using an Object as type of a field in struct - -Objects can help represent complicated types by using them in structs. For example, - -```move filename="Example.move" -module my_addr::object_playground { - use aptos_framework::object::{Self, Object}; - use aptos_framework::fungible_asset::Metadata; - use aptos_framework::primary_fungible_store; - use std::signer; - use std::option; - use std::string::utf8; - - struct MyStruct has key { - fungible_asset_object: Object - } - - entry fun create_fungible_asset(creator: &signer) { - let fa_obj_constructor_ref = &object::create_sticky_object(@my_addr); - let fa_obj_signer = object::generate_signer(fa_obj_constructor_ref); - let fa_obj_addr = signer::address_of(&fa_obj_signer); - primary_fungible_store::create_primary_store_enabled_fungible_asset( - fa_obj_constructor_ref, - option::none(), - utf8(b"Asset name"), - utf8(b"Asset symbol"), - 2, - utf8(b"Icon uri"), - utf8(b"Project uri") - ); - move_to(creator, MyStruct { - fungible_asset_object: object::address_to_object(fa_obj_addr) - }); - } -} -``` - -## Looking up who owns an Object - -When writing contracts for Objects, it is often important to verify ownership before modifying the Object. Because an Object can be owned by any address, verifying ownership needs to account for whether the owner is an Account, a Resource Account or another Object like so: - -```move filename="Example.move" -module my_addr::object_playground { - use std::signer; - use aptos_framework::object::{Self, Object}; - - // Not authorized! - const E_NOT_AUTHORIZED: u64 = 1; - - fun check_owner_is_caller(caller: &signer, object: Object) { - assert!( - object::is_owner(object, signer::address_of(caller)), - E_NOT_AUTHORIZED - ); - } - - fun check_is_owner_of_object(addr: address, object: Object) { - assert!(object::owner(object) == addr, E_NOT_AUTHORIZED); - } - - fun check_is_nested_owner_of_object( - caller: &signer, - outside_object: Object, - inside_object: Object - ) { - // Ownership expected - // Caller account -> Outside object -> inside object - - // Check outside object owns inside object - let outside_address = object::object_address(&outside_object); - assert!(object::owns(inside_object, outside_address), E_NOT_AUTHORIZED); - - // Check that the caller owns the outside object - let caller_address = signer::address_of(caller); - assert!(object::owns(outside_object, caller_address), E_NOT_AUTHORIZED); - - // Check that the caller owns the inside object (via the outside object) - // This can skip the first two calls (and even more nested) - assert!(object::owns(inside_object, caller_address), E_NOT_AUTHORIZED); - } -} -``` - -## Transfer of ownership - -By default, all Objects are transferrable. Some Objects are configured to disable `ungated_transfer`s when they are constructed (see Constructing Objects for more details). - -You can transfer an Object like so: - -```move filename="Example.move" -module my_addr::object_playground { - use aptos_framework::object::{Self, Object}; - - /// Transfer to another address, this can be an object or account - fun transfer(owner: &signer, object: Object, destination: address) { - object::transfer(owner, object, destination); - } - - /// Transfer to another object - fun transfer_to_object( - owner: &signer, - object: Object, - destination: Object - ) { - object::transfer_to_object(owner, object, destination); - } -} -``` - - -If `ungated_transfer` is **disabled**, then all transfers need to use a special permission given by a `TransferRef` or `LinearTransferRef`. - - -## Events -By default, Objects only have a `TransferEvent` which triggers whenever the Object is transferred. - -Objects can be extended to have additional events. - -You can use the following functions to create event handles for Objects: - -```move filename="example.move" -module 0x1::object { - /// Create a guid for the object, typically used for events - public fun create_guid(object: &signer): guid::GUID {} - - /// Generate a new event handle. - public fun new_event_handle(object: &signer): event::EventHandle {} -} -``` - -Generated event handles can be transferred to the Object as long as you have the Object's `SignerRef`. For example: - -```move filename="example.move" -module 0x42::example { - use aptos_framework::event; - use aptos_framework::fungible_asset::Metadata; - use aptos_framework::object::{Self, Object}; - - #[resource_group_member(group = aptos_framework::object::ObjectGroup)] - struct LiquidityPoolResourceGroup has key { - pool: LiquidityPool, - event_store: LiquidityPoolEventStore, - } - - struct LiquidityPool has store { - metadata_token_a: Object, - metadata_token_b: Object, - reserves_a: u128, - reserves_b: u128, - } - - struct LiquidityPoolEventStore has store { - create_events: event::EventHandle, - } - - #[event] - struct CreateLiquidityPoolEvent has store, drop { - token_a: address, - token_b: address, - reserves_a: u128, - reserves_b: u128, - } - - public entry fun create_liquidity_pool_with_events( - account_signer: &signer, - metadata_token_a: Object, - metadata_token_b: Object, - reserves_a: u128, - reserves_b: u128 - ) { - let liquidity_pool_constructor_ref = &object::create_object_from_account( - account_signer - ); - let liquidity_pool_signer = &object::generate_signer( - liquidity_pool_constructor_ref - ); - let event_handle = object::new_event_handle( - liquidity_pool_signer - ); - - event::emit_event(&mut event_handle, CreateLiquidityPoolEvent { - token_a: object::object_address(&metadata_token_a), - token_b: object::object_address(&metadata_token_b), - reserves_a, - reserves_b, - }); - - move_to(liquidity_pool_signer, LiquidityPoolResourceGroup { - pool: LiquidityPool { - metadata_token_a, - metadata_token_b, - reserves_a, - reserves_b - }, - event_store: LiquidityPoolEventStore { - create_events: event_handle - } - }); - } -} -``` - -## Modifying Objects after creation - -In general, Objects can only be modified with `Refs` generated during construction. See [Creating and Configuring Objects](creating-objects.mdx) for more details on what `Refs` are available, how to generate them, and how to use them. This is how you add additional resources to an Object, delete it, and extend it. - -## Example contracts - -Here are three real-world code snippets which use Objects: - -- [Digital Asset Marketplace Example](https://github.com/aptos-labs/aptos-core/tree/main/aptos-move/move-examples/marketplace) -- [Digital Assets Examples](https://github.com/aptos-labs/aptos-core/tree/main/aptos-move/move-examples/token_objects) -- [Fungible Asset Examples](https://github.com/aptos-labs/aptos-core/tree/main/aptos-move/move-examples/fungible_asset) diff --git a/apps/nextra/pages/zh/build/smart-contracts/objects.mdx b/apps/nextra/pages/zh/build/smart-contracts/objects.mdx deleted file mode 100644 index b2e72574b..000000000 --- a/apps/nextra/pages/zh/build/smart-contracts/objects.mdx +++ /dev/null @@ -1,62 +0,0 @@ ---- -title: "Building with Objects" ---- - -# Move Objects -In Move, Objects group resources together so they can be treated as a single entity on chain. - -Objects have their own address and can own resources similar to an account. They are useful for representing more complicated data types on-chain as Objects can be used in entry functions directly, and can be transferred as complete packages instead of one resource at a time. - -Here's an example of creating an Object and transferring it: - -```move filename="Example.move" -module my_addr::object_playground { - use std::signer; - use std::string::{Self, String}; - use aptos_framework::object::{Self, ObjectCore}; - - struct MyStruct1 has key { - message: String, - } - - struct MyStruct2 has key { - message: String, - } - - entry fun create_and_transfer(caller: &signer, destination: address) { - // Create object - let caller_address = signer::address_of(caller); - let constructor_ref = object::create_object(caller_address); - let object_signer = object::generate_signer(&constructor_ref); - - // Set up the object by creating 2 resources in it - move_to(&object_signer, MyStruct1 { - message: string::utf8(b"hello") - }); - move_to(&object_signer, MyStruct2 { - message: string::utf8(b"world") - }); - - // Transfer to destination - let object = object::object_from_constructor_ref( - &constructor_ref - ); - object::transfer(caller, object, destination); - } -} -``` - -During construction, Objects can be configured to be transferrable and extensible. - -For example, you could use an Object to represent a soulbound NFT by making it only transferrable once, and have it own resources for an image link and metadata. Objects can also own other Objects, so you could implement your own NFT collection Object by transferring several of the soulbound NFTs to it. - -## Learn how to - -- [Create and configure a new Object.](object/creating-objects.mdx) -- [Use Objects you created.](object/using-objects.mdx) - -## Examples with Object contracts - -- [Digital Asset Marketplace Example](https://github.com/aptos-labs/aptos-core/tree/main/aptos-move/move-examples/marketplace) -- [Digital Assets Examples](https://github.com/aptos-labs/aptos-core/tree/main/aptos-move/move-examples/token_objects) -- [Fungible Asset Examples](https://github.com/aptos-labs/aptos-core/tree/main/aptos-move/move-examples/fungible_asset) diff --git a/apps/nextra/pages/zh/build/smart-contracts/prover.mdx b/apps/nextra/pages/zh/build/smart-contracts/prover.mdx deleted file mode 100644 index 1c5a7769f..000000000 --- a/apps/nextra/pages/zh/build/smart-contracts/prover.mdx +++ /dev/null @@ -1,25 +0,0 @@ ---- -title: "Overview" ---- - -# Move Prover - -The Move Prover supports formal [specification](prover/spec-lang.mdx) and -[verification](prover/prover-guide.mdx) of Move code. The Move Prover can -automatically validate logical properties of Move smart contracts while offering -a user experience similar to a type checker or linter. - -The Move Prover exists to make contracts more _trustworthy_; it: - -- Protects massive assets managed by the Aptos blockchain from smart contract bugs -- Protects against well-resourced adversaries -- Anticipates justified regulator scrutiny and compliance requirements -- Allows domain experts with a mathematical background, but not necessarily a -software engineering background, to understand what smart contracts do - -For more information, refer to the documentation: - -- [Installation](../cli/setup-cli/install-move-prover.mdx) -- [Move Prover User Guide](prover/prover-guide.mdx) -- [Move Specification Language](prover/spec-lang.mdx) -- [Move Prover Supporting Resources](prover/supporting-resources.mdx) diff --git a/apps/nextra/pages/zh/build/smart-contracts/prover/_meta.tsx b/apps/nextra/pages/zh/build/smart-contracts/prover/_meta.tsx index 518acf802..825098fab 100644 --- a/apps/nextra/pages/zh/build/smart-contracts/prover/_meta.tsx +++ b/apps/nextra/pages/zh/build/smart-contracts/prover/_meta.tsx @@ -1,11 +1,14 @@ export default { "prover-guide": { title: "Move Prover User Guide", + href: "/en/build/smart-contracts/prover/prover-guide", }, "supporting-resources": { title: "Move Prover Supporting Resources", + href: "/en/build/smart-contracts/prover/suporting-resources", }, "spec-lang": { title: "Move Specification Language", + href: "/en/build/smart-contracts/prover/spec-lang", }, }; diff --git a/apps/nextra/pages/zh/build/smart-contracts/prover/prover-guide.mdx b/apps/nextra/pages/zh/build/smart-contracts/prover/prover-guide.mdx deleted file mode 100644 index 876bcc5da..000000000 --- a/apps/nextra/pages/zh/build/smart-contracts/prover/prover-guide.mdx +++ /dev/null @@ -1,243 +0,0 @@ ---- -title: "Move Prover User Guide" ---- - -# Move Prover User Guide - -This is the user guide for the Move Prover. This document accompanies the -[Move specification language](spec-lang.mdx). See the sections below for details. - -## Running the Move Prover - -The Move Prover is invoked via the [Aptos CLI](../../cli.mdx). In order -to call the CLI, you must have a [_Move package_](../book/packages.mdx) in place. -In the simplest case, a Move package is defined by a directory with a set of -`.move` files in it and a manifest of the name `Move.toml`. You can create a new -Move package at a given location by running the command: -`aptos move init --name ` - -Once the package exists, call the Move Prover from the directory to be tested or -by supplying its path to the `--package-dir` argument: - -- Prove the sources of the package in the current directory: - ```bash filename="Terminal" - aptos move prove - ``` -- Prove the sources of the package in a specific directly: - ```bash filename="Terminal" - aptos move prove --package-dir - ``` -See example output and other available options in the -[Proving Move](../../cli/working-with-move-contracts.mdx#6-optional-formally-verifying-move-scripts) -section of Use Aptos CLI. - -### Target filtering - -By default, the `aptos move prove` command verifies all files of a package. -During iterative development of larger packages, it is often more effective to -focus verification on particular files with the `-f` (`--filter`) option, like so: - -```bash filename="Terminal" -aptos move prove -f coin -``` - -In general, if the string provided to the `-f` option is contained somewhere in -the file name of a source, that source will be included for verification. - -> NOTE: the Move Prover ensures there is no semantic difference between -> verifying modules one-by-one or all at once. However, if your goal is to -> verify all modules, verifying them in a single `aptos move prove` run will be -> significantly faster than sequentially. - -### Prover options - -The Move Prover has a number of options (such as the filter option above) that -you pass with an invocation of: `aptos move prove `. The most commonly -used option is the `-t` (`--trace`) option that causes the Move Prover to -produce richer diagnosis when it encounters errors: - -```bash filename="Terminal" -aptos move prove -f coin -t -``` - -To see the list of all command line options, run: `aptos move prove --help` - -### Prover configuration file - -You can also create a Move Prover configuration file named `Prover.toml` that -lives side-by-side with the `Move.toml` manifest file in the root of the package -directory. For example, to enable tracing by default for a package, add a -`Prover.toml` file with the following configuration: - -```toml -[prover] -auto_trace_level = "VerifiedFunction" -``` - -Find the most commonly used options in the example `.toml` below, which you can -cut and paste and adopt for your needs (adjusting the defaults shown in the -displayed values as needed): - -```toml -# Verbosity level -# Possible values: "ERROR", "WARN", "INFO", "DEBUG". Each level subsumes the output of the previous one. -verbosity_level = "INFO" - -[prover] -# Set auto-tracing level, which enhances the diagnosis the Move Prover produces on verification errors. -# Possible values: "Off", "VerifiedFunction", "AllFunctions" -auto_trace_level = "Off" - -# Minimal severity level for diagnosis to be reported. -# Possible values: "Error", "Warning", "Note" -report_severity = "Warning" - -[backend] -# Timeout in seconds for the solver backend. Note that this is a soft timeout and may not always -# be respected. -vc_timeout = 40 - -# Random seed for the solver backend. Different seeds can result in different verification run times, -# as the solver uses heuristics. -random_seed = 1 - -# The number of processor cores to assume for concurrent check of verification conditions. -proc_cores = 4 -``` - -> HINT: For local verification, you may want to set `proc_cores` to an -> aggressive number (your actual cores) to speed up the turnaround cycle. - -## Prover diagnosis - -When the Move Prover finds a verification error, it prints diagnosis to standard -output in a style similar to a compiler or a debugger. We explain the different -types of diagnoses below, based on the following evolving example: - -```move -module 0x42::m { - struct Counter has key { - value: u8, - } - - public fun increment(a: address) acquires Counter { - let r = borrow_global_mut(a); - r.value = r.value + 1; - } - - spec increment { - aborts_if !exists(a); - ensures global(a).value == old(global(a)).value + 1; - } -} -``` - -We will modify this example as we demonstrate different types of diagnoses. - -### Unexpected abort - -If we run the Move Prover on the example immediately above, we get the following -error: - -```bash filename="Terminal" -error: abort not covered by any of the `aborts_if` clauses - ┌─ m.move:11:5 - │ - 8 │ r.value = r.value + 1; - │ - abort happened here with execution failure - · -11 │ ╭ spec increment { -12 │ │ aborts_if !exists(a); -13 │ │ ensures global(a).value == old(global(a)).value + 1; -14 │ │ } - │ ╰─────^ - │ - = at m.move:6: increment - = a = 0x29 - = at m.move:7: increment - = r = &mmm.Counter{value = 255u8} - = at m.move:8: increment - = ABORTED - -{ - "Error": "Move Prover failed: exiting with verification errors" -} -``` - -The Move Prover has generated an example counter that leads to an overflow when -adding 1 to the value of 255 for an `u8`. This overflow occurs if the function -specification calls for abort behavior, but the condition under which the -function is aborting is not covered by the specification. And in fact, with -`aborts_if !exists(a)`, we only cover the abort caused by the absence -of the resource, but not the abort caused by the arithmetic overflow. - -Let's fix the above and add the following condition: - -```move -module 0x42::m { - spec increment { - aborts_if global(a).value == 255; - // ... - } -} -``` - -With this, the Move Prover will succeed without any errors. - -### Post-condition failure - -Let us inject an error into the `ensures` condition of the above example: - -```move -module 0x42::m { - spec increment { - ensures global(a).value == /*old*/(global(a).value) + 1; - } -} -``` - -With this, the Move Prover will produce the following diagnosis: - -```bash filename="Terminal" -error: post-condition does not hold - ┌─ m.move:14:9 - │ -14 │ ensures global(a).value == /*old*/(global(a).value) + 1; - │ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - │ - = at m.move:6: increment - = a = 0x29 - = at m.move:7: increment - = r = &mmm.Counter{value = 0u8} - = at m.move:8: increment - = at m.move:9: increment - = at m.move:12: increment (spec) - = at m.move:15: increment (spec) - = at m.move:13: increment (spec) - = at m.move:14: increment (spec) - -{ - "Error": "Move Prover failed: exiting with verification errors" -} -``` - -While we know what the error is (as we just injected it), this is not -particularly obvious in the output This is because we don't directly see on -which values the `ensures` condition was actually evaluated. To see this, use -the `-t` (`--trace`) option; this is not enabled by default because it makes the -verification problem slightly harder for the solver. - -Instead or in addition to the `--trace` option, you can use the built-in -function `TRACE(exp)` in conditions to explicitly mark expressions whose values -should be printed on verification failures. - -> NOTE: Expressions that depend on quantified symbols cannot be traced. Also, -> expressions appearing in specification functions can not currently be traced. - -## Debugging the Move Prover - -The Move Prover is an evolving tool with bugs and deficiencies. Sometimes it -might be necessary to debug a problem based on the output the Move Prover passes -to the underlying backends. If you pass the option `--dump`, the Move Prover -will output the original Move bytecode, as well as the Move Prover bytecode, -as the former is transformed during compilation. diff --git a/apps/nextra/pages/zh/build/smart-contracts/prover/spec-lang.mdx b/apps/nextra/pages/zh/build/smart-contracts/prover/spec-lang.mdx deleted file mode 100644 index 9553cf10e..000000000 --- a/apps/nextra/pages/zh/build/smart-contracts/prover/spec-lang.mdx +++ /dev/null @@ -1,1351 +0,0 @@ ---- -title: "Move Specification Language" ---- - -# Move Specification Language - -This document describes the _Move specification language (MSL)_, a subset of the -[Move](../../smart-contracts.mdx) language that supports specification of the -behavior of Move programs. MSL works together with the [Move Prover](../prover.mdx), -a tool that can statically verify the correctness of MSL specifications against -Move programs. In contrast to traditional testing, verification of MSL is -exhaustive and holds for all possible inputs and global states of a -[Move module](../../../network/glossary.mdx#move-module) or -[Move script](../../../network/glossary.mdx#move-script). At the -same time, this verification of MSL is fast and automated enough that it can be -used at a similar place in the developer workflow where tests are typically -conducted (for example, for qualification of pull requests in continuous -integration). - -While the Move programming language at this point is stable, the subset -represented by MSL should be considered evolving. This has no impact on platform -stability, since MSL is not running in production; yet MSL is used for offline -quality assurance where it is continuously improved for evolving objectives. - -This document describes the language only; see [Use the Move Prover](prover-guide.mdx) -for instructions. The reader is expected to have basic knowledge of the Move -language, as well as basic principles of pre- and post-condition specifications. -(See for example the [Design by contract](https://en.wikipedia.org/wiki/Design_by_contract)). -For examples of specifications, we refer to the [Aptos framework](https://github.com/aptos-labs/aptos-core/blob/main/aptos-move/framework/aptos-framework/doc/overview.md) -documentation, which has specifications embedded. - -# Expressions - -Expressions in MSL are a subset of Move program expressions plus a set of -additional constructs, as discussed in the following sections. - -## Type System - -The type system of MSL is similar to that of Move, with the following differences: - -- There are two types of encodings for integer types: `num` and `bv` (bit vector). - If an integer (either a constant or a variable) is not involved in any bitwise operations directly or indirectly, - regardless of its type in Move (`u8`, `u16`, `u32`, `u64`, `u128` and `u256`), it is treated as the same type. In - specifications, this type is called `num`, which is an arbitrary precision _signed_ integer type. - When MSL refers to a Move name that represents an `u8` or such, it will be automatically widened - to `num`. This allows writing MSL expressions like `x + 1 <= MAX_U128` or `x - y >= 0` without - needing to worry about overflow or underflow. - Different from `num`, `bv` cannot and does not need to be explicitly used in specifications: if an integer is involved in bitwise operations such as `&`, `|` or `^`, it will be automatically encoded as `bv`at the backend. - Moreover, a `bv` integer has a fixed precision, which is consistent with its precision in Move (`bv8`, `bv16`, `bv32`, `bv64`, `bv128` and `bv256`). - Note that, in general using `bv` is not so efficient as `num` in the [SMT](https://en.wikipedia.org/wiki/Satisfiability_modulo_theories) solver such as [Z3](https://github.com/Z3Prover/z3). Consequently, - the Move Prover has some restrictions when using bitwise operations, which are stated in detail below. -- The Move types `&T`, `&mut T`, and `T` are considered equivalent for MSL. Equality is interpreted - as value equality. There is no need to worry about dereferencing a reference from the Move - program: these are automatically dereferenced as needed. This simplification is possible because - MSL cannot modify values from a Move program, and the program cannot directly reason about - reference equality (which eliminates the need for doing so in MSL). (Note there is also a - restriction in expressiveness coming with this, namely - for [functions which return `&mut T`](#expressiveness). However, this is rarely hit in practice, - and there are workarounds.) -- There is the additional type `type`, which is the type of all types. It can be used only in - quantifiers. -- There is the additional type `range`, which represents an integer range (and the notation `n..m` to - denote a value). - -## Naming - -Name resolution in MSL works similar to the Move language. `use` declarations can introduce aliases for -imported names. MSL functions and variable names must start with a lowercase letter. Schema names -are treated like types and must start with a capital letter. ([Schemas](#schemas) are a named construct -discussed later). - -Move functions, MSL functions, Move types, and schemas all share the same namespace and are -therefore unambiguous if aliased via a Move `use` clause. Because of the common name space, an MSL -function cannot have the same name as a Move function. This is often handled via the convention to -prefix MSL functions as in `spec_has_access` when the related Move function is called `has_access`. - -## Operators - -All Move operators are supported in MSL, except `&`, `&mut`, and `*` (dereference). - -In addition to the existing operators, vector subscript `v[i]`, slicing `v[i..j]`, and range -construction `i..j` are supported (the type of integer ranges is a new builtin type called `range`). Moreover, boolean implication `p ==> q` is supported as a more intuitive form than `!p || q`. - -## Function calls - -In MSL expressions, functions can be called like in Move. However, the callee must either be -an [MSL helper function](#helper-functions) or a **pure** Move function. - -Move functions are considered pure if they do not modify global state and do not use Move expression -features that are not supported in MSL expressions (as defined in this document). - -There is one extension. If a Move function definition contains a direct `assert`, this will be -ignored when it is called from an MSL expression, and the function will be considered pure. For -example: - -```move -module 0x42::m { - fun get(addr: address): &T { - assert(exists(addr), ERROR_CODE); - borrow_global(addr) - } -} -``` - -This function is pure and can be called from an MSL expression. The assertion will be ignored, and -the function will be interpreted as: - -```move -module 0x42::m { - spec fun get(addr: address): T { global(addr) } -} -``` - -This is justified by that MSL having [_partial semantics_](#partial-semantics). - -## Statements - -Limited sequencing of the form `{ let x = foo(); x + x }` is supported, as well as if-then-else. -Other statement forms of the Move language are not supported. - -## Pack and unpack - -Pack expressions are supported. Unpack expressions are currently _not_ supported. - -## Quantifiers - -Universal and existential quantification is supported. The general form is: - -```move -forall , ..., [ where ] : -exists , ..., [ where ] : -``` - -- Bindings can either be of the form `name: ` or `name in `. For the second form, the - expression must either be a `range` or a vector. -- The optional constraint `where ` allows to restrict the quantified range. `forall x: T where p: q` - is equivalent to `forall x: T : p ==> q` and `exists x: T where p: q` is equivalent to `exists x: T : p && q`. - -## Choice operator - -The choice operator allows selecting a value that satisfies a predicate: - -```move -choose a: address where exists(a) && global(a).value > 0 -``` - -If the predicate is not satisfiable, the result of the choice will be undetermined. (See [partial semantics](#partial-semantics)). - -The choice also comes in a form to select the _minimal_ value from a set of integers, as in: - -```move -choose min i: num where in_range(v, i) && v[i] == 2 -``` - -## Cast operator - -In the specification language, we can use the same syntax `(e as T)` to cast an expression `e` with one integer type to `T`, an integer type of another size. - -## Shift operator - -Shift operators `<<` and `>>` are supported in the specification language, and both of them have the same semantics with the Move language. As for abort, if a value `v` has width `n`, then `v << m` or `v >> m` will abort if `m >= n`. - -## Bitwise operators - -Move programs using bitwise operators `&`, `|` and `^` can be verified in the prover, and these operators are also supported in the specification language. -Due to encoding and efficiency issues, using bitwise operators has more caveats: - -- Integers involved in bitwise operations are encoded as `bv` types at the backend, and two encodings of integers are not compatible. For instance, if a variable `v` is involved in a bitwise operation such as `v & 2` or `v = a ^ b`, then when it is used in an arithmetic operation `v * w` or a shift operation `v << w`, `w` will be implicitly cast to a `bv` type in the Move program. - However, the specification language does not support implicit type cast so users must explicitly use the built-in function `int2bv` in the specification: `v << int2bv(w)`. - Not that since each `bv` type has a fixed length (from 8 to 256), values with type `num` cannot be converted into `bv`. - -- Verification of `bv` types is not efficient and may lead to timeout. As a result, users may prefer isolating bitwise operations from other operations and not using `int2bv` if possible. Moreover, users need to use pragmas to explicitly specify which integer-typed function arguments or struct fields will be used in bitwise computations: - -```move -module 0x42::m { - struct C has drop { - a: u64, - b: u64 - } - spec C { - // b, the second field of C, will be of bv type - pragma bv = b"1"; - } - public fun foo_generic(i: T): T { - i - } - - spec foo_generic { - // The first parameter will be of bv type if T is instantiated as a number type - pragma bv = b"0"; - // The first return value will be of bv type if T is instantiated as a number type - pragma bv_ret = b"0"; - } - - public fun test(i: C): u64 { - let x1 = foo_generic(C.b); - x1 ^ x1 - } - - spec test { - // Explicit type cast is mandatory for generating correct boogie program - ensures result == (0 as u64); - } -} -``` - -Note that if arguments or fields of a generic function or struct are specified with `bv` types, -they will be of `bv` types in all instances of the function or the struct when the instantiated type is an integer type. - -- Values with integer types in vectors and tables can be encoded as `bv` types; indices and keys in tables cannot be `bv` types for now. Using other types will lead to internal errors. - -## Built-in functions - -MSL supports a number of built-in constants and functions. Most of them are not available in the Move -language: - -- `MAX_U8: num`, `MAX_U64: num`, `MAX_U128: num` returns the maximum value of the corresponding - type. -- `exists(address): bool` returns true if the resource T exists at address. -- `global(address): T` returns the resource value at address. -- `len(vector): num` returns the length of the vector. -- `update(vector, num, T>): vector` returns a new vector with the element replaced at the - given index. -- `vec(): vector` returns an empty vector. -- `vec(x): vector` returns a singleton vector. -- `concat(vector, vector): vector` returns the concatenation of the parameters. -- `contains(vector, T): bool` returns true if element is in vector. -- `index_of(vector, T): num` returns the index of the element in the vector, or the length of - the vector if it does not contain it. -- `range(vector): range` returns the index range of the vector. -- `in_range(vector, num): bool` returns true if the number is in the index range of the - vector. -- `in_range(range, num): bool` returns true if the number is in the range. -- `update_field(S, F, T): S` updates a field in a struct, preserving the values of other fields, - where `S` is some struct, `F` the name of a field in `S`, and `T` a value for this field. -- `old(T): T` delivers the value of the passed argument at point of entry into a Move function. This - is allowed in - `ensures` post-conditions, - inline spec blocks (with additional restrictions), and - certain forms of invariants, as discussed later. -- `TRACE(T): T` is semantically the identity function and causes visualization of the argument's - value in error messages created by the prover. -- `int2bv(v)` explicitly converts an integer `v` into its `bv` representation. -- `bv2int(b)` explicitly converts a 'bv' integer 'b' into the `num` representation. However, it is not encouraged to use it due to efficiency issue. - -Built-in functions live in an unnamed outer scope of a module. If the module defines a function `len`, -then this definition will shadow that of the according built-in function. To access the built-in -function in such a situation, one can use the notation `::len(v)`. - -## Partial semantics - -In MSL, expressions have partial semantics. This is in contrast to Move program expressions, which -have total semantics, since they either deliver a value or abort. - -An expression `e[X]` that depends on some variables `X` may have a known interpretation for -some assignments to variables in `X` but is unknown for others. An unknown interpretation for a -sub-expression causes no issue if its value is not needed for the overall expression result. -Therefore, it does not matter if we say `y != 0 && x / y > 0` -or `x / y > 0 && y != 0`: boolean operators are commutative. - -This basic principle inherits to higher-level language constructs. For example, in specifications, -it does not matter in which order conditions are supplied: `aborts_if y != 0; ensures result == x / y;` is the same as -`ensures result == x / y; aborts_if y != 0;`. Also, `aborts_if P; aborts_if Q;` is the same -as `aborts_if Q || P` -. - -Moreover, the principle of partial semantics is inherited to [specification helper functions](#helper-functions), which behave transparently. Specifically, inlining those functions is equivalent to calling them (call-by-expression parameter passing semantics). - -# Specifications - -Specifications are contained in so-called _specification blocks_ (abbreviated **spec block**) that -can appear as module members and inside Move functions. The various types of spec blocks are shown -below, and will be discussed in subsequent sections. - -```move -module addr::M { - struct Counter has key { - value: u8, - } - - public fun increment(a: address) acquires Counter { - let r = borrow_global_mut(a); - spec { - // spec block targeting this code position - // ... - }; - r.value = r.value + 1; - } - - spec increment { - // spec block targeting function increment - // ... - } - - spec Counter { - // spec block targeting struct Counter - // ... - } - - spec schema Schema { - // spec block declaring a schema - // ... - } - - spec fun f(x: num): num { - // spec block declaring a helper function - // ... - } - - spec module { - // spec block targeting the whole module - // ... - } -} -``` - -Apart from spec blocks inside Move functions, the textual position of spec block is irrelevant. Also, -a spec block for a struct, function, or module can be repeated multiple times, accumulating the -content. - -## Separating specifications - -Instead of putting specifications into the same module as the regular Move definitions, one can also -put them into a separate "specification" module, which can live in the same or a different file: - -```move -module addr::M { - //... -} -spec addr::M { - spec increment { /* ... */ } -} -``` - -The syntax of a specification module is the same as for a regular module; however, Move functions -and structures are not allowed. - -A specification module must be compiled together with the Move module it is targeting and cannot be -compiled and verified standalone. - -In case Move definitions are far apart (e.g. in different files), it is possible to augment the -specification of a Move function with a signature of this function to give sufficient context to -understand the specification. This syntax is optionally enabled in regular and in specification -modules: - -```move -module 0x42::m { - public fun increment(a: address) acquires Counter { /* ... */ } - // ... - spec increment(a: address) { /* ... */ } -} -``` - -## Pragmas and properties - -Pragmas and properties are a generic mechanism to influence interpretation of specifications. They -are also an extension point to experiment with new concepts before they become part of the mainstream -syntax. Here we give a brief introduction into their general syntax; individual instances are -discussed later. - -The general form of a pragma is: - -```move -module 0x42::m { - spec item { - pragma = ; - } -} -``` - -The general form of a property is: - -```move -module 0x42::m { - spec item { - [ = ] ; // ensures, aborts_if, include, etc.. - } -} -``` - -The `` can be any value supported by MSL (or the Move language). A value assignment can -also be omitted, in which case a default is used. For example, it is common to use `pragma option;` -as a shortcut for `pragma option = true;`. - -Instead of a single pragma or property, a list can also be provided, as in `invariant [global, isolated] P`. - -### Pragma inheritance - -A pragma in a module spec block sets a value that applies to all other spec blocks in the module. A -pragma in a function or struct spec block can override this value for the function or struct. -Furthermore, the default value of some pragmas can be defined via the prover configuration. - -As an example, we look at the `verify` pragma. This pragma is used to turn verification on or off. - -```move -module 0x42::m { - spec module { - pragma verify = false; // By default, do not verify specs in this module ... - } - - spec increment { - pragma verify = true; // ... but do verify this function. - // ... - } -} -``` - -### General pragmas and properties - -A number of pragmas control general behavior of verification. Those are listed in the table below. - -| Name | Description | -| -------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| `verify` | Turns on or off verification. | -| `intrinsic` | Marks a function to skip the Move implementation and use a prover native implementation. This makes a function behave like a native function even if it not so in Move. | -| `timeout` | Sets a timeout (in seconds) for function or module. Overrides the timeout provided by command line flags. | -| `verify_duration_estimate` | Sets an estimate (in seconds) for how long the verification of function takes. If the configured `timeout` is less than this value, verification will be skipped. | -| `seed` | Sets a random seed for function or module. Overrides the seed provided by command line flags. | - -The following properties control general behavior of verification: - -| Name | Description | -| --------------- | ---------------------------------------------------- | -| `[deactivated]` | Excludes the associated condition from verification. | - -## Pre and post state - -Multiple conditions in spec blocks work with a _pre_ and _post_ state, relating them to each other. -Function specifications are one example of this: in the `ensures P` condition, the pre-state (at -function entry) and the post-state (at function exit) are related via the predicate `P`. However, -the concept is more general and also applied for invariants, where the pre-state is before and -post-state after a global update. - -In contexts where a pre/post-state is active, expressions are evaluated implicitly in the -post-state. To evaluate an expression in a pre-state, one uses the built-in function `old(exp)`, -which evaluates its parameter in the pre-state and returns its value. It is important to understand -that every sub-expression in `exp` is computed in the pre-state as well, including calls to helper -functions. - -The 'state' in question here consists of assignments to global resource memory, as well as to any -parameters of the function of type `&mut T`. Examples: - -```move -module 0x42::m { - fun increment(counter: &mut u64) { *counter = *counter + 1 } - spec increment { - ensures counter == old(counter) + 1; - } - - fun increment_R(addr: address) { - let r = borrow_global_mut(addr); - r.value = r.value + 1; - } - spec increment_R { - ensures global(addr).value == old(global(addr).value) + 1; - } -} -``` - -## Helper functions - -MSL allows defining helper functions. Those functions can then be used in expressions. - -Helper functions are defined using the following syntax: - -```move -module 0x42::m { - spec fun exists_balance(a: address): bool { exists>(a) } -} -``` - -As seen in the example, helper functions can be generic. Moreover, they can access global state. - -Definitions of helper functions are neutral regarding whether they apply to -a [pre- or post-state](#pre-and-post-state). They are evaluated in the currently active state. For -instance, in order to see whether a balance existed in the pre-state, one -uses `old(exists_balance(a))`. Consequently, the expression `old(..)` is not allowed -within the definition of a helper function. - -Helper functions are partial functions; see the discussion of [partial semantics](#partial-semantics). - -### Uninterpreted functions - -A helper function can be defined as **uninterpreted** by simply omitting its body: - -```move -module 0x42::m { - spec fun something(x: num): num; -} -``` - -An uninterpreted function is one of the prover is allowed to assign some arbitrary meaning to, as long -as it is consistent within a given verification context. Uninterpreted functions are a useful tool -for abstraction in specifications (see also [abstract specifications](#abstract-specifications)). - -### Axioms - -The meaning of helper functions can be further constrained by using **axioms**. Currently, axioms -must be contained in module spec blocks: - -```move -module 0x42::m { - spec module { - axiom forall x: num: something(x) == x + 1; - } -} -``` - -Axioms should be used with care as they can introduce unsoundness in the specification logic via -contradicting assumptions. The Move Prover supports a smoke test for detecting unsoundness via -the `--check-inconsistency` flag. - -## Let bindings - -A spec block can contain let bindings that introduce names for expressions: - -```move -module 0x42::m { - fun get_R(account: signer): R { /* ... */ } - spec get_R { - let addr = signer::spec_address_of(account); - aborts_if addr != ROOT; - ensures result == global(addr); - } -} -``` - -In a spec block that has a pre-state and post-state (like a function specification), the `let name = e` -form will evaluate `e` in the pre-state. In order to evaluate an expression in the post-state, use `let post name = e`. In the rhs expression of this form, one can use `old(..)` to refer to the -pre-state. - -## Aborts_if condition - -The `aborts_if` condition is a spec block member that can appear only in a function context. It -specifies conditions under which the function aborts. - -In the following example, we specify that the function `increment` aborts if the `Counter` resource -does not exist at address `a` (recall that `a` is the name of the parameter of `increment`). - -```move -module 0x42::m { - spec increment { - aborts_if !exists(a); - } -} -``` - -If a function has more than one `aborts_if` condition, those conditions are or-ed with each other. -The evaluation of the combined aborts condition (or-ed from each individual condition) depends on -the value of the pragma `aborts_if_is_partial`. If this value is false (the default), the function -aborts _if and only if_ the combined aborts condition is true. In this case, the above aborts -specification for `increment` will lead to a verification error, since there are additional -situations where `increment` can abort, namely if incrementing `Counter.value` would lead to an -overflow. To fix this, the specification can be completed like this: - -```move -module 0x42::m { - spec increment { - pragma aborts_if_is_partial = false; // This is the default, but added here for illustration. - aborts_if !exists(a); - aborts_if global(a).value == 255; - } -} -``` - -If the value of `aborts_if_is_partial` is true, the combined aborts condition (the or-ed individual -conditions) only _implies_ that the function aborts. Formally, if `A` is the combined aborts condition, then -with `aborts_if_is_partial = true`, we have `A ==> function_aborts`; otherwise we have -`A <==> function_aborts`. Therefore, the following does verify: - -```move -module 0x42::m { - spec increment { - pragma aborts_if_is_partial = true; - aborts_if !exists(a); - } -} -``` - - - -> Note that there is a certain risk in setting `aborts_if_is_partial` to true, and best practice is to avoid it in specifications of public functions and Move scripts once those are considered finalized. This is because changing the code after finalization of the spec can add new (non-trivial, undesired) abort situations the original specification did not anticipate yet will nevertheless silently pass verification. - -If no aborts condition is specified for a function, abort behavior is unspecified. The function may -or may not abort, and verification will not raise any errors, whether `aborts_if_is_partial` is set -or not. In order to state that a function never aborts, use `aborts_if false`. One can use the -pragma `aborts_if_is_strict` to change this behavior; this is equivalent to an `aborts_if false` being added to each function that does not have an explicit `aborts_if` clause. - -### Aborts_if condition with code - -The `aborts_if` condition can be augmented with code: - -```move -module 0x42::m { - fun get_value(addr: address): u64 { - aborts(exists(addr), 3); - borrow_global(addr).value - } - spec get_value { - aborts_if !exists(addr) with 3; - } -} -``` - -It is a verification error if the above function does not abort with code `3` under the given -condition. - -In order to specify a direct VM abort, one can use the special constant `EXECUTION_FAILURE`: - -```move -module 0x42::m { - fun get(addr: address): &Counter acquires Counter { - borrow_global(addr) - } - spec get { - aborts_if !exists(addr) with EXECUTION_FAILURE; - } -} -``` - -This same constant can be used for all other VM failures (division by zero, overflow, etc.). - -## Aborts_with condition - -The `aborts_with` condition allows specifying with which codes a function can abort, independent -under which condition. It is similar to a 'throws' clause in languages like Java. - -```move -module 0x42::m { - fun get_one_off(addr: address): u64 { - aborts(exists(addr), 3); - borrow_global(addr).value - 1 - } - spec get_one_off { - aborts_with 3, EXECUTION_FAILURE; - } -} -``` - -If the function aborts with any other or none of the specified codes, a verification error will be -produced. - -The `aborts_with` condition can be combined with `aborts_if` conditions. In this case, the `aborts_with` -specifies any other codes with which the function may abort, in addition to the ones given in the `aborts_if`: - -```move -module 0x42::m { - spec get_one_off { - aborts_if !exists(addr) with 3; - aborts_with EXECUTION_FAILURE; - } -} -``` - -## Requires condition - -The `requires` condition is a spec block member that postulates a pre-condition for a function. The -Move Prover will produce verification errors for functions that are called with violating -pre-conditions. - -A `requires` is different from an `aborts_if`: in the latter case, the function can be called, and -any aborts it produces will be propagated to the caller context. In the `requires` case, the Move Prover -will not allow the function to be called in the first place. Nevertheless, the function can _still -be called at runtime_ if verification is skipped. Because of this, `requires` are rare in Move -specifications, and `aborts_if` are more common. Specifically, `requires` should be avoided for public APIs. - -An example of `requires` is: - -```move -module 0x42::m { - spec increment { - requires global(a).value < 255; - } -} -``` - -## Ensures condition - -The `ensures` condition postulates a post-condition for a function that must be satisfied when the -function terminates successfully (i.e. does not abort). The Move Prover will verify each `ensures` to -this end. - -An example for the `ensures` condition is the following: - -```move -module 0x42::m { - spec increment { - ensures global(a) == old(global(a)) + 1; - } -} -``` - -Within the expression for the `ensures` condition, one can use the `old` function, as discussed in -[Pre and post state](#pre-and-post-state). - -## Modifies condition - -The `modifies` condition is used to provide permissions to a function to modify global storage. The -annotation itself comprises a list of global access expressions. It is specifically used together -with [opaque function specifications](#opaque-specifications). - -```move -module 0x42::m { - struct S has key { - x: u64 - } - - fun mutate_at(addr: address) acquires S { - let s = borrow_global_mut(addr); - s.x = 2; - } - spec mutate_at { - pragma opaque; - modifies global(addr); - } -} -``` - -In general, a global access expression has the form `global(address_expr)`. The -address-valued expression is evaluated in the pre-state of the annotated function. - -```move -module 0x42::m { - fun read_at(addr: address): u64 acquires S { - let s = borrow_global(addr); - s.x - } - - fun mutate_S_test(addr1: address, addr2: address): bool acquires T { - assert(addr1 != addr2, 43); - let x = read_at(addr2); - mutate_at( - addr1 - ); // Note we are mutating a different address than the one read before and after - x == read_at(addr2) - } - spec mutate_S_test { - aborts_if addr1 == addr2; - ensures result == true; - } -} -``` - -In the function `mutate_S_test`, the assertion in the spec block is expected to hold. A benefit of -the modifies specification on `mutate_at` is that this assertion can be proved whether `mutate_at` is inlined. - -If the modifies annotation is omitted on a function, then that function is deemed to have all -possible permissions for those resources it may modify during its execution. The set of all -resources that may be modified by a function is obtained via an interprocedural analysis of the -code. In the example above, `mutate_S_test` does not have a modifies specification and modifies -resource `S` via the call to `mutate_at`. Therefore, it is considered to have modified `S` at any -possible address. Instead, if the programmer adds `modifies global(addr1)` -to the specification of `mutate_S_test`, then the call to `mutate_at` is checked to make sure that -modify permissions granted to `mutate_S_test` cover the permissions it grants to `mutate_at`. - -## Invariant condition - -The invariant condition can be applied on structs and on global level. - -### Function invariants - -The `invariant` condition on a function is simply a shortcut for a `requires` and `ensures` with the -same predicate. - -Thus, the following spec block: - -```move -module 0x42::m { - spec increment { - invariant global(a).value < 128; - } -} -``` - -... is equivalent to: - -```move -module 0x42::m { - spec increment { - requires global(a).value < 128; - ensures global(a).value < 128; - } -} -``` - -### Struct invariants - -When the `invariant` condition is applied to a struct, it expresses a well-formedness property of -the struct data. Any instance of this struct that is currently not mutated will satisfy this -property (with exceptions as outlined below). - -For example, we can postulate an invariant on our counter that it never must exceed the value of -127: - -```move -module 0x42::m { - spec Counter { - invariant value < 128; - } -} -``` - -A struct invariant is checked by the Move Prover whenever the struct value is constructed (packed). While -the struct is mutated (e.g. via a `&mut Counter`) the invariant does _not_ hold (but see exception -below). In general, we consider mutation as an implicit unpack, and end of mutation as a pack. - -The Move language semantics unambiguously identifies the point when mutation ends and starts. This -follows from the borrow semantics of Move and includes mutation via an enclosing struct. -(The mutation of an inner struct ends when the mutation of the root struct where mutation started -ends.) - -There is one exception to this rule. When a mutable reference to a struct declared in module M is -passed into a _public_ function of M which does by itself _not_ return any other mutable reference (which could be borrowed from the input parameter), we treat this parameter as "packed". That means, on function entry, we will unpack it and on function exit we will pack again, enforcing the invariant. This reflects that in Move, struct data can be mutated only within the module that declares the struct; so for an outside caller of the public function, the mutable reference can actually not be mutated unless by calling public functions of module M again. It is a significant simplification of the verification problem to exploit this in the semantics. - -### Global invariants - -A global invariant appears as a member of module. It can express a condition over the global state -of the Move program, as represented by resources stored in memory. For example, the below invariant -states that a `Counter` resource stored at any given address can never be zero: - -```move -module addr::M { - invariant forall a: addr where exists(a): global(a).value > 0; -} -``` - -A global invariant is assumed to hold when data is read from the global state, and is asserted (and -may fail to verify) at the moment the state is updated. For example, the below function will never abort with arithmetic underflow because the counter value is always greater than zero; however, it will create a verification error since the counter can drop to zero: - -```move -module 0x42::m { - fun decrement_ad(addr: address) acquires Counter { - let counter = borrow_global_mut(addr); - let new_value = counter.value - 1; // Will not abort because counter.value > 0 - *counter.value = new_value; // Fails verification since value can drop to zero - } -} -``` - -Notice that type parameters are supported in global invariants. For example, the invariant above can be rewritten into the below one if `Counter` is generic: - -```move -module addr::M { - invariant forall a: addr where exists>(a): global>(a).value > 0; -} -``` - -#### Disabling invariants - -There are times when a global invariant holds almost everywhere, except for a brief interval inside a function. In current Move code, this often occurs when something (e.g. an account) is being set up and -several structs are published together. Almost everywhere, an invariant holds that all the structs are published or none of them are. But the code that publishes the structs must do so sequentially. While the structs are being published, there will be a point where some are published and others are not. - -In order to verify invariants that hold except during small regions, there is a feature to allow users to disable invariants temporarily. Consider the following code fragment: - -```move -module 0x42::m { - fun setup() { - publish1(); - publish2(); - } -} -``` - -where `publish1` and `publish2` publish two different structs, `T1` and `T2` at address `a`. - -```move -module addr::M { - invariant [global] exists(a) == exists(a) -} -``` - -As written, the Move Prover will report that the invariant is violated after the call to `publish1` and before the call to `publish2`. If either of `publish1` or `publish2` is without the other, the Move Prover -will also report a violation of the invariant. - -By default, a global invariant is checked immediately after the instruction `I` that touches the resources mentioned in the global invariant. The `[suspendable]` attribute (at the invariant side) together with two pragmas (specified in function spec block) provide fine-grained control on where we hope this invariant to be checked: - -- `disable_invariants_in_body`: the invariant will be checked at the end of the function where `I` resides. -- `delegate_invariants_to_caller`: the invariant will be checked by all callers of the function where `I` resides. - -For the example above, we can add the pragma `disable_invariants_in_body`: - -```move -module 0x42::m { - spec setup { - pragma disable_invariants_in_body; - } -} -``` - -which says that invariants are not required to hold while `setup` is executing but are assumed to hold on entry to and exit from `setup`. - -This pragma changes the Move Prover's behavior. The invariants are assumed on entry to `setup` but not proved during or after `publish1` and `publish2`. Instead, all invariants that could be invalidated in the -body of `setup` are asserted and proved at the point of return from `setup`. A consequence of this processing is that the user may need to provide stronger post-conditions on `publish1` and `publish2` to -make it possible to prove the invariants on exit from `setup`. - -Another consequence of this processing is that invariants cannot safely be assumed to hold during the execution of `publish1` and `publish2` (unless nothing in the body of `setup` changes state -mentioned in the invariant). Therefore, if proving a post-condition requires the invariant to be assumed, the post-condition will fail. - -In the example, invariants hold at the call sites of `setup` but not in the body. For `publish1`, invariants don't necessarily hold at the call site _or_ in the body of the function. In the example, that -behavior is implied because `publish1` is called in a context where invariants are disabled. - -When invariants are disabled in `setup` in the above example, the Move Prover cannot assume them on entry to `publish1` and `publish2` and should not try to prove them on exit from those functions. The Move Prover -would have the same behavior for any functions called by `publish1` or `publish2`. The Move Prover _automatically_ adopts this behavior when invariants are disabled in a calling function, but it is possible for the user to declare that a function be treated like `publish1`. - -For example, if `publish2` is _only_ called from the setup function above, and we did _not_ disable invariants in `setup`, we could achieve a similar effect by using the pragma `delegate_invariants_to_caller`, instead. - -```move -module 0x42::m { - spec setup { - pragma delegate_invariants_to_caller; - } -} -``` - -This would be legal only if `setup` is a private or `public (friend)` function. The difference between this and disabling invariants in `setup` is that the invariants would not be assumed at the beginning of `setup` and would be proved after `setup` returns at each site where it is called. - -While both pragmas disable invariants in the body of a function, the difference is that `disable_invariants_in_body` assumes invariants on entry and proves them on exit, while `delegate_invariants_to_caller` does neither. - -There are some limitations on how these pragmas can be used. `disable_invariants_in_body` cannot be declared for functions where invariants are delegated to a caller, either explicitly via the pragma -or implicitly because the function is called in a context where invariants have been disabled. (This restriction is to ensure consistent processing, because on pragma assumes that invariants hold -in the calling context and the other does not). Second, it is illegal for a public or script function to delegate invariant checking to its callers (since the Move Prover does not know all the call sites), _unless_ the function cannot possibly invalidate an invariant because it doesn't change any of the state mentioned in `exists` and `global` expressions appearing in the invariant. - -#### Update invariants - -The `update` form of a global invariant allows to express a relation between [pre-state and post-state](#pre-and-post-state) of a global state update. For example, the following invariant states that the counter must decrease monotonically whenever it is updated: - -```move -module addr::M { - invariant update [global] forall a: addr where old(exists(a)) && exists(addr): - global(a).value <= old(global(a)); -} -``` - -#### Isolated global invariants - -A global invariant can be marked as `[isolated]` to indicate that it is not relevant for proving -other properties of the program. An isolated global invariant will not be assumed when the related -global state is read. It will only be assumed before the state is updated to help prove that the -invariant still holds after the update. This feature is for improving performance in situations -where there are many global invariants, but they have no direct influence on verification. - -#### Modular verification and global invariants - -Certain usage of global invariants leads to verification problems that cannot be checked in a modular fashion. "Modular" here means that a module can be verified standalone and proven to be universally correct in all usage contexts (if preconditions are met). - -A non-modular verification problem may arise if a global invariant refers to state from multiple modules. Consider a situation where module `M1` uses module `M2`, and `M1` contains the following invariant, with the helper function `condition` referring to global state of each respective module: - -```move -module addr::M1 { - invariant M1::condition() ==> M2::condition(); -} -``` - -When we verify `M1` standalone, the Move Prover will determine that it also needs to verify functions in `M2`, namely those which update the M2 memory such that the invariant in M1 can fail. - -## Assume and assert conditions in code - -A spec block might also occur anywhere an ordinary Move statement block can occur. -Here is an example: - -```move -module 0x42::m { - fun simple1(x: u64, y: u64) { - let z; - y = x; - z = x + y; - spec { - assert x == y; - assert z == 2 * x; - } - } -} -``` - -In such inline spec blocks, only a subset of conditions are permitted: - -- `assume` and `assert` statements are allowed in any code locations. -- loop `invariant` statements are allowed only in code locations that represent loop headers. - -An assert statement inside a spec block indicates a condition that must hold when control reaches -that block. If the condition does not hold, an error is reported by the Move Prover. An `assume` -statement, on the other hand, blocks executions violating the condition in the statement. The -function `simple2` shown below is verified by the Move Prover. However, if the first spec block -containing the assume statement is removed, Move Prover will show a violation to the `assert` -statement in the second spec block. - -```move -module 0x42::m { - fun simple2(x: u64, y: u64) { - let z: u64; - spec { - assume x > y; - }; - z = x + y; - spec { - assert z > 2 * y; - } - } -} -``` - -### Loop invariants - -An `invariant` statement encodes a loop invariant and must be placed at a loop head, as in the -following example: - -```move -module 0x42::m { - fun simple3(n: u64) { - let x = 0; - loop { - spec { - invariant x <= n; - }; - if (x < n) { - x = x + 1 - } else { - break - } - }; - spec { - assert x == n; - } - } -} -``` - -A loop invariant is translated into two `assert` statements and one `assume` statement to facilitate the inductive reasoning of properties about the loop. In break down, a loop invariant is translated to: - -- An `assert` statement that confirms the invariant holds when the loop is first encountered in the - execution -- establishing the base case. -- An `assume` statement that encodes the property that the invariant holds at loop iteration `I`. -- An `assert` statement that checks whether the invariant continues to hold at loop iteration `I+1`. - -### Referring to pre-state - -Occasionally, we would like to refer to the pre-state of a mutable function argument in inline spec -blocks. In MSL, this can be done with the `old(T)` expression. Similar to the semantics of `old(..)` -in post conditions, an `old(T)` expression in an `assume` or `assert` statement always yields the -value of `T` at the function entry point. Here is an example that illustrate the use of -`old(..)` in an inline spec block: - -```move -module 0x42::m { - fun swap(x: &mut u64, y: &mut u64) { - let t = *x; - *x = *y; - *y = t; - spec { - assert x == old(y); - assert y == old(x); - }; - } -} -``` - -The above example is trivial as the same property can be expressed with post conditions -(i.e., `ensures`) too. But there are cases where we must use `old(..)` to refer to the pre-state, especially in the specification of loop invariants. Consider the following example -where we verify that the `vector_reverse` function properly reverses the order of all elements -in a vector: - -```move -module 0x42::m { - fun verify_reverse(v: &mut vector) { - let vlen = vector::length(v); - if (vlen == 0) return; - - let front_index = 0; - let back_index = vlen - 1; - while ({ - spec { - assert front_index + back_index == vlen - 1; - assert forall i in 0..front_index: v[i] == old(v)[vlen - 1 - i]; - assert forall i in 0..front_index: v[vlen - 1 - i] == old(v)[i]; - assert forall j in front_index..back_index + 1: v[j] == old(v)[j]; - assert len(v) == vlen; - }; - (front_index < back_index) - }) { - vector::swap(v, front_index, back_index); - front_index = front_index + 1; - back_index = back_index - 1; - }; - } - spec verify_reverse { - aborts_if false; - ensures forall i in 0..len(v): v[i] == old(v)[len(v) - 1 - i]; - } -} -``` - -Note the usage of `old(v)` in the loop invariants. Without them, it is hard to express the -invariant that the vector is partially reversed while the loop is iterating and the rest -remain unchanged. - -However, unlike the `old(T)` expressions in `ensures` conditions where `T` can be any valid -expression (e.g., `old(v[i])` is allowed), the `old(T)` expressions in `assert` and `assumes` -statements accept only a single variable as `T` and that variable must be a function argument of -a mutable reference type. In the above example, `old(v[i])` is not allowed, and we should use -`old(v)[i]` instead. - -## Specification variables - -MSL supports _spec variables_, also called _ghost variables_ in the verification community. These -variables are used only in specifications and represent information derived from the global state of -resources. An example use case is to compute the sum of all coins available in the system and -specify that the sum can be changed only in certain scenarios. - -We illustrate this feature by introducing a spec variable that maintains the sum of all `Counter` -resources from our running example. First, a spec variable is introduced via spec module block as -follows: - -```move -module 0x42::m { - spec module { - global sum_of_counters: num; - } -} -``` - -This value is going to be updated whenever a `Counter` is packed or unpacked. (Recall that mutation -is interpreted as an implicit unpack and pack): - -```move -module 0x42::m { - spec Counter { - invariant pack sum_of_counters = sum_of_counters + value; - invariant unpack sum_of_counters = sum_of_counters - value; - } -} -``` - -> TODO: `invariant pack` and `invariant unpack` are currently not implemented - -Now we may for example want to specify that the sum of all Counter instances in the global state -should never exceed a particular value. We can do this as follows: - -```move -module 0x42::m { - spec module { - invariant [global] sum_of_counters < 4711; - } -} -``` - -Note that spec variables can also be referenced from helper functions. Moreover, spec variables can -be generic: - -```move -module 0x42::m { - spec module { - global some_generic_var: num; - } -} -``` - -When using such a spec variable, a type parameter must be provided, as in `some_generic_var`. Effectively, a generic spec variable is like a family of variables indexed by types. - -## Schemas - -Schemas are a means for structuring specifications by grouping properties together. Semantically, -they are just syntactic sugar that expand to conditions on functions, structs, or modules. - -### Basic Schema Usage - -Schemas are used as such: - -```move -module 0x42::m { - spec schema IncrementAborts { - a: address; - aborts_if !exists(a); - aborts_if global(a).value == 255; - } - - spec increment { - include IncrementAborts; - } -} -``` - -Each schema may declare a number of typed variable names and a list of conditions over those -variables. All supported condition types can be used in schemas. The schema can then be included in -another spec block: - -- If that spec block is for a function or a struct, all variable names the schema declares must be - matched against existing names of compatible type in the context. -- If a schema is included in another schema, existing names are matched and must have the same type, - but non-existing names will be added as new declarations to the inclusion context. - -When a schema is included in another spec block, it will be checked whether the conditions it -contains are allowed in this block. For example, including the schema `IncrementAborts` into a -struct spec block will lead to a compile-time error. - -When a schema is included, the names it declares can also bound by expressions. For example, one can -write `include IncrementAborts{a: some_helper_address()}`. Effectively, not providing a binding is -equivalent to writing `IncrementAborts{a: a}` if `a` is an existing name in scope. - -Schemas can be generic. Generic schemas must be fully instantiated where they are included; type -inference is not available for schemas. - -### Schema expressions - -When a schema is included, one can use a limited set of Boolean operators as follows: - -- `P ==> SchemaExp`: all conditions in the schema will be prefixed with `P ==> ..`. Conditions that - are not based on Boolean expressions will be rejected. -- `if (P) SchemaExp1 else SchemaExp2`: this is treated similar to including both - `P ==> SchemaExp1` and `!P ==> SchemaExp2`. -- `SchemaExp1 && SchemaExp2`: this is treated as two includes for both schema expressions. - -### Schema apply operation - -One of the main use cases for schemas is to be able to name a group of properties and then apply -those to a set of functions. This is achieved by the `apply` operator. The `apply` spec block member -can appear only in module spec blocks. - -The general form of the apply operator is `apply Schema to FunctionPattern, .. except FunctionPattern, ..`. Here, `Schema` can be a schema name or a schema name plus formal type arguments. `FunctionPatterns` consists of an optional visibility modifier `public` or `internal` (if not provided, both visibilities will match), a name pattern in the style of a shell file pattern ( e.g. `*`, `foo*`, `foo*bar`, etc.), and finally an optional type argument list. All type arguments provided to `Schema` must be bound -in this list and vice versa. - -The `apply` operator includes the given schema in all function spec blocks that match the patterns, -except those excluded via the `except` patterns. - -A typical use of the `apply` operator is to provide common pre-conditions and post-conditions to all functions in -a module with some exceptions. Example: - -```move -module 0x42::m { - spec schema Unchanged { - let resource = global(ADDR); - ensures resource == old(resource); - } - - spec module { - // Enforce Unchanged for all functions except the initialize function. - apply Unchanged to * except initialize; - } -} -``` - -Notice that while with [global invariants](#global-invariants) we can express similar things, we _cannot_ -express the restriction of the invariant to only specific functions. - -## Opaque specifications - -With the pragma `opaque`, a function is declared to be solely defined by its specification at caller -sides. In contrast, if this pragma is not provided, then the function's implementation will be used -as the basis to verify the caller. - -Using `opaque` requires the specification to be sufficiently complete for the verification problem at hand. Without `opaque`, the Move Prover will use the implementation as the source of truth for the definition of the function. But with `opaque`, if there is an aspect of the function definition unspecified, an arbitrary meaning will be assumed. For example, with the specification below, the `increment` function can abort under arbitrary conditions: - -```move -module 0x42::m { - spec increment { - pragma opaque; - // aborts_if !exists(a); // We need to add this to make the function not abort arbitrarily - ensures global(a) == old(global(a)) + 1; - } -} -``` - -In general, `opaque` functions enable modular verification, as they abstract from the implementation -of functions, resulting in much faster verification. - -If an `opaque` function modifies state, it is advised to use the [`modifies` condition](#modifies-condition) in its specification. If this is omitted, verification of the state changes will fail. - -## Abstract specifications - -The `[abstract]` property allows specifying a function such that abstract semantics are used at the -caller side that is different from the actual implementation. This is useful if the implementation -is too complex for verification, and abstract semantics are sufficient for verification goals. -The `[concrete]` property, in turn, still allows specifying conditions that are verified against -the implementation but not used at the caller side. - -Consider the following example of a hash function. The actual value of the hash is not relevant for -verification of callers, and we use an [uninterpreted helper function](#uninterpreted-functions) delivering an arbitrary value chosen by the Move Prover. We can still specify the concrete implementation and verify its correctness: - -```move -module 0x42::m { - fun hash(v: vector): u64 { - <>(v) - } - spec hash { - pragma opaque; - aborts_if false; - ensures [concrete] result == << sum up values >> (v); - ensures [abstract] result == spec_hash_abstract(v); - } - spec fun abstract_hash(v: vector): u64; // uninterpreted function -} -``` - -The soundness of the abstraction is the responsibility of the specifier and not verified by the -Move Prover. - -> NOTE: The abstract/concrete properties should only be used with opaque specifications, but the Move Prover will currently not generate an error message even though they are not used with opaque specifications. - -> NOTE: The `modifies` clause does not currently support abstract/concrete. Also, if no modifies is given, the modified state will be computed from the implementation anyway, possibly conflicting with `[abstract]` properties. - -## Documentation generation - -The organization of specification blocks in a file is relevant for documentation generation -- even -though it is not for the semantics. - -# Expressiveness - -The Move specification language is expressive enough to represent the full Move language semantics (formal argument outstanding) with one exception: functions that return a `&mut T` type. - -Consider the following code: - -```move -module 0x42::m { - struct S { x: u64, y: u64 } - - fun x_or_y(b: bool, s: &mut S): &mut u64 { - if (b) &mut s.x else &mut s.y - } - spec x_or_y { - ensures b ==> result == s.x; - ensures !b ==> result == s.y; - } -} -``` - -We are not able to specify the _full_ semantics of `x_or_y` in MSL because we cannot capture the -semantics of mutable references. While we can say something about the value behind the reference at -function exit, subsequent effects as in `*x_or_y(b, &mut s) = 2` cannot be specified. - -However, the Move Prover _does_ understand the meaning of such functions -- the restriction is only -in what we can specify. Practically, this means we cannot make the function `x_or_y` opaque and must -let verification rely on that the Move Prover directly works with the implementation. Specifically, we -can verify the following (which can then be opaque): - -```move -module 0x42::m { - fun x_or_y_test(s: S): S { - *x_or_y(true, &mut s) = 2; - s - } - spec x_or_y_test { - pragma opaque; - ensures result.x == 2; - ensures result.y == s.y; - } -} -``` - -## Supporting resources - -- [Design by contract PRE_POST_REFERENCE](https://en.wikipedia.org/wiki/Design_by_contract) -- [APTOS_FRAMEWORK](https://github.com/aptos-labs/aptos-core/blob/main/aptos-move/framework/aptos-framework/doc/overview.md) diff --git a/apps/nextra/pages/zh/build/smart-contracts/prover/supporting-resources.mdx b/apps/nextra/pages/zh/build/smart-contracts/prover/supporting-resources.mdx deleted file mode 100644 index d2de9904b..000000000 --- a/apps/nextra/pages/zh/build/smart-contracts/prover/supporting-resources.mdx +++ /dev/null @@ -1,50 +0,0 @@ ---- -title: "Supporting Resources" ---- - -# Move Prover Supporting Resources - -## Standard Library and Framework Specifications - -- [Move Stdlib](https://github.com/aptos-labs/aptos-core/tree/main/aptos-move/framework/move-stdlib) -- [Aptos Stdlib](https://github.com/aptos-labs/aptos-core/tree/main/aptos-move/framework/aptos-stdlib) -- [Aptos Framework](https://github.com/aptos-labs/aptos-core/tree/main/aptos-move/framework/aptos-framework) -- [Diem Framework](https://github.com/move-language/move/tree/main/language/documentation/examples/diem-framework/move-packages/DPN) - -## Examples - -- [`hello_prover` example](https://github.com/aptos-labs/aptos-core/tree/main/aptos-move/move-examples/hello_prover) -- [`basic-coin` example](https://github.com/move-language/move/tree/main/language/documentation/examples/experimental/basic-coin) -- [`math-puzzle` example](https://github.com/move-language/move/tree/main/language/documentation/examples/experimental/math-puzzle) -- [`rounding-error` example](https://github.com/move-language/move/tree/main/language/documentation/examples/experimental/rounding-error) -- [`verify-sort` example](https://github.com/move-language/move/tree/main/language/documentation/examples/experimental/verify-sort) -- [Move Prover Examples by Zellic](https://github.com/zellic/move-prover-examples) - -## Tutorials - -- [The Move Tutorial, steps 7 and 8](https://github.com/aptos-labs/aptos-core/tree/main/aptos-move/move-examples/move-tutorial#step-7--use-the-move-prover) -- [Verify Smart Contracts in Aptos with the Move Prover by MoveBit](https://www.movebit.xyz/blog/post/move-prover-tutorial-part-1.html) -- [The Move Prover: A Practical Guide by OtterSec](https://osec.io/blog/2022-09-16-move-prover) -- [Formal Verification, the Move Language, and the Move Prover by Certik](https://www.certik.com/resources/blog/2wSOZ3mC55AB6CYol6Q2rP-formal-verification-the-move-language-and-the-move-prover) -- [The Move Prover: Quality Assurance of Formal Verification by Certik](https://www.certik.com/resources/blog/1NygvVeqIwhbUk1U1q3vJF-the-move-prover-quality-assurance-of-formal-verification) - -## Presentations - -- [Verifying Smart Contracts with Move Prover by Wolfgang Grieskamp (video)](https://drive.google.com/file/d/1DpI-rQ25Kq1jqMGioLgVrG3YuCqJHVMm/view?usp=share_link) -- [Formal verification of Move programs for the Libra blockchain by David Dill (video)](https://www.fields.utoronto.ca/talks/Formal-verification-Move-programs-Libra-blockchain) -- [Move Prover - Best Practices & Tricks - A User’s Perspective by Xu-Dong@MoveBit (slides)](https://docs.google.com/presentation/d/1SuV0m5gGxSN9SaLdj9lLmTjspJ2xN1TOWgnwvdWbKEY/edit?usp=sharing) - -## Conference papers - -- Zhong, Jingyi Emma, Kevin Cheang, Shaz Qadeer, Wolfgang Grieskamp, -Sam Blackshear, Junkil Park, Yoni Zohar, Clark Barrett, and David L. Dill. -"The move prover." In _International Conference on Computer Aided Verification_, -pp. 137-150. Springer, Cham, 2020.Harvard - - https://research.facebook.com/publications/the-move-prover/ -- Dill, David, Wolfgang Grieskamp, Junkil Park, Shaz Qadeer, Meng Xu, and Emma -Zhong. "Fast and reliable formal verification of smart contracts with the Move -prover." In _International Conference on Tools and Algorithms for the -Construction and Analysis of Systems_, pp. 183-200. Springer, Cham, 2022.Harvard - - https://research.facebook.com/publications/fast-and-reliable-formal-verification-of-smart-contracts-with-the-move-prover/ -- Park, Junkil, Teng Zhang, Wolfgang Grieskamp, Meng Xu, Gerardo Di Giacomo, Kundu Chen, Yi Lu, and Robert Chen. "Securing Aptos framework with formal verification." In _5th International Workshop on Formal Methods for Blockchains (FMBC 2024)_. Schloss Dagstuhl–Leibniz-Zentrum für Informatik, 2024. - - https://drops.dagstuhl.de/storage/01oasics/oasics-vol118-fmbc2024/OASIcs.FMBC.2024.9/OASIcs.FMBC.2024.9.pdf diff --git a/apps/nextra/pages/zh/build/smart-contracts/randomness.mdx b/apps/nextra/pages/zh/build/smart-contracts/randomness.mdx deleted file mode 100644 index 09f35a3d1..000000000 --- a/apps/nextra/pages/zh/build/smart-contracts/randomness.mdx +++ /dev/null @@ -1,346 +0,0 @@ ---- -title: "Randomness API" ---- -import { Callout } from 'nextra/components' - -# Aptos Roll: A Randomness API - -## What does it do: a quick example - -### How random numbers have been obtained, insecurely/awkwardly - -Building a lottery system and pick a random winner from `n` participants is trivial, at least in the centralized world with a trusted server: -the backend simply calls a random integer sampling function -(`random.randint(0, n-1)` in python, or `Math.floor(Math.random() * n)` in JS). - -Unfortunately, without an equivalent of `random.randint()` in Aptos Move, building a dApp version of it was actually much harder. - -One may have written a contract where the random numbers are sampled insecurely (e.g., from the blockchain timestamp): - -```move -module module_owner::lottery { - // ... - - struct LotteryState { - players: vector
, - winner_idx: std::option::Option, - } - - fun load_lottery_state_mut(): &mut LotteryState { - // ... - } - - entry fun decide_winner() { - let lottery_state = load_lottery_state_mut(); - let n = std::vector::length(&lottery_state.players); - let winner_idx = aptos_framework::timestamp::now_microseconds() % n; - lottery_state.winner_idx = std::option::some(winner_idx); - } -} -``` - -The implementation above is insecure in multiple ways: - -- a malicious user may bias the result by picking the transaction submission time; -- a malicious validator can bias the result easily by selecting which block the `decide_winner` transaction goes to. - -Other dApps may have chosen to use an external secure randomness source -(e.g., [drand](https://drand.love/)), which is typically a complicated flow: - -1. The participants agree on using a future randomness seed promised by the randomness source to determine the winner. -2. Once the randomness seed is revealed, the clients fetch it and derive the winner locally. -3. One of the participants submits the seed and the winner on chain. - -```move -module module_owner::lottery { - // ... - - struct LotteryState { - players: vector
, - /// public info about the "future randomness", tyipcally a VRF public key and an input. - seed_verifier: vector, - winner_idx: std::option::Option, - } - - fun load_lottery_state_mut(): &mut LotteryState { - // ... - } - - fun is_valid_seed(seed_verifier: vector, seed: vector): bool { - // ... - } - - fun derive_winner(n: u64, seed: vector): u64 { - // ... - } - - entry fun update_winner(winner_idx: u64, seed: vector) { - let lottery_state = load_lottery_state_mut(); - assert!(is_valid_seed(lottery_state.seed_verifier, seed), ERR_INVALID_SEED); - let n = std::vector::length(players); - let expected_winner_idx = derive_winner(n, seed); - assert!(expected_winner_idx == winner_idx, ERR_INCORRECT_DERIVATION); - lottery_state.winner_idx = std::option::some(winner_idx); - } -} -``` - -### Achieve simplicity + security with Aptos randomness API - -Using Aptos randomness API, the implementation will look like this: - -```move -module module_owner::lottery { - // ... - - struct LotteryState { - players: vector
, - winner_idx: std::option::Option, - } - - fun load_lottery_state_mut(): &mut Lottery { - // ... - } - - #[randomness] - entry fun decide_winner() { - let lottery_state = load_lottery_state_mut(); - let n = vector::length(&lottery_state.players); - let winner_idx = aptos_framework::randomness::u64_range(0, n); - lottery_state.winner_idx = std::option::some(winner_idx); - } -} -``` - -where: - -- `let winner_idx = aptos_framework::randomness::u64_range(0, n);` is the randomness API call that returns an u64 integer in range `[0, n)` uniformly at random. -- `#[randomness]` is a required attribute to enable the API call at runtime. - - -### Security Considerations -Compiler helps with test and abort attacks, requiring functions using randomness to be private. However, the randomness API currently does not prevent undergasing attacks. The smart contract will need to be written in a certain way to avoid it. - - -## How to use Aptos randomness API - -### Prerequisites -Ensure you have the latest [aptos-cli](https://aptos.dev/en/build/cli) installed. - -### Keep undergasing attacks in mind - - -**The randomness API currently does not prevent undergasing attacks.** Carefully read the undergasing section to understand about undergasing attacks and how to prevent them. As a dApp developer, you will need to design applications using randomness with safety in mind. - - -### Identify randomness-dependent entry functions and make them compliant - -For safety (discussed with more details later), randomness API calls are only allowed from an entry function that is: - -- private, and -- annotated with `#[randomness]`. - -It's now a good time to think about what user actions need randomness API, write them down, and make sure they are private and have the right attribute, as shown in the example below. - -```move -module module_owner::lottery { - // ... - - #[randomness] - entry fun decide_winner() { - // ... - } -} -``` - -At runtime, when randomness API is called, the VM checks whether the outermost of the callstack is a private entry function with `#[randomness]` attribute. -**If not, the entire transaction is aborted.** - - - NOTE: It also means randomness API calls are supported only in entry function-based transactions. - (For example, using randomness API in a Move script is impossible.) - - -### Call the API - -The APIs are public functions under `0x1::randomness` and can be referenced directly, as demonstrated in the lottery example above. - -```move -module module_owner::lottery { - // ... - - #[randomness] - entry fun decide_winner() { - // ... - let winner_idx = aptos_framework::randomness::u64_range(0, n); - lottery_state.winner_idx = std::option::some(winner_idx); - } -} -``` - -The above example uses function `u64_range()` but many other basic types are also supported. -Here's a quick overview of all the API, where `T` can be one of `u8, u16, u32, u64, u128, u256`. - -```move -module aptos_framework::randomness { - /// Generates an number uniformly at random. - fun u8_integer(): u8 {} - - /// Generates an number uniformly at random. - fun u16_integer(): u16 {} - - // fun u32_integer(), fun u64_integer() ... - - /// Generates a number `[min_incl, max_excl)` uniformly at random. - fun u8_range(min_incl: u8, max_excl: u8): u8 {} - - /// Generates a number `[min_incl, max_excl)` uniformly at random. - fun u16_range(min_incl: u16, max_excl: u16): u16 {} - - // fun u32_range(), fun u64_range() ... - - /// Generates a sequence of bytes uniformly at random - /// n is the number of bytes - /// If n is 0, returns the empty vector. - fun bytes(n: u64): vector {} - - /// Generate a permutation of `[0, 1, ..., n-1]` uniformly at random. - /// n is the number of bytes - /// If n is 0, returns the empty vector. - fun permutation(n: u64): vector {} -} -``` - -The full API function list and documentation can be found [here](https://github.com/aptos-labs/aptos-core/blob/main/aptos-move/framework/aptos-framework/doc/randomness.md). - -## Security considerations - -Randomness API is powerful in many ways: it unlocks new dApp designs; -but if used incorrectly, it may leave your dApps open to attacks! -Below are some common mistakes you should avoid. - -### Randomness API calls in public functions - -As your dApp gets more complicated, you may have multiple entry functions that need to share the same randomness-dependent logic, and want to pull the logic out as a separate helper function. - -While this is supported as shown below, extra care must be taken. - -```move -module module_owner::lottery { - // ... - - #[randomness] - entry fun decide_winner_v0() { - // ... - decide_winner_internal(lottery_state); - } - - #[randomness] - entry fun decide_winner_v1() { - // ... - decide_winner_internal(lottery_state); - } - - // A private helper function - fun decide_winner_internal(lottery_state: &mut lottery_state) { - let n = std::vector::length(&lottery_state.players); - let winner_idx = aptos_framework::randomness::u64_range(0, n); - lottery_state.winner_idx = std::option::some(winner_idx); - } -} -``` - -If `decide_winner_internal()` were accidentally marked public, -malicious players can deploy their own contract to: - -1. call`decide_winner_internal()`; -2. read the lottery result (assuming the `lottery` module has some getter functions for the result); -3. abort if the result is not in their favor. - By repeatedly calling their own contract until a txn succeeds, - malicious users can bias the uniform distribution of the winner (dApp developer's initial design). - This is referred to as a [test-and-abort attack](https://github.com/aptos-foundation/AIPs/blob/main/aips/aip-41.md#test-and-abort-attacks). - -The Aptos Move compiler has been updated to prevent this attack for your contract safety: -a randomness-dependent public function is treated as a compile error. -If you have finished the steps in the ["build Aptos CLI"](#build-aptos-cli-from-latest-source)) section, -then your Aptos CLI are equipped with the updated compiler. - -```move -module module_owner::lottery { - // Compile error! - public fun decide_winner_internal(lottery_state: &mut lottery_state) { - let n = std::vector::length(&lottery_state.players); - let winner_idx = aptos_framework::randomness::u64_range(0, n); - lottery_state.winner_idx = std::option::some(winner_idx); - } -} -``` - -Not recommended, but if you intend to expose such a randomness-dependent function to the public, you can bypass the compiler check by annotating your function with `#[lint::allow_unsafe_randomness]`. - -```move -module module_owner::lottery { - // Can compile, but use it at your own risk! - #[lint::allow_unsafe_randomness] - public fun decide_winner_internal(lottery_state: &mut lottery_state) { - let n = std::vector::length(&lottery_state.players); - let winner_idx = aptos_framework::randomness::u64_range(0, n); - lottery_state.winner_idx = std::option::some(winner_idx); - } -} -``` - -### Undergasing attacks, and how to prevent - -Imagine such a dApp. It defines a private entry function for a user to: - -1. toss a coin (gas cost: 9), then -2. get a reward (gas cost: 10) if coin=1, or do some cleanup (gas cost: 100) otherwise. - -A malicious user can control its account balance, so it covers at most 108 gas units (or set transaction parameter `max_gas=108`), -and the cleanup branch (total gas cost: 110) will always abort with an out-of-gas error. -The user then repeatedly call the entry function until it gets the reward. - -Formally, this is referred to as an [undergasing attack](https://github.com/aptos-foundation/AIPs/blob/main/aips/aip-41.md#undergasing-attacks), -where an attacker can control how much gas is left for the entry function to execute, -and so can arbitrarily decide to abort paths that cost more gas, -biasing the outcome (i.e. effectively changing the distribution of random numbers). - - - **WARNING: randomness API currently does not prevent undergasing attacks.** - As a dApp developer, you need to be very careful in your design to avoid this type of attack. - Here are some ideas of how to prevent undergasing attack generally. - - - Make your entry function gas independent from the randomness outcome. - The simplest example is to not "act" on the randomness outcome, i.e. read it and store it for later. Note that calling any other functions can have variable gas costs. For example, when calling randomness to decide which player should win, and then depositing the winnings to the winner might seem like a fixed gas cost. But, `0x1::coin::transfer` / `0x1::fungible_asset::transfer` can have a variable cost based on the user's on-chain state. - - If your dApp involves a trusted admin/admin group, only allow the trusted to execute randomness transaction (i.e. require an admin signer). - - Make the path that is most beneficial have the highest gas (as attacker can only abort paths with gas above a threshold he chooses. - NOTE: that this can be tricky to get right, and gas schedule can change, and is even harder to get right when there are more than 2 possible outcomes. - - Note that everything that does not fall in above categories can be susceptible to undergasing attack in a subtle ways. Reach out if you need help. - - We will be providing more functionality in the future, to allow for more complex code to be able to be safe against undergasing attacks. - - -### It's random, but not a secret - -While the randomness API mimics the standard libraries you use to implement a private centralized server, -keep in mind that **the seed is public, and so is your transaction execution**, -and not every randomness-dependent logic in your private centralized server can be transferred on chain safely, -**especially when it involves a secret that only the server should see**. - -For example, in your contract, DO NOT try to do the following. - -- Use randomness API to generate an asymmetric key pair, discard the private key, then think the public key is safe. -- Use randomness API to shuffle some opened cards, veil them, and think no one knows the permutation. - -## Read more - -[Aptogotchi Random Mint](https://github.com/aptos-labs/aptogotchi-random-mint/tree/main) is an official demo dApp built to demonstrate the use of randomness API. - -The full API function list and documentation can be found [here](https://github.com/aptos-labs/aptos-core/blob/main/aptos-move/framework/aptos-framework/doc/randomness.md). - -You can also find the partial implementation of the API functions and example unit tests [here](https://github.com/aptos-labs/aptos-core/blob/main/aptos-move/framework/aptos-framework/sources/randomness.move). - -See [AIP-41](https://github.com/aptos-foundation/AIPs/blob/main/aips/aip-41.md) for the API design, -and [AIP-79](https://github.com/aptos-foundation/AIPs/blob/main/aips/aip-79.md) if you are interested in system-level/cryptography details. diff --git a/apps/nextra/pages/zh/build/smart-contracts/reference.mdx b/apps/nextra/pages/zh/build/smart-contracts/reference.mdx deleted file mode 100644 index 15aa83a4b..000000000 --- a/apps/nextra/pages/zh/build/smart-contracts/reference.mdx +++ /dev/null @@ -1,9 +0,0 @@ ---- -title: "Move Reference" ---- - -import { MoveReference } from '@components/index' - -# Move Reference - - \ No newline at end of file diff --git a/apps/nextra/pages/zh/build/smart-contracts/resource-accounts.mdx b/apps/nextra/pages/zh/build/smart-contracts/resource-accounts.mdx deleted file mode 100644 index fe53d42c3..000000000 --- a/apps/nextra/pages/zh/build/smart-contracts/resource-accounts.mdx +++ /dev/null @@ -1,86 +0,0 @@ ---- -title: "Resource Accounts" ---- - -# Resource Accounts - -[Object Code Deployment](deployment.mdx) is the preferred method for deploying and upgrading smart contracts in Aptos. Resource accounts require developers to generate seeds -each time a resource account is created, and upgrading contracts requires specific steps which is prone to error. - -A [resource account](https://github.com/aptos-labs/aptos-core/blob/main/aptos-move/framework/aptos-framework/sources/resource_account.move) -is a developer feature used to manage resources independent of an account -managed by a user, specifically publishing modules and providing on-chain-only -access control, e.g., signers. - -Typically, a resource account is used for two main purposes: - -- Store and isolate resources; a module creates a resource account just to host -specific resources. -- Publish module as a standalone (resource) account, a building block in a -decentralized design where no private keys can control the resource account. The -ownership (SignerCap) can be kept in another module, such as governance. - -## Restrictions - -In Aptos, a resource account is created based upon the SHA3-256 hash of the -source's address and additional seed data. A resource account can be created -only once; for a given source address and seed, there can be only one resource -account. That is because the calculation of the resource account address is -fully determined by the former. - -An entity may call `create_account` in an attempt to claim an account ahead of -the creation of a resource account. But if a resource account is 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_capbility_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 improbably low. - -## Setup - -The easiest way to set up a resource account is by: - -1. Using Aptos CLI: `aptos account create-resource-account` creates a resource -account, and `aptos move create-resource-account-and-publish-package` creates a -resource account and publishes the specified package under the resource account's -address. -2. Writing custom smart contracts code: in the `resource_account.move` module, -developers can find the resource account creation functions -`create_resource_account`, `create_resource_account_and_fund`, and -`create_resource_account_and_publish_package`. Developers can then call those -functions to create resource accounts in their smart contracts. - -Each of those options offers slightly different functionality: - -- `create_resource_account` - merely creates the resource account but doesn't -fund it, retaining access to the resource account's signer until explicitly -calling `retrieve_resource_account_cap`. -- `create_resource_account_and_fund` - creates the resource account and funds it, -retaining access to the resource account's signer until explicitly calling -`retrieve_resource_account_cap`. -- `create_resource_account_and_publish_package` - creates the resource account -and results in loss of access to the resource account by design, because -resource accounts are used to make contracts autonomous and immutable. - -In this example, you will [initialize](https://github.com/aptos-labs/aptos-core/blob/2e9d8ee759fcd3f6e831034f05c1656b1c48efc4/aptos-move/move-examples/mint_nft/sources/minting.move#L73) the `mint_nft` module and retrieve -the signer capability from both the resource account and module account. To do -so, call `create_resource_account_and_publish_package` to publish the module -under the resource account's address. - -1. Initialize the module as shown in the [`minting.move`](https://github.com/aptos-labs/aptos-core/blob/2e9d8ee759fcd3f6e831034f05c1656b1c48efc4/aptos-move/move-examples/mint_nft/sources/minting.move#L73) example. -2. Call `create_resource_account_and_publish_package` to publish the module -under the resource account's address, such as in the [`mint_nft.rs`](https://github.com/aptos-labs/aptos-core/blob/main/aptos-move/e2e-move-tests/src/tests/mint_nft.rs#L62) -end-to-end example. -3. Retrieve the signer cap from the resource account + module account as shown -in the [`minting.move`](https://github.com/aptos-labs/aptos-core/blob/2e9d8ee759fcd3f6e831034f05c1656b1c48efc4/aptos-move/move-examples/mint_nft/sources/minting.move#L83) example. - -Note, if the above `resource_account` signer is **not** already set up as a -resource account, retrieving the signer cap will fail. The `source_addr` field -in the `retrieve_resource_account_cap` function refers to the address of the -source account, or the account that creates the resource account. - -For an example, see the `SignerCapability` employed by the `mint_nft` function -in [`minting.move`](https://github.com/aptos-labs/aptos-core/blob/2e9d8ee759fcd3f6e831034f05c1656b1c48efc4/aptos-move/move-examples/mint_nft/sources/minting.move#L143-L181). - -For more details, see the "resource account" references in [`resource_account.move`](https://github.com/aptos-labs/aptos-core/blob/main/aptos-move/framework/aptos-framework/sources/resource_account.move) -and [`account.move`](https://github.com/aptos-labs/aptos-core/blob/main/aptos-move/framework/aptos-framework/sources/account/account.move). diff --git a/apps/nextra/pages/zh/build/smart-contracts/scripts.mdx b/apps/nextra/pages/zh/build/smart-contracts/scripts.mdx deleted file mode 100644 index 8944f1da0..000000000 --- a/apps/nextra/pages/zh/build/smart-contracts/scripts.mdx +++ /dev/null @@ -1,44 +0,0 @@ ---- -title: "Building with Move Scripts" ---- - -# What are Move Scripts? - -Move Scripts are a way to run multiple public functions on Aptos in a single -transaction. This is similar to deploying a helper module that would do common -tasks, but allows for the flexibility of not having to deploy beforehand. - -An example would be a function to transfer a half of a user's balance to another -account. This is something that is easily programmable, but likely would not -need a module deployed for it: - -```move -script { - use std::signer; - use aptos_framework::coin; - use aptos_framework::aptos_account; - - fun transfer_half(caller: &signer, receiver_address: address) { - // Retrieve the balance of the caller - let caller_address: address = signer::address_of(caller); - let balance: u64 = coin::balance(caller_address); - - // Send half to the receiver - let half = balance / 2; - aptos_account::transfer_coins(caller, receiver_address, half); - } -} -``` - -# Learn more about using Move Scripts - -- [Writing scripts](scripts/writing-scripts.mdx) -- [Compiling scripts](scripts/compiling-scripts.mdx) -- [Running scripts](scripts/running-scripts.mdx) - -# More details - -For more details on Move Scripts, checkout: - -- [Move Book on Scripts](book/modules-and-scripts.mdx) -- [Tutorial on Scripts](scripts/script-tutorial.mdx) diff --git a/apps/nextra/pages/zh/build/smart-contracts/scripts/_meta.tsx b/apps/nextra/pages/zh/build/smart-contracts/scripts/_meta.tsx index 02ce3a81b..a0c324ab2 100644 --- a/apps/nextra/pages/zh/build/smart-contracts/scripts/_meta.tsx +++ b/apps/nextra/pages/zh/build/smart-contracts/scripts/_meta.tsx @@ -1,14 +1,18 @@ export default { "writing-scripts": { title: "Writing Move Scripts", + href: "/en/build/smart-contracts/scripts/writing-scripts", }, "compiling-scripts": { title: "Compiling Move Scripts", + href: "/en/build/smart-contracts/scripts/compiling-scripts", }, "running-scripts": { title: "Running Move Scripts", + href: "/en/build/smart-contracts/scripts/running-scripts", }, "script-tutorial": { title: "Move Scripts Tutorial", + href: "/en/build/smart-contracts/scripts/script-tutorial", }, }; diff --git a/apps/nextra/pages/zh/build/smart-contracts/scripts/compiling-scripts.mdx b/apps/nextra/pages/zh/build/smart-contracts/scripts/compiling-scripts.mdx deleted file mode 100644 index daaec78e6..000000000 --- a/apps/nextra/pages/zh/build/smart-contracts/scripts/compiling-scripts.mdx +++ /dev/null @@ -1,63 +0,0 @@ ---- -title: "Compiling Move Scripts" ---- - -# How can I compile Move Scripts? - -Move scripts can be compiled with the already existing Aptos Move compiler in -the Aptos CLI. For more on how to install and use the Aptos CLI with Move contracts, go to the [Working With Move Contracts](../../cli/working-with-move-contracts.mdx) page. - -Once you have the Aptos CLI installed, you can compile a script by running the following command from within the script package: - -```bash filename="Terminal" -aptos move compile -``` - -There will then be compiled bytecode files under `build/` with the same name as -the function in Move. - -For example this script in package `transfer_half`, would compile -to `build/transfer_half/bytecode_scripts/transfer_half.mv` - -```move -script { - use std::signer; - use aptos_framework::coin; - use aptos_framework::aptos_account; - - fun transfer_half(caller: &signer, receiver_address: address) { - // Retrieve the balance of the caller - let caller_address: address = signer::address_of(caller); - let balance: u64 = coin::balance(caller_address); - - // Send half to the receiver - let half = balance / 2; - aptos_account::transfer_coins(caller, receiver_address, half); - } -} -``` - -Additionally, there is a convenience function for a package with exactly one -script with the below command: - -```bash filename="Terminal" -aptos move compile-script -``` - -Providing output like below returning the exact location of the script and a -hash for convenience - -```bash filename="Terminal" -Compiling, may take a little while to download git dependencies... -UPDATING GIT DEPENDENCY https://github.com/aptos-labs/aptos-framework.git -INCLUDING DEPENDENCY AptosFramework -INCLUDING DEPENDENCY AptosStdlib -INCLUDING DEPENDENCY MoveStdlib -BUILDING transfer_half -{ - "Result": { - "script_location": "/opt/git/developer-docs/apps/docusaurus/static/move-examples/scripts/transfer_half/script.mv", - "script_hash": "9b57ffa952da2a35438e2cf7e941ef2120bb6c2e4674d4fcefb51d5e8431a148" - } -} -``` diff --git a/apps/nextra/pages/zh/build/smart-contracts/scripts/running-scripts.mdx b/apps/nextra/pages/zh/build/smart-contracts/scripts/running-scripts.mdx deleted file mode 100644 index 68fd856b5..000000000 --- a/apps/nextra/pages/zh/build/smart-contracts/scripts/running-scripts.mdx +++ /dev/null @@ -1,105 +0,0 @@ ---- -title: "Running Move Scripts" ---- - -import {Callout} from "nextra/components"; - -# How can I run Move Scripts? - -Move scripts are supported in the Aptos TypeScript SDK, Aptos Wallet Adapter, -and in the Aptos CLI. - -## Running scripts with the TypeScript SDK - -To use a script with the TypeScript SDK, add the `bytecode` directly to the -transaction in place of an entry function name. - -```ts -import { readFileSync } from "fs"; -import { Aptos, Account, AccountAddress } from "@aptos-labs/ts-sdk"; - -// Setup client, and account to sign -const aptos = new Aptos(); -const account = Account.generate(); - -// Load script bytecode -const buffer = readFileSync("./transfer_half.mv", "buffer"); -const bytecode = new Uint8Array.from(buffer); - -// Build a transaction with the bytecode of the script -const transaction = await aptos.transaction.build.simple({ - sender: account.accountAddress, - data: { - bytecode, - typeArguments: [parseTypeTag("0x1::aptos_coin::AptosCoin")], - functionArguments: [AccountAddress.from("0x1")], - }, -}); - -// Submit and wait for the transaction to complete -const pendingTxn = await aptos.signAndSubmitTransaction({ - signer: account, - transaction, -}); -await aptos.waitForTransaction({ transactionHash: pendingTxn.hash }); -``` - -## Running scripts with the Aptos Wallet Adapter - - -Not all wallets support scripts, but when the wallet supports scripts, it can be -provided as below - - -Similar to the TypeScript SDK, the same inputs are accepted as a transaction -type on the wallet adapter. Just simply load the bytecode as a hex `string` or -a `uint8array`. - -```ts -import { useWallet } from "@aptos-labs/wallet-adapter-react"; - -//... - -// Load the bytecode either as a uint8array or a hex encoded string -const BYTECODE_IN_HEX = "0xa11ceb0b...78979"; - -export default function App() { - const { signAndSubmitTransaction } = useWallet(); - - function submitScript() { - signAndSubmitTransaction({ - data: { - bytecode: BYTECODE_IN_HEX, - typeArguments: [parseTypeTag("0x1::aptos_coin::AptosCoin")], - functionArguments: [AccountAddress.from("0x1")], - }, - }); - } - - // ... -} -``` - -## Running scripts with the CLI - -Running scripts with the CLI can be run with the command - -```bash filename="Terminal" -aptos move run-script -``` - -There are two ways to run it, with a pre-compiled script, or it will compile the -script in-place similar to the compile step. - -If you already have a compiled script, you can run it -with `--compiled-script-path` like the example below: - -```bash filename="Terminal" -aptos move run-script --compiled-script-path /opt/git/developer-docs/apps/docusaurus/static/move-examples/scripts/transfer_half/script.mv -``` - -Similarly, if it's not compiled, just use `--script-path` - -```bash filename="Terminal" -aptos move run-script --script-path ./sources/transfer_half.move -``` diff --git a/apps/nextra/pages/zh/build/smart-contracts/scripts/script-tutorial.mdx b/apps/nextra/pages/zh/build/smart-contracts/scripts/script-tutorial.mdx deleted file mode 100644 index 4d123550e..000000000 --- a/apps/nextra/pages/zh/build/smart-contracts/scripts/script-tutorial.mdx +++ /dev/null @@ -1,156 +0,0 @@ ---- -title: "Move Scripts Tutorial" ---- -import { FileTree } from "nextra/components"; - -# Move Scripts - -This tutorial explains how to write and execute a [Move script](../book/modules-and-scripts.mdx). You can use Move scripts to execute a series of commands across published Move module interfaces. - -For more information about scripts, see the [Move scripts docs](../scripts.mdx) - -## Example use case - -The following example calls functions on the [aptos_coin.move](https://github.com/aptos-labs/aptos-core/blob/main/aptos-move/framework/aptos-framework/sources/aptos_coin.move) module to confirm the balance of the destination account is less than `desired_balance`, and if so, tops it up to `desired_balance`. - -```move -script { - use std::signer; - use aptos_framework::aptos_account; - use aptos_framework::aptos_coin; - use aptos_framework::coin; - - fun main(src: &signer, dest: address, desired_balance: u64) { - let src_addr = signer::address_of(src); - - addr::my_module::do_nothing(); - - let balance = coin::balance(src_addr); - if (balance < desired_balance) { - aptos_account::transfer(src, dest, desired_balance - balance); - }; - } -} -``` - -## Execution - -Now that you know what you would like to accomplish, you need to determine: - -- Where do I put these files? -- What do I name them? -- Do I need a `Move.toml`? -- How do I run my script with the CLI? - -Let us run through how to execute a Move script with a step-by-step example using the [Aptos CLI](../../cli/working-with-move-contracts.mdx). - -1. Make a new directory for your work: - - ```bash filename="Terminal" - mkdir testing - cd testing - ``` - -2. Set up the Aptos CLI and [create an account](../../cli/setup-cli.mdx): - - ```bash filename="Terminal" - aptos init --network devnet - ``` - - You may reuse an existing private key (which looks like this: `0xbd944102bf5b5dfafa7fe865d8fa719da6a1f0eafa3cd600f93385482d2c37a4`), or it can generate a new one for you, as part of setting up your account. Let's say your account looks like the example below: - - ```bash filename="Terminal" - --- - profiles: - default: - private_key: "0xbd944102bf5b5dfafa7fe865d8fa719da6a1f0eafa3cd600f93385482d2c37a4" - public_key: "0x47673ec83bb254cc9a8bfdb31846daacd0c96fe41f81855462f5fc5306312b1b" - account: cb265645385819f3dbe71aac266e319e7f77aed252cacf2930b68102828bf615 - rest_url: "https://api.devnet.aptoslabs.com" - faucet_url: "https://faucet.devnet.aptoslabs.com" - ``` - -3. From this same directory, initialize a new Move project: - - ```bash filename="Terminal" - aptos move init --name run_script - ``` - -4. Create a `my_script.move` file containing the example script above in a `sources/` subdirectory of your `testing/` directory. Also, create a `my_module.move` file as seen in the example below: - - ```move - module addr::my_module { - public entry fun do_nothing() { } - } - ``` - - This results in the following file structure: - - - - - - - - - - - -5. Compile the script: - - ```bash filename="Terminal" - $ aptos move compile --named-addresses addr=cb265645385819f3dbe71aac266e319e7f77aed252cacf2930b68102828bf615 - Compiling, may take a little while to download git dependencies... - INCLUDING DEPENDENCY AptosFramework - INCLUDING DEPENDENCY AptosStdlib - INCLUDING DEPENDENCY MoveStdlib - BUILDING run_script - { - "Result": [ - "cb265645385819f3dbe71aac266e319e7f77aed252cacf2930b68102828bf615::my_module" - ] - } - ``` - - Note how we use the `--named-addresses` argument. This is necessary because in the code we refer to this named address called `addr`. The compiler needs to know what this refers to. Instead of using this CLI argument, you could put something like this in your `Move.toml`: - - ```toml - [addresses] - addr = "cb265645385819f3dbe71aac266e319e7f77aed252cacf2930b68102828bf615" - ``` - -6. Run the compiled script: - ```bash filename="Terminal" - $ aptos move run-script --compiled-script-path build/run_script/bytecode_scripts/main.mv --args address:b078d693856a65401d492f99ca0d6a29a0c5c0e371bc2521570a86e40d95f823 --args u64:5 - Do you want to submit a transaction for a range of [17000 - 25500] Octas at a gas unit price of 100 Octas? [yes/no] > - yes - { - "Result": { - "transaction_hash": "0xa6ca6275c73f82638b88a830015ab81734a533aebd36cc4647b48ff342434cdf", - "gas_used": 3, - "gas_unit_price": 100, - "sender": "cb265645385819f3dbe71aac266e319e7f77aed252cacf2930b68102828bf615", - "sequence_number": 4, - "success": true, - "timestamp_us": 1683030933803632, - "version": 3347495, - "vm_status": "Executed successfully" - } - } - ``` - -Note that the path of the compiled script is under `build/run_script/`, not `build/my_script/`. This is because it uses the name of the project contained in `Move.toml`, which is `run_script` from when we ran `aptos move init --name run_script`. - -See the [code](https://github.com/banool/move-examples/tree/main/run_script) used for this document. The full example explains how to use a Move script that relies on a user-created Move module as well. - -See also how to do this with the [Rust SDK](https://github.com/aptos-labs/aptos-developer-discussions/discussions/24) instead of the Aptos CLI in Aptos Developer Discussions. - -## Advanced - -You may execute a script in a more streamlined fashion; instead of running `aptos move compile` and then `aptos move run-script --compiled-script-path` separately, you can just do this: - -```bash filename="Terminal" -$ aptos move run-script --script-path sources/my_script.move --args address:b078d693856a65401d492f99ca0d6a29a0c5c0e371bc2521570a86e40d95f823 --args u64:5 -``` - -This will conduct both steps with a single CLI command yet has [issues](https://github.com/aptos-labs/aptos-core/issues/5733). For this reason, we recommend using the previous two-step approach for now. diff --git a/apps/nextra/pages/zh/build/smart-contracts/scripts/writing-scripts.mdx b/apps/nextra/pages/zh/build/smart-contracts/scripts/writing-scripts.mdx deleted file mode 100644 index c49c1df4c..000000000 --- a/apps/nextra/pages/zh/build/smart-contracts/scripts/writing-scripts.mdx +++ /dev/null @@ -1,56 +0,0 @@ -import { FileTree } from "nextra/components"; - -# How can I write Move Scripts? - -Move scripts can be written in tandem with Move contracts, but it's highly -suggested to use a separate Move package for it. This will make it easier for -you to determine which bytecode file comes from the script. - -## Package layout - -The package needs a Move.toml and a sources directory, similar to code modules. - -For example, we may have a directory layout like: - - - - - - - - - - -## Script syntax - -Scripts can be written exactly the same as modules on Aptos. Imports can be used -for any dependencies in the Move.toml file, and all public functions, including -entry functions, can be called from the contract. There are a few limitations: - -- There must be only one function in the contract, it will compile to that name. -- Input arguments can only be one of - [`u8`, `u16`, `u32`, `u64`, `u256`, `address`, `bool`, `signer`, `&signer`, `vector`]. - There is no support for vectors of other types, or structs. - -An example below: - -```move -script { - use std::signer; - use aptos_framework::coin; - use aptos_framework::aptos_account; - - fun transfer_half(caller: &signer, receiver_address: address) { - // Retrieve the balance of the caller - let caller_address: address = signer::address_of(caller); - let balance: u64 = coin::balance(caller_address); - - // Send half to the receiver - let half = balance / 2; - aptos_account::transfer_coins(caller, receiver_address, half); - } -} -``` - -For more specific details see: -[Move Book on Scripts](../book/modules-and-scripts.mdx) diff --git a/apps/nextra/pages/zh/build/smart-contracts/smart-table.mdx b/apps/nextra/pages/zh/build/smart-contracts/smart-table.mdx deleted file mode 100644 index 4ebf4fc4a..000000000 --- a/apps/nextra/pages/zh/build/smart-contracts/smart-table.mdx +++ /dev/null @@ -1,193 +0,0 @@ ---- -title: "Smart Table" ---- - -# Smart Table - -The Smart Table is a scalable hash table implementation based on linear hashing. -This data structure aims to optimize storage and performance by utilizing linear hashing, which splits one bucket at a time instead of doubling the number of buckets, thus avoiding unexpected gas costs. -The Smart Table uses the SipHash function for faster hash computations while tolerating collisions. - -## Core Features of SmartTable - -### Structure - -The `SmartTable` struct is designed to handle dynamic data efficiently: - -- `buckets`: A table with a length that stores vectors of entries. -- `num_buckets`: The current number of buckets. -- `level`: The number of bits representing `num_buckets`. -- `size`: The total number of items in the table. -- `split_load_threshold`: The load threshold percentage that triggers bucket splits. -- `target_bucket_size`: The target size of each bucket, which is not strictly enforced. - -### Constants - -The following constants define various error codes used within the module: - -- `ENOT_FOUND`: 1 -- `EZERO_CAPACITY`: 2 -- `ENOT_EMPTY`: 3 -- `EALREADY_EXIST`: 4 -- `EINVALID_LOAD_THRESHOLD_PERCENT`: 5 -- `EINVALID_TARGET_BUCKET_SIZE`: 6 -- `EEXCEED_MAX_BUCKET_SIZE`: 7 -- `EINVALID_BUCKET_INDEX`: 8 -- `EINVALID_VECTOR_INDEX`: 9 - -### API Overview - -#### Creating Tables - -- `new(): SmartTable`: Creates an empty table with default configurations. -- `new_with_config(num_initial_buckets: u64, split_load_threshold: u8, target_bucket_size: u64): SmartTable`: Creates an empty table with custom configurations. - -#### Destroying Tables - -- `destroy_empty(table: SmartTable)`: Destroys an empty table. -- `destroy(table: SmartTable)`: Destroys a table and its elements. -- `clear(table: &mut SmartTable)`: Clears all elements from the table. - -#### Managing Entries - -- `add(table: &mut SmartTable, key: K, value: V)`: Adds a key-value pair to the table. -- `add_all(table: &mut SmartTable, keys: vector, values: vector)`: Adds multiple key-value pairs to the table. -- `remove(table: &mut SmartTable, key: K): V`: Removes and returns the value associated with a key. -- `upsert(table: &mut SmartTable, key: K, value: V)`: Inserts or updates a key-value pair. - -#### Retrieving Entries - -- `borrow(table: &SmartTable, key: K): &V`: Returns an immutable reference to the value associated with a key. -- `borrow_with_default(table: &SmartTable, key: K, default: &V): &V`: Returns the value associated with a key or a default value if the key is not found. -- `borrow_mut(table: &mut SmartTable, key: K): &mut V`: Returns a mutable reference to the value associated with a key. -- `borrow_mut_with_default(table: &mut SmartTable, key: K, default: V): &mut V`: Inserts a key-value pair if the key is not found, then returns a mutable reference to the value. - -#### Utility Functions - -- `length(table: &SmartTable): u64`: Returns the number of entries in the table. -- `load_factor(table: &SmartTable): u64`: Returns the load factor of the table. -- `update_split_load_threshold(table: &mut SmartTable, split_load_threshold: u8)`: Updates the split load threshold. -- `update_target_bucket_size(table: &mut SmartTable, target_bucket_size: u64)`: Updates the target bucket size. -- `to_simple_map(table: &SmartTable): SimpleMap`: Converts the smart table to a simple map. - -## Example Usage - -### Creating and Using a SmartTable - -```move filename="smart_table.move" -module 0x42::smart_table_usage { - use aptos_std::smart_table; - - public entry fun main() { - let table = smart_table::new(); - smart_table::add(&mut table, 1, 100); - smart_table::add(&mut table, 2, 200); - - let length = smart_table::length(&table); - assert!(length == 2, 0); - - let value1 = smart_table::borrow(&table, 1); - assert!(*value1 == 100, 0); - - let value2 = smart_table::borrow(&table, 2); - assert!(*value2 == 200, 0); - - let removed_value = smart_table::remove(&mut table, 1); - assert!(removed_value == 100, 0); - - smart_table::destroy_empty(table); - } -} -``` - -### Adding Multiple Entries to a SmartTable - -```move filename="smart_table.move" -module 0x42::smart_table_usage { - use aptos_std::smart_table; - - public fun add_multiple_entries() { - let table = smart_table::new(); - let keys = vector[1, 2, 3]; - let values = vector[100, 200, 300]; - - smart_table::add_all(&mut table, keys, values); - - let length = smart_table::length(&table); - assert!(length == 3, 0); - - let value1 = smart_table::borrow(&table, 1); - assert!(*value1 == 100, 0); - - let value2 = smart_table::borrow(&table, 2); - assert!(*value2 == 200, 0); - - let value3 = smart_table::borrow(&table, 3); - assert!(*value3 == 300, 0); - - smart_table::destroy_empty(table); - } -} -``` - -### Updating and Clearing Table - -```move filename="smart_table.move" -module 0x42::smart_table_usage { - use aptos_std::smart_table; - - public fun update_and_clear_table() { - let table = smart_table::new(); - smart_table::add(&mut table, 1, 100); - smart_table::add(&mut table, 2, 200); - - smart_table::upsert(&mut table, 2, 300); - let value2 = smart_table::borrow(&table, 2); - assert!(*value2 == 300, 0); - - smart_table::clear(&mut table); - let length = smart_table::length(&table); - assert!(length == 0, 0); - - smart_table::destroy_empty(table); - } -} -``` - - -### Converting to Simple Map - -```move filename="smart_table.move" -module 0x42::smart_table_usage { - use aptos_std::smart_table; - use aptos_std::simple_map; - - public fun convert_to_simple_map() { - let table = smart_table::new(); - smart_table::add(&mut table, 1, 100); - smart_table::add(&mut table, 2, 200); - - let map = smart_table::to_simple_map(&table); - let length = simple_map::length(&map); - assert!(length == 2, 0); - - let value1 = simple_map::borrow(&map, &1); - assert!(*value1 == 100, 0); - - let value2 = simple_map::borrow(&map, &2); - assert!(*value2 == 200, 0); - - smart_table::destroy(table); - } -} -``` - -## Source Code - -- [smart_table.move](https://github.com/aptos-labs/aptos-core/blob/main/aptos-move/framework/aptos-stdlib/sources/data_structures/smart_table.move) - -## Other Examples - -- [Move Spiders Smart Table](https://movespiders.com/courses/modules/datastructures/lessonId/7) -- [Move Spiders Querying Smart Table via FullNode APIs](https://movespiders.com/courses/modules/datastructures/lessonId/9) -- [Move Spiders Querying Smart Table via View Function](https://movespiders.com/courses/modules/datastructures/lessonId/10) diff --git a/apps/nextra/pages/zh/build/smart-contracts/smart-vector.mdx b/apps/nextra/pages/zh/build/smart-contracts/smart-vector.mdx deleted file mode 100644 index 99934b5cc..000000000 --- a/apps/nextra/pages/zh/build/smart-contracts/smart-vector.mdx +++ /dev/null @@ -1,200 +0,0 @@ ---- -title: "Smart Vector" ---- - -# Smart Vector - -The Smart Vector is a scalable vector implementation based on `tables`, where elements are grouped into buckets. This data structure allows for efficient handling of large data sets by combining the flexibility of small vectors with the scalability of larger structures. - -## Core Features of `SmartVector` - -### Structure - -The `SmartVector` struct is designed to handle dynamic data with efficiency: - -- `inline_vec`: A small vector that stores elements directly. -- `big_vec`: An optional large vector for scalable storage. -- `inline_capacity`: An optional value defining the capacity of `inline_vec`. -- `bucket_size`: An optional value defining the size of buckets in `big_vec`. - -### Constants - -The following constants define various error codes used within the module: - -- `EINDEX_OUT_OF_BOUNDS`: 1 -- `EVECTOR_NOT_EMPTY`: 2 -- `EVECTOR_EMPTY`: 3 -- `EZERO_BUCKET_SIZE`: 4 -- `ESMART_VECTORS_LENGTH_MISMATCH`: 0x20005 - -## API Overview - -### Creating Vectors - -- `new(): SmartVector`: Creates an empty vector. -- `empty_with_config(inline_capacity: u64, bucket_size: u64): SmartVector`: Creates an empty vector with custom capacity and bucket size. -- `singleton(element: T): SmartVector`: Creates a vector with a single element. - -### Destroying Vectors - -- `destroy_empty(v: SmartVector)`: Destroys an empty vector. -- `destroy(v: SmartVector)`: Destroys a vector and its elements. - -### Managing Elements - -- `push_back(v: &mut SmartVector, val: T)`: Adds an element to the end of the vector. -- `pop_back(v: &mut SmartVector): T`: Removes the last element from the vector. -- `remove(v: &mut SmartVector, i: u64): T`: Removes an element at a specific index. -- `swap_remove(v: &mut SmartVector, i: u64): T`: Swaps an element at a specific index with the last element and removes it. -- `borrow(v: &SmartVector, i: u64): &T`: Returns an immutable reference to an element at a specific index. -- `borrow_mut(v: &mut SmartVector, i: u64): &mut T`: Returns a mutable reference to an element at a specific index. - -### Utility Functions - -- `length(v: &SmartVector): u64`: Returns the number of elements in the vector. -- `is_empty(v: &SmartVector): bool`: Checks if the vector is empty. -- `clear(v: &mut SmartVector)`: Clears all elements from the vector. -- `to_vector(v: &SmartVector): vector`: Converts a smart vector to a native vector. - -## Example Usage - -### Creating and Using a SmartVector - -```move filename="smart_vector.move" -module 0x42::smart_vector_usage { - use aptos_std::smart_vector; - - public entry fun main() { - let v = smart_vector::new(); - smart_vector::push_back(&mut v, 10); - smart_vector::push_back(&mut v, 20); - let length = smart_vector::length(&v); - assert!(length == 2, 0); - let first_elem = smart_vector::borrow(&v, 0); - assert!(*first_elem == 10, 0); - let second_elem = smart_vector::borrow(&v, 1); - assert!(*second_elem == 20, 0); - let last_elem = smart_vector::pop_back(&mut v); - assert!(last_elem == 20, 0); - smart_vector::destroy_empty(v); - } -} -``` - -### Appending Vectors - -```move filename="smart_vector.move" -module 0x42::smart_vector_usage { - use aptos_std::smart_vector; - - public fun append_vectors() { - let v1 = smart_vector::new(); - let v2 = smart_vector::new(); - - smart_vector::push_back(&mut v1, 1); - smart_vector::push_back(&mut v1, 2); - - smart_vector::push_back(&mut v2, 3); - smart_vector::push_back(&mut v2, 4); - - smart_vector::append(&mut v1, v2); - - let length = smart_vector::length(&v1); - assert!(length == 4, 0); - - let first_elem = smart_vector::borrow(&v1, 0); - assert!(*first_elem == 1, 0); - - let second_elem = smart_vector::borrow(&v1, 1); - assert!(*second_elem == 2, 0); - - let third_elem = smart_vector::borrow(&v1, 2); - assert!(*third_elem == 3, 0); - - let fourth_elem = smart_vector::borrow(&v1, 3); - assert!(*fourth_elem == 4, 0); - } -} -``` - -### Removing Elements - -```move filename="smart_vector.move" -module 0x42::smart_vector_usage { - use aptos_std::smart_vector; - - public fun remove_elements() { - let v = smart_vector::new(); - - smart_vector::push_back(&mut v, 1); - smart_vector::push_back(&mut v, 2); - smart_vector::push_back(&mut v, 3); - - let removed_elem = smart_vector::remove(&mut v, 1); - assert!(removed_elem == 2, 0); - - let length = smart_vector::length(&v); - assert!(length == 2, 0); - - let first_elem = smart_vector::borrow(&v, 0); - assert!(*first_elem == 1, 0); - - let second_elem = smart_vector::borrow(&v, 1); - assert!(*second_elem == 3, 0); - } -} -``` - - -### Clearing the Vector - -```move filename="smart_vector.move" -module 0x42::smart_vector_usage { - use aptos_std::smart_vector; - - public fun clear_vector() { - let v = smart_vector::new(); - - smart_vector::push_back(&mut v, 1); - smart_vector::push_back(&mut v, 2); - smart_vector::push_back(&mut v, 3); - - smart_vector::clear(&mut v); - let length = smart_vector::length(&v); - assert!(length == 0, 0); - } -} -``` - -### Swapping Elements - -```move filename="smart_vector.move" -module 0x42::smart_vector_usage { - use aptos_std::smart_vector; - - public fun swap_elements() { - let v = smart_vector::new(); - - smart_vector::push_back(&mut v, 1); - smart_vector::push_back(&mut v, 2); - smart_vector::push_back(&mut v, 3); - - smart_vector::swap(&mut v, 0, 2); - - let first_elem = smart_vector::borrow(&v, 0); - assert!(*first_elem == 3, 0); - - let third_elem = smart_vector::borrow(&v, 2); - assert!(*third_elem == 1, 0); - } -} -``` - -## Source Code - -- [smart_vector.move](https://github.com/aptos-labs/aptos-core/blob/main/aptos-move/framework/aptos-stdlib/sources/data_structures/smart_vector.move) - -## Other Examples - -- [move spiders tutorial on smart vectors](https://movespiders.com/courses/modules/datastructures/lessonId/6) -- [move spiders tutorial on querying smart vectors](https://movespiders.com/courses/modules/datastructures/lessonId/9) diff --git a/apps/nextra/pages/zh/build/smart-contracts/table.mdx b/apps/nextra/pages/zh/build/smart-contracts/table.mdx deleted file mode 100644 index 33a55d1ad..000000000 --- a/apps/nextra/pages/zh/build/smart-contracts/table.mdx +++ /dev/null @@ -1,130 +0,0 @@ ---- -title: "Table" ---- - -# Table - -The `Table` provides a flexible and efficient way to manage large amounts of data in a table format. Each table item is represented as a separate global state item, allowing for scalable storage solutions. - -## Core Features of Table - -### Structure - -The `Table` struct is designed to handle large-scale storage with efficiency: - -- `handle`: An address that uniquely identifies the table. - -### Constants - -The following constants define various error codes used within the module (these are implied but not explicitly stated in the provided code): - -- `ENOT_FOUND`: Key not found in the table. -- `EALREADY_EXIST`: Key already exists in the table. -- `EINVALID_ARGUMENT`: Invalid argument passed to a function. - -### API Overview - -#### Creating Tables - -- `new(): Table`: Creates a new table. - -#### Managing Entries - -- `add(table: &mut Table, key: K, val: V)`: Adds a key-value pair to the table. Aborts if the key already exists. -- `remove(table: &mut Table, key: K): V`: Removes and returns the value associated with a key. Aborts if the key is not found. -- `upsert(table: &mut Table, key: K, value: V)`: Inserts or updates a key-value pair. - -#### Retrieving Entries - -- `borrow(table: &Table, key: K): &V`: Returns an immutable reference to the value associated with a key. Aborts if the key is not found. -- `borrow_with_default(table: &Table, key: K, default: &V): &V`: Returns the value associated with a key or a default value if the key is not found. -- `borrow_mut(table: &mut Table, key: K): &mut V`: Returns a mutable reference to the value associated with a key. Aborts if the key is not found. -- `borrow_mut_with_default(table: &mut Table, key: K, default: V): &mut V`: Inserts a key-value pair if the key is not found, then returns a mutable reference to the value. - -#### Utility Functions - -- `contains(table: &Table, key: K): bool`: Checks if the table contains a key. - -## Example Usage - -### Creating and Using a Table - -```move filename="table.move" -module 0x42::table_usage { - use aptos_std::table; - - public entry fun main() { - let table = table::new(); - table::add(&mut table, 1, 100); - table::add(&mut table, 2, 200); - - let value1 = table::borrow(&table, 1); - assert!(*value1 == 100, 0); - - let value2 = table::borrow(&table, 2); - assert!(*value2 == 200, 0); - - let removed_value = table::remove(&mut table, 1); - assert!(removed_value == 100, 0); - - let contains_key = table::contains(&table, 2); - assert!(contains_key, 0); - - // Note: A table must be stored in a resource at the end of a function - } -} -``` - -### Adding and Upserting Multiple Entries - -```move filename="table.move" -module 0x42::table_usage { - use aptos_std::table; - - public fun add_and_upsert_entries() { - let table = table::new(); - table::add(&mut table, 1, 100); - table::upsert(&mut table, 1, 200); - table::upsert(&mut table, 2, 300); - - let value1 = table::borrow(&table, 1); - assert!(*value1 == 200, 0); - - let value2 = table::borrow(&table, 2); - assert!(*value2 == 300, 0); - - // Note: A table must be stored in a resource at the end of a function - } -} -``` - -### Borrowing Mutable References - -```move filename="table.move" -module 0x42::table_usage { - use aptos_std::table; - - public fun borrow_mutable_references() { - let table = table::new(); - table::add(&mut table, 1, 100); - - let value_mut = table::borrow_mut(&mut table, 1); - *value_mut = 200; - - let value = table::borrow(&table, 1); - assert!(*value == 200, 0); - - // Note: A table must be stored in a resource at the end of a function - } -} -``` - -## Source Code - -- [table.move](https://github.com/aptos-labs/aptos-core/blob/main/aptos-move/framework/aptos-stdlib/sources/table.move) - -## Other Examples - -- [Move Spiders Tables Tutorial](https://movespiders.com/courses/modules/datastructures/lessonId/4) -- [Move Spiders Query Table via FullNode](https://movespiders.com/courses/modules/datastructures/lessonId/9) -- [Move Spiders Query Table via View Function](https://movespiders.com/courses/modules/datastructures/lessonId/10) diff --git a/apps/nextra/pages/zh/build/smart-contracts/third-party-dependencies.mdx b/apps/nextra/pages/zh/build/smart-contracts/third-party-dependencies.mdx deleted file mode 100644 index dcc3577bd..000000000 --- a/apps/nextra/pages/zh/build/smart-contracts/third-party-dependencies.mdx +++ /dev/null @@ -1,277 +0,0 @@ ---- -title: "Third Party Dependencies" ---- - -import { FileTree } from "nextra/components"; -import { Callout } from 'nextra/components' - - -# Third Party Dependencies - -Third party dependencies are external [modules](book/modules-and-scripts.mdx) that a controlled module interacts with. Typically, these external modules exist under different accounts. - - -## Multi-DEX router example - -A multi-DEX router actively utilizes third party dependencies. Instead of submitting multiple transactions to interact with different DEXs and their individual [entry](book/functions.mdx#entry-modifier) functions within a swap route, a module (or [script](book/modules-and-scripts.mdx#scripts)) can consolidate all independent DEX invocations into a single, atomic transaction. The multi-DEX router references and calls to functions present in each of the third party DEX modules to achieve this. - -## Sources - -Third party dependencies will have varying amounts of reliability and available information based on where they’re sourced from. Specifically, documentation for a few instances will be non-existant, as well as logic potentially being refactored. - -Source code that is verified against the on-chain deployed module, like the [Git Repository](#git-repository) and [Local Source Code](#local-source-code), should always be preferred. If neither of those are available, there are other options to depend on usable code, like [decompiled code](#decompiled-code), [bytecode](#bytecode), and [ABI](#abi)-crafted code. - -### Git Repository - -The default `Move.toml` includes `AptosFramework` as a git repository dependency: - -```toml filename="Move.toml" - [dependencies.AptosFramework] - git = "" - rev = "mainnet" - subdir = "aptos-framework" -``` - -When Aptos CLI commands are ran, updates to the dependency are automatically retrieved and compiled against. - -### Local Source Code - -Third party source code can be included in the `sources` directory. Essentially treating it the same as custom code. - - - - - - - - - - - - - Any upgrades to the Third Party dependency will need to be retrieved, manually. - - -### Decompiled code - -Move code can be reconstructed by using the [Revela Compiler](https://aptoslabs.medium.com/move-revealed-the-revela-decompiler-b206eaf48b45#27ad) against a package’s bytecode: - -```bash filename="Terminal" -aptos move decompile --package-path ./bytecode_modules -``` - -Corresponding `{ModuleName}.mv.move` files will be generated in `bytecode_modules`. - - - - - - - - - - -Reference it as a local source file after changing the file type to `{ModuleName}.move` and placing it in the workspace’s `sources` directory. - - - - - - - - - - - Decompiled code will keep behaviors of on-chain execution, but will be refactored. - - - -### Bytecode - -The Aptos CLI allows for downloading a [package's](book/packages.mdx) bytecode. - -```bash filename="Terminal" -aptos move download --account {account_addr} --bytecode --package {package_name} -``` - -Each bytecode dependency requires their own package, with a structure of: -- `Move.toml` file, with the package address pre-defined. -- `build/{ModuleName}/bytecode_modules` directory with the bytecode inside. -- Empty sources directory. - -The controlled module can then reference the dependency, upon it's inclusion in the controlled package's `Move.toml`. - -
- -Aptos Token example - -A controlled `invoking_code.move` module interacts with the external `aptos_token` module: -```move filename="invoking_code.move" -module invoker::invoking_code { - use aptos_token_objects_addr::aptos_token; - - public entry fun wrapper_add_property(): u64 { - aptos_token::add_property(...); - } -} -``` - -The below command retrieves the necessary [AptosTokenObjects package](https://explorer.aptoslabs.com/account/0x4/modules/code/aptos_token/mint?network=mainnet) bytecode from the Mainnet. - -```bash filename="Terminal" -aptos move download --account 0x4 \ ---bytecode --package AptosTokenObjects \ ---output-dir ./aptos_token_bytecode_output/ -``` - - - - - - - - - - - - - - - - - - -The created dependency package structure and contents for `aptos_token` is: - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -```toml filename="aptos_token_objects_addr/Move.toml" -[package] -name = "aptos_token" -version = "0.0.0" -[addresses] -aptos_token_module_addr = "0x4" -``` - - -The dependency list from the controlled `invoking_code.move` module will include a local reference to the bytecode package, allowing for compilation. -```toml filename="invoking_code/Move.toml" -[package] -name = "invoking_code" -[addresses] -invoker = "_" -[dependencies] -aptos_token = { local = "../aptos_token_objects_addr" } -``` -
- -### ABI - -Move interface code can be manually crafted by reading a package's ABI. Notably, while the function header is required to be exact, the logic within is not. - - - All available public and entry methods are defined with their name, arguments, return values, and more, within the ABI. Structs and resources will also be included. - - -Afterwards, the interface code is treated equivalent to local source code. - - - - -
- Aptos Token example - - Below is a portion of the `AptosTokenObjects` ABI, gathered from the [Aptos Explorer](https://explorer.aptoslabs.com/account/0x0000000000000000000000000000000000000000000000000000000000000004/modules/code/aptos_token?network=mainnet#:~:text=1114-,ABI,-%7B): - - - - ```json - { - "address": "0x4", - "name": "aptos_token", - "friends": [], - "exposed_functions": [ - { - "name": "add_property", - "visibility": "public", - "is_entry": true, - "is_view": false, - "generic_type_params": [ - { - "constraints": [ - "key" - ] - } - ], - "params": [ - "&signer", - "0x1::object::Object", - "0x1::string::String", - "0x1::string::String", - "vector" - ], - "return": [] - }, - ... - ] - } - ``` - - - An interface Move file can be handwritten and treated as a source file. Looking similar to: - - ```move - module 0x4::aptos_token { - // ... - public entry fun add_property( - creator: &signer, - token: Object, - key: String, - type: String, - value: vector, - ) acquires AptosCollection, AptosToken { - abort 0 - } - } - ``` - - - - - - - - - - - - -
- - diff --git a/apps/nextra/pages/zh/build/smart-contracts/tokens.mdx b/apps/nextra/pages/zh/build/smart-contracts/tokens.mdx deleted file mode 100644 index fbb8ed706..000000000 --- a/apps/nextra/pages/zh/build/smart-contracts/tokens.mdx +++ /dev/null @@ -1,136 +0,0 @@ ---- -title: "Aptos Token Standard Overview" ---- - -# Aptos Token Standards - -The [Aptos Digital Asset Standard](digital-asset.mdx) defines the -canonical Non-fungible Token on Aptos. Aptos leverages composability to extend -the digital asset standard with features like fungibility via -the [Fungible Asset standard](fungible-asset.mdx). The concept of -composability comes from the underlying data model for these constructs: -the [Move object](objects.mdx) data model. - -The rest of this document discusses how the Aptos token standards compare to the -standards on Ethereum and Solana. - -## Data models - -To understand tokens, we begin by comparing the data models across different -blockchains. - -### Ethereum - -Ethereum has two types of accounts: - -- Externally-owned accounts which store a balance of Ether. -- Contract accounts which manage their underlying smart contracts and have an -associated storage for persistent state, which can only be mutated by the -associated contract. - -In order to create a new NFT collection, a creator must deploy their own -contract to the blockchain, which in turn will create a collection and set of -NFTs within its storage. - -### Solana - -Unlike Ethereum or Aptos where data and code co-exist, Solana stores data and -programs in separate accounts. There are two types of accounts on the Solana -blockchain: - -- Executable accounts only store contract code -- Non-executable accounts store data associated with and owned by executable -accounts. - -In order to create a new NFT collection, a creator calls an existing deployed -program to populate a new collection and set of NFTs. - -### Aptos - -The [accounts](../../network/blockchain/accounts.mdx) in Aptos store both smart contracts -and data. Unlike Ethereum, the associated data of a smart contract is -distributed across the space of all accounts -in [resources](../../network/blockchain/resources.mdx) -within [accounts](../../network/blockchain/accounts.mdx) -or [objects](objects.mdx). For example, a collection and an -NFT within that collection are stored in distinct objects at different addresses -with the smart contract defining them at another address. A smart contract -developer could also store data associated with the NFT and collection at the -same address as the smart contract or in other objects. - -There are two means to create NFTs on Aptos: - -- The [no-code standard](https://github.com/aptos-foundation/AIPs/blob/main/aips/aip-22.md) -allows creators to call into the contract to create new collections and tokens -without deploying a new contract. -- Custom NFT contracts allow creators to customize their NFTs by extending the -object model that can manage all aspects of their collection. - -Aptos strikes a balance between the customizability offered by Ethereum with the -simplicity of creating new collections like Solana. - -Like Ethereum, Aptos requires indexing to determine the set of all NFTs owned by -an account, while Solana has no need. - -## Token standard comparison - -The Fungible Token (FT) was initially introduced -by [EIP-20](https://eips.ethereum.org/EIPS/eip-20), and Non-Fungible Token (NFT) -was defined in [EIP-721](https://eips.ethereum.org/EIPS/eip-721). -Later, [EIP-1155](https://eips.ethereum.org/EIPS/eip-1155) combined FT and NFT -or even Semi-Fungible Token (SFT) into one standard. - -The Ethereum token standards requires each token to deploy their own individual -contract code to distinguish collection of tokens. Solana account model enables -another pattern where code can be reused so that one generic program operates on -various data. To create a new token, you could create an account that can mint -tokens and more accounts that can receive them. The mint account itself uniquely -determines the token type instead of contract account, and these are all passed -as arguments to the one contract deployed to some executable account. - -The collection of Aptos token standards shares some similarities with Solana, -especially how it covers FT, NFT and SFT into a common on-chain code. Instead of -deploying a new smart contract for each new token, a creator calls a function in -the contract with the necessary arguments. Depending on which function you call, -the token contract will mint/transfer/burn/... tokens. - -### Token identification - -Aptos identifies a token by its `Address` or `ObjectId`, a location within -global storage. Collections are stored at a location determined by the address -of the creator and the name of the collection. - -In Ethereum, contracts are deployed on accounts determined by the account that -is deploying the contract. NFTs are then stored as indexes into data tables -within the contract. - -In Solana, NFT data is stored under a mint account, independent of the program -account. - -### Token metadata - -Aptos token has metadata in its `Token` resource with the data most commonly -required by dapps to interact with tokens. Some examples include: - -- `name`: The name of the token. It must be unique within a collection. -- `description`: The description of the token. -- `uri`: A URL pointer to off-chain for more information about the token. The -asset could be media such as an image or video or more metadata in a JSON -file. -- `collection`: A pointer to the ObjectId of the collection. - -Additional fields can be stored in creator-defined resources or -the `PropertyMap` resource that defines a generalizable key-value map. - -In Ethereum, only a small portion of such properties are defined as methods, -such as `name()`, `symbol()`, `decimals()`, `totalSupply()` of ERC-20; -or `name()` and `symbol()` and `tokenURI()` of the optional metadata extension -for ERC-721; ERC-1155 also has a similar method `uri()` in its own optional -metadata extension. Token metadata is not standardized so that dapps have to -take special treatment case by case. - -In Solana, the Token Metadata program offers a Metadata Account defining -numerous metadata fields associated with a token as well, including `collection` -which is defined in `TokenDataId` in Aptos. Solana, however, does not offer -mutability for assets, unlike Aptos. Like Aptos, Token Metadata v1.1.0 offers -an `attribute` container for customized properties. diff --git a/apps/nextra/pages/zh/build/smart-contracts/vector.mdx b/apps/nextra/pages/zh/build/smart-contracts/vector.mdx deleted file mode 100644 index b90a6460d..000000000 --- a/apps/nextra/pages/zh/build/smart-contracts/vector.mdx +++ /dev/null @@ -1,192 +0,0 @@ ---- -title: "Vector" ---- - -# Vector - -The Vector in Move provides a flexible and dynamic array-like data structure that supports various operations such -as indexing, adding, and removing elements. Vectors in Move are growable and support 0-based indexing. - -## Core Features of Vector - -### Structure - -The `vector` module provides various native and Move functions to manage dynamic arrays: - -- `empty`: Creates an empty vector. -- `length`: Returns the length of the vector. -- `borrow`: Returns an immutable reference to an element at a given index. -- `push_back`: Adds an element to the end of the vector. -- `borrow_mut`: Returns a mutable reference to an element at a given index. -- `pop_back`: Removes and returns the last element of the vector. -- `destroy_empty`: Destroys an empty vector. -- `swap`: Swaps elements at two given indices. - -### Constants - -The following constants define various error codes used within the module: - -- `EINDEX_OUT_OF_BOUNDS`: 0x20000 - -### API Overview - -#### Creating Vectors - -- `empty(): vector`: Creates an empty vector. -- `singleton(e: Element): vector`: Creates a vector with a single element. - -#### Managing Elements - -- `push_back(v: &mut vector, e: Element)`: Adds an element to the end of the vector. -- `pop_back(v: &mut vector): Element`: Removes and returns the last element from the vector. -- `remove(v: &mut vector, i: u64): Element`: Removes an element at a specific index and shifts subsequent elements. -- `swap_remove(v: &mut vector, i: u64): Element`: Swaps the element at the given index with the last element and removes it. - -#### Retrieving Elements - -- `borrow(v: &vector, i: u64): &Element`: Returns an immutable reference to an element at a given index. -- `borrow_with_default(v: &vector, i: u64, default: &Element): &Element`: Returns a reference to an element or a default value if the index is out of bounds. -- `borrow_mut(v: &mut vector, i: u64): &mut Element`: Returns a mutable reference to an element at a given index. - -#### Utility Functions - -- `length(v: &vector): u64`: Returns the number of elements in the vector. -- `is_empty(v: &vector): bool`: Checks if the vector is empty. -- `contains(v: &vector, e: &Element): bool`: Checks if the vector contains a given element. -- `index_of(v: &vector, e: &Element): (bool, u64)`: Returns the index of a given element if found. -- `reverse(v: &mut vector)`: Reverses the order of elements in the vector. -- `append(lhs: &mut vector, other: vector)`: Appends all elements of one vector to another. -- `for_each(v: vector, f: |Element|)`: Applies a function to each element in the vector. -- `for_each_ref(v: &vector, f: |&Element|)`: Applies a function to a reference of each element in the vector. -- `for_each_mut(v: &mut vector, f: |&mut Element|)`: Applies a function to a mutable reference of each element in the vector. -- `fold(v: vector, init: Accumulator, f: |Accumulator, Element|Accumulator): Accumulator`: Applies a function to accumulate a value over the elements of the vector. -- `map(v: vector, f: |Element|NewElement): vector`: Maps a function over the elements of the vector, producing a new vector. -- `filter(v: vector, p: |&Element|bool): vector`: Filters the vector using a predicate function. - -## Example Usage - -### Creating and Using a Vector - -```move filename="vector.move" -module 0x42::vector_usage { - use std::vector; - - public entry fun main() { - let v = vector::empty(); - vector::push_back(&mut v, 10); - vector::push_back(&mut v, 20); - - let length = vector::length(&v); - assert!(length == 2, 0); - - let first_elem = vector::borrow(&v, 0); - assert!(*first_elem == 10, 0); - - let second_elem = vector::borrow(&v, 1); - assert!(*second_elem == 20, 0); - - let last_elem = vector::pop_back(&mut v); - assert!(last_elem == 20, 0); - - vector::destroy_empty(v); - } -} -``` - -### Appending Vectors - -```move filename="vector.move" -module 0x42::vector_usage { - use std::vector; - - public fun append_vectors() { - let v1 = vector::empty(); - let v2 = vector::empty(); - - vector::push_back(&mut v1, 1); - vector::push_back(&mut v1, 2); - - vector::push_back(&mut v2, 3); - vector::push_back(&mut v2, 4); - - vector::append(&mut v1, v2); - - let length = vector::length(&v1); - assert!(length == 4, 0); - - let first_elem = vector::borrow(&v1, 0); - assert!(*first_elem == 1, 0); - - let second_elem = vector::borrow(&v1, 1); - assert!(*second_elem == 2, 0); - - let third_elem = vector::borrow(&v1, 2); - assert!(*third_elem == 3, 0); - - let fourth_elem = vector::borrow(&v1, 3); - assert!(*fourth_elem == 4, 0); - } -} -``` - -### Removing Elements - -```move filename="vector.move" -module 0x42::vector_usage { - use std::vector; - - public fun remove_elements() { - let v = vector::empty(); - - vector::push_back(&mut v, 1); - vector::push_back(&mut v, 2); - vector::push_back(&mut v, 3); - - let removed_elem = vector::remove(&mut v, 1); - assert!(removed_elem == 2, 0); - - let length = vector::length(&v); - assert!(length == 2, 0); - - let first_elem = vector::borrow(&v, 0); - assert!(*first_elem == 1, 0); - - let second_elem = vector::borrow(&v, 1); - assert!(*second_elem == 3, 0); - } -} -``` - -### Swapping Elements - -```move filename="vector.move" -module 0x42::vector_usage { - use std::vector; - - public fun swap_elements() { - let v = vector::empty(); - - vector::push_back(&mut v, 1); - vector::push_back(&mut v, 2); - vector::push_back(&mut v, 3); - - vector::swap(&mut v, 0, 2); - - let first_elem = vector::borrow(&v, 0); - assert!(*first_elem == 3, 0); - - let third_elem = vector::borrow(&v, 2); - assert!(*third_elem == 1, 0); - } -} -``` - - -## Source Code - -- [vector.move](https://github.com/aptos-labs/aptos-core/blob/main/third_party/move/move-stdlib/sources/vector.move) - -## Other Examples - -- [Move Spiders Vector Tutorial](https://movespiders.com/courses/modules/datastructures/lessonId/1) -- [Move Spiders Vector Tutorial 2](https://movespiders.com/courses/modules/basics/lessonId/7) diff --git a/apps/nextra/pages/zh/build/smart-contracts/why-move.mdx b/apps/nextra/pages/zh/build/smart-contracts/why-move.mdx deleted file mode 100644 index 4aabc9faa..000000000 --- a/apps/nextra/pages/zh/build/smart-contracts/why-move.mdx +++ /dev/null @@ -1,51 +0,0 @@ ---- -title: "Why Move?" ---- - -# Why Move? - -The Move programming language was originally created by a team of engineers at -Facebook for the Diem Payment Network. Move is designed to be a platform-agnostic -language to enable common libraries, tooling, and developer communities across -diverse blockchains with vastly different data and execution models. -At Aptos, we believe in building a strong developer community in Move and invite -them to build upon the Move on Aptos stack and contribute to the open source software. - -Move is built upon the following principles: - -| **Principle** | **Explanation** | -|----------------------------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| -| **Secure by default** | Financial systems are built to ensure users don't lose funds. Move was designed to prevent entire classes of attacks and bugs such as reentrancy attacks, double spends, and arithmetic overflow. Type safety and compile time checks are at the forefront of the security. | -| **Runtime Verification** | The bytecode can be verified at runtime to verify that nothing has gone wrong, providing extra safety and preventing malicious actors. | -| **Formal Verification** | Move on Aptos provides a specification language to provide formal verification of contracts. This allows for proving invariants and assists with code auditing. | -| **Simplicity** | The commands and bytecode are purposely simple. This allows for easy decompilation, runtime verification, and code inspection. Using regular programming languages for blockchains often requires to ignore large part of the language to make them suitable for smart contracts (e.g. Rust). | - - -## Why Move on Aptos? - -Move on Aptos supports the full language built by the team at Facebook, with -additional extensions built to improve the security and the developer experience. - -### Security - -| **Advantages** | **Explanation** | -|-----------------------------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| -| **Formal Verification** | Aptos framework is fully specified and formally verified with the Move Prover. This includes the core contracts involving governance, NFTs, and Tokens. | -| **Gas Coverage** | Move VM has 100% gas coverage. Gas is charged based upon actual usage in the system (CPU, memory, storage, I/O). In other words, no gas exploits. | -| **Security Redundancy** | Security redundancy provided by runtime safety checks. | -| **Permission Controls** | Permission controls can flexibly be built at various levels. For example, token level permission controls exist by default to enable RWA tokenization. | - - -### Developer Experience - -| **Advantages** | **Explanation** | -|-----------------------------|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| -| **Move Development Tools** | - **Unit testing**: Move has built-in unit testing for all contracts. Aptos additionally provides test functionality in the framework to test different scenarios.
- **Coverage**: Coverage tooling allows for both source and bytecode level coverage reporting.
- **Decompiler**: For better security, on-chain bytecode can be disassembled or decompiled to provide visibility into the actual contracts.
- **IDE Plugins**: Aptos has support for two major IDEs: [Visual Studio Code](https://marketplace.visualstudio.com/items?itemName=MoveBit.aptos-move-analyzer) and [IntelliJ](https://plugins.jetbrains.com/plugin/14721-move-on-aptos). | -| **Data Model** | Aptos has an accessible data model with the data definition stored on-chain. Objects and accounts can have multiple distinct structures in an easy-to-parse format. | -| **Upgradability** | Upgradability ensures that application interfaces cannot be broken and doesn't require explicit adoption from downstream applications. Contracts can simply be upgraded in-place to fix bugs. | -| **Cross-Interaction** | Move allows for interaction between contracts by using type-safe structs. | -| **Code Storage** | Aptos stores source code on-chain improving the ability to audit and ensure contract to bytecode correctness. | -| **Sponsored Transactions** | Native sponsored transaction support allows for having transactions be paid by other users with no special services or contract-specific code required. | -| **Robust Token Standards** | The Digital Asset and Fungible Asset standards provide flexibility and a unified standard for diverse types of tokens and digital assets on-chain. These were influenced by existing standards such as ERC-20, ERC-721, ERC-1155 and Token-2022. | -| **On-chain Randomness** | Native on-chain unbiasable randomness provides a safe and consistent way of getting random numbers, with extra safety checks at compile time. | -