SNS asset canisters
The asset canister provides users with a way to store and retrieve static assets from a canister deployed on ICP. Generally, asset canisters are used to serve HTML, CSS, or JavaScript assets, which are typically part of a dapp's frontend. For this reason, the asset canister is also referred to as the frontend canister. For purposes of this guide, it will be referred to as the asset canister.
In the context of the SNS, a dapp's associated asset canister serves the frontend assets related to the dapp and may also include a frontend to the SNS DAO, e.g., through which users can vote on governance proposals.
The contents of the asset canister can be configured prior to the launch of the SNS. If configured correctly, any changes afterwards must be made by a principal with the Prepare
permission. Principals with this Prepare
permission can make a batch of changes to the asset canister and then 'lock' those changes. To have those changes applied, a proposal must be submitted. Anyone can submit the proposal for the 'locked' changes. Once changes are proposed, they can be voted on by the SNS DAO. If approved, the SNS governance canister is the only one that can commit the approved changes. This configuration assures that changes to the asset canister are only made by approved proposals. These changes are referred to as 'updates' to the asset canister in the remainder of this document.
This section is relevant if your project contains an asset canister and describes how you can test handing over control of an asset canister to an SNS.
In this document, the term upgrade refers to deploying an upgraded version of a canister's WebAssembly module.
The term update refers to changing or updating the assets stored within an asset canister.
Asset canister example
An example of an SNS asset canister is canister sqbzf-5aaaa-aaaam-aavya-cai
, which is an asset canister part of the Dragginz Dapp SNS.
Deploying an asset canister
The general overview of deploying an asset canister during an SNS launch is as follows:
- First, the asset canister must be created with or upgraded to a Wasm file from dfx 0.15.2+.
- Then, developers should use
revoke_permission
to remove their own permissions (especiallyCommit
) that allow them to update the assets arbitrarily. - Then, after launching the SNS, the SNS's function should be registered to commit proposed changes.
- Last, using upgrade arguments, updated permissions can be set. This is only possible after the SNS canisters have been successfully deployed and initialized because before the launch the principal id of SNS governance is not known yet
- The SNS governance canister is given
Commit
permissions. WithCommit
, SNS governance may apply a batch of proposed asset updates to the assets served by the asset canister. - To give certain individuals the permission to upload changes to the asset canister that can then be put to vote, these principals can be granted
Prepare
permissions. Changes created usingPrepare
permissions must be approved through a proposal before they are applied to the asset canister. - To facilitate permission management via custom SNS proposals, it can make sense to grant SNS governance the
ManagePermissions
permission. With this permission, it is not necessary to upgrade the asset canister every time permissions are supposed to change.
- The SNS governance canister is given
To deploy an asset canister, any canister in the dfx.json
file can be set as "type": "assets"
, and dfx will automatically generate the required files, such as an assets.wasm.gz file and a candid/assets.did file once the canister has been built.
An example of configuring an asset canister within the dfx.json
file can be found below:
"assets": {
"source": [],
"type": "assets"
}
Deploying locally for testing
To deploy your asset canister locally for testing purposes, the following command can be used:
dfx deploy assets
Deploying on the mainnet
To deploy to the mainnet, you will need a wallet that contains cycles. For more information on cycles wallets, please see here.
To deploy your asset canister to the mainnet, the following command can be used:
dfx deploy assets --network ic
Configuring an asset canister's permissions
When configuring an asset canister, a set of permissions that contains a whitelist of principals must be created. This whitelist details who is allowed to submit changes that update assets in the asset canister. Principals that are allowed to submit changes are given the Prepare
permission.
Once a principal with Prepare
permissions submits changes to the asset canister, these changes are set in a 'locked' state. Then, anyone can submit a proposal that proposes the 'locked' changes be applied; there is no permission necessary to submit this proposal.
Only principals with Commit
rights may apply proposed changes.
Once the asset canister has been handed over to the SNS, only the governance canister should have Commit
rights, and principals in the whitelist should have Prepare
rights. The developer who configured and deployed the SNS should have their permissions removed prior to the SNS launch.
Granting permissions
To grant a principal permission within an asset canister, the following command can be used:
dfx canister call --network ic <canister-id> grant_permission '(record {permission = variant {<Permission: Commit / Prepare / ManagePermissions >}; to_principal = principal "<principal>"})'
Revoking permissions
To revoke a principal's permission within an asset canister, the following command can be used:
dfx canister call --network ic <canister-id> revoke_permission '(record {permission = variant {<Permission: Commit / Prepare / ManagePermissions >}; of_principal = principal "<principal>"})'
Listing permissions
To list permissions for an asset canister, the following command can be used:
dfx canister call --network ic <canister-id> list_permitted '(record {permission = variant {<Permission>}})'
For example, to list all principals with the Commit
permission:
dfx canister call --network ic oa7fk-maaaa-aaaam-abgka-cai list_permitted '(record {permission = variant {Commit}})'
To list all principals with the Prepare
permission:
dfx canister call --network ic oa7fk-maaaa-aaaam-abgka-cai list_permitted '(record {permission = variant {Prepare}})'
Once the asset canister has been deployed and permissions have been configured, the SNS decentralization swap can be started. You can learn more about launching an SNS here
Overwriting permissions
Upgrade arguments can be used by the permissions lists to set new values, regardless of the current state of the permissions.
The upgrade argument is of type (AssetCanisterArgs)
, where the contained types are:
type AssetCanisterArgs = variant {
Init: InitArgs;
Upgrade: UpgradeArgs;
};
type InitArgs = record {};
type UpgradeArgs = record {
set_permissions: opt SetPermissions;
};
/// Sets the list of principals granted each permission.
type SetPermissions = record {
prepare: vec principal;
commit: vec principal;
manage_permissions: vec principal;
};
Therefore, to set the permissions such that SNS governance has ManagePermissions
and Commit
permissions and principal1
has Prepare
permissions, the upgrade argument would look like this:
(variant {
Upgrade = record {
set_permissions = opt record {
prepare = vec { principal "<principal1>"; };
commit = vec { principal "<SNS governance>"; };
manage_permissions = vec { principal "<SNS governance>"; };
}
}
})
SNS GenericNervousSystemFunctions
Generic proposals are a way for each SNS to define custom proposals. To commit changes to the asset canister, such generic proposals are used.
To use a generic proposal, first this new proposal type has to be "registered" in the SNS. To do so, one uses a AddGenericNervousSystemFunction
proposal.
To submit a new AddGenericNervousSystemFunction
SNS Proposal to support the commit_proposed_batch
API, the target canister id should be the asset canister (that is updated in the update steps below) and the target function is commit_proposed_batch
. The validate function should be validate_commit_proposed_batch
.
The ExecuteGenericNervousSystemFunction
SNS proposal is used to execute the newly registered proposal. To submit an ExecuteGenericNervousSystemFunction
SNS Proposal with the output from dfx deploy FRONTEND_CANISTER_NAME --network ic --by-proposal
see update steps below.
Submitting an SNS proposal to upgrade an asset canister
Once an asset canister is under the control of the SNS, any changes must be done via an SNS proposal.
Before submitting an SNS proposal to update an asset canister, assure that the asset canister has been upgraded (by proposal) to use the Wasm bundled with dfx 0.14.1+.
Step 1: To submit an SNS proposal, first have a principal with
Prepare
permission run:
dfx deploy FRONTEND_CANISTER_NAME --network ic --by-proposal
The output will contain something like this:
Proposed commit of batch 2 with evidence e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855. Either commit it by proposal, or delete it.
Step 2: Encode the evidence and batch number.
The evidence bytes must be Candid-encoded bytes. These are the arguments you will submit with the ExecuteGenericNervousSystemFunction
proposal.
EVIDENCE_STRING=e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855
BATCH_ID=123
didc encode "(record{ batch_id = $BATCH_ID : nat; evidence = blob \"$(echo $EVIDENCE_STRING | sed 's/../\\&/g')\"; })"
Step 3: To ensure that others would be able to verify the evidence in the proposal, have someone else clone the dapp repo and run:
dfx deploy FRONTEND_CANISTER_NAME --network ic --compute-evidence
The computed evidence should match the evidence from step 1.
Step 4: Submit a new proposal to commit the batch, using the APIs below:
Use these asset canister APIs in the proposal:
type CommitProposedBatchArguments = record {
batch_id: BatchId;
evidence: blob;
};
type ValidationResult = variant { Ok : text; Err : text };
validate_commit_proposed_batch: (CommitProposedBatchArguments) -> (ValidationResult);
commit_proposed_batch: (CommitProposedBatchArguments) -> ();
If the proposal is rejected, the preparer should use this new asset canister API:
type DeleteBatchArguments = record {
batch_id: BatchId;
};
delete_batch: (DeleteBatchArguments) -> ();
Asset canister upgrade proposal example
Below is an example script that can be run to setup the necessary proposals to upgrade an asset canister:
#!/bin/bash
# Set current directory to the scripts root
SCRIPT=$(readlink -f "$0")
SCRIPT_DIR=$(dirname "$SCRIPT")
cd $SCRIPT_DIR
TITLE="Update Frontend Canister Permissions."
SUMMARY="Update the frontend canister permissions, adding commit permissions for the governance canister."
URL="https://example.com"
CANISTER_NAME=example_frontend
NETWORK="ic"
# Get the target canister id
TARGET_CANISTER_ID=$(dfx -qq canister --network $NETWORK id $CANISTER_NAME)
dfx identity use ic_admin
OWNER_IDENTITY=$(dfx identity whoami)
PEM_FILE="$(readlink -f "$HOME/.config/dfx/identity/${OWNER_IDENTITY}/identity.pem")"
PROPOSER_NEURON_ID=be1741c21c35da7e386d2ccf59a3d5d14c97d9b82b36045f4a8d4c336864f6dc
UPGRADE_ARG="(opt variant {
Upgrade = record {
set_permissions = opt record {
prepare = vec {
principal \"detjl-sqaaa-aaaaq-aacqa-cai\";
principal \"4jijx-ekel7-4t2kx-32cyf-wzo3t-i4tas-qsq4k-ujnug-oxke7-o5aci-eae\"
};
commit = vec { principal \"detjl-sqaaa-aaaaq-aacqa-cai\"};
manage_permissions = vec { principal \"detjl-sqaaa-aaaaq-aacqa-cai\"}
}
}
})"
echo UPGRADE_ARG
# Make the proposal using quill
quill sns --canister-ids-file ../../utils/sns_canister_ids.json --pem-file $PEM_FILE make-upgrade-canister-proposal --title "$TITLE" --url "$URL" --summary "$SUMMARY" $PROPOSER_NEURON_ID --target-canister-id $TARGET_CANISTER_ID --wasm-path '../../../.dfx/ic/canisters/example_frontend/assetstorage.wasm.gz' --canister-upgrade-arg "$UPGRADE_ARG" > msg.json
quill send msg.json
rm -f msg.json
Then, deploy the asset canister to be upgraded via proposal:
dfx deploy example_frontend --network ic --by-proposal
Lastly, submit the proposal to upgrade the asset canister:
#!/bin/bash
# Set current directory to the scripts root
SCRIPT=$(readlink -f "$0")
SCRIPT_DIR=$(dirname "$SCRIPT")
cd $SCRIPT_DIR
TITLE="Update Frontend Canister."
SUMMARY="Update frontend canister with evidence batch id 1."
URL="https://example.com"
EVIDENCE_STRING=55d25c6fb1d28bff2f1f5df99c39d5b50f60d97e892caad03933e2622af4a797
BATCH_ID=1
FUNCTION_ID=24000
# Submit the proposal
../../utils/make_custom_function_proposal_frontend.sh $FUNCTION_ID "$TITLE" "$SUMMARY" "$URL" "$EVIDENCE_STRING" "$BATCH_ID" "commit_proposed_batch"