A module needing a governance can use this module to set up basic governance permissions, which we call Phase 0 of governance.
The workflow is as follows:
The deployer of the governance sets up a new package with a GovernanceType struct.
The deployer of GovernanceType deploys the module needing governance using deploy<GovernanceType>(). This step can be skipped if governance does not control upgradability of the contract code, but only parameters of the protocol.
During initialization of the contract needing governance, initialize_permissions is called to set up permissions.
Any method in the contract needing governance that should be under governance must use assert_authority<GovernanceType>().
The deployer of GovernanceType can initialize full fledged governance, which we call Phase 1 of governance, at any point.
use 0x1::account;
use 0x1::code;
use 0x1::error;
use 0x1::signer;
use 0xc0ded0c1::utils;
Resource AuthorizationCapability
When a governance proposal passes, this signer capability is used to create a signer which can execute the proposal in the main_contract which is under governance.
struct AuthorizationCapability<GovernanceType> has key
is_deployer_authorized: boolWhether the deployer is authorized to perform actions on the contract.
The governance, or the deployer, can set this to false, which results in the deployer of the main contract loosing all authorization privileges in the main contract, leaving all authorized actions to the governance.
Resource Admins
struct Admins<GovernanceType> has key
Fields
admin: address
Constants
When the petitioner is not authorized to perform an action.
const EPERMISSION_DENIED: u64 = 1;
When the governance has already been initialized.
const EALREADY_INITIALIZED: u64 = 4;
When the deployer attempts to revoke their own deployer permission.
const EDEPLOYER_REVOKE_NOT_ALLOWED: u64 = 2;
When the deployer is not authorized to perform an action.
const EDEPLOYER_UNAUTHORIZED: u64 = 0;
When the governance has not been initialized.
const EPERMISSIONS_NOT_INITIALIZED: u64 = 3;
Random seed generated using aptos-crypto crate. This is used in creating new signer capabilities.
Returns whether governance deployer's permissions are still valid
public fun is_deployer_authorized<GovernanceType>(): bool
Implementation
public fun is_deployer_authorized<GovernanceType>(): bool acquires DeployerPermission {
assert_initialization<GovernanceType>();
let governance_address = utils::governance_address<GovernanceType>();
borrow_global<DeployerPermission<GovernanceType>>(
governance_address
).is_deployer_authorized
}
Function initialize_permissions
Initializes the permissions for governance and deployer.
Phase 0 initialization must happen when the main contract needing governance is initialized, otherwise, none of the permissioned methods would work in the main contract.
public fun initialize_permissions<GovernanceType>(deployer: &signer)
Implementation
public fun initialize_permissions<GovernanceType>(
deployer: &signer,
) {
let deployer_address = signer::address_of(deployer);
assert_governance_permission<GovernanceType>(deployer_address);
// If the contract needing governance was not deployed using governance,
// we initialize the governance now (only useful for testing purposes)
if(!exists<AuthorizationCapability<GovernanceType>>(deployer_address)) {
initialize<GovernanceType>(deployer);
};
}
Function assert_initialization
Assert if permissions have been initialized.
Abort conditions
If the permissions have not been initialized.
public fun assert_initialization<GovernanceType>()
Implementation
public fun assert_initialization<GovernanceType>() {
assert!(
exists<AuthorizationCapability<GovernanceType>>(
utils::governance_address<GovernanceType>()
),
error::invalid_state(EPERMISSIONS_NOT_INITIALIZED)
);
}
Function assert_deploy_authority
Assert if the petitioner has the deploy authority. Checked upon deploy or fallback for admin.
Abort conditions
If the petitioner is not authorized to perform the action.
public fun assert_deploy_authority<GovernanceType>(petitioner: &signer)
Implementation
public fun assert_deploy_authority<GovernanceType>(
petitioner: &signer
) acquires DeployerPermission, AuthorizationCapability {
let governance_address = utils::governance_address<GovernanceType>();
let petitioner_address = signer::address_of(petitioner);
assert_initialization<GovernanceType>();
if (petitioner_address == governance_address) {
// Check if the petitioner is the deployer, and if so,
// that deployer permissions are still valid
let permission = borrow_global<DeployerPermission<GovernanceType>>(
governance_address
);
assert!(
permission.is_deployer_authorized,
error::unauthenticated(EDEPLOYER_UNAUTHORIZED)
);
} else {
// Check whether the signer is the signer for the correct signer cap
let auth_cap =
borrow_global<AuthorizationCapability<GovernanceType>>(
governance_address
);
assert!(
petitioner_address ==
account::get_signer_capability_address(&auth_cap.signer_cap),
error::unauthenticated(EPERMISSION_DENIED)
);
};
}
Function assert_authority
Assert if the petitioner has the authority to perform an action.
All methods in the main contract which need governance permissions must call this method. The main contract's methods that are not under the control of governance should not use this method.
Abort conditions
If the petitioner is not authorized to perform the action.
public fun assert_authority<GovernanceType>(petitioner: &signer)
Implementation
public fun assert_authority<GovernanceType>(
petitioner: &signer
) acquires DeployerPermission, AuthorizationCapability, Admins {
assert_initialization<GovernanceType>();
let admin = borrow_global<Admins<GovernanceType>>(utils::governance_address<GovernanceType>());
if (signer::address_of(petitioner) == admin.admin) {
return
};
assert_deploy_authority<GovernanceType>(petitioner);
}
Function create_signer_for_an_upgrade_or_deploy
Creates a signer for the deployer which can be used to perform actions on the main contract.
Abort conditions
If the deployer is not authorized to perform the action.
public fun create_signer_for_an_upgrade_or_deploy<GovernanceType>(deployer: &signer): signer
Implementation
public fun create_signer_for_an_upgrade_or_deploy<GovernanceType>(
deployer: &signer
): signer acquires AuthorizationCapability, DeployerPermission {
assert_deploy_authority<GovernanceType>(deployer);
let auth_cap = borrow_global<AuthorizationCapability<GovernanceType>>(
utils::governance_address<GovernanceType>()
);
// Return the signer so that deployer can upgrade the module
create_signer_with_capability(&auth_cap.signer_cap)
}
Function revoke_deployer_authorization_privilege
Revokes the deployer's permission to perform actions on the main.
The governance of type GovernanceType can permanently revoke GovernanceType's deployer's authorization privileges This cannot be reversed, so it should only be called via a successful governance proposal.
Abort conditions
If the dao is not authorized to perform the action.
If the dao signer is the same as the governance deployer.
public fun revoke_deployer_authorization_privilege<GovernanceType>(dao: &signer)
Implementation
public fun revoke_deployer_authorization_privilege<GovernanceType>(
dao: &signer,
) acquires DeployerPermission, AuthorizationCapability {
assert_deploy_authority<GovernanceType>(dao);
// Make sure that the deployer doesn't revoke
// it's own permissions.
// It must be done via a successful governance proposal.
// The following assert is only added as a deterrent, as the deployer
// can still use `create_signer_for_an_upgrade_or_deploy` to
// revoke its own permissions.
let governance_address = utils::governance_address<GovernanceType>();
assert!(
signer::address_of(dao) != governance_address,
error::invalid_argument(EDEPLOYER_REVOKE_NOT_ALLOWED)
);
let permission = borrow_global_mut<DeployerPermission<GovernanceType>>(
governance_address
);
permission.is_deployer_authorized = false;
}
Function initialize
Creates a resource account derived from the deployer, gives the deployer the AuthorizationCapability to create a signer for running authorized actions on the contract and the DeployerPermission which tracks the permissions the deployer has.
Upon initialization, the deployer is authorized to perform all actions on the contract, and can also upgrade the contract code.
public fun initialize<GovernanceType>(deployer: &signer)
Implementation
public entry fun initialize<GovernanceType>(deployer: &signer) {
let deployer_address = signer::address_of(deployer);
assert_governance_permission<GovernanceType>(deployer_address);
assert!(
!exists<AuthorizationCapability<GovernanceType>>(deployer_address),
error::invalid_state(EALREADY_INITIALIZED)
);
let (_signer, signer_cap) =
account::create_resource_account(deployer, RANDOM_SEED);
move_to(deployer, AuthorizationCapability<GovernanceType> {
signer_cap: signer_cap,
});
move_to(deployer, DeployerPermission<GovernanceType> {
is_deployer_authorized: true,
});
// No admin by default
move_to(deployer, Admins<GovernanceType> {
admin: @0x0,
});
}
Function deploy
The governance can deploy the code using this method, which will enable governance to control upgradability of the code.
public fun deploy<GovernanceType>(deployer: &signer, metadata_serialized: vector<u8>, code: vector<vector<u8>>)
Implementation
public entry fun deploy<GovernanceType>(
deployer: &signer,
metadata_serialized: vector<u8>,
code: vector<vector<u8>>,
) acquires AuthorizationCapability, DeployerPermission {
assert_initialization<GovernanceType>();
// below also calls assert_deploy_authority
let auth_signer =
create_signer_for_an_upgrade_or_deploy<GovernanceType>(
deployer
);
aptos_framework::code::publish_package_txn(
&auth_signer,
metadata_serialized, code
);
}
Function set_admin
set the admin for the governance
public fun set_admin<GovernanceType>(auth_signer: &signer, new_admin: address)
Implementation
public entry fun set_admin<GovernanceType>(
auth_signer: &signer,
new_admin: address,
) acquires AuthorizationCapability, DeployerPermission, Admins {
assert_initialization<GovernanceType>();
assert_deploy_authority<GovernanceType>(auth_signer);
let admin = borrow_global_mut<Admins<GovernanceType>>(utils::governance_address<GovernanceType>());
admin.admin = new_admin;
}
Function assert_governance_permission
Assert if the deployer_address is the same as the governance_address().
Abort conditions
If the deployer_address is not the governance address.
fun assert_governance_permission<GovernanceType>(deployer_address: address)
This function creates a signer in order to resolve a governance proposal.
public(friend) fun create_signer_for_proposal_resolution<GovernanceType>(): signer
Implementation
public(friend) fun create_signer_for_proposal_resolution<GovernanceType>(
): signer acquires AuthorizationCapability {
let auth_cap = borrow_global<AuthorizationCapability<GovernanceType>>(
utils::governance_address<GovernanceType>()
);
// Return the signer to execute the proposal
create_signer_with_capability(&auth_cap.signer_cap)
}