governance::permissions

Module 0xc0ded0c1::permissions

module 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 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.

  • 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.

  • The governance can use create_signer_for_proposal_resolution() to resolve successful proposals.

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;

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
Fields

signer_cap: account::SignerCapabilityThe capability to sign on behalf of the governance

Resource DeployerPermission

Keeps track of deployer's permission.

struct DeployerPermission<GovernanceType> has key
Fields

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.

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];

Function is_deployer_authorized

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)
Implementation
fun assert_governance_permission<GovernanceType>(
    deployer_address: address
) {
    assert!(
        utils::governance_address<GovernanceType>() == deployer_address,
        error::unauthenticated(EPERMISSION_DENIED)
    );
}

Function create_signer_for_proposal_resolution

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)
}

Last updated