governance::permissions
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 usingdeploy
<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. - Phase 1 governance can vote to revoke deployer permission. This uses
revoke_deployer_authorization_privilege
()
. - After revocation of deployer permission, governance is in full control.
Note: Once the control of the contract is fully transferred to governance, it is not possible to return it to the deployer.
use 0x1::account;
use 0x1::code;
use 0x1::error;
use 0x1::signer;
use 0xc0ded0c1::utils;
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
Keeps track of deployer's permission.
struct DeployerPermission<GovernanceType> has key
is_deployer_authorized: bool
Whether 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.struct Admins<GovernanceType> has key
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.
const RANDOM_SEED: vector<u8> = [16, 255, 222, 172, 207, 31, 104, 153, 235, 236, 70, 179, 127, 42, 232, 183, 157, 58, 223, 63, 36, 34, 133, 8, 219, 195, 166, 178, 84, 108, 90, 247];
Returns whether governance deployer's permissions are still valid
public fun is_deployer_authorized<GovernanceType>(): bool
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
}
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)
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);
};
}
Assert if permissions have been initialized.
- If the permissions have not been initialized.
public fun assert_initialization<GovernanceType>()
Assert if the
petitioner
has the deploy authority. Checked upon deploy or fallback for admin.- If the
petitioner
is not authorized to perform the action.
public fun assert_deploy_authority<GovernanceType>(petitioner: &signer)
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)
);
};
}
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.
- If the
petitioner
is not authorized to perform the action.
public fun assert_authority<GovernanceType>(petitioner: &signer)
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);
}
- If the
deployer
is not authorized to perform the action.
public fun create_signer_for_an_upgrade_or_deploy<GovernanceType>(deployer: &signer): signer
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)
}
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.- 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)
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;
}
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)
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,
});
}
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>>)
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
);
}
set the admin for the governance
public fun set_admin<GovernanceType>(auth_signer: &signer, new_admin: address)
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;
}
Assert if the
deployer_address
is the same as the governance_address()
.- If the
deployer_address
is not the governance address.
fun assert_governance_permission<GovernanceType>(deployer_address: address)
public(friend) fun create_signer_for_proposal_resolution<GovernanceType>(): signer
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)
}
Last modified 10mo ago