From ed12788568b305cfbe3fe7594e51c57d2a7d288d Mon Sep 17 00:00:00 2001 From: xbtmatt <90358481+xbtmatt@users.noreply.github.com> Date: Sun, 11 Jun 2023 18:13:50 -0700 Subject: [PATCH] Adding a simple view function to demonstrate the upgradeability of the contract. Updated the tutorial to show how to use it. The tutorial shows how to use the newly added `build-publish-package` function to get the bytecode in a JSON file and run the upgrade_contract function with it. --- .../sources/upgrader.move | 5 + .../upgrade_contract.json | 16 ++ .../docs/concepts/resource-accounts.md | 152 ++++++++++++++++-- 3 files changed, 161 insertions(+), 12 deletions(-) create mode 100644 aptos-move/move-examples/upgrade_resource_contract/upgrade_contract.json diff --git a/aptos-move/move-examples/upgrade_resource_contract/sources/upgrader.move b/aptos-move/move-examples/upgrade_resource_contract/sources/upgrader.move index 03574402b819b..37f264a909082 100644 --- a/aptos-move/move-examples/upgrade_resource_contract/sources/upgrader.move +++ b/aptos-move/move-examples/upgrade_resource_contract/sources/upgrader.move @@ -32,4 +32,9 @@ module upgrade_resource_contract::upgrader { code, ); } + + #[view] + public fun upgradeable_function(): u64 { + 9000 + } } \ No newline at end of file diff --git a/aptos-move/move-examples/upgrade_resource_contract/upgrade_contract.json b/aptos-move/move-examples/upgrade_resource_contract/upgrade_contract.json new file mode 100644 index 0000000000000..2ab16336189c8 --- /dev/null +++ b/aptos-move/move-examples/upgrade_resource_contract/upgrade_contract.json @@ -0,0 +1,16 @@ +{ + "function_id": "be326762ddd27624743223991c2223027621e62b7d0849a40a970fa2df385da9::upgrader::upgrade_contract", + "type_args": [], + "args": [ + { + "type": "hex", + "value": "0x2155706772616465205265736f75726365204163636f756e7420436f6e747261637401000000000000000040423434313945414132343332454643453639443743433842333935364336454333363833344536343044434244314437343732323334363743434635414545329d011f8b08000000000002ff3d8dcd0ac2301084ef798a25775b5fc043117c00c15329b26ed6129a66437e1411dfdd04b1cc5c86f9861903d282334fcae3ca70007d097344c370e6242512c34024c567388acf11296bf5e098acf846efbb2aadca6f740de22cbd5a41b206ccf6e6582b351a0eec0d7bb29c2635842ce914ebe153e252e9373821746dd7757df5fd5ff6d8d8dd96357cbe9710eb1ab3000000010875706772616465729d041f8b08000000000002ff8d534d6fdb300cbdf7577018d0d94090a6c0306c4e5314d86987edb2e330188ccc24421dc9d347d2acc87f9fa428f2479d603ec9241f1ff9f4b49595ad096cb3565851a9484bab18954c0aa39099a2882905af37e03eab09b4a98a42f3b52035ef079131698543bdfe0ce9afd8e092d7dc1c8e83cac41421e38d0651262b9adf849836ca3203df0f4322d8a086673ac479fd97b84e33970c9b0286b849283f9ebaafac002eb829b7419f6cd0a180dbd321efb0a0d6a4ccbb2c961458550ea64bb91ac273582ce0e9a2e61398e5f3d4b62633b6002c6028a117d5284ebb4ecf98f288e1141378927b3f4dcbb5950e6be4dbca1199dbc52f4a3c129c24d431f246c5efeee0873404664351492e85fb4313420cebda7990eb38f414bead604f4e1ddc79904bd45c1048ebe4437190c263c4d9d8a71e495f7a69a4e6621dc2e729216af541a79076adaa1ed63b83f9d1d08b300da33776597306e42a0ec139e79b3d83b2b474983db9a715634b062b3458bac5b96bfd97aa0276c48c540ff6f3635be85f40cab405b122773bfcb1dc8d7ffdc6ae58f5648960d0709cc0fd7f99f176299592fb725dcb25d60f6fe91fb3cb8ecfa7234dafb23ac6e47aa6084d42eeb9d97878a4cd463a77160a7a16e106f5a66c903de3da3d811791f5ec7d3b7c11bdecc8edf50b3c4b1be91bfffdaf1da7fdefae933a1ec2654de5d973595e80fdf4b173915f66b3fbd8ecf80f9d1d74aacb05000000000300000000000000000000000000000000000000000000000000000000000000010e4170746f734672616d65776f726b00000000000000000000000000000000000000000000000000000000000000010b4170746f735374646c696200000000000000000000000000000000000000000000000000000000000000010a4d6f76655374646c696200" + }, + { + "type": "hex", + "value": [ + "0xa11ceb0b060000000b01000a020a0803122305351d0752f30108c502400685034410c9032f0af803060cfe036e0dec04020000010101020103010400050800010a0600000600010000070201000008010300040b000500030c060400010d080700020e02010001060c0003060c0a020a0a020103010801010502060c05010c01060801087570677261646572076163636f756e7404636f6465107265736f757263655f6163636f756e74067369676e6572124d795369676e65724361706162696c6974790b696e69745f6d6f64756c6510757067726164655f636f6e7472616374147570677261646561626c655f66756e6374696f6e137265736f757263655f7369676e65725f636170105369676e65724361706162696c6974790a616464726573735f6f661d72657472696576655f7265736f757263655f6163636f756e745f6361701d6372656174655f7369676e65725f776974685f6361706162696c697479137075626c6973685f7061636b6167655f74786e5189d8b84793e93065f28893ab3e01cf5883e5daf93404d567a79be29958e423000000000000000000000000000000000000000000000000000000000000000105205189d8b84793e93065f28893ab3e01cf5883e5daf93404d567a79be29958e4230520f43c3dbad12a67d2c242a3e3bbbc8718cde26b36f75c075e96bf55dda9b0af76126170746f733a3a6d657461646174615f76311b000001147570677261646561626c655f66756e6374696f6e0101000002010908010000000004130a0011030700210406050a0b0001060000000000000000270a00070111040c010b000b0112002d0002010104010007120b001103070121040605080601000000000000002707002b00100011050c030e030b010b0211060202010000010206292300000000000002000000" + ] + } + ] +} \ No newline at end of file diff --git a/developer-docs-site/docs/concepts/resource-accounts.md b/developer-docs-site/docs/concepts/resource-accounts.md index 02f231a83ccc4..2c29ea78aa147 100644 --- a/developer-docs-site/docs/concepts/resource-accounts.md +++ b/developer-docs-site/docs/concepts/resource-accounts.md @@ -7,11 +7,14 @@ id: "resource-accounts" A resource account is an [Account](https://aptos.dev/concepts/accounts/) that's used to store and manage resources. It can be a simple storage account that's used merely to separate different resources for an account or a module, or it can be utilized to programmatically control resource management in a contract. -There are two distinct ways to manage a resource account. In this guide we'll discuss how to implement each technique, variations on the implementations, and any configuration details relevant to the creation process. +There are two distinct ways to manage a resource account: -## How to utilize a resource account +1. The auth key is rotated to a separate account that can control it manually +2. The Move VM rotates the auth key to 0x0 and controls the account through a SignerCapability -### Manually controlling a resource account by rotating its auth key to another account's auth key +In this guide we'll discuss how to implement each technique, variations on the implementations, and any configuration details relevant to the creation process. + +## Rotating the auth key to another account The first technique we're going to discuss is through the `create_resource_account` function in the `resource_account.move` contract. View the code [here](https://github.com/aptos-labs/aptos-core/blob/4beb914a168bd358ec375bfd9854cffaa271199a/aptos-move/framework/aptos-framework/sources/account.move#L602). @@ -39,7 +42,7 @@ Notice that there is nothing returned here- we are not given anything to store o If you don't specify an auth key, that is, if you pass in `vector::empty()` or `vector []` to the `optional_auth_key` field, it will automatically rotate the auth key to the `origin` account's auth key. ::: -### Managing a resource account programmatically with a SignerCapability +## Rotating the auth key to 0x0 to create a SignerCapability The second technique is the `create_resource_account` function in the `account.move` contract. View the code [here](https://github.com/aptos-labs/aptos-core/blob/4beb914a168bd358ec375bfd9854cffaa271199a/aptos-move/framework/aptos-framework/sources/account.move#L602). @@ -110,7 +113,7 @@ To intuitively understand why a `SignerCapability` is allowed to be so powerful, Upon creating the `SignerCapability`, you're free to decide how you want to expose it. You can store it somewhere, give it away, or gate its access to functions that use it or conditionally return it. ::: -### Using a resource account to publish a module +## Publishing a module to a resource account There are a few other ways we can utilize a resource account. One common usage is to use it to publish a module: @@ -141,7 +144,7 @@ If you don't store the `SignerCapability` there is no way to retrieve the resour You *also* need to provide some way to use or retrieve the `SignerCapability`, too, or you won't even be able to use it. ::: -### Publishing an upgradeable module with a resource account +## Publishing an upgradeable module to a resource account If you want to publish to a resource account and also have an upgradeable contract, use the `init_module` function to use the resource account's signer to retrieve and store the `SignerCapability`. Here's a full working example: @@ -180,6 +183,11 @@ module upgrade_resource_contract::upgrader { code, ); } + + #[view] + public fun upgradeable_function(): u64 { + 9000 + } } ``` @@ -195,13 +203,133 @@ aptos move create-resource-account-and-publish-package --address-name upgrade_re Where `CONTRACT_DEPLOYER` is the profile. Read more about [Aptos CLI profiles here](https://aptos.dev/tools/aptos-cli-tool/use-aptos-cli/#creating-other-profiles). -If you want to see an end to end unit test displaying how to publish and then upgrade the code above by calling `upgrade_contract`, you can run the cargo test: +Let's run through an example of how to publish the above upgradeable contract to a resource account and upgrade it. + +1. Publish the module to a resource account +2. Run the `upgradeable_function` view function and see what it returns +3. Upgrade the module using the json output from the `aptos move build-publish-package` command +4. Run the `upgradeable_function` view function again to see the new return value + +First make sure you have a default profile initialized to devnet. + +```shell +aptos init --profile default +``` + +Choose `devnet` and leave the private key part empty so it will generate an account for you. When we write `default` in our commands, it will automatically use this profile. + +Navigate to the `move-examples/upgrade_resource_contract` directory. + +### Publish the module + +```shell +aptos move create-resource-account-and-publish-package --address-name upgrade_resource_contract --seed '' --named-addresses owner=default +``` + +The `--address-name` flag denotes that the resource address created from the resource account we make will be supplied as the `upgrade_resource_contract` address in our module. Since we declared it as the module address with `module upgrade_resource_contract::upgrader { ... }` at the very top of our contract, this is where our contract will be deployed. + +When you run this command, it will ask you something like this: + +``` +Do you want to publish this package under the resource account's address be326762ddd27624743223991c2223027621e62b7d0849a40a970fa2df385da9? [yes/no] > +``` + +Say yes and copy that address to your clipboard. That's our resource account address where the contract is deployed. + +Now you can run the view function! + +### Run the view function + +```shell +aptos move view --function-id RESOURCE_ACCOUNT_ADDRESS::upgrader::upgradeable_function +``` + +Remember to replace `RESOURCE_ACCOUNT_ADDRESS` with the resource account address you deployed your module to; it is different from the one posted above, so this will specifically only work for *your* contract. + +It should output: +```json +Result: [ + 9000 +] +``` + +### Change the view function + +Now let's change the value returned in the view function from `9000` to `9001` so we can see that we've upgraded the contract: + +```rust +#[view] +public fun upgradeable_function(): u64 { + 9001 +} +``` + +Save that file, and then use the `build-publish-package` command to get the bytecode output in JSON format. + +### Get the bytecode for the module ```shell -cargo test --package e2e-move-tests --lib -- tests::upgrade_resource_contract::code_upgrading_using_resource_account --exact --nocapture +aptos move build-publish-payload --json-output-file upgrade_contract.json --named-addresses upgrade_resource_contract=RESOURCE_ACCOUNT_ADDRESS,owner=default +``` + +Replace `RESOURCE_ACCOUNT_ADDRESS` with your resource account address and run the command. Once you do this, there will now be a `upgrade_contract.json` file with the bytecode output of the new, upgraded module in it. + +The hex values in this JSON file are arguments that we'd normally use to pass into the `0x1::code::publish_package_txn` function, but since we made our own `upgrade_contract` function that wraps it, we need to change the function call value to something else. + +Your JSON should look something like the below output, just with expanded `value` fields (truncated here for simplicity's sake): + +```json +{ + "function_id": "0x1::code::publish_package_txn", + "type_args": [], + "args": [ + { + "type": "hex", + "value": "0x2155...6200" + }, + { + "type": "hex", + "value": [ + "0xa11c...0000" + ] + } + ] +} +``` + +Change the `function_id` value in the JSON file to match your contract's upgrade function contract, with your resource account address filled in: + +``` +"function_id": "RESOURCE_ACCOUNT_ADDRESS::upgrader::upgrade_contract", ``` -### Creating and funding a resource account +Save this file so we can use it to run an entry function with JSON parameters. + +### Run the upgrade_contract function + +```shell +aptos move run --json-file upgrade_contract.json +``` + +Confirm yes to publish the upgraded module where the view function will return 9001 instead of 9000. + +### Run the upgraded view function + +```shell +aptos move view --function-id RESOURCE_ACCOUNT_ADDRESS::upgrader::upgradeable_function +``` + +You should get: + +```json +Result: [ + 9001 +] +``` + +Now you know how to publish an upgradeable module to a resource account! + +## Creating and funding a resource account Another common usage is to create and fund a resource account, in case the account needs access to functions that need access to `Coin`: @@ -225,9 +353,9 @@ public entry fun create_resource_account_and_fund( } ``` -#### Can I acquire a SignerCapability later? +## Acquiring a SignerCapability later -Yes. Say you create a resource account and rotate its auth key to your account's auth key. You'd just need to sign for the account and call `retrieve_resource_account_cap` in order to get the `SignerCapability` and store it somewhere: +Say you create a resource account and rotate its auth key to your account's auth key. You'd just need to sign for the account and call `retrieve_resource_account_cap` in order to get the `SignerCapability` and store it somewhere: ```rust struct MySignerCapability has key { @@ -248,7 +376,7 @@ Call the function, but change the sender account to appear as the resource accou aptos move run --function-id MODULE_ADDRESS::MODULE_NAME::retrieve_cap --args address:default --sender-account RESOURCE_ADDRESS_HERE --profile default ``` -#### How is the address for a resource account derived? +## How is the address for a resource account derived? When a resource account is created, the address is derived from a SHA3-256 hash of the requesting account's address plus an optional byte vector `seed`. If you want to know the resource address generated by an account + a given arbitrary seed, you can call the `create_resource_address` function in `account.move`: