Module 0x1::staking_proxy
- Struct
StakeProxyPermission - Constants
- Function
check_stake_proxy_permission - Function
grant_permission - Function
set_operator - Function
set_voter - Function
set_vesting_contract_operator - Function
set_staking_contract_operator - Function
set_stake_pool_operator - Function
set_vesting_contract_voter - Function
set_staking_contract_voter - Function
set_stake_pool_voter - Specification
- High-level Requirements
- Module-level Specification
- Function
grant_permission - Function
set_operator - Function
set_voter - Function
set_vesting_contract_operator - Function
set_staking_contract_operator - Function
set_stake_pool_operator - Function
set_vesting_contract_voter - Function
set_staking_contract_voter - Function
set_stake_pool_voter
use 0x1::error;
use 0x1::permissioned_signer;
use 0x1::signer;
use 0x1::stake;
use 0x1::staking_contract;
use 0x1::vesting;
Struct StakeProxyPermission
struct StakeProxyPermission has copy, drop, store
Fields
-
dummy_field: bool
Constants
Signer does not have permission to perform stake proxy logic.
const ENO_STAKE_PERMISSION: u64 = 28;
Function check_stake_proxy_permission
Permissions
fun check_stake_proxy_permission(s: &signer)
Implementation
inline fun check_stake_proxy_permission(s: &signer) {
assert!(
permissioned_signer::check_permission_exists(s, StakeProxyPermission {}),
error::permission_denied(ENO_STAKE_PERMISSION),
);
}
Function grant_permission
Grant permission to mutate staking on behalf of the master signer.
public fun grant_permission(master: &signer, permissioned_signer: &signer)
Implementation
public fun grant_permission(master: &signer, permissioned_signer: &signer) {
permissioned_signer::authorize_unlimited(master, permissioned_signer, StakeProxyPermission {})
}
Function set_operator
public entry fun set_operator(owner: &signer, old_operator: address, new_operator: address)
Implementation
public entry fun set_operator(owner: &signer, old_operator: address, new_operator: address) {
set_vesting_contract_operator(owner, old_operator, new_operator);
set_staking_contract_operator(owner, old_operator, new_operator);
set_stake_pool_operator(owner, new_operator);
}
Function set_voter
public entry fun set_voter(owner: &signer, operator: address, new_voter: address)
Implementation
public entry fun set_voter(owner: &signer, operator: address, new_voter: address) {
set_vesting_contract_voter(owner, operator, new_voter);
set_staking_contract_voter(owner, operator, new_voter);
set_stake_pool_voter(owner, new_voter);
}
Function set_vesting_contract_operator
public entry fun set_vesting_contract_operator(owner: &signer, old_operator: address, new_operator: address)
Implementation
public entry fun set_vesting_contract_operator(owner: &signer, old_operator: address, new_operator: address) {
check_stake_proxy_permission(owner);
let owner_address = signer::address_of(owner);
let vesting_contracts = &vesting::vesting_contracts(owner_address);
vesting_contracts.for_each_ref(|vesting_contract| {
let vesting_contract = *vesting_contract;
if (vesting::operator(vesting_contract) == old_operator) {
let current_commission_percentage = vesting::operator_commission_percentage(vesting_contract);
vesting::update_operator(owner, vesting_contract, new_operator, current_commission_percentage);
};
});
}
Function set_staking_contract_operator
public entry fun set_staking_contract_operator(owner: &signer, old_operator: address, new_operator: address)
Implementation
public entry fun set_staking_contract_operator(owner: &signer, old_operator: address, new_operator: address) {
check_stake_proxy_permission(owner);
let owner_address = signer::address_of(owner);
if (staking_contract::staking_contract_exists(owner_address, old_operator)) {
let current_commission_percentage = staking_contract::commission_percentage(owner_address, old_operator);
staking_contract::switch_operator(owner, old_operator, new_operator, current_commission_percentage);
};
}
Function set_stake_pool_operator
public entry fun set_stake_pool_operator(owner: &signer, new_operator: address)
Implementation
public entry fun set_stake_pool_operator(owner: &signer, new_operator: address) {
check_stake_proxy_permission(owner);
let owner_address = signer::address_of(owner);
if (stake::stake_pool_exists(owner_address)) {
stake::set_operator(owner, new_operator);
};
}
Function set_vesting_contract_voter
public entry fun set_vesting_contract_voter(owner: &signer, operator: address, new_voter: address)
Implementation
public entry fun set_vesting_contract_voter(owner: &signer, operator: address, new_voter: address) {
check_stake_proxy_permission(owner);
let owner_address = signer::address_of(owner);
let vesting_contracts = &vesting::vesting_contracts(owner_address);
vesting_contracts.for_each_ref(|vesting_contract| {
let vesting_contract = *vesting_contract;
if (vesting::operator(vesting_contract) == operator) {
vesting::update_voter(owner, vesting_contract, new_voter);
};
});
}
Function set_staking_contract_voter
public entry fun set_staking_contract_voter(owner: &signer, operator: address, new_voter: address)
Implementation
public entry fun set_staking_contract_voter(owner: &signer, operator: address, new_voter: address) {
check_stake_proxy_permission(owner);
let owner_address = signer::address_of(owner);
if (staking_contract::staking_contract_exists(owner_address, operator)) {
staking_contract::update_voter(owner, operator, new_voter);
};
}
Function set_stake_pool_voter
public entry fun set_stake_pool_voter(owner: &signer, new_voter: address)
Implementation
public entry fun set_stake_pool_voter(owner: &signer, new_voter: address) {
check_stake_proxy_permission(owner);
if (stake::stake_pool_exists(signer::address_of(owner))) {
stake::set_delegated_voter(owner, new_voter);
};
}
Specification
High-level Requirements
| No. | Requirement | Criticality | Implementation | Enforcement |
|---|---|---|---|---|
| 1 | When updating the Vesting operator, it should be updated throughout all depending units. | Medium | The VestingContract contains a StakingInfo object that has an operator field, and this operator is mapped to a StakingContract object that in turn encompasses a StakePool object where the operator matches. | Audited that it ensures the two operator fields hold the new value after the update. |
| 2 | When updating the Vesting voter, it should be updated throughout all depending units. | Medium | The VestingContract contains a StakingInfo object that has an operator field, and this operator is mapped to a StakingContract object that in turn encompasses a StakePool object where the operator matches. | Audited that it ensures the two operator fields hold the new value after the update. |
| 3 | The operator and voter of a Vesting Contract should only be updated by the owner of the contract. | High | The owner-operator-voter model, as defined in the documentation, grants distinct abilities to each role. Therefore, it's crucial to ensure that only the owner has the authority to modify the operator or voter, to prevent the compromise of the StakePool. | Audited that it ensures the signer owns the AdminStore resource and that the operator or voter intended for the update actually exists. |
| 4 | The operator and voter of a Staking Contract should only be updated by the owner of the contract. | High | The owner-operator-voter model, as defined in the documentation, grants distinct abilities to each role. Therefore, it's crucial to ensure that only the owner has the authority to modify the operator or voter, to prevent the compromise of the StakePool. | Audited the patterns of updating operators and voters in the staking contract. |
| 5 | Staking Contract's operators should be unique inside a store. | Medium | Duplicates among operators could result in incorrectly updating the operator or voter associated with the incorrect StakingContract. | Enforced via SimpleMap. |
Module-level Specification
pragma verify = true;
pragma aborts_if_is_partial;
Function grant_permission
public fun grant_permission(master: &signer, permissioned_signer: &signer)
pragma aborts_if_is_partial;
aborts_if !permissioned_signer::spec_is_permissioned_signer(permissioned_signer);
aborts_if permissioned_signer::spec_is_permissioned_signer(master);
aborts_if signer::address_of(master) != signer::address_of(permissioned_signer);
Function set_operator
public entry fun set_operator(owner: &signer, old_operator: address, new_operator: address)
Aborts if conditions of SetStakePoolOperator are not met
pragma verify = false;
pragma aborts_if_is_partial;
include SetStakePoolOperator;
include SetStakingContractOperator;
Function set_voter
public entry fun set_voter(owner: &signer, operator: address, new_voter: address)
Aborts if conditions of SetStackingContractVoter and SetStackPoolVoterAbortsIf are not met
pragma aborts_if_is_partial;
pragma verify_duration_estimate = 120;
include SetStakingContractVoter;
include SetStakePoolVoterAbortsIf;
Function set_vesting_contract_operator
public entry fun set_vesting_contract_operator(owner: &signer, old_operator: address, new_operator: address)
pragma verify = false;
Function set_staking_contract_operator
public entry fun set_staking_contract_operator(owner: &signer, old_operator: address, new_operator: address)
pragma aborts_if_is_partial;
pragma verify = false;
include SetStakingContractOperator;
schema SetStakingContractOperator {
owner: signer;
old_operator: address;
new_operator: address;
let owner_address = signer::address_of(owner);
let store = global<Store>(owner_address);
let staking_contract_exists = exists<Store>(owner_address) && simple_map::spec_contains_key(store.staking_contracts, old_operator);
aborts_if staking_contract_exists && simple_map::spec_contains_key(store.staking_contracts, new_operator);
let post post_store = global<Store>(owner_address);
ensures staking_contract_exists ==> !simple_map::spec_contains_key(post_store.staking_contracts, old_operator);
let staking_contract = simple_map::spec_get(store.staking_contracts, old_operator);
let stake_pool = global<stake::StakePool>(staking_contract.pool_address);
let active = coin::value(stake_pool.active);
let pending_active = coin::value(stake_pool.pending_active);
let total_active_stake = active + pending_active;
let accumulated_rewards = total_active_stake - staking_contract.principal;
let commission_amount = accumulated_rewards * staking_contract.commission_percentage / 100;
aborts_if staking_contract_exists && !exists<stake::StakePool>(staking_contract.pool_address);
ensures staking_contract_exists ==>
simple_map::spec_get(post_store.staking_contracts, new_operator).principal == total_active_stake - commission_amount;
let pool_address = staking_contract.owner_cap.pool_address;
let current_commission_percentage = staking_contract.commission_percentage;
aborts_if staking_contract_exists && commission_amount != 0 && !exists<stake::StakePool>(pool_address);
ensures staking_contract_exists && commission_amount != 0 ==>
global<stake::StakePool>(pool_address).operator_address == new_operator
&& simple_map::spec_get(post_store.staking_contracts, new_operator).commission_percentage == current_commission_percentage;
ensures staking_contract_exists ==> simple_map::spec_contains_key(post_store.staking_contracts, new_operator);
}
Function set_stake_pool_operator
public entry fun set_stake_pool_operator(owner: &signer, new_operator: address)
Aborts if stake_pool is exists and when OwnerCapability or stake_pool_exists One of them are not exists
include SetStakePoolOperator;
include AbortsIfSignerPermissionStakeProxy {
s: owner
};
include exists<stake::StakePool>(signer::address_of(owner)) ==> stake::AbortsIfSignerPermissionStake {
s:owner
};
schema SetStakePoolOperator {
owner: &signer;
new_operator: address;
include AbortsIfSignerPermissionStakeProxy {
s: owner
};
let owner_address = signer::address_of(owner);
let ownership_cap = borrow_global<stake::OwnerCapability>(owner_address);
let pool_address = ownership_cap.pool_address;
aborts_if stake::stake_pool_exists(owner_address) && !(exists<stake::OwnerCapability>(owner_address) && stake::stake_pool_exists(pool_address));
ensures stake::stake_pool_exists(owner_address) ==> global<stake::StakePool>(pool_address).operator_address == new_operator;
}
Function set_vesting_contract_voter
public entry fun set_vesting_contract_voter(owner: &signer, operator: address, new_voter: address)
pragma verify = false;
Function set_staking_contract_voter
public entry fun set_staking_contract_voter(owner: &signer, operator: address, new_voter: address)
include SetStakingContractVoter;
include AbortsIfSignerPermissionStakeProxy {
s: owner
};
Make sure staking_contract_exists first Then abort if the resource is not exist
schema SetStakingContractVoter {
owner: &signer;
operator: address;
new_voter: address;
let owner_address = signer::address_of(owner);
let staker = owner_address;
let store = global<Store>(staker);
let staking_contract_exists = exists<Store>(staker) && simple_map::spec_contains_key(store.staking_contracts, operator);
let staker_address = owner_address;
let staking_contract = simple_map::spec_get(store.staking_contracts, operator);
let pool_address = staking_contract.pool_address;
let pool_address1 = staking_contract.owner_cap.pool_address;
aborts_if staking_contract_exists && !exists<stake::StakePool>(pool_address);
aborts_if staking_contract_exists && !exists<stake::StakePool>(staking_contract.owner_cap.pool_address);
ensures staking_contract_exists ==> global<stake::StakePool>(pool_address1).delegated_voter == new_voter;
}
Function set_stake_pool_voter
public entry fun set_stake_pool_voter(owner: &signer, new_voter: address)
include SetStakePoolVoterAbortsIf;
include AbortsIfSignerPermissionStakeProxy {
s: owner
};
include exists<stake::StakePool>(signer::address_of(owner)) ==> stake::AbortsIfSignerPermissionStake {
s:owner
};
schema SetStakePoolVoterAbortsIf {
owner: &signer;
new_voter: address;
include AbortsIfSignerPermissionStakeProxy {
s: owner
};
let owner_address = signer::address_of(owner);
let ownership_cap = global<stake::OwnerCapability>(owner_address);
let pool_address = ownership_cap.pool_address;
aborts_if stake::stake_pool_exists(owner_address) && !(exists<stake::OwnerCapability>(owner_address) && stake::stake_pool_exists(pool_address));
ensures stake::stake_pool_exists(owner_address) ==> global<stake::StakePool>(pool_address).delegated_voter == new_voter;
}
schema AbortsIfSignerPermissionStakeProxy {
s: signer;
let perm = StakeProxyPermission {};
aborts_if !permissioned_signer::spec_check_permission_exists(s, perm);
}