Owner's Methods
We shall took at the methods that can be called by the owner. The owner is the original caller of the contract, at least in this case. For example, Bob wants to stack some NEAR to the staking pool. Bob will be the owner. We shall see later on (in the next page) when transfers no longer refer to the original caller of the contract.
As the owner, of course he can select a staking pool to stake, be it staking with NEAR, staking with Aurora, or staking with Everstake, for example. So that's our first function: select_staking_pool
.
use crate::*;
use near_sdk::{near_bindgen, AccountId, Promise, PublicKey, require};
#[near_bindgen]
impl LockupContract {
fn repeated_assertions(&mut self) {
self.assert_owner();
self.assert_staking_pool_is_idle();
self.assert_no_termination();
}
/// OWNER'S METHOD
///
/// Requires 75 TGas
///
/// Select staking pool contract at the given account ID. The staking pool
/// first has to be checked against staking pool whitelist contract.
pub fn select_staking_pool(
&mut self,
staking_pool_account_id: AccountId
) -> Promise {
self.assert_owner();
require!(
env::is_valid_account_id(staking_pool_account_id.as_bytes()),
"The staking pool account ID is invalid"
);
self.assert_staking_pool_is_not_selected();
self.assert_no_termination();
env::log_str(
format!(
"Selecting staking pool @{}. Checking if this pool is whitelisted.",
staking_pool_account_id
).as_str(),
);
ext_whitelist::is_whitelisted(
staking_pool_account_id.clone(),
self.staking_pool_whitelist_account_id.clone(),
NO_DEPOSIT,
gas::whitelist::IS_WHITELISTED,
)
.then(
ext_self_owner::on_whitelist_is_whitelisted(
staking_pool_account_id,
env::current_account_id(),
NO_DEPOSIT,
gas::owner_callbacks::ON_WHITELIST_IS_WHITELISTED,
)
)
}
/// OWNER'S METHOD
///
/// Requires 25 TGas
///
/// Unselects the current(ly selected) staking pool.
/// It requires that there are no known deposits left on
/// the currently selected staking pool.
pub fn unselect_staking_pool(&mut self) {
// self.assert_owner();
// self.assert_staking_pool_is_idle();
// self.assert_no_termination();
self.repeated_assertions();
// This is best effort check. There may still be leftovers
// in the staking pool. Owner can choose to or not to
// unselect. The contract doesn't care about leftovers.
require!(
self.staking_information.as_ref().unwrap()
.deposit_amount.0 == 0,
"There is still deposit on staking pool."
);
env::log_str(
format!(
"Unselecting current staking pool @{}",
self.staking_information
.as_ref()
.unwrap()
.staking_pool_account_id
.clone()
).as_str(),
);
self.staking_information = None;
}
/// OWNER'S METHOD
///
/// Requires 100 TGas
///
/// Deposits the given extra amount to the staking pool.
pub fn deposit_to_staking_pool(
&mut self,
amount: WrappedBalance
) -> Promise {
self.repeated_assertions();
require!(
amount.0 > 0,
"Deposit amount should be positive."
);
require!(
self.get_account_balance().0 >= amount.0,
"Trying to stake more than you have is not possible."
);
env::log_str(
format!(
"Depositing {} to @{}",
amount.0,
self.staking_information
.as_ref()
.unwrap()
.staking_pool_account_id
.clone()
).as_str(),
);
self.set_staking_pool_status(TransactionStatus::Busy);
ext_staking_pool::deposit(
self.staking_information
.as_ref()
.unwrap()
.staking_pool_account_id
.clone(),
amount.0,
gas::staking_pool::DEPOSIT,
)
.then(
ext_self_owner::on_staking_pool_deposit(
amount,
env::current_account_id(),
NO_DEPOSIT,
gas::owner_callbacks::ON_STAKING_POOL_DEPOSIT,
)
)
}
/// OWNER'S METHOD
///
/// Requires 125 TGas
///
/// Deposits and stakes the given extra amount to the
/// selected staking pool.
pub fn deposit_and_stake(
&mut self,
amount: WrappedBalance
) -> Promise {
self.repeated_assertions();
require!(amount.0 > 0, "Deposit amount should be positive.");
require!(
self.get_account_balance().0 >= amount.0,
"Trying to stake more than you have is not possible."
);
env::log_str(
format!(
"Depositing and staking {} to @{}",
amount.0,
self.staking_information
.as_ref()
.unwrap()
.staking_pool_account_id
.clone()
).as_str(),
);
self.set_staking_pool_status(TransactionStatus::Busy);
ext_staking_pool::deposit_and_stake(
self.staking_information
.as_ref()
.unwrap()
.staking_pool_account_id
.clone(),
amount.0,
gas::staking_pool::DEPOSIT_AND_STAKE,
)
.then(
ext_self_owner::on_staking_pool_deposit_and_stake(
amount,
env::current_account_id(),
NO_DEPOSIT,
gas::owner_callbacks::ON_STAKING_POOL_DEPOSIT_AND_STAKE,
)
)
}
/// OWNER'S METHOD
///
/// Requires 75 TGas
///
/// Retrieves total balance from staking pool and save it
/// internally. Useful when owner wants to receive rewards
/// during unstaking for querying total balance in the pool
/// that could be withdrawn.
pub fn refresh_staking_pool_balance(&mut self) -> Promise {
self.repeated_assertions();
env::log_str(
format!(
"Fetching total balance from staking pool @{}",
self.staking_information
.as_ref()
.unwrap()
.staking_pool_account_id
.clone()
).as_str(),
);
self.set_staking_pool_status(TransactionStatus::Busy);
ext_staking_pool::get_account_total_balance(
env::current_account_id(),
self.staking_information
.as_ref()
.unwrap()
.staking_pool_account_id
.clone(),
NO_DEPOSIT,
gas::staking_pool::GET_ACCOUNT_TOTAL_BALANCE,
)
.then(
ext_self_owner::on_get_account_total_balance(
env::current_account_id(),
NO_DEPOSIT,
gas::owner_callbacks::ON_GET_ACCOUNT_TOTAL_BALANCE,
)
)
}
/// OWNER'S METHOD
///
/// Requires 125 TGas
///
/// Withdraws the given amount from the staking pool.
pub fn withdraw_from_staking_pool(
&mut self,
amount: WrappedBalance
) -> Promise {
self.repeated_assertions();
require!(
amount.0 > 0,
"Amount should be positive"
);
env::log_str(
format!(
"Withdrawing {} from @{}",
amount.0,
self.staking_information
.as_ref()
.unwrap()
.staking_pool_account_id
.clone()
).as_str(),
);
self.set_staking_pool_status(TransactionStatus::Busy);
ext_staking_pool::withdraw(
amount,
self.staking_information
.as_ref()
.unwrap()
.staking_pool_account_id
.clone(),
NO_DEPOSIT,
gas::staking_pool::WITHDRAW,
)
.then(
ext_self_owner::on_staking_pool_withdraw(
amount,
env::current_account_id(),
NO_DEPOSIT,
gas::owner_callbacks::ON_STAKING_POOL_WITHDRAW,
)
)
}
/// OWNER'S METHOD
///
/// Requires 175 TGas
///
/// Tries to withdraw all unstaked balance from staking pool.
pub fn withdraw_all_from_staking_pool(&mut self) -> Promise {
self.repeated_assertions();
env::log_str(
format!(
"Querying unstaked balance on @{} to withdraw everything.",
self.staking_information
.as_ref()
.unwrap()
.staking_pool_account_id
.clone()
).as_str(),
);
self.set_staking_pool_status(TransactionStatus::Busy);
ext_staking_pool::get_account_unstaked_balance(
env::current_account_id(),
self.staking_information
.as_ref()
.unwrap()
.staking_pool_account_id
.clone(),
NO_DEPOSIT,
gas::staking_pool::GET_ACCOUNT_UNSTAKED_BALANCE,
)
.then(
ext_self_owner::on_get_account_unstaked_balance_to_withdraw_by_owner(
env::current_account_id(),
NO_DEPOSIT,
gas::owner_callbacks::ON_GET_ACCOUNT_UNSTAKED_BALANCE_TO_WITHDRAW_BY_OWNER,
),
)
}
/// ONWER'S METHOD
///
/// Requires 125 TGas
///
/// Stakes the given extra amount at the staking pool.
pub fn stake(
&mut self,
amount: WrappedBalance
) -> Promise {
self.repeated_assertions();
require!(
amount.0 > 0,
"Amount should be positive."
);
env::log_str(
format!(
"Staking {} at @{}",
amount.0,
self.staking_information
.as_ref()
.unwrap()
.staking_pool_account_id
).as_str(),
);
self.set_staking_pool_status(TransactionStatus::Busy);
ext_staking_pool::stake(
amount,
self.staking_information
.as_ref()
.unwrap()
.staking_pool_account_id
.clone(),
NO_DEPOSIT,
gas::staking_pool::STAKE,
)
.then(
ext_self_owner::on_staking_pool_stake(
amount,
env::current_account_id(),
NO_DEPOSIT,
gas::owner_callbacks::ON_STAKING_POOL_STAKE,
)
)
}
/// OWNER'S METHOD
///
/// Requires 125 TGas
///
/// Unstakes the given amount at the staking pool.
pub fn unstake(
&mut self,
amount: WrappedBalance
) -> Promise {
self.repeated_assertions();
require!(
amount.0 > 0,
"Amount should be positive"
);
env::log_str(
format!(
"Unstaking {} from @{}",
amount.0,
self.staking_information
.as_ref()
.unwrap()
.staking_pool_account_id
.clone()
).as_str(),
);
self.set_staking_pool_status(TransactionStatus::Busy);
ext_staking_pool::unstake(
amount,
self.staking_information
.as_ref()
.unwrap()
.staking_pool_account_id
.clone(),
NO_DEPOSIT,
gas::staking_pool::UNSTAKE,
)
.then(
ext_self_owner::on_staking_pool_unstake(
amount,
env::current_account_id(),
NO_DEPOSIT,
gas::owner_callbacks::ON_STAKING_POOL_UNSTAKE,
)
)
}
/// OWNER'S METHOD
///
/// Requires 125 TGas
///
/// Unstakes all tokens from staking pool
pub fn unstake_all(&mut self) -> Promise {
self.repeated_assertions();
env::log_str(
format!(
"Unstaking all tokens from @{}",
self.staking_information
.as_ref()
.unwrap()
.staking_pool_account_id
.clone()
).as_str(),
);
self.set_staking_pool_status(TransactionStatus::Busy);
ext_staking_pool::unstake_all(
self.staking_information
.as_ref()
.unwrap()
.staking_pool_account_id
.clone(),
NO_DEPOSIT,
gas::staking_pool::UNSTAKE_ALL,
).then(
ext_self_owner::on_staking_pool_unstake_all(
env::current_account_id(),
NO_DEPOSIT,
gas::owner_callbacks::ON_STAKING_POOL_UNSTAKE_ALL,
)
)
}
/// Requires 75 TGas
/// Not intended to hand over the access to someone else
/// except the owner.
///
/// Calls voting contract to validate if the transfers were enabled by
/// voting. Once transfers are enabled, they can't be disabled anymore.
pub fn check_transfers_vote(&mut self) -> Promise {
self.assert_owner();
self.assert_transfers_disabled();
self.assert_no_termination();
let transfer_poll_account_id =
match &self.lockup_information.transfers_information
{
TransfersInformation::TransfersDisabled {
transfer_poll_account_id
} => transfer_poll_account_id,
_ => unreachable!(),
};
env::log_str(
format!(
"Checking that transfers are enabled at the transfer poll contract @{}",
&transfer_poll_account_id.clone()
).as_str(),
);
ext_transfer_poll::get_result(
transfer_poll_account_id.clone(),
NO_DEPOSIT,
gas::transfer_poll::GET_RESULT,
)
.then(
ext_self_owner::on_get_result_from_transfer_poll(
env::current_account_id(),
NO_DEPOSIT,
gas::owner_callbacks::ON_VOTING_GET_RESULT,
)
)
}
/// Requires 50 TGas
/// Not intended to hand over the access to someone else
/// except the owner.
///
/// Transfers the given amount to the given receiver
/// account ID. This requires transfers to be enabled
/// within the voting contract.
pub fn transfer(
&mut self,
amount: WrappedBalance,
receiver_id: AccountId
) -> Promise {
self.assert_owner();
require!(
env::is_valid_account_id(receiver_id.as_bytes()),
"The receiver account ID is invalid"
);
self.assert_transfers_enabled();
self.assert_no_staking_or_idle();
self.assert_no_termination();
require!(
self.get_liquid_owners_balance().0 >= amount.0,
format!(
concat!(
"The available liquid balance {} is smaller than ",
"the requested tranfer amount {}"),
self.get_liquid_owners_balance().0,
amount.0,
),
);
env::log_str(
format!(
"Transferring {} to @{}",
amount.0,
receiver_id
).as_str()
);
Promise::new(receiver_id).transfer(amount.0)
}
/// Requires 50 TGas
/// Not intended to hand over the access to someone else
/// except the owner.
///
/// Adds full access key with the given public key to the
/// account. You need:
/// - Fully-vested account
/// - Lockup duration expired.
/// - Transfers enabled.
/// - No termination in progress (either none or must be finished)
/// This account will become a regular account, contract will be removed.
pub fn add_full_access_key(
&mut self,
new_public_key: PublicKey
) -> Promise {
self.assert_owner();
self.assert_transfers_enabled();
self.assert_no_staking_or_idle();
self.assert_no_termination();
require!(
self.get_locked_amount().0 == 0,
"Tokens are still locked/unvested"
);
env::log_str("Adding a full access key");
// We pass in PublicKey, not Base58PublicKey, so no need this.
// let new_public_key: PublicKey = new_public_key.into();
Promise::new(
env::current_account_id()
).add_full_access_key(new_public_key)
}
}
As with every owner's method, we need to assert that these methods can be called only by the owner. Then we can have some other assertions specific to the method. For example, here we need to check that the selected staking pool is a valid staking pool, we need to check that a staking pool has not yet been selected already, and we need to check that there's no termination (of staking) in progress (if you terminated, you might have to wait for finished termination before restaking again).
The contract also needs to check whether the pool you want to stake to is the current whitelisted pool. If it is blacklisted then you cannot stake to the pool; if it is on queue, perhaps you have to wait for it to be current validators before you can stake, one don't know. If it's paused, one don't know.
While you're reading this, one fill in the unfilled assertions and gas methods. As usual, you can check the assertions from internal.rs
yourself on how it works, but that's not totally required (but one encourages you to check it out yourself to know what is being compared to raise assertions). The name itself explains a lot. You can also check the gas methods in gas.rs
to see how much gas is used.
We also see that checking for whitelist is done in the future using a callback (a Promise).
With the ability to select a staking pool, owner can unselect the selected staking pool!
use crate::*;
use near_sdk::{near_bindgen, AccountId, Promise, PublicKey, require};
#[near_bindgen]
impl LockupContract {
fn repeated_assertions(&mut self) {
self.assert_owner();
self.assert_staking_pool_is_idle();
self.assert_no_termination();
}
/// OWNER'S METHOD
///
/// Requires 75 TGas
///
/// Select staking pool contract at the given account ID. The staking pool
/// first has to be checked against staking pool whitelist contract.
pub fn select_staking_pool(
&mut self,
staking_pool_account_id: AccountId
) -> Promise {
self.assert_owner();
require!(
env::is_valid_account_id(staking_pool_account_id.as_bytes()),
"The staking pool account ID is invalid"
);
self.assert_staking_pool_is_not_selected();
self.assert_no_termination();
env::log_str(
format!(
"Selecting staking pool @{}. Checking if this pool is whitelisted.",
staking_pool_account_id
).as_str(),
);
ext_whitelist::is_whitelisted(
staking_pool_account_id.clone(),
self.staking_pool_whitelist_account_id.clone(),
NO_DEPOSIT,
gas::whitelist::IS_WHITELISTED,
)
.then(
ext_self_owner::on_whitelist_is_whitelisted(
staking_pool_account_id,
env::current_account_id(),
NO_DEPOSIT,
gas::owner_callbacks::ON_WHITELIST_IS_WHITELISTED,
)
)
}
/// OWNER'S METHOD
///
/// Requires 25 TGas
///
/// Unselects the current(ly selected) staking pool.
/// It requires that there are no known deposits left on
/// the currently selected staking pool.
pub fn unselect_staking_pool(&mut self) {
// self.assert_owner();
// self.assert_staking_pool_is_idle();
// self.assert_no_termination();
self.repeated_assertions();
// This is best effort check. There may still be leftovers
// in the staking pool. Owner can choose to or not to
// unselect. The contract doesn't care about leftovers.
require!(
self.staking_information.as_ref().unwrap()
.deposit_amount.0 == 0,
"There is still deposit on staking pool."
);
env::log_str(
format!(
"Unselecting current staking pool @{}",
self.staking_information
.as_ref()
.unwrap()
.staking_pool_account_id
.clone()
).as_str(),
);
self.staking_information = None;
}
/// OWNER'S METHOD
///
/// Requires 100 TGas
///
/// Deposits the given extra amount to the staking pool.
pub fn deposit_to_staking_pool(
&mut self,
amount: WrappedBalance
) -> Promise {
self.repeated_assertions();
require!(
amount.0 > 0,
"Deposit amount should be positive."
);
require!(
self.get_account_balance().0 >= amount.0,
"Trying to stake more than you have is not possible."
);
env::log_str(
format!(
"Depositing {} to @{}",
amount.0,
self.staking_information
.as_ref()
.unwrap()
.staking_pool_account_id
.clone()
).as_str(),
);
self.set_staking_pool_status(TransactionStatus::Busy);
ext_staking_pool::deposit(
self.staking_information
.as_ref()
.unwrap()
.staking_pool_account_id
.clone(),
amount.0,
gas::staking_pool::DEPOSIT,
)
.then(
ext_self_owner::on_staking_pool_deposit(
amount,
env::current_account_id(),
NO_DEPOSIT,
gas::owner_callbacks::ON_STAKING_POOL_DEPOSIT,
)
)
}
/// OWNER'S METHOD
///
/// Requires 125 TGas
///
/// Deposits and stakes the given extra amount to the
/// selected staking pool.
pub fn deposit_and_stake(
&mut self,
amount: WrappedBalance
) -> Promise {
self.repeated_assertions();
require!(amount.0 > 0, "Deposit amount should be positive.");
require!(
self.get_account_balance().0 >= amount.0,
"Trying to stake more than you have is not possible."
);
env::log_str(
format!(
"Depositing and staking {} to @{}",
amount.0,
self.staking_information
.as_ref()
.unwrap()
.staking_pool_account_id
.clone()
).as_str(),
);
self.set_staking_pool_status(TransactionStatus::Busy);
ext_staking_pool::deposit_and_stake(
self.staking_information
.as_ref()
.unwrap()
.staking_pool_account_id
.clone(),
amount.0,
gas::staking_pool::DEPOSIT_AND_STAKE,
)
.then(
ext_self_owner::on_staking_pool_deposit_and_stake(
amount,
env::current_account_id(),
NO_DEPOSIT,
gas::owner_callbacks::ON_STAKING_POOL_DEPOSIT_AND_STAKE,
)
)
}
/// OWNER'S METHOD
///
/// Requires 75 TGas
///
/// Retrieves total balance from staking pool and save it
/// internally. Useful when owner wants to receive rewards
/// during unstaking for querying total balance in the pool
/// that could be withdrawn.
pub fn refresh_staking_pool_balance(&mut self) -> Promise {
self.repeated_assertions();
env::log_str(
format!(
"Fetching total balance from staking pool @{}",
self.staking_information
.as_ref()
.unwrap()
.staking_pool_account_id
.clone()
).as_str(),
);
self.set_staking_pool_status(TransactionStatus::Busy);
ext_staking_pool::get_account_total_balance(
env::current_account_id(),
self.staking_information
.as_ref()
.unwrap()
.staking_pool_account_id
.clone(),
NO_DEPOSIT,
gas::staking_pool::GET_ACCOUNT_TOTAL_BALANCE,
)
.then(
ext_self_owner::on_get_account_total_balance(
env::current_account_id(),
NO_DEPOSIT,
gas::owner_callbacks::ON_GET_ACCOUNT_TOTAL_BALANCE,
)
)
}
/// OWNER'S METHOD
///
/// Requires 125 TGas
///
/// Withdraws the given amount from the staking pool.
pub fn withdraw_from_staking_pool(
&mut self,
amount: WrappedBalance
) -> Promise {
self.repeated_assertions();
require!(
amount.0 > 0,
"Amount should be positive"
);
env::log_str(
format!(
"Withdrawing {} from @{}",
amount.0,
self.staking_information
.as_ref()
.unwrap()
.staking_pool_account_id
.clone()
).as_str(),
);
self.set_staking_pool_status(TransactionStatus::Busy);
ext_staking_pool::withdraw(
amount,
self.staking_information
.as_ref()
.unwrap()
.staking_pool_account_id
.clone(),
NO_DEPOSIT,
gas::staking_pool::WITHDRAW,
)
.then(
ext_self_owner::on_staking_pool_withdraw(
amount,
env::current_account_id(),
NO_DEPOSIT,
gas::owner_callbacks::ON_STAKING_POOL_WITHDRAW,
)
)
}
/// OWNER'S METHOD
///
/// Requires 175 TGas
///
/// Tries to withdraw all unstaked balance from staking pool.
pub fn withdraw_all_from_staking_pool(&mut self) -> Promise {
self.repeated_assertions();
env::log_str(
format!(
"Querying unstaked balance on @{} to withdraw everything.",
self.staking_information
.as_ref()
.unwrap()
.staking_pool_account_id
.clone()
).as_str(),
);
self.set_staking_pool_status(TransactionStatus::Busy);
ext_staking_pool::get_account_unstaked_balance(
env::current_account_id(),
self.staking_information
.as_ref()
.unwrap()
.staking_pool_account_id
.clone(),
NO_DEPOSIT,
gas::staking_pool::GET_ACCOUNT_UNSTAKED_BALANCE,
)
.then(
ext_self_owner::on_get_account_unstaked_balance_to_withdraw_by_owner(
env::current_account_id(),
NO_DEPOSIT,
gas::owner_callbacks::ON_GET_ACCOUNT_UNSTAKED_BALANCE_TO_WITHDRAW_BY_OWNER,
),
)
}
/// ONWER'S METHOD
///
/// Requires 125 TGas
///
/// Stakes the given extra amount at the staking pool.
pub fn stake(
&mut self,
amount: WrappedBalance
) -> Promise {
self.repeated_assertions();
require!(
amount.0 > 0,
"Amount should be positive."
);
env::log_str(
format!(
"Staking {} at @{}",
amount.0,
self.staking_information
.as_ref()
.unwrap()
.staking_pool_account_id
).as_str(),
);
self.set_staking_pool_status(TransactionStatus::Busy);
ext_staking_pool::stake(
amount,
self.staking_information
.as_ref()
.unwrap()
.staking_pool_account_id
.clone(),
NO_DEPOSIT,
gas::staking_pool::STAKE,
)
.then(
ext_self_owner::on_staking_pool_stake(
amount,
env::current_account_id(),
NO_DEPOSIT,
gas::owner_callbacks::ON_STAKING_POOL_STAKE,
)
)
}
/// OWNER'S METHOD
///
/// Requires 125 TGas
///
/// Unstakes the given amount at the staking pool.
pub fn unstake(
&mut self,
amount: WrappedBalance
) -> Promise {
self.repeated_assertions();
require!(
amount.0 > 0,
"Amount should be positive"
);
env::log_str(
format!(
"Unstaking {} from @{}",
amount.0,
self.staking_information
.as_ref()
.unwrap()
.staking_pool_account_id
.clone()
).as_str(),
);
self.set_staking_pool_status(TransactionStatus::Busy);
ext_staking_pool::unstake(
amount,
self.staking_information
.as_ref()
.unwrap()
.staking_pool_account_id
.clone(),
NO_DEPOSIT,
gas::staking_pool::UNSTAKE,
)
.then(
ext_self_owner::on_staking_pool_unstake(
amount,
env::current_account_id(),
NO_DEPOSIT,
gas::owner_callbacks::ON_STAKING_POOL_UNSTAKE,
)
)
}
/// OWNER'S METHOD
///
/// Requires 125 TGas
///
/// Unstakes all tokens from staking pool
pub fn unstake_all(&mut self) -> Promise {
self.repeated_assertions();
env::log_str(
format!(
"Unstaking all tokens from @{}",
self.staking_information
.as_ref()
.unwrap()
.staking_pool_account_id
.clone()
).as_str(),
);
self.set_staking_pool_status(TransactionStatus::Busy);
ext_staking_pool::unstake_all(
self.staking_information
.as_ref()
.unwrap()
.staking_pool_account_id
.clone(),
NO_DEPOSIT,
gas::staking_pool::UNSTAKE_ALL,
).then(
ext_self_owner::on_staking_pool_unstake_all(
env::current_account_id(),
NO_DEPOSIT,
gas::owner_callbacks::ON_STAKING_POOL_UNSTAKE_ALL,
)
)
}
/// Requires 75 TGas
/// Not intended to hand over the access to someone else
/// except the owner.
///
/// Calls voting contract to validate if the transfers were enabled by
/// voting. Once transfers are enabled, they can't be disabled anymore.
pub fn check_transfers_vote(&mut self) -> Promise {
self.assert_owner();
self.assert_transfers_disabled();
self.assert_no_termination();
let transfer_poll_account_id =
match &self.lockup_information.transfers_information
{
TransfersInformation::TransfersDisabled {
transfer_poll_account_id
} => transfer_poll_account_id,
_ => unreachable!(),
};
env::log_str(
format!(
"Checking that transfers are enabled at the transfer poll contract @{}",
&transfer_poll_account_id.clone()
).as_str(),
);
ext_transfer_poll::get_result(
transfer_poll_account_id.clone(),
NO_DEPOSIT,
gas::transfer_poll::GET_RESULT,
)
.then(
ext_self_owner::on_get_result_from_transfer_poll(
env::current_account_id(),
NO_DEPOSIT,
gas::owner_callbacks::ON_VOTING_GET_RESULT,
)
)
}
/// Requires 50 TGas
/// Not intended to hand over the access to someone else
/// except the owner.
///
/// Transfers the given amount to the given receiver
/// account ID. This requires transfers to be enabled
/// within the voting contract.
pub fn transfer(
&mut self,
amount: WrappedBalance,
receiver_id: AccountId
) -> Promise {
self.assert_owner();
require!(
env::is_valid_account_id(receiver_id.as_bytes()),
"The receiver account ID is invalid"
);
self.assert_transfers_enabled();
self.assert_no_staking_or_idle();
self.assert_no_termination();
require!(
self.get_liquid_owners_balance().0 >= amount.0,
format!(
concat!(
"The available liquid balance {} is smaller than ",
"the requested tranfer amount {}"),
self.get_liquid_owners_balance().0,
amount.0,
),
);
env::log_str(
format!(
"Transferring {} to @{}",
amount.0,
receiver_id
).as_str()
);
Promise::new(receiver_id).transfer(amount.0)
}
/// Requires 50 TGas
/// Not intended to hand over the access to someone else
/// except the owner.
///
/// Adds full access key with the given public key to the
/// account. You need:
/// - Fully-vested account
/// - Lockup duration expired.
/// - Transfers enabled.
/// - No termination in progress (either none or must be finished)
/// This account will become a regular account, contract will be removed.
pub fn add_full_access_key(
&mut self,
new_public_key: PublicKey
) -> Promise {
self.assert_owner();
self.assert_transfers_enabled();
self.assert_no_staking_or_idle();
self.assert_no_termination();
require!(
self.get_locked_amount().0 == 0,
"Tokens are still locked/unvested"
);
env::log_str("Adding a full access key");
// We pass in PublicKey, not Base58PublicKey, so no need this.
// let new_public_key: PublicKey = new_public_key.into();
Promise::new(
env::current_account_id()
).add_full_access_key(new_public_key)
}
}
Before we move on, there is a repeated code block of assertions in the upcoming few functions, hence we moved them out into its own function for DRY.
use crate::*;
use near_sdk::{near_bindgen, AccountId, Promise, PublicKey, require};
#[near_bindgen]
impl LockupContract {
fn repeated_assertions(&mut self) {
self.assert_owner();
self.assert_staking_pool_is_idle();
self.assert_no_termination();
}
/// OWNER'S METHOD
///
/// Requires 75 TGas
///
/// Select staking pool contract at the given account ID. The staking pool
/// first has to be checked against staking pool whitelist contract.
pub fn select_staking_pool(
&mut self,
staking_pool_account_id: AccountId
) -> Promise {
self.assert_owner();
require!(
env::is_valid_account_id(staking_pool_account_id.as_bytes()),
"The staking pool account ID is invalid"
);
self.assert_staking_pool_is_not_selected();
self.assert_no_termination();
env::log_str(
format!(
"Selecting staking pool @{}. Checking if this pool is whitelisted.",
staking_pool_account_id
).as_str(),
);
ext_whitelist::is_whitelisted(
staking_pool_account_id.clone(),
self.staking_pool_whitelist_account_id.clone(),
NO_DEPOSIT,
gas::whitelist::IS_WHITELISTED,
)
.then(
ext_self_owner::on_whitelist_is_whitelisted(
staking_pool_account_id,
env::current_account_id(),
NO_DEPOSIT,
gas::owner_callbacks::ON_WHITELIST_IS_WHITELISTED,
)
)
}
/// OWNER'S METHOD
///
/// Requires 25 TGas
///
/// Unselects the current(ly selected) staking pool.
/// It requires that there are no known deposits left on
/// the currently selected staking pool.
pub fn unselect_staking_pool(&mut self) {
// self.assert_owner();
// self.assert_staking_pool_is_idle();
// self.assert_no_termination();
self.repeated_assertions();
// This is best effort check. There may still be leftovers
// in the staking pool. Owner can choose to or not to
// unselect. The contract doesn't care about leftovers.
require!(
self.staking_information.as_ref().unwrap()
.deposit_amount.0 == 0,
"There is still deposit on staking pool."
);
env::log_str(
format!(
"Unselecting current staking pool @{}",
self.staking_information
.as_ref()
.unwrap()
.staking_pool_account_id
.clone()
).as_str(),
);
self.staking_information = None;
}
/// OWNER'S METHOD
///
/// Requires 100 TGas
///
/// Deposits the given extra amount to the staking pool.
pub fn deposit_to_staking_pool(
&mut self,
amount: WrappedBalance
) -> Promise {
self.repeated_assertions();
require!(
amount.0 > 0,
"Deposit amount should be positive."
);
require!(
self.get_account_balance().0 >= amount.0,
"Trying to stake more than you have is not possible."
);
env::log_str(
format!(
"Depositing {} to @{}",
amount.0,
self.staking_information
.as_ref()
.unwrap()
.staking_pool_account_id
.clone()
).as_str(),
);
self.set_staking_pool_status(TransactionStatus::Busy);
ext_staking_pool::deposit(
self.staking_information
.as_ref()
.unwrap()
.staking_pool_account_id
.clone(),
amount.0,
gas::staking_pool::DEPOSIT,
)
.then(
ext_self_owner::on_staking_pool_deposit(
amount,
env::current_account_id(),
NO_DEPOSIT,
gas::owner_callbacks::ON_STAKING_POOL_DEPOSIT,
)
)
}
/// OWNER'S METHOD
///
/// Requires 125 TGas
///
/// Deposits and stakes the given extra amount to the
/// selected staking pool.
pub fn deposit_and_stake(
&mut self,
amount: WrappedBalance
) -> Promise {
self.repeated_assertions();
require!(amount.0 > 0, "Deposit amount should be positive.");
require!(
self.get_account_balance().0 >= amount.0,
"Trying to stake more than you have is not possible."
);
env::log_str(
format!(
"Depositing and staking {} to @{}",
amount.0,
self.staking_information
.as_ref()
.unwrap()
.staking_pool_account_id
.clone()
).as_str(),
);
self.set_staking_pool_status(TransactionStatus::Busy);
ext_staking_pool::deposit_and_stake(
self.staking_information
.as_ref()
.unwrap()
.staking_pool_account_id
.clone(),
amount.0,
gas::staking_pool::DEPOSIT_AND_STAKE,
)
.then(
ext_self_owner::on_staking_pool_deposit_and_stake(
amount,
env::current_account_id(),
NO_DEPOSIT,
gas::owner_callbacks::ON_STAKING_POOL_DEPOSIT_AND_STAKE,
)
)
}
/// OWNER'S METHOD
///
/// Requires 75 TGas
///
/// Retrieves total balance from staking pool and save it
/// internally. Useful when owner wants to receive rewards
/// during unstaking for querying total balance in the pool
/// that could be withdrawn.
pub fn refresh_staking_pool_balance(&mut self) -> Promise {
self.repeated_assertions();
env::log_str(
format!(
"Fetching total balance from staking pool @{}",
self.staking_information
.as_ref()
.unwrap()
.staking_pool_account_id
.clone()
).as_str(),
);
self.set_staking_pool_status(TransactionStatus::Busy);
ext_staking_pool::get_account_total_balance(
env::current_account_id(),
self.staking_information
.as_ref()
.unwrap()
.staking_pool_account_id
.clone(),
NO_DEPOSIT,
gas::staking_pool::GET_ACCOUNT_TOTAL_BALANCE,
)
.then(
ext_self_owner::on_get_account_total_balance(
env::current_account_id(),
NO_DEPOSIT,
gas::owner_callbacks::ON_GET_ACCOUNT_TOTAL_BALANCE,
)
)
}
/// OWNER'S METHOD
///
/// Requires 125 TGas
///
/// Withdraws the given amount from the staking pool.
pub fn withdraw_from_staking_pool(
&mut self,
amount: WrappedBalance
) -> Promise {
self.repeated_assertions();
require!(
amount.0 > 0,
"Amount should be positive"
);
env::log_str(
format!(
"Withdrawing {} from @{}",
amount.0,
self.staking_information
.as_ref()
.unwrap()
.staking_pool_account_id
.clone()
).as_str(),
);
self.set_staking_pool_status(TransactionStatus::Busy);
ext_staking_pool::withdraw(
amount,
self.staking_information
.as_ref()
.unwrap()
.staking_pool_account_id
.clone(),
NO_DEPOSIT,
gas::staking_pool::WITHDRAW,
)
.then(
ext_self_owner::on_staking_pool_withdraw(
amount,
env::current_account_id(),
NO_DEPOSIT,
gas::owner_callbacks::ON_STAKING_POOL_WITHDRAW,
)
)
}
/// OWNER'S METHOD
///
/// Requires 175 TGas
///
/// Tries to withdraw all unstaked balance from staking pool.
pub fn withdraw_all_from_staking_pool(&mut self) -> Promise {
self.repeated_assertions();
env::log_str(
format!(
"Querying unstaked balance on @{} to withdraw everything.",
self.staking_information
.as_ref()
.unwrap()
.staking_pool_account_id
.clone()
).as_str(),
);
self.set_staking_pool_status(TransactionStatus::Busy);
ext_staking_pool::get_account_unstaked_balance(
env::current_account_id(),
self.staking_information
.as_ref()
.unwrap()
.staking_pool_account_id
.clone(),
NO_DEPOSIT,
gas::staking_pool::GET_ACCOUNT_UNSTAKED_BALANCE,
)
.then(
ext_self_owner::on_get_account_unstaked_balance_to_withdraw_by_owner(
env::current_account_id(),
NO_DEPOSIT,
gas::owner_callbacks::ON_GET_ACCOUNT_UNSTAKED_BALANCE_TO_WITHDRAW_BY_OWNER,
),
)
}
/// ONWER'S METHOD
///
/// Requires 125 TGas
///
/// Stakes the given extra amount at the staking pool.
pub fn stake(
&mut self,
amount: WrappedBalance
) -> Promise {
self.repeated_assertions();
require!(
amount.0 > 0,
"Amount should be positive."
);
env::log_str(
format!(
"Staking {} at @{}",
amount.0,
self.staking_information
.as_ref()
.unwrap()
.staking_pool_account_id
).as_str(),
);
self.set_staking_pool_status(TransactionStatus::Busy);
ext_staking_pool::stake(
amount,
self.staking_information
.as_ref()
.unwrap()
.staking_pool_account_id
.clone(),
NO_DEPOSIT,
gas::staking_pool::STAKE,
)
.then(
ext_self_owner::on_staking_pool_stake(
amount,
env::current_account_id(),
NO_DEPOSIT,
gas::owner_callbacks::ON_STAKING_POOL_STAKE,
)
)
}
/// OWNER'S METHOD
///
/// Requires 125 TGas
///
/// Unstakes the given amount at the staking pool.
pub fn unstake(
&mut self,
amount: WrappedBalance
) -> Promise {
self.repeated_assertions();
require!(
amount.0 > 0,
"Amount should be positive"
);
env::log_str(
format!(
"Unstaking {} from @{}",
amount.0,
self.staking_information
.as_ref()
.unwrap()
.staking_pool_account_id
.clone()
).as_str(),
);
self.set_staking_pool_status(TransactionStatus::Busy);
ext_staking_pool::unstake(
amount,
self.staking_information
.as_ref()
.unwrap()
.staking_pool_account_id
.clone(),
NO_DEPOSIT,
gas::staking_pool::UNSTAKE,
)
.then(
ext_self_owner::on_staking_pool_unstake(
amount,
env::current_account_id(),
NO_DEPOSIT,
gas::owner_callbacks::ON_STAKING_POOL_UNSTAKE,
)
)
}
/// OWNER'S METHOD
///
/// Requires 125 TGas
///
/// Unstakes all tokens from staking pool
pub fn unstake_all(&mut self) -> Promise {
self.repeated_assertions();
env::log_str(
format!(
"Unstaking all tokens from @{}",
self.staking_information
.as_ref()
.unwrap()
.staking_pool_account_id
.clone()
).as_str(),
);
self.set_staking_pool_status(TransactionStatus::Busy);
ext_staking_pool::unstake_all(
self.staking_information
.as_ref()
.unwrap()
.staking_pool_account_id
.clone(),
NO_DEPOSIT,
gas::staking_pool::UNSTAKE_ALL,
).then(
ext_self_owner::on_staking_pool_unstake_all(
env::current_account_id(),
NO_DEPOSIT,
gas::owner_callbacks::ON_STAKING_POOL_UNSTAKE_ALL,
)
)
}
/// Requires 75 TGas
/// Not intended to hand over the access to someone else
/// except the owner.
///
/// Calls voting contract to validate if the transfers were enabled by
/// voting. Once transfers are enabled, they can't be disabled anymore.
pub fn check_transfers_vote(&mut self) -> Promise {
self.assert_owner();
self.assert_transfers_disabled();
self.assert_no_termination();
let transfer_poll_account_id =
match &self.lockup_information.transfers_information
{
TransfersInformation::TransfersDisabled {
transfer_poll_account_id
} => transfer_poll_account_id,
_ => unreachable!(),
};
env::log_str(
format!(
"Checking that transfers are enabled at the transfer poll contract @{}",
&transfer_poll_account_id.clone()
).as_str(),
);
ext_transfer_poll::get_result(
transfer_poll_account_id.clone(),
NO_DEPOSIT,
gas::transfer_poll::GET_RESULT,
)
.then(
ext_self_owner::on_get_result_from_transfer_poll(
env::current_account_id(),
NO_DEPOSIT,
gas::owner_callbacks::ON_VOTING_GET_RESULT,
)
)
}
/// Requires 50 TGas
/// Not intended to hand over the access to someone else
/// except the owner.
///
/// Transfers the given amount to the given receiver
/// account ID. This requires transfers to be enabled
/// within the voting contract.
pub fn transfer(
&mut self,
amount: WrappedBalance,
receiver_id: AccountId
) -> Promise {
self.assert_owner();
require!(
env::is_valid_account_id(receiver_id.as_bytes()),
"The receiver account ID is invalid"
);
self.assert_transfers_enabled();
self.assert_no_staking_or_idle();
self.assert_no_termination();
require!(
self.get_liquid_owners_balance().0 >= amount.0,
format!(
concat!(
"The available liquid balance {} is smaller than ",
"the requested tranfer amount {}"),
self.get_liquid_owners_balance().0,
amount.0,
),
);
env::log_str(
format!(
"Transferring {} to @{}",
amount.0,
receiver_id
).as_str()
);
Promise::new(receiver_id).transfer(amount.0)
}
/// Requires 50 TGas
/// Not intended to hand over the access to someone else
/// except the owner.
///
/// Adds full access key with the given public key to the
/// account. You need:
/// - Fully-vested account
/// - Lockup duration expired.
/// - Transfers enabled.
/// - No termination in progress (either none or must be finished)
/// This account will become a regular account, contract will be removed.
pub fn add_full_access_key(
&mut self,
new_public_key: PublicKey
) -> Promise {
self.assert_owner();
self.assert_transfers_enabled();
self.assert_no_staking_or_idle();
self.assert_no_termination();
require!(
self.get_locked_amount().0 == 0,
"Tokens are still locked/unvested"
);
env::log_str("Adding a full access key");
// We pass in PublicKey, not Base58PublicKey, so no need this.
// let new_public_key: PublicKey = new_public_key.into();
Promise::new(
env::current_account_id()
).add_full_access_key(new_public_key)
}
}
Perhaps the developer think that repeating might save gas fee, or it doesn't work with repeating? We don't know until we find out. Luckily, we have some test cases written by the original developer that we could copy them and see if they still passes and determine the problem!
assert_staking_pool_is_idle
will check that the staking pool contract is currently idle (not working on another stuff) before performing cross-contract call. You can imagine this: there are multiple caller that could stake with the validator. This is a many-to-one relationship: one staking pool contract serving multiple owner's contract. Hence you need this assertion.
Continue with our owner's method, you also see the comments that the contract doesn't care about leftovers. When you try to unstake things, there are just some value that are too small (perhaps few yoctoNEARs, but may be larger value; though compare to overall, they're too many decimal places that the contract think it's too small to care about). So if you want to continue accumulate these until it round off to a large enough value, you can. If you don't want to, you can just throw it away and unstake now. It's up to the owner (which may be you if you're the one staking) to make his/her choice.
Selecting and unselecting staking pool isn't very useful it you can deposit NEAR to it, and potentially staking on it.
use crate::*;
use near_sdk::{near_bindgen, AccountId, Promise, PublicKey, require};
#[near_bindgen]
impl LockupContract {
fn repeated_assertions(&mut self) {
self.assert_owner();
self.assert_staking_pool_is_idle();
self.assert_no_termination();
}
/// OWNER'S METHOD
///
/// Requires 75 TGas
///
/// Select staking pool contract at the given account ID. The staking pool
/// first has to be checked against staking pool whitelist contract.
pub fn select_staking_pool(
&mut self,
staking_pool_account_id: AccountId
) -> Promise {
self.assert_owner();
require!(
env::is_valid_account_id(staking_pool_account_id.as_bytes()),
"The staking pool account ID is invalid"
);
self.assert_staking_pool_is_not_selected();
self.assert_no_termination();
env::log_str(
format!(
"Selecting staking pool @{}. Checking if this pool is whitelisted.",
staking_pool_account_id
).as_str(),
);
ext_whitelist::is_whitelisted(
staking_pool_account_id.clone(),
self.staking_pool_whitelist_account_id.clone(),
NO_DEPOSIT,
gas::whitelist::IS_WHITELISTED,
)
.then(
ext_self_owner::on_whitelist_is_whitelisted(
staking_pool_account_id,
env::current_account_id(),
NO_DEPOSIT,
gas::owner_callbacks::ON_WHITELIST_IS_WHITELISTED,
)
)
}
/// OWNER'S METHOD
///
/// Requires 25 TGas
///
/// Unselects the current(ly selected) staking pool.
/// It requires that there are no known deposits left on
/// the currently selected staking pool.
pub fn unselect_staking_pool(&mut self) {
// self.assert_owner();
// self.assert_staking_pool_is_idle();
// self.assert_no_termination();
self.repeated_assertions();
// This is best effort check. There may still be leftovers
// in the staking pool. Owner can choose to or not to
// unselect. The contract doesn't care about leftovers.
require!(
self.staking_information.as_ref().unwrap()
.deposit_amount.0 == 0,
"There is still deposit on staking pool."
);
env::log_str(
format!(
"Unselecting current staking pool @{}",
self.staking_information
.as_ref()
.unwrap()
.staking_pool_account_id
.clone()
).as_str(),
);
self.staking_information = None;
}
/// OWNER'S METHOD
///
/// Requires 100 TGas
///
/// Deposits the given extra amount to the staking pool.
pub fn deposit_to_staking_pool(
&mut self,
amount: WrappedBalance
) -> Promise {
self.repeated_assertions();
require!(
amount.0 > 0,
"Deposit amount should be positive."
);
require!(
self.get_account_balance().0 >= amount.0,
"Trying to stake more than you have is not possible."
);
env::log_str(
format!(
"Depositing {} to @{}",
amount.0,
self.staking_information
.as_ref()
.unwrap()
.staking_pool_account_id
.clone()
).as_str(),
);
self.set_staking_pool_status(TransactionStatus::Busy);
ext_staking_pool::deposit(
self.staking_information
.as_ref()
.unwrap()
.staking_pool_account_id
.clone(),
amount.0,
gas::staking_pool::DEPOSIT,
)
.then(
ext_self_owner::on_staking_pool_deposit(
amount,
env::current_account_id(),
NO_DEPOSIT,
gas::owner_callbacks::ON_STAKING_POOL_DEPOSIT,
)
)
}
/// OWNER'S METHOD
///
/// Requires 125 TGas
///
/// Deposits and stakes the given extra amount to the
/// selected staking pool.
pub fn deposit_and_stake(
&mut self,
amount: WrappedBalance
) -> Promise {
self.repeated_assertions();
require!(amount.0 > 0, "Deposit amount should be positive.");
require!(
self.get_account_balance().0 >= amount.0,
"Trying to stake more than you have is not possible."
);
env::log_str(
format!(
"Depositing and staking {} to @{}",
amount.0,
self.staking_information
.as_ref()
.unwrap()
.staking_pool_account_id
.clone()
).as_str(),
);
self.set_staking_pool_status(TransactionStatus::Busy);
ext_staking_pool::deposit_and_stake(
self.staking_information
.as_ref()
.unwrap()
.staking_pool_account_id
.clone(),
amount.0,
gas::staking_pool::DEPOSIT_AND_STAKE,
)
.then(
ext_self_owner::on_staking_pool_deposit_and_stake(
amount,
env::current_account_id(),
NO_DEPOSIT,
gas::owner_callbacks::ON_STAKING_POOL_DEPOSIT_AND_STAKE,
)
)
}
/// OWNER'S METHOD
///
/// Requires 75 TGas
///
/// Retrieves total balance from staking pool and save it
/// internally. Useful when owner wants to receive rewards
/// during unstaking for querying total balance in the pool
/// that could be withdrawn.
pub fn refresh_staking_pool_balance(&mut self) -> Promise {
self.repeated_assertions();
env::log_str(
format!(
"Fetching total balance from staking pool @{}",
self.staking_information
.as_ref()
.unwrap()
.staking_pool_account_id
.clone()
).as_str(),
);
self.set_staking_pool_status(TransactionStatus::Busy);
ext_staking_pool::get_account_total_balance(
env::current_account_id(),
self.staking_information
.as_ref()
.unwrap()
.staking_pool_account_id
.clone(),
NO_DEPOSIT,
gas::staking_pool::GET_ACCOUNT_TOTAL_BALANCE,
)
.then(
ext_self_owner::on_get_account_total_balance(
env::current_account_id(),
NO_DEPOSIT,
gas::owner_callbacks::ON_GET_ACCOUNT_TOTAL_BALANCE,
)
)
}
/// OWNER'S METHOD
///
/// Requires 125 TGas
///
/// Withdraws the given amount from the staking pool.
pub fn withdraw_from_staking_pool(
&mut self,
amount: WrappedBalance
) -> Promise {
self.repeated_assertions();
require!(
amount.0 > 0,
"Amount should be positive"
);
env::log_str(
format!(
"Withdrawing {} from @{}",
amount.0,
self.staking_information
.as_ref()
.unwrap()
.staking_pool_account_id
.clone()
).as_str(),
);
self.set_staking_pool_status(TransactionStatus::Busy);
ext_staking_pool::withdraw(
amount,
self.staking_information
.as_ref()
.unwrap()
.staking_pool_account_id
.clone(),
NO_DEPOSIT,
gas::staking_pool::WITHDRAW,
)
.then(
ext_self_owner::on_staking_pool_withdraw(
amount,
env::current_account_id(),
NO_DEPOSIT,
gas::owner_callbacks::ON_STAKING_POOL_WITHDRAW,
)
)
}
/// OWNER'S METHOD
///
/// Requires 175 TGas
///
/// Tries to withdraw all unstaked balance from staking pool.
pub fn withdraw_all_from_staking_pool(&mut self) -> Promise {
self.repeated_assertions();
env::log_str(
format!(
"Querying unstaked balance on @{} to withdraw everything.",
self.staking_information
.as_ref()
.unwrap()
.staking_pool_account_id
.clone()
).as_str(),
);
self.set_staking_pool_status(TransactionStatus::Busy);
ext_staking_pool::get_account_unstaked_balance(
env::current_account_id(),
self.staking_information
.as_ref()
.unwrap()
.staking_pool_account_id
.clone(),
NO_DEPOSIT,
gas::staking_pool::GET_ACCOUNT_UNSTAKED_BALANCE,
)
.then(
ext_self_owner::on_get_account_unstaked_balance_to_withdraw_by_owner(
env::current_account_id(),
NO_DEPOSIT,
gas::owner_callbacks::ON_GET_ACCOUNT_UNSTAKED_BALANCE_TO_WITHDRAW_BY_OWNER,
),
)
}
/// ONWER'S METHOD
///
/// Requires 125 TGas
///
/// Stakes the given extra amount at the staking pool.
pub fn stake(
&mut self,
amount: WrappedBalance
) -> Promise {
self.repeated_assertions();
require!(
amount.0 > 0,
"Amount should be positive."
);
env::log_str(
format!(
"Staking {} at @{}",
amount.0,
self.staking_information
.as_ref()
.unwrap()
.staking_pool_account_id
).as_str(),
);
self.set_staking_pool_status(TransactionStatus::Busy);
ext_staking_pool::stake(
amount,
self.staking_information
.as_ref()
.unwrap()
.staking_pool_account_id
.clone(),
NO_DEPOSIT,
gas::staking_pool::STAKE,
)
.then(
ext_self_owner::on_staking_pool_stake(
amount,
env::current_account_id(),
NO_DEPOSIT,
gas::owner_callbacks::ON_STAKING_POOL_STAKE,
)
)
}
/// OWNER'S METHOD
///
/// Requires 125 TGas
///
/// Unstakes the given amount at the staking pool.
pub fn unstake(
&mut self,
amount: WrappedBalance
) -> Promise {
self.repeated_assertions();
require!(
amount.0 > 0,
"Amount should be positive"
);
env::log_str(
format!(
"Unstaking {} from @{}",
amount.0,
self.staking_information
.as_ref()
.unwrap()
.staking_pool_account_id
.clone()
).as_str(),
);
self.set_staking_pool_status(TransactionStatus::Busy);
ext_staking_pool::unstake(
amount,
self.staking_information
.as_ref()
.unwrap()
.staking_pool_account_id
.clone(),
NO_DEPOSIT,
gas::staking_pool::UNSTAKE,
)
.then(
ext_self_owner::on_staking_pool_unstake(
amount,
env::current_account_id(),
NO_DEPOSIT,
gas::owner_callbacks::ON_STAKING_POOL_UNSTAKE,
)
)
}
/// OWNER'S METHOD
///
/// Requires 125 TGas
///
/// Unstakes all tokens from staking pool
pub fn unstake_all(&mut self) -> Promise {
self.repeated_assertions();
env::log_str(
format!(
"Unstaking all tokens from @{}",
self.staking_information
.as_ref()
.unwrap()
.staking_pool_account_id
.clone()
).as_str(),
);
self.set_staking_pool_status(TransactionStatus::Busy);
ext_staking_pool::unstake_all(
self.staking_information
.as_ref()
.unwrap()
.staking_pool_account_id
.clone(),
NO_DEPOSIT,
gas::staking_pool::UNSTAKE_ALL,
).then(
ext_self_owner::on_staking_pool_unstake_all(
env::current_account_id(),
NO_DEPOSIT,
gas::owner_callbacks::ON_STAKING_POOL_UNSTAKE_ALL,
)
)
}
/// Requires 75 TGas
/// Not intended to hand over the access to someone else
/// except the owner.
///
/// Calls voting contract to validate if the transfers were enabled by
/// voting. Once transfers are enabled, they can't be disabled anymore.
pub fn check_transfers_vote(&mut self) -> Promise {
self.assert_owner();
self.assert_transfers_disabled();
self.assert_no_termination();
let transfer_poll_account_id =
match &self.lockup_information.transfers_information
{
TransfersInformation::TransfersDisabled {
transfer_poll_account_id
} => transfer_poll_account_id,
_ => unreachable!(),
};
env::log_str(
format!(
"Checking that transfers are enabled at the transfer poll contract @{}",
&transfer_poll_account_id.clone()
).as_str(),
);
ext_transfer_poll::get_result(
transfer_poll_account_id.clone(),
NO_DEPOSIT,
gas::transfer_poll::GET_RESULT,
)
.then(
ext_self_owner::on_get_result_from_transfer_poll(
env::current_account_id(),
NO_DEPOSIT,
gas::owner_callbacks::ON_VOTING_GET_RESULT,
)
)
}
/// Requires 50 TGas
/// Not intended to hand over the access to someone else
/// except the owner.
///
/// Transfers the given amount to the given receiver
/// account ID. This requires transfers to be enabled
/// within the voting contract.
pub fn transfer(
&mut self,
amount: WrappedBalance,
receiver_id: AccountId
) -> Promise {
self.assert_owner();
require!(
env::is_valid_account_id(receiver_id.as_bytes()),
"The receiver account ID is invalid"
);
self.assert_transfers_enabled();
self.assert_no_staking_or_idle();
self.assert_no_termination();
require!(
self.get_liquid_owners_balance().0 >= amount.0,
format!(
concat!(
"The available liquid balance {} is smaller than ",
"the requested tranfer amount {}"),
self.get_liquid_owners_balance().0,
amount.0,
),
);
env::log_str(
format!(
"Transferring {} to @{}",
amount.0,
receiver_id
).as_str()
);
Promise::new(receiver_id).transfer(amount.0)
}
/// Requires 50 TGas
/// Not intended to hand over the access to someone else
/// except the owner.
///
/// Adds full access key with the given public key to the
/// account. You need:
/// - Fully-vested account
/// - Lockup duration expired.
/// - Transfers enabled.
/// - No termination in progress (either none or must be finished)
/// This account will become a regular account, contract will be removed.
pub fn add_full_access_key(
&mut self,
new_public_key: PublicKey
) -> Promise {
self.assert_owner();
self.assert_transfers_enabled();
self.assert_no_staking_or_idle();
self.assert_no_termination();
require!(
self.get_locked_amount().0 == 0,
"Tokens are still locked/unvested"
);
env::log_str("Adding a full access key");
// We pass in PublicKey, not Base58PublicKey, so no need this.
// let new_public_key: PublicKey = new_public_key.into();
Promise::new(
env::current_account_id()
).add_full_access_key(new_public_key)
}
}
Note the above function is deposit to staking pool an amount, not staking on it, just pure deposit an amount to the pool. We shall see a deposit_and_stake
next that deposits + stake.
use crate::*;
use near_sdk::{near_bindgen, AccountId, Promise, PublicKey, require};
#[near_bindgen]
impl LockupContract {
fn repeated_assertions(&mut self) {
self.assert_owner();
self.assert_staking_pool_is_idle();
self.assert_no_termination();
}
/// OWNER'S METHOD
///
/// Requires 75 TGas
///
/// Select staking pool contract at the given account ID. The staking pool
/// first has to be checked against staking pool whitelist contract.
pub fn select_staking_pool(
&mut self,
staking_pool_account_id: AccountId
) -> Promise {
self.assert_owner();
require!(
env::is_valid_account_id(staking_pool_account_id.as_bytes()),
"The staking pool account ID is invalid"
);
self.assert_staking_pool_is_not_selected();
self.assert_no_termination();
env::log_str(
format!(
"Selecting staking pool @{}. Checking if this pool is whitelisted.",
staking_pool_account_id
).as_str(),
);
ext_whitelist::is_whitelisted(
staking_pool_account_id.clone(),
self.staking_pool_whitelist_account_id.clone(),
NO_DEPOSIT,
gas::whitelist::IS_WHITELISTED,
)
.then(
ext_self_owner::on_whitelist_is_whitelisted(
staking_pool_account_id,
env::current_account_id(),
NO_DEPOSIT,
gas::owner_callbacks::ON_WHITELIST_IS_WHITELISTED,
)
)
}
/// OWNER'S METHOD
///
/// Requires 25 TGas
///
/// Unselects the current(ly selected) staking pool.
/// It requires that there are no known deposits left on
/// the currently selected staking pool.
pub fn unselect_staking_pool(&mut self) {
// self.assert_owner();
// self.assert_staking_pool_is_idle();
// self.assert_no_termination();
self.repeated_assertions();
// This is best effort check. There may still be leftovers
// in the staking pool. Owner can choose to or not to
// unselect. The contract doesn't care about leftovers.
require!(
self.staking_information.as_ref().unwrap()
.deposit_amount.0 == 0,
"There is still deposit on staking pool."
);
env::log_str(
format!(
"Unselecting current staking pool @{}",
self.staking_information
.as_ref()
.unwrap()
.staking_pool_account_id
.clone()
).as_str(),
);
self.staking_information = None;
}
/// OWNER'S METHOD
///
/// Requires 100 TGas
///
/// Deposits the given extra amount to the staking pool.
pub fn deposit_to_staking_pool(
&mut self,
amount: WrappedBalance
) -> Promise {
self.repeated_assertions();
require!(
amount.0 > 0,
"Deposit amount should be positive."
);
require!(
self.get_account_balance().0 >= amount.0,
"Trying to stake more than you have is not possible."
);
env::log_str(
format!(
"Depositing {} to @{}",
amount.0,
self.staking_information
.as_ref()
.unwrap()
.staking_pool_account_id
.clone()
).as_str(),
);
self.set_staking_pool_status(TransactionStatus::Busy);
ext_staking_pool::deposit(
self.staking_information
.as_ref()
.unwrap()
.staking_pool_account_id
.clone(),
amount.0,
gas::staking_pool::DEPOSIT,
)
.then(
ext_self_owner::on_staking_pool_deposit(
amount,
env::current_account_id(),
NO_DEPOSIT,
gas::owner_callbacks::ON_STAKING_POOL_DEPOSIT,
)
)
}
/// OWNER'S METHOD
///
/// Requires 125 TGas
///
/// Deposits and stakes the given extra amount to the
/// selected staking pool.
pub fn deposit_and_stake(
&mut self,
amount: WrappedBalance
) -> Promise {
self.repeated_assertions();
require!(amount.0 > 0, "Deposit amount should be positive.");
require!(
self.get_account_balance().0 >= amount.0,
"Trying to stake more than you have is not possible."
);
env::log_str(
format!(
"Depositing and staking {} to @{}",
amount.0,
self.staking_information
.as_ref()
.unwrap()
.staking_pool_account_id
.clone()
).as_str(),
);
self.set_staking_pool_status(TransactionStatus::Busy);
ext_staking_pool::deposit_and_stake(
self.staking_information
.as_ref()
.unwrap()
.staking_pool_account_id
.clone(),
amount.0,
gas::staking_pool::DEPOSIT_AND_STAKE,
)
.then(
ext_self_owner::on_staking_pool_deposit_and_stake(
amount,
env::current_account_id(),
NO_DEPOSIT,
gas::owner_callbacks::ON_STAKING_POOL_DEPOSIT_AND_STAKE,
)
)
}
/// OWNER'S METHOD
///
/// Requires 75 TGas
///
/// Retrieves total balance from staking pool and save it
/// internally. Useful when owner wants to receive rewards
/// during unstaking for querying total balance in the pool
/// that could be withdrawn.
pub fn refresh_staking_pool_balance(&mut self) -> Promise {
self.repeated_assertions();
env::log_str(
format!(
"Fetching total balance from staking pool @{}",
self.staking_information
.as_ref()
.unwrap()
.staking_pool_account_id
.clone()
).as_str(),
);
self.set_staking_pool_status(TransactionStatus::Busy);
ext_staking_pool::get_account_total_balance(
env::current_account_id(),
self.staking_information
.as_ref()
.unwrap()
.staking_pool_account_id
.clone(),
NO_DEPOSIT,
gas::staking_pool::GET_ACCOUNT_TOTAL_BALANCE,
)
.then(
ext_self_owner::on_get_account_total_balance(
env::current_account_id(),
NO_DEPOSIT,
gas::owner_callbacks::ON_GET_ACCOUNT_TOTAL_BALANCE,
)
)
}
/// OWNER'S METHOD
///
/// Requires 125 TGas
///
/// Withdraws the given amount from the staking pool.
pub fn withdraw_from_staking_pool(
&mut self,
amount: WrappedBalance
) -> Promise {
self.repeated_assertions();
require!(
amount.0 > 0,
"Amount should be positive"
);
env::log_str(
format!(
"Withdrawing {} from @{}",
amount.0,
self.staking_information
.as_ref()
.unwrap()
.staking_pool_account_id
.clone()
).as_str(),
);
self.set_staking_pool_status(TransactionStatus::Busy);
ext_staking_pool::withdraw(
amount,
self.staking_information
.as_ref()
.unwrap()
.staking_pool_account_id
.clone(),
NO_DEPOSIT,
gas::staking_pool::WITHDRAW,
)
.then(
ext_self_owner::on_staking_pool_withdraw(
amount,
env::current_account_id(),
NO_DEPOSIT,
gas::owner_callbacks::ON_STAKING_POOL_WITHDRAW,
)
)
}
/// OWNER'S METHOD
///
/// Requires 175 TGas
///
/// Tries to withdraw all unstaked balance from staking pool.
pub fn withdraw_all_from_staking_pool(&mut self) -> Promise {
self.repeated_assertions();
env::log_str(
format!(
"Querying unstaked balance on @{} to withdraw everything.",
self.staking_information
.as_ref()
.unwrap()
.staking_pool_account_id
.clone()
).as_str(),
);
self.set_staking_pool_status(TransactionStatus::Busy);
ext_staking_pool::get_account_unstaked_balance(
env::current_account_id(),
self.staking_information
.as_ref()
.unwrap()
.staking_pool_account_id
.clone(),
NO_DEPOSIT,
gas::staking_pool::GET_ACCOUNT_UNSTAKED_BALANCE,
)
.then(
ext_self_owner::on_get_account_unstaked_balance_to_withdraw_by_owner(
env::current_account_id(),
NO_DEPOSIT,
gas::owner_callbacks::ON_GET_ACCOUNT_UNSTAKED_BALANCE_TO_WITHDRAW_BY_OWNER,
),
)
}
/// ONWER'S METHOD
///
/// Requires 125 TGas
///
/// Stakes the given extra amount at the staking pool.
pub fn stake(
&mut self,
amount: WrappedBalance
) -> Promise {
self.repeated_assertions();
require!(
amount.0 > 0,
"Amount should be positive."
);
env::log_str(
format!(
"Staking {} at @{}",
amount.0,
self.staking_information
.as_ref()
.unwrap()
.staking_pool_account_id
).as_str(),
);
self.set_staking_pool_status(TransactionStatus::Busy);
ext_staking_pool::stake(
amount,
self.staking_information
.as_ref()
.unwrap()
.staking_pool_account_id
.clone(),
NO_DEPOSIT,
gas::staking_pool::STAKE,
)
.then(
ext_self_owner::on_staking_pool_stake(
amount,
env::current_account_id(),
NO_DEPOSIT,
gas::owner_callbacks::ON_STAKING_POOL_STAKE,
)
)
}
/// OWNER'S METHOD
///
/// Requires 125 TGas
///
/// Unstakes the given amount at the staking pool.
pub fn unstake(
&mut self,
amount: WrappedBalance
) -> Promise {
self.repeated_assertions();
require!(
amount.0 > 0,
"Amount should be positive"
);
env::log_str(
format!(
"Unstaking {} from @{}",
amount.0,
self.staking_information
.as_ref()
.unwrap()
.staking_pool_account_id
.clone()
).as_str(),
);
self.set_staking_pool_status(TransactionStatus::Busy);
ext_staking_pool::unstake(
amount,
self.staking_information
.as_ref()
.unwrap()
.staking_pool_account_id
.clone(),
NO_DEPOSIT,
gas::staking_pool::UNSTAKE,
)
.then(
ext_self_owner::on_staking_pool_unstake(
amount,
env::current_account_id(),
NO_DEPOSIT,
gas::owner_callbacks::ON_STAKING_POOL_UNSTAKE,
)
)
}
/// OWNER'S METHOD
///
/// Requires 125 TGas
///
/// Unstakes all tokens from staking pool
pub fn unstake_all(&mut self) -> Promise {
self.repeated_assertions();
env::log_str(
format!(
"Unstaking all tokens from @{}",
self.staking_information
.as_ref()
.unwrap()
.staking_pool_account_id
.clone()
).as_str(),
);
self.set_staking_pool_status(TransactionStatus::Busy);
ext_staking_pool::unstake_all(
self.staking_information
.as_ref()
.unwrap()
.staking_pool_account_id
.clone(),
NO_DEPOSIT,
gas::staking_pool::UNSTAKE_ALL,
).then(
ext_self_owner::on_staking_pool_unstake_all(
env::current_account_id(),
NO_DEPOSIT,
gas::owner_callbacks::ON_STAKING_POOL_UNSTAKE_ALL,
)
)
}
/// Requires 75 TGas
/// Not intended to hand over the access to someone else
/// except the owner.
///
/// Calls voting contract to validate if the transfers were enabled by
/// voting. Once transfers are enabled, they can't be disabled anymore.
pub fn check_transfers_vote(&mut self) -> Promise {
self.assert_owner();
self.assert_transfers_disabled();
self.assert_no_termination();
let transfer_poll_account_id =
match &self.lockup_information.transfers_information
{
TransfersInformation::TransfersDisabled {
transfer_poll_account_id
} => transfer_poll_account_id,
_ => unreachable!(),
};
env::log_str(
format!(
"Checking that transfers are enabled at the transfer poll contract @{}",
&transfer_poll_account_id.clone()
).as_str(),
);
ext_transfer_poll::get_result(
transfer_poll_account_id.clone(),
NO_DEPOSIT,
gas::transfer_poll::GET_RESULT,
)
.then(
ext_self_owner::on_get_result_from_transfer_poll(
env::current_account_id(),
NO_DEPOSIT,
gas::owner_callbacks::ON_VOTING_GET_RESULT,
)
)
}
/// Requires 50 TGas
/// Not intended to hand over the access to someone else
/// except the owner.
///
/// Transfers the given amount to the given receiver
/// account ID. This requires transfers to be enabled
/// within the voting contract.
pub fn transfer(
&mut self,
amount: WrappedBalance,
receiver_id: AccountId
) -> Promise {
self.assert_owner();
require!(
env::is_valid_account_id(receiver_id.as_bytes()),
"The receiver account ID is invalid"
);
self.assert_transfers_enabled();
self.assert_no_staking_or_idle();
self.assert_no_termination();
require!(
self.get_liquid_owners_balance().0 >= amount.0,
format!(
concat!(
"The available liquid balance {} is smaller than ",
"the requested tranfer amount {}"),
self.get_liquid_owners_balance().0,
amount.0,
),
);
env::log_str(
format!(
"Transferring {} to @{}",
amount.0,
receiver_id
).as_str()
);
Promise::new(receiver_id).transfer(amount.0)
}
/// Requires 50 TGas
/// Not intended to hand over the access to someone else
/// except the owner.
///
/// Adds full access key with the given public key to the
/// account. You need:
/// - Fully-vested account
/// - Lockup duration expired.
/// - Transfers enabled.
/// - No termination in progress (either none or must be finished)
/// This account will become a regular account, contract will be removed.
pub fn add_full_access_key(
&mut self,
new_public_key: PublicKey
) -> Promise {
self.assert_owner();
self.assert_transfers_enabled();
self.assert_no_staking_or_idle();
self.assert_no_termination();
require!(
self.get_locked_amount().0 == 0,
"Tokens are still locked/unvested"
);
env::log_str("Adding a full access key");
// We pass in PublicKey, not Base58PublicKey, so no need this.
// let new_public_key: PublicKey = new_public_key.into();
Promise::new(
env::current_account_id()
).add_full_access_key(new_public_key)
}
}
The logic didn't really lies in the above function; instead, they're in the staking pool contract. Visit the Staking Pool Contract to find out more. The above function mostly is including the required checks before passing the request on to staking pool contract to handle it (and internal callbacks to clean up).
Next is a function that checks the liquid balance available in the pool. This is useful when owner earn rewards and want to withdraw these rewards (plus originally staked balance). This contract query the pool (it does not makes decision whether it has enough balance or not for the owner to withdraw; that is for another function).
use crate::*;
use near_sdk::{near_bindgen, AccountId, Promise, PublicKey, require};
#[near_bindgen]
impl LockupContract {
fn repeated_assertions(&mut self) {
self.assert_owner();
self.assert_staking_pool_is_idle();
self.assert_no_termination();
}
/// OWNER'S METHOD
///
/// Requires 75 TGas
///
/// Select staking pool contract at the given account ID. The staking pool
/// first has to be checked against staking pool whitelist contract.
pub fn select_staking_pool(
&mut self,
staking_pool_account_id: AccountId
) -> Promise {
self.assert_owner();
require!(
env::is_valid_account_id(staking_pool_account_id.as_bytes()),
"The staking pool account ID is invalid"
);
self.assert_staking_pool_is_not_selected();
self.assert_no_termination();
env::log_str(
format!(
"Selecting staking pool @{}. Checking if this pool is whitelisted.",
staking_pool_account_id
).as_str(),
);
ext_whitelist::is_whitelisted(
staking_pool_account_id.clone(),
self.staking_pool_whitelist_account_id.clone(),
NO_DEPOSIT,
gas::whitelist::IS_WHITELISTED,
)
.then(
ext_self_owner::on_whitelist_is_whitelisted(
staking_pool_account_id,
env::current_account_id(),
NO_DEPOSIT,
gas::owner_callbacks::ON_WHITELIST_IS_WHITELISTED,
)
)
}
/// OWNER'S METHOD
///
/// Requires 25 TGas
///
/// Unselects the current(ly selected) staking pool.
/// It requires that there are no known deposits left on
/// the currently selected staking pool.
pub fn unselect_staking_pool(&mut self) {
// self.assert_owner();
// self.assert_staking_pool_is_idle();
// self.assert_no_termination();
self.repeated_assertions();
// This is best effort check. There may still be leftovers
// in the staking pool. Owner can choose to or not to
// unselect. The contract doesn't care about leftovers.
require!(
self.staking_information.as_ref().unwrap()
.deposit_amount.0 == 0,
"There is still deposit on staking pool."
);
env::log_str(
format!(
"Unselecting current staking pool @{}",
self.staking_information
.as_ref()
.unwrap()
.staking_pool_account_id
.clone()
).as_str(),
);
self.staking_information = None;
}
/// OWNER'S METHOD
///
/// Requires 100 TGas
///
/// Deposits the given extra amount to the staking pool.
pub fn deposit_to_staking_pool(
&mut self,
amount: WrappedBalance
) -> Promise {
self.repeated_assertions();
require!(
amount.0 > 0,
"Deposit amount should be positive."
);
require!(
self.get_account_balance().0 >= amount.0,
"Trying to stake more than you have is not possible."
);
env::log_str(
format!(
"Depositing {} to @{}",
amount.0,
self.staking_information
.as_ref()
.unwrap()
.staking_pool_account_id
.clone()
).as_str(),
);
self.set_staking_pool_status(TransactionStatus::Busy);
ext_staking_pool::deposit(
self.staking_information
.as_ref()
.unwrap()
.staking_pool_account_id
.clone(),
amount.0,
gas::staking_pool::DEPOSIT,
)
.then(
ext_self_owner::on_staking_pool_deposit(
amount,
env::current_account_id(),
NO_DEPOSIT,
gas::owner_callbacks::ON_STAKING_POOL_DEPOSIT,
)
)
}
/// OWNER'S METHOD
///
/// Requires 125 TGas
///
/// Deposits and stakes the given extra amount to the
/// selected staking pool.
pub fn deposit_and_stake(
&mut self,
amount: WrappedBalance
) -> Promise {
self.repeated_assertions();
require!(amount.0 > 0, "Deposit amount should be positive.");
require!(
self.get_account_balance().0 >= amount.0,
"Trying to stake more than you have is not possible."
);
env::log_str(
format!(
"Depositing and staking {} to @{}",
amount.0,
self.staking_information
.as_ref()
.unwrap()
.staking_pool_account_id
.clone()
).as_str(),
);
self.set_staking_pool_status(TransactionStatus::Busy);
ext_staking_pool::deposit_and_stake(
self.staking_information
.as_ref()
.unwrap()
.staking_pool_account_id
.clone(),
amount.0,
gas::staking_pool::DEPOSIT_AND_STAKE,
)
.then(
ext_self_owner::on_staking_pool_deposit_and_stake(
amount,
env::current_account_id(),
NO_DEPOSIT,
gas::owner_callbacks::ON_STAKING_POOL_DEPOSIT_AND_STAKE,
)
)
}
/// OWNER'S METHOD
///
/// Requires 75 TGas
///
/// Retrieves total balance from staking pool and save it
/// internally. Useful when owner wants to receive rewards
/// during unstaking for querying total balance in the pool
/// that could be withdrawn.
pub fn refresh_staking_pool_balance(&mut self) -> Promise {
self.repeated_assertions();
env::log_str(
format!(
"Fetching total balance from staking pool @{}",
self.staking_information
.as_ref()
.unwrap()
.staking_pool_account_id
.clone()
).as_str(),
);
self.set_staking_pool_status(TransactionStatus::Busy);
ext_staking_pool::get_account_total_balance(
env::current_account_id(),
self.staking_information
.as_ref()
.unwrap()
.staking_pool_account_id
.clone(),
NO_DEPOSIT,
gas::staking_pool::GET_ACCOUNT_TOTAL_BALANCE,
)
.then(
ext_self_owner::on_get_account_total_balance(
env::current_account_id(),
NO_DEPOSIT,
gas::owner_callbacks::ON_GET_ACCOUNT_TOTAL_BALANCE,
)
)
}
/// OWNER'S METHOD
///
/// Requires 125 TGas
///
/// Withdraws the given amount from the staking pool.
pub fn withdraw_from_staking_pool(
&mut self,
amount: WrappedBalance
) -> Promise {
self.repeated_assertions();
require!(
amount.0 > 0,
"Amount should be positive"
);
env::log_str(
format!(
"Withdrawing {} from @{}",
amount.0,
self.staking_information
.as_ref()
.unwrap()
.staking_pool_account_id
.clone()
).as_str(),
);
self.set_staking_pool_status(TransactionStatus::Busy);
ext_staking_pool::withdraw(
amount,
self.staking_information
.as_ref()
.unwrap()
.staking_pool_account_id
.clone(),
NO_DEPOSIT,
gas::staking_pool::WITHDRAW,
)
.then(
ext_self_owner::on_staking_pool_withdraw(
amount,
env::current_account_id(),
NO_DEPOSIT,
gas::owner_callbacks::ON_STAKING_POOL_WITHDRAW,
)
)
}
/// OWNER'S METHOD
///
/// Requires 175 TGas
///
/// Tries to withdraw all unstaked balance from staking pool.
pub fn withdraw_all_from_staking_pool(&mut self) -> Promise {
self.repeated_assertions();
env::log_str(
format!(
"Querying unstaked balance on @{} to withdraw everything.",
self.staking_information
.as_ref()
.unwrap()
.staking_pool_account_id
.clone()
).as_str(),
);
self.set_staking_pool_status(TransactionStatus::Busy);
ext_staking_pool::get_account_unstaked_balance(
env::current_account_id(),
self.staking_information
.as_ref()
.unwrap()
.staking_pool_account_id
.clone(),
NO_DEPOSIT,
gas::staking_pool::GET_ACCOUNT_UNSTAKED_BALANCE,
)
.then(
ext_self_owner::on_get_account_unstaked_balance_to_withdraw_by_owner(
env::current_account_id(),
NO_DEPOSIT,
gas::owner_callbacks::ON_GET_ACCOUNT_UNSTAKED_BALANCE_TO_WITHDRAW_BY_OWNER,
),
)
}
/// ONWER'S METHOD
///
/// Requires 125 TGas
///
/// Stakes the given extra amount at the staking pool.
pub fn stake(
&mut self,
amount: WrappedBalance
) -> Promise {
self.repeated_assertions();
require!(
amount.0 > 0,
"Amount should be positive."
);
env::log_str(
format!(
"Staking {} at @{}",
amount.0,
self.staking_information
.as_ref()
.unwrap()
.staking_pool_account_id
).as_str(),
);
self.set_staking_pool_status(TransactionStatus::Busy);
ext_staking_pool::stake(
amount,
self.staking_information
.as_ref()
.unwrap()
.staking_pool_account_id
.clone(),
NO_DEPOSIT,
gas::staking_pool::STAKE,
)
.then(
ext_self_owner::on_staking_pool_stake(
amount,
env::current_account_id(),
NO_DEPOSIT,
gas::owner_callbacks::ON_STAKING_POOL_STAKE,
)
)
}
/// OWNER'S METHOD
///
/// Requires 125 TGas
///
/// Unstakes the given amount at the staking pool.
pub fn unstake(
&mut self,
amount: WrappedBalance
) -> Promise {
self.repeated_assertions();
require!(
amount.0 > 0,
"Amount should be positive"
);
env::log_str(
format!(
"Unstaking {} from @{}",
amount.0,
self.staking_information
.as_ref()
.unwrap()
.staking_pool_account_id
.clone()
).as_str(),
);
self.set_staking_pool_status(TransactionStatus::Busy);
ext_staking_pool::unstake(
amount,
self.staking_information
.as_ref()
.unwrap()
.staking_pool_account_id
.clone(),
NO_DEPOSIT,
gas::staking_pool::UNSTAKE,
)
.then(
ext_self_owner::on_staking_pool_unstake(
amount,
env::current_account_id(),
NO_DEPOSIT,
gas::owner_callbacks::ON_STAKING_POOL_UNSTAKE,
)
)
}
/// OWNER'S METHOD
///
/// Requires 125 TGas
///
/// Unstakes all tokens from staking pool
pub fn unstake_all(&mut self) -> Promise {
self.repeated_assertions();
env::log_str(
format!(
"Unstaking all tokens from @{}",
self.staking_information
.as_ref()
.unwrap()
.staking_pool_account_id
.clone()
).as_str(),
);
self.set_staking_pool_status(TransactionStatus::Busy);
ext_staking_pool::unstake_all(
self.staking_information
.as_ref()
.unwrap()
.staking_pool_account_id
.clone(),
NO_DEPOSIT,
gas::staking_pool::UNSTAKE_ALL,
).then(
ext_self_owner::on_staking_pool_unstake_all(
env::current_account_id(),
NO_DEPOSIT,
gas::owner_callbacks::ON_STAKING_POOL_UNSTAKE_ALL,
)
)
}
/// Requires 75 TGas
/// Not intended to hand over the access to someone else
/// except the owner.
///
/// Calls voting contract to validate if the transfers were enabled by
/// voting. Once transfers are enabled, they can't be disabled anymore.
pub fn check_transfers_vote(&mut self) -> Promise {
self.assert_owner();
self.assert_transfers_disabled();
self.assert_no_termination();
let transfer_poll_account_id =
match &self.lockup_information.transfers_information
{
TransfersInformation::TransfersDisabled {
transfer_poll_account_id
} => transfer_poll_account_id,
_ => unreachable!(),
};
env::log_str(
format!(
"Checking that transfers are enabled at the transfer poll contract @{}",
&transfer_poll_account_id.clone()
).as_str(),
);
ext_transfer_poll::get_result(
transfer_poll_account_id.clone(),
NO_DEPOSIT,
gas::transfer_poll::GET_RESULT,
)
.then(
ext_self_owner::on_get_result_from_transfer_poll(
env::current_account_id(),
NO_DEPOSIT,
gas::owner_callbacks::ON_VOTING_GET_RESULT,
)
)
}
/// Requires 50 TGas
/// Not intended to hand over the access to someone else
/// except the owner.
///
/// Transfers the given amount to the given receiver
/// account ID. This requires transfers to be enabled
/// within the voting contract.
pub fn transfer(
&mut self,
amount: WrappedBalance,
receiver_id: AccountId
) -> Promise {
self.assert_owner();
require!(
env::is_valid_account_id(receiver_id.as_bytes()),
"The receiver account ID is invalid"
);
self.assert_transfers_enabled();
self.assert_no_staking_or_idle();
self.assert_no_termination();
require!(
self.get_liquid_owners_balance().0 >= amount.0,
format!(
concat!(
"The available liquid balance {} is smaller than ",
"the requested tranfer amount {}"),
self.get_liquid_owners_balance().0,
amount.0,
),
);
env::log_str(
format!(
"Transferring {} to @{}",
amount.0,
receiver_id
).as_str()
);
Promise::new(receiver_id).transfer(amount.0)
}
/// Requires 50 TGas
/// Not intended to hand over the access to someone else
/// except the owner.
///
/// Adds full access key with the given public key to the
/// account. You need:
/// - Fully-vested account
/// - Lockup duration expired.
/// - Transfers enabled.
/// - No termination in progress (either none or must be finished)
/// This account will become a regular account, contract will be removed.
pub fn add_full_access_key(
&mut self,
new_public_key: PublicKey
) -> Promise {
self.assert_owner();
self.assert_transfers_enabled();
self.assert_no_staking_or_idle();
self.assert_no_termination();
require!(
self.get_locked_amount().0 == 0,
"Tokens are still locked/unvested"
);
env::log_str("Adding a full access key");
// We pass in PublicKey, not Base58PublicKey, so no need this.
// let new_public_key: PublicKey = new_public_key.into();
Promise::new(
env::current_account_id()
).add_full_access_key(new_public_key)
}
}
Of course, we check so we can withdraw, hence we requires the withdrawal function.
use crate::*;
use near_sdk::{near_bindgen, AccountId, Promise, PublicKey, require};
#[near_bindgen]
impl LockupContract {
fn repeated_assertions(&mut self) {
self.assert_owner();
self.assert_staking_pool_is_idle();
self.assert_no_termination();
}
/// OWNER'S METHOD
///
/// Requires 75 TGas
///
/// Select staking pool contract at the given account ID. The staking pool
/// first has to be checked against staking pool whitelist contract.
pub fn select_staking_pool(
&mut self,
staking_pool_account_id: AccountId
) -> Promise {
self.assert_owner();
require!(
env::is_valid_account_id(staking_pool_account_id.as_bytes()),
"The staking pool account ID is invalid"
);
self.assert_staking_pool_is_not_selected();
self.assert_no_termination();
env::log_str(
format!(
"Selecting staking pool @{}. Checking if this pool is whitelisted.",
staking_pool_account_id
).as_str(),
);
ext_whitelist::is_whitelisted(
staking_pool_account_id.clone(),
self.staking_pool_whitelist_account_id.clone(),
NO_DEPOSIT,
gas::whitelist::IS_WHITELISTED,
)
.then(
ext_self_owner::on_whitelist_is_whitelisted(
staking_pool_account_id,
env::current_account_id(),
NO_DEPOSIT,
gas::owner_callbacks::ON_WHITELIST_IS_WHITELISTED,
)
)
}
/// OWNER'S METHOD
///
/// Requires 25 TGas
///
/// Unselects the current(ly selected) staking pool.
/// It requires that there are no known deposits left on
/// the currently selected staking pool.
pub fn unselect_staking_pool(&mut self) {
// self.assert_owner();
// self.assert_staking_pool_is_idle();
// self.assert_no_termination();
self.repeated_assertions();
// This is best effort check. There may still be leftovers
// in the staking pool. Owner can choose to or not to
// unselect. The contract doesn't care about leftovers.
require!(
self.staking_information.as_ref().unwrap()
.deposit_amount.0 == 0,
"There is still deposit on staking pool."
);
env::log_str(
format!(
"Unselecting current staking pool @{}",
self.staking_information
.as_ref()
.unwrap()
.staking_pool_account_id
.clone()
).as_str(),
);
self.staking_information = None;
}
/// OWNER'S METHOD
///
/// Requires 100 TGas
///
/// Deposits the given extra amount to the staking pool.
pub fn deposit_to_staking_pool(
&mut self,
amount: WrappedBalance
) -> Promise {
self.repeated_assertions();
require!(
amount.0 > 0,
"Deposit amount should be positive."
);
require!(
self.get_account_balance().0 >= amount.0,
"Trying to stake more than you have is not possible."
);
env::log_str(
format!(
"Depositing {} to @{}",
amount.0,
self.staking_information
.as_ref()
.unwrap()
.staking_pool_account_id
.clone()
).as_str(),
);
self.set_staking_pool_status(TransactionStatus::Busy);
ext_staking_pool::deposit(
self.staking_information
.as_ref()
.unwrap()
.staking_pool_account_id
.clone(),
amount.0,
gas::staking_pool::DEPOSIT,
)
.then(
ext_self_owner::on_staking_pool_deposit(
amount,
env::current_account_id(),
NO_DEPOSIT,
gas::owner_callbacks::ON_STAKING_POOL_DEPOSIT,
)
)
}
/// OWNER'S METHOD
///
/// Requires 125 TGas
///
/// Deposits and stakes the given extra amount to the
/// selected staking pool.
pub fn deposit_and_stake(
&mut self,
amount: WrappedBalance
) -> Promise {
self.repeated_assertions();
require!(amount.0 > 0, "Deposit amount should be positive.");
require!(
self.get_account_balance().0 >= amount.0,
"Trying to stake more than you have is not possible."
);
env::log_str(
format!(
"Depositing and staking {} to @{}",
amount.0,
self.staking_information
.as_ref()
.unwrap()
.staking_pool_account_id
.clone()
).as_str(),
);
self.set_staking_pool_status(TransactionStatus::Busy);
ext_staking_pool::deposit_and_stake(
self.staking_information
.as_ref()
.unwrap()
.staking_pool_account_id
.clone(),
amount.0,
gas::staking_pool::DEPOSIT_AND_STAKE,
)
.then(
ext_self_owner::on_staking_pool_deposit_and_stake(
amount,
env::current_account_id(),
NO_DEPOSIT,
gas::owner_callbacks::ON_STAKING_POOL_DEPOSIT_AND_STAKE,
)
)
}
/// OWNER'S METHOD
///
/// Requires 75 TGas
///
/// Retrieves total balance from staking pool and save it
/// internally. Useful when owner wants to receive rewards
/// during unstaking for querying total balance in the pool
/// that could be withdrawn.
pub fn refresh_staking_pool_balance(&mut self) -> Promise {
self.repeated_assertions();
env::log_str(
format!(
"Fetching total balance from staking pool @{}",
self.staking_information
.as_ref()
.unwrap()
.staking_pool_account_id
.clone()
).as_str(),
);
self.set_staking_pool_status(TransactionStatus::Busy);
ext_staking_pool::get_account_total_balance(
env::current_account_id(),
self.staking_information
.as_ref()
.unwrap()
.staking_pool_account_id
.clone(),
NO_DEPOSIT,
gas::staking_pool::GET_ACCOUNT_TOTAL_BALANCE,
)
.then(
ext_self_owner::on_get_account_total_balance(
env::current_account_id(),
NO_DEPOSIT,
gas::owner_callbacks::ON_GET_ACCOUNT_TOTAL_BALANCE,
)
)
}
/// OWNER'S METHOD
///
/// Requires 125 TGas
///
/// Withdraws the given amount from the staking pool.
pub fn withdraw_from_staking_pool(
&mut self,
amount: WrappedBalance
) -> Promise {
self.repeated_assertions();
require!(
amount.0 > 0,
"Amount should be positive"
);
env::log_str(
format!(
"Withdrawing {} from @{}",
amount.0,
self.staking_information
.as_ref()
.unwrap()
.staking_pool_account_id
.clone()
).as_str(),
);
self.set_staking_pool_status(TransactionStatus::Busy);
ext_staking_pool::withdraw(
amount,
self.staking_information
.as_ref()
.unwrap()
.staking_pool_account_id
.clone(),
NO_DEPOSIT,
gas::staking_pool::WITHDRAW,
)
.then(
ext_self_owner::on_staking_pool_withdraw(
amount,
env::current_account_id(),
NO_DEPOSIT,
gas::owner_callbacks::ON_STAKING_POOL_WITHDRAW,
)
)
}
/// OWNER'S METHOD
///
/// Requires 175 TGas
///
/// Tries to withdraw all unstaked balance from staking pool.
pub fn withdraw_all_from_staking_pool(&mut self) -> Promise {
self.repeated_assertions();
env::log_str(
format!(
"Querying unstaked balance on @{} to withdraw everything.",
self.staking_information
.as_ref()
.unwrap()
.staking_pool_account_id
.clone()
).as_str(),
);
self.set_staking_pool_status(TransactionStatus::Busy);
ext_staking_pool::get_account_unstaked_balance(
env::current_account_id(),
self.staking_information
.as_ref()
.unwrap()
.staking_pool_account_id
.clone(),
NO_DEPOSIT,
gas::staking_pool::GET_ACCOUNT_UNSTAKED_BALANCE,
)
.then(
ext_self_owner::on_get_account_unstaked_balance_to_withdraw_by_owner(
env::current_account_id(),
NO_DEPOSIT,
gas::owner_callbacks::ON_GET_ACCOUNT_UNSTAKED_BALANCE_TO_WITHDRAW_BY_OWNER,
),
)
}
/// ONWER'S METHOD
///
/// Requires 125 TGas
///
/// Stakes the given extra amount at the staking pool.
pub fn stake(
&mut self,
amount: WrappedBalance
) -> Promise {
self.repeated_assertions();
require!(
amount.0 > 0,
"Amount should be positive."
);
env::log_str(
format!(
"Staking {} at @{}",
amount.0,
self.staking_information
.as_ref()
.unwrap()
.staking_pool_account_id
).as_str(),
);
self.set_staking_pool_status(TransactionStatus::Busy);
ext_staking_pool::stake(
amount,
self.staking_information
.as_ref()
.unwrap()
.staking_pool_account_id
.clone(),
NO_DEPOSIT,
gas::staking_pool::STAKE,
)
.then(
ext_self_owner::on_staking_pool_stake(
amount,
env::current_account_id(),
NO_DEPOSIT,
gas::owner_callbacks::ON_STAKING_POOL_STAKE,
)
)
}
/// OWNER'S METHOD
///
/// Requires 125 TGas
///
/// Unstakes the given amount at the staking pool.
pub fn unstake(
&mut self,
amount: WrappedBalance
) -> Promise {
self.repeated_assertions();
require!(
amount.0 > 0,
"Amount should be positive"
);
env::log_str(
format!(
"Unstaking {} from @{}",
amount.0,
self.staking_information
.as_ref()
.unwrap()
.staking_pool_account_id
.clone()
).as_str(),
);
self.set_staking_pool_status(TransactionStatus::Busy);
ext_staking_pool::unstake(
amount,
self.staking_information
.as_ref()
.unwrap()
.staking_pool_account_id
.clone(),
NO_DEPOSIT,
gas::staking_pool::UNSTAKE,
)
.then(
ext_self_owner::on_staking_pool_unstake(
amount,
env::current_account_id(),
NO_DEPOSIT,
gas::owner_callbacks::ON_STAKING_POOL_UNSTAKE,
)
)
}
/// OWNER'S METHOD
///
/// Requires 125 TGas
///
/// Unstakes all tokens from staking pool
pub fn unstake_all(&mut self) -> Promise {
self.repeated_assertions();
env::log_str(
format!(
"Unstaking all tokens from @{}",
self.staking_information
.as_ref()
.unwrap()
.staking_pool_account_id
.clone()
).as_str(),
);
self.set_staking_pool_status(TransactionStatus::Busy);
ext_staking_pool::unstake_all(
self.staking_information
.as_ref()
.unwrap()
.staking_pool_account_id
.clone(),
NO_DEPOSIT,
gas::staking_pool::UNSTAKE_ALL,
).then(
ext_self_owner::on_staking_pool_unstake_all(
env::current_account_id(),
NO_DEPOSIT,
gas::owner_callbacks::ON_STAKING_POOL_UNSTAKE_ALL,
)
)
}
/// Requires 75 TGas
/// Not intended to hand over the access to someone else
/// except the owner.
///
/// Calls voting contract to validate if the transfers were enabled by
/// voting. Once transfers are enabled, they can't be disabled anymore.
pub fn check_transfers_vote(&mut self) -> Promise {
self.assert_owner();
self.assert_transfers_disabled();
self.assert_no_termination();
let transfer_poll_account_id =
match &self.lockup_information.transfers_information
{
TransfersInformation::TransfersDisabled {
transfer_poll_account_id
} => transfer_poll_account_id,
_ => unreachable!(),
};
env::log_str(
format!(
"Checking that transfers are enabled at the transfer poll contract @{}",
&transfer_poll_account_id.clone()
).as_str(),
);
ext_transfer_poll::get_result(
transfer_poll_account_id.clone(),
NO_DEPOSIT,
gas::transfer_poll::GET_RESULT,
)
.then(
ext_self_owner::on_get_result_from_transfer_poll(
env::current_account_id(),
NO_DEPOSIT,
gas::owner_callbacks::ON_VOTING_GET_RESULT,
)
)
}
/// Requires 50 TGas
/// Not intended to hand over the access to someone else
/// except the owner.
///
/// Transfers the given amount to the given receiver
/// account ID. This requires transfers to be enabled
/// within the voting contract.
pub fn transfer(
&mut self,
amount: WrappedBalance,
receiver_id: AccountId
) -> Promise {
self.assert_owner();
require!(
env::is_valid_account_id(receiver_id.as_bytes()),
"The receiver account ID is invalid"
);
self.assert_transfers_enabled();
self.assert_no_staking_or_idle();
self.assert_no_termination();
require!(
self.get_liquid_owners_balance().0 >= amount.0,
format!(
concat!(
"The available liquid balance {} is smaller than ",
"the requested tranfer amount {}"),
self.get_liquid_owners_balance().0,
amount.0,
),
);
env::log_str(
format!(
"Transferring {} to @{}",
amount.0,
receiver_id
).as_str()
);
Promise::new(receiver_id).transfer(amount.0)
}
/// Requires 50 TGas
/// Not intended to hand over the access to someone else
/// except the owner.
///
/// Adds full access key with the given public key to the
/// account. You need:
/// - Fully-vested account
/// - Lockup duration expired.
/// - Transfers enabled.
/// - No termination in progress (either none or must be finished)
/// This account will become a regular account, contract will be removed.
pub fn add_full_access_key(
&mut self,
new_public_key: PublicKey
) -> Promise {
self.assert_owner();
self.assert_transfers_enabled();
self.assert_no_staking_or_idle();
self.assert_no_termination();
require!(
self.get_locked_amount().0 == 0,
"Tokens are still locked/unvested"
);
env::log_str("Adding a full access key");
// We pass in PublicKey, not Base58PublicKey, so no need this.
// let new_public_key: PublicKey = new_public_key.into();
Promise::new(
env::current_account_id()
).add_full_access_key(new_public_key)
}
}
We can choose not to specify an amount and withdraw everything. Most code are similar except it calls a different cross-contract function.
use crate::*;
use near_sdk::{near_bindgen, AccountId, Promise, PublicKey, require};
#[near_bindgen]
impl LockupContract {
fn repeated_assertions(&mut self) {
self.assert_owner();
self.assert_staking_pool_is_idle();
self.assert_no_termination();
}
/// OWNER'S METHOD
///
/// Requires 75 TGas
///
/// Select staking pool contract at the given account ID. The staking pool
/// first has to be checked against staking pool whitelist contract.
pub fn select_staking_pool(
&mut self,
staking_pool_account_id: AccountId
) -> Promise {
self.assert_owner();
require!(
env::is_valid_account_id(staking_pool_account_id.as_bytes()),
"The staking pool account ID is invalid"
);
self.assert_staking_pool_is_not_selected();
self.assert_no_termination();
env::log_str(
format!(
"Selecting staking pool @{}. Checking if this pool is whitelisted.",
staking_pool_account_id
).as_str(),
);
ext_whitelist::is_whitelisted(
staking_pool_account_id.clone(),
self.staking_pool_whitelist_account_id.clone(),
NO_DEPOSIT,
gas::whitelist::IS_WHITELISTED,
)
.then(
ext_self_owner::on_whitelist_is_whitelisted(
staking_pool_account_id,
env::current_account_id(),
NO_DEPOSIT,
gas::owner_callbacks::ON_WHITELIST_IS_WHITELISTED,
)
)
}
/// OWNER'S METHOD
///
/// Requires 25 TGas
///
/// Unselects the current(ly selected) staking pool.
/// It requires that there are no known deposits left on
/// the currently selected staking pool.
pub fn unselect_staking_pool(&mut self) {
// self.assert_owner();
// self.assert_staking_pool_is_idle();
// self.assert_no_termination();
self.repeated_assertions();
// This is best effort check. There may still be leftovers
// in the staking pool. Owner can choose to or not to
// unselect. The contract doesn't care about leftovers.
require!(
self.staking_information.as_ref().unwrap()
.deposit_amount.0 == 0,
"There is still deposit on staking pool."
);
env::log_str(
format!(
"Unselecting current staking pool @{}",
self.staking_information
.as_ref()
.unwrap()
.staking_pool_account_id
.clone()
).as_str(),
);
self.staking_information = None;
}
/// OWNER'S METHOD
///
/// Requires 100 TGas
///
/// Deposits the given extra amount to the staking pool.
pub fn deposit_to_staking_pool(
&mut self,
amount: WrappedBalance
) -> Promise {
self.repeated_assertions();
require!(
amount.0 > 0,
"Deposit amount should be positive."
);
require!(
self.get_account_balance().0 >= amount.0,
"Trying to stake more than you have is not possible."
);
env::log_str(
format!(
"Depositing {} to @{}",
amount.0,
self.staking_information
.as_ref()
.unwrap()
.staking_pool_account_id
.clone()
).as_str(),
);
self.set_staking_pool_status(TransactionStatus::Busy);
ext_staking_pool::deposit(
self.staking_information
.as_ref()
.unwrap()
.staking_pool_account_id
.clone(),
amount.0,
gas::staking_pool::DEPOSIT,
)
.then(
ext_self_owner::on_staking_pool_deposit(
amount,
env::current_account_id(),
NO_DEPOSIT,
gas::owner_callbacks::ON_STAKING_POOL_DEPOSIT,
)
)
}
/// OWNER'S METHOD
///
/// Requires 125 TGas
///
/// Deposits and stakes the given extra amount to the
/// selected staking pool.
pub fn deposit_and_stake(
&mut self,
amount: WrappedBalance
) -> Promise {
self.repeated_assertions();
require!(amount.0 > 0, "Deposit amount should be positive.");
require!(
self.get_account_balance().0 >= amount.0,
"Trying to stake more than you have is not possible."
);
env::log_str(
format!(
"Depositing and staking {} to @{}",
amount.0,
self.staking_information
.as_ref()
.unwrap()
.staking_pool_account_id
.clone()
).as_str(),
);
self.set_staking_pool_status(TransactionStatus::Busy);
ext_staking_pool::deposit_and_stake(
self.staking_information
.as_ref()
.unwrap()
.staking_pool_account_id
.clone(),
amount.0,
gas::staking_pool::DEPOSIT_AND_STAKE,
)
.then(
ext_self_owner::on_staking_pool_deposit_and_stake(
amount,
env::current_account_id(),
NO_DEPOSIT,
gas::owner_callbacks::ON_STAKING_POOL_DEPOSIT_AND_STAKE,
)
)
}
/// OWNER'S METHOD
///
/// Requires 75 TGas
///
/// Retrieves total balance from staking pool and save it
/// internally. Useful when owner wants to receive rewards
/// during unstaking for querying total balance in the pool
/// that could be withdrawn.
pub fn refresh_staking_pool_balance(&mut self) -> Promise {
self.repeated_assertions();
env::log_str(
format!(
"Fetching total balance from staking pool @{}",
self.staking_information
.as_ref()
.unwrap()
.staking_pool_account_id
.clone()
).as_str(),
);
self.set_staking_pool_status(TransactionStatus::Busy);
ext_staking_pool::get_account_total_balance(
env::current_account_id(),
self.staking_information
.as_ref()
.unwrap()
.staking_pool_account_id
.clone(),
NO_DEPOSIT,
gas::staking_pool::GET_ACCOUNT_TOTAL_BALANCE,
)
.then(
ext_self_owner::on_get_account_total_balance(
env::current_account_id(),
NO_DEPOSIT,
gas::owner_callbacks::ON_GET_ACCOUNT_TOTAL_BALANCE,
)
)
}
/// OWNER'S METHOD
///
/// Requires 125 TGas
///
/// Withdraws the given amount from the staking pool.
pub fn withdraw_from_staking_pool(
&mut self,
amount: WrappedBalance
) -> Promise {
self.repeated_assertions();
require!(
amount.0 > 0,
"Amount should be positive"
);
env::log_str(
format!(
"Withdrawing {} from @{}",
amount.0,
self.staking_information
.as_ref()
.unwrap()
.staking_pool_account_id
.clone()
).as_str(),
);
self.set_staking_pool_status(TransactionStatus::Busy);
ext_staking_pool::withdraw(
amount,
self.staking_information
.as_ref()
.unwrap()
.staking_pool_account_id
.clone(),
NO_DEPOSIT,
gas::staking_pool::WITHDRAW,
)
.then(
ext_self_owner::on_staking_pool_withdraw(
amount,
env::current_account_id(),
NO_DEPOSIT,
gas::owner_callbacks::ON_STAKING_POOL_WITHDRAW,
)
)
}
/// OWNER'S METHOD
///
/// Requires 175 TGas
///
/// Tries to withdraw all unstaked balance from staking pool.
pub fn withdraw_all_from_staking_pool(&mut self) -> Promise {
self.repeated_assertions();
env::log_str(
format!(
"Querying unstaked balance on @{} to withdraw everything.",
self.staking_information
.as_ref()
.unwrap()
.staking_pool_account_id
.clone()
).as_str(),
);
self.set_staking_pool_status(TransactionStatus::Busy);
ext_staking_pool::get_account_unstaked_balance(
env::current_account_id(),
self.staking_information
.as_ref()
.unwrap()
.staking_pool_account_id
.clone(),
NO_DEPOSIT,
gas::staking_pool::GET_ACCOUNT_UNSTAKED_BALANCE,
)
.then(
ext_self_owner::on_get_account_unstaked_balance_to_withdraw_by_owner(
env::current_account_id(),
NO_DEPOSIT,
gas::owner_callbacks::ON_GET_ACCOUNT_UNSTAKED_BALANCE_TO_WITHDRAW_BY_OWNER,
),
)
}
/// ONWER'S METHOD
///
/// Requires 125 TGas
///
/// Stakes the given extra amount at the staking pool.
pub fn stake(
&mut self,
amount: WrappedBalance
) -> Promise {
self.repeated_assertions();
require!(
amount.0 > 0,
"Amount should be positive."
);
env::log_str(
format!(
"Staking {} at @{}",
amount.0,
self.staking_information
.as_ref()
.unwrap()
.staking_pool_account_id
).as_str(),
);
self.set_staking_pool_status(TransactionStatus::Busy);
ext_staking_pool::stake(
amount,
self.staking_information
.as_ref()
.unwrap()
.staking_pool_account_id
.clone(),
NO_DEPOSIT,
gas::staking_pool::STAKE,
)
.then(
ext_self_owner::on_staking_pool_stake(
amount,
env::current_account_id(),
NO_DEPOSIT,
gas::owner_callbacks::ON_STAKING_POOL_STAKE,
)
)
}
/// OWNER'S METHOD
///
/// Requires 125 TGas
///
/// Unstakes the given amount at the staking pool.
pub fn unstake(
&mut self,
amount: WrappedBalance
) -> Promise {
self.repeated_assertions();
require!(
amount.0 > 0,
"Amount should be positive"
);
env::log_str(
format!(
"Unstaking {} from @{}",
amount.0,
self.staking_information
.as_ref()
.unwrap()
.staking_pool_account_id
.clone()
).as_str(),
);
self.set_staking_pool_status(TransactionStatus::Busy);
ext_staking_pool::unstake(
amount,
self.staking_information
.as_ref()
.unwrap()
.staking_pool_account_id
.clone(),
NO_DEPOSIT,
gas::staking_pool::UNSTAKE,
)
.then(
ext_self_owner::on_staking_pool_unstake(
amount,
env::current_account_id(),
NO_DEPOSIT,
gas::owner_callbacks::ON_STAKING_POOL_UNSTAKE,
)
)
}
/// OWNER'S METHOD
///
/// Requires 125 TGas
///
/// Unstakes all tokens from staking pool
pub fn unstake_all(&mut self) -> Promise {
self.repeated_assertions();
env::log_str(
format!(
"Unstaking all tokens from @{}",
self.staking_information
.as_ref()
.unwrap()
.staking_pool_account_id
.clone()
).as_str(),
);
self.set_staking_pool_status(TransactionStatus::Busy);
ext_staking_pool::unstake_all(
self.staking_information
.as_ref()
.unwrap()
.staking_pool_account_id
.clone(),
NO_DEPOSIT,
gas::staking_pool::UNSTAKE_ALL,
).then(
ext_self_owner::on_staking_pool_unstake_all(
env::current_account_id(),
NO_DEPOSIT,
gas::owner_callbacks::ON_STAKING_POOL_UNSTAKE_ALL,
)
)
}
/// Requires 75 TGas
/// Not intended to hand over the access to someone else
/// except the owner.
///
/// Calls voting contract to validate if the transfers were enabled by
/// voting. Once transfers are enabled, they can't be disabled anymore.
pub fn check_transfers_vote(&mut self) -> Promise {
self.assert_owner();
self.assert_transfers_disabled();
self.assert_no_termination();
let transfer_poll_account_id =
match &self.lockup_information.transfers_information
{
TransfersInformation::TransfersDisabled {
transfer_poll_account_id
} => transfer_poll_account_id,
_ => unreachable!(),
};
env::log_str(
format!(
"Checking that transfers are enabled at the transfer poll contract @{}",
&transfer_poll_account_id.clone()
).as_str(),
);
ext_transfer_poll::get_result(
transfer_poll_account_id.clone(),
NO_DEPOSIT,
gas::transfer_poll::GET_RESULT,
)
.then(
ext_self_owner::on_get_result_from_transfer_poll(
env::current_account_id(),
NO_DEPOSIT,
gas::owner_callbacks::ON_VOTING_GET_RESULT,
)
)
}
/// Requires 50 TGas
/// Not intended to hand over the access to someone else
/// except the owner.
///
/// Transfers the given amount to the given receiver
/// account ID. This requires transfers to be enabled
/// within the voting contract.
pub fn transfer(
&mut self,
amount: WrappedBalance,
receiver_id: AccountId
) -> Promise {
self.assert_owner();
require!(
env::is_valid_account_id(receiver_id.as_bytes()),
"The receiver account ID is invalid"
);
self.assert_transfers_enabled();
self.assert_no_staking_or_idle();
self.assert_no_termination();
require!(
self.get_liquid_owners_balance().0 >= amount.0,
format!(
concat!(
"The available liquid balance {} is smaller than ",
"the requested tranfer amount {}"),
self.get_liquid_owners_balance().0,
amount.0,
),
);
env::log_str(
format!(
"Transferring {} to @{}",
amount.0,
receiver_id
).as_str()
);
Promise::new(receiver_id).transfer(amount.0)
}
/// Requires 50 TGas
/// Not intended to hand over the access to someone else
/// except the owner.
///
/// Adds full access key with the given public key to the
/// account. You need:
/// - Fully-vested account
/// - Lockup duration expired.
/// - Transfers enabled.
/// - No termination in progress (either none or must be finished)
/// This account will become a regular account, contract will be removed.
pub fn add_full_access_key(
&mut self,
new_public_key: PublicKey
) -> Promise {
self.assert_owner();
self.assert_transfers_enabled();
self.assert_no_staking_or_idle();
self.assert_no_termination();
require!(
self.get_locked_amount().0 == 0,
"Tokens are still locked/unvested"
);
env::log_str("Adding a full access key");
// We pass in PublicKey, not Base58PublicKey, so no need this.
// let new_public_key: PublicKey = new_public_key.into();
Promise::new(
env::current_account_id()
).add_full_access_key(new_public_key)
}
}
In particular, since we don't enter an amount, it will query for the unstaked balance your account has on the staking pool to prepare for withdrawal.
Up till now, we only have functions to deposit NEAR to the staking pool, but we didn't have any function to stake NEAR yet. Let's do that now.
use crate::*;
use near_sdk::{near_bindgen, AccountId, Promise, PublicKey, require};
#[near_bindgen]
impl LockupContract {
fn repeated_assertions(&mut self) {
self.assert_owner();
self.assert_staking_pool_is_idle();
self.assert_no_termination();
}
/// OWNER'S METHOD
///
/// Requires 75 TGas
///
/// Select staking pool contract at the given account ID. The staking pool
/// first has to be checked against staking pool whitelist contract.
pub fn select_staking_pool(
&mut self,
staking_pool_account_id: AccountId
) -> Promise {
self.assert_owner();
require!(
env::is_valid_account_id(staking_pool_account_id.as_bytes()),
"The staking pool account ID is invalid"
);
self.assert_staking_pool_is_not_selected();
self.assert_no_termination();
env::log_str(
format!(
"Selecting staking pool @{}. Checking if this pool is whitelisted.",
staking_pool_account_id
).as_str(),
);
ext_whitelist::is_whitelisted(
staking_pool_account_id.clone(),
self.staking_pool_whitelist_account_id.clone(),
NO_DEPOSIT,
gas::whitelist::IS_WHITELISTED,
)
.then(
ext_self_owner::on_whitelist_is_whitelisted(
staking_pool_account_id,
env::current_account_id(),
NO_DEPOSIT,
gas::owner_callbacks::ON_WHITELIST_IS_WHITELISTED,
)
)
}
/// OWNER'S METHOD
///
/// Requires 25 TGas
///
/// Unselects the current(ly selected) staking pool.
/// It requires that there are no known deposits left on
/// the currently selected staking pool.
pub fn unselect_staking_pool(&mut self) {
// self.assert_owner();
// self.assert_staking_pool_is_idle();
// self.assert_no_termination();
self.repeated_assertions();
// This is best effort check. There may still be leftovers
// in the staking pool. Owner can choose to or not to
// unselect. The contract doesn't care about leftovers.
require!(
self.staking_information.as_ref().unwrap()
.deposit_amount.0 == 0,
"There is still deposit on staking pool."
);
env::log_str(
format!(
"Unselecting current staking pool @{}",
self.staking_information
.as_ref()
.unwrap()
.staking_pool_account_id
.clone()
).as_str(),
);
self.staking_information = None;
}
/// OWNER'S METHOD
///
/// Requires 100 TGas
///
/// Deposits the given extra amount to the staking pool.
pub fn deposit_to_staking_pool(
&mut self,
amount: WrappedBalance
) -> Promise {
self.repeated_assertions();
require!(
amount.0 > 0,
"Deposit amount should be positive."
);
require!(
self.get_account_balance().0 >= amount.0,
"Trying to stake more than you have is not possible."
);
env::log_str(
format!(
"Depositing {} to @{}",
amount.0,
self.staking_information
.as_ref()
.unwrap()
.staking_pool_account_id
.clone()
).as_str(),
);
self.set_staking_pool_status(TransactionStatus::Busy);
ext_staking_pool::deposit(
self.staking_information
.as_ref()
.unwrap()
.staking_pool_account_id
.clone(),
amount.0,
gas::staking_pool::DEPOSIT,
)
.then(
ext_self_owner::on_staking_pool_deposit(
amount,
env::current_account_id(),
NO_DEPOSIT,
gas::owner_callbacks::ON_STAKING_POOL_DEPOSIT,
)
)
}
/// OWNER'S METHOD
///
/// Requires 125 TGas
///
/// Deposits and stakes the given extra amount to the
/// selected staking pool.
pub fn deposit_and_stake(
&mut self,
amount: WrappedBalance
) -> Promise {
self.repeated_assertions();
require!(amount.0 > 0, "Deposit amount should be positive.");
require!(
self.get_account_balance().0 >= amount.0,
"Trying to stake more than you have is not possible."
);
env::log_str(
format!(
"Depositing and staking {} to @{}",
amount.0,
self.staking_information
.as_ref()
.unwrap()
.staking_pool_account_id
.clone()
).as_str(),
);
self.set_staking_pool_status(TransactionStatus::Busy);
ext_staking_pool::deposit_and_stake(
self.staking_information
.as_ref()
.unwrap()
.staking_pool_account_id
.clone(),
amount.0,
gas::staking_pool::DEPOSIT_AND_STAKE,
)
.then(
ext_self_owner::on_staking_pool_deposit_and_stake(
amount,
env::current_account_id(),
NO_DEPOSIT,
gas::owner_callbacks::ON_STAKING_POOL_DEPOSIT_AND_STAKE,
)
)
}
/// OWNER'S METHOD
///
/// Requires 75 TGas
///
/// Retrieves total balance from staking pool and save it
/// internally. Useful when owner wants to receive rewards
/// during unstaking for querying total balance in the pool
/// that could be withdrawn.
pub fn refresh_staking_pool_balance(&mut self) -> Promise {
self.repeated_assertions();
env::log_str(
format!(
"Fetching total balance from staking pool @{}",
self.staking_information
.as_ref()
.unwrap()
.staking_pool_account_id
.clone()
).as_str(),
);
self.set_staking_pool_status(TransactionStatus::Busy);
ext_staking_pool::get_account_total_balance(
env::current_account_id(),
self.staking_information
.as_ref()
.unwrap()
.staking_pool_account_id
.clone(),
NO_DEPOSIT,
gas::staking_pool::GET_ACCOUNT_TOTAL_BALANCE,
)
.then(
ext_self_owner::on_get_account_total_balance(
env::current_account_id(),
NO_DEPOSIT,
gas::owner_callbacks::ON_GET_ACCOUNT_TOTAL_BALANCE,
)
)
}
/// OWNER'S METHOD
///
/// Requires 125 TGas
///
/// Withdraws the given amount from the staking pool.
pub fn withdraw_from_staking_pool(
&mut self,
amount: WrappedBalance
) -> Promise {
self.repeated_assertions();
require!(
amount.0 > 0,
"Amount should be positive"
);
env::log_str(
format!(
"Withdrawing {} from @{}",
amount.0,
self.staking_information
.as_ref()
.unwrap()
.staking_pool_account_id
.clone()
).as_str(),
);
self.set_staking_pool_status(TransactionStatus::Busy);
ext_staking_pool::withdraw(
amount,
self.staking_information
.as_ref()
.unwrap()
.staking_pool_account_id
.clone(),
NO_DEPOSIT,
gas::staking_pool::WITHDRAW,
)
.then(
ext_self_owner::on_staking_pool_withdraw(
amount,
env::current_account_id(),
NO_DEPOSIT,
gas::owner_callbacks::ON_STAKING_POOL_WITHDRAW,
)
)
}
/// OWNER'S METHOD
///
/// Requires 175 TGas
///
/// Tries to withdraw all unstaked balance from staking pool.
pub fn withdraw_all_from_staking_pool(&mut self) -> Promise {
self.repeated_assertions();
env::log_str(
format!(
"Querying unstaked balance on @{} to withdraw everything.",
self.staking_information
.as_ref()
.unwrap()
.staking_pool_account_id
.clone()
).as_str(),
);
self.set_staking_pool_status(TransactionStatus::Busy);
ext_staking_pool::get_account_unstaked_balance(
env::current_account_id(),
self.staking_information
.as_ref()
.unwrap()
.staking_pool_account_id
.clone(),
NO_DEPOSIT,
gas::staking_pool::GET_ACCOUNT_UNSTAKED_BALANCE,
)
.then(
ext_self_owner::on_get_account_unstaked_balance_to_withdraw_by_owner(
env::current_account_id(),
NO_DEPOSIT,
gas::owner_callbacks::ON_GET_ACCOUNT_UNSTAKED_BALANCE_TO_WITHDRAW_BY_OWNER,
),
)
}
/// ONWER'S METHOD
///
/// Requires 125 TGas
///
/// Stakes the given extra amount at the staking pool.
pub fn stake(
&mut self,
amount: WrappedBalance
) -> Promise {
self.repeated_assertions();
require!(
amount.0 > 0,
"Amount should be positive."
);
env::log_str(
format!(
"Staking {} at @{}",
amount.0,
self.staking_information
.as_ref()
.unwrap()
.staking_pool_account_id
).as_str(),
);
self.set_staking_pool_status(TransactionStatus::Busy);
ext_staking_pool::stake(
amount,
self.staking_information
.as_ref()
.unwrap()
.staking_pool_account_id
.clone(),
NO_DEPOSIT,
gas::staking_pool::STAKE,
)
.then(
ext_self_owner::on_staking_pool_stake(
amount,
env::current_account_id(),
NO_DEPOSIT,
gas::owner_callbacks::ON_STAKING_POOL_STAKE,
)
)
}
/// OWNER'S METHOD
///
/// Requires 125 TGas
///
/// Unstakes the given amount at the staking pool.
pub fn unstake(
&mut self,
amount: WrappedBalance
) -> Promise {
self.repeated_assertions();
require!(
amount.0 > 0,
"Amount should be positive"
);
env::log_str(
format!(
"Unstaking {} from @{}",
amount.0,
self.staking_information
.as_ref()
.unwrap()
.staking_pool_account_id
.clone()
).as_str(),
);
self.set_staking_pool_status(TransactionStatus::Busy);
ext_staking_pool::unstake(
amount,
self.staking_information
.as_ref()
.unwrap()
.staking_pool_account_id
.clone(),
NO_DEPOSIT,
gas::staking_pool::UNSTAKE,
)
.then(
ext_self_owner::on_staking_pool_unstake(
amount,
env::current_account_id(),
NO_DEPOSIT,
gas::owner_callbacks::ON_STAKING_POOL_UNSTAKE,
)
)
}
/// OWNER'S METHOD
///
/// Requires 125 TGas
///
/// Unstakes all tokens from staking pool
pub fn unstake_all(&mut self) -> Promise {
self.repeated_assertions();
env::log_str(
format!(
"Unstaking all tokens from @{}",
self.staking_information
.as_ref()
.unwrap()
.staking_pool_account_id
.clone()
).as_str(),
);
self.set_staking_pool_status(TransactionStatus::Busy);
ext_staking_pool::unstake_all(
self.staking_information
.as_ref()
.unwrap()
.staking_pool_account_id
.clone(),
NO_DEPOSIT,
gas::staking_pool::UNSTAKE_ALL,
).then(
ext_self_owner::on_staking_pool_unstake_all(
env::current_account_id(),
NO_DEPOSIT,
gas::owner_callbacks::ON_STAKING_POOL_UNSTAKE_ALL,
)
)
}
/// Requires 75 TGas
/// Not intended to hand over the access to someone else
/// except the owner.
///
/// Calls voting contract to validate if the transfers were enabled by
/// voting. Once transfers are enabled, they can't be disabled anymore.
pub fn check_transfers_vote(&mut self) -> Promise {
self.assert_owner();
self.assert_transfers_disabled();
self.assert_no_termination();
let transfer_poll_account_id =
match &self.lockup_information.transfers_information
{
TransfersInformation::TransfersDisabled {
transfer_poll_account_id
} => transfer_poll_account_id,
_ => unreachable!(),
};
env::log_str(
format!(
"Checking that transfers are enabled at the transfer poll contract @{}",
&transfer_poll_account_id.clone()
).as_str(),
);
ext_transfer_poll::get_result(
transfer_poll_account_id.clone(),
NO_DEPOSIT,
gas::transfer_poll::GET_RESULT,
)
.then(
ext_self_owner::on_get_result_from_transfer_poll(
env::current_account_id(),
NO_DEPOSIT,
gas::owner_callbacks::ON_VOTING_GET_RESULT,
)
)
}
/// Requires 50 TGas
/// Not intended to hand over the access to someone else
/// except the owner.
///
/// Transfers the given amount to the given receiver
/// account ID. This requires transfers to be enabled
/// within the voting contract.
pub fn transfer(
&mut self,
amount: WrappedBalance,
receiver_id: AccountId
) -> Promise {
self.assert_owner();
require!(
env::is_valid_account_id(receiver_id.as_bytes()),
"The receiver account ID is invalid"
);
self.assert_transfers_enabled();
self.assert_no_staking_or_idle();
self.assert_no_termination();
require!(
self.get_liquid_owners_balance().0 >= amount.0,
format!(
concat!(
"The available liquid balance {} is smaller than ",
"the requested tranfer amount {}"),
self.get_liquid_owners_balance().0,
amount.0,
),
);
env::log_str(
format!(
"Transferring {} to @{}",
amount.0,
receiver_id
).as_str()
);
Promise::new(receiver_id).transfer(amount.0)
}
/// Requires 50 TGas
/// Not intended to hand over the access to someone else
/// except the owner.
///
/// Adds full access key with the given public key to the
/// account. You need:
/// - Fully-vested account
/// - Lockup duration expired.
/// - Transfers enabled.
/// - No termination in progress (either none or must be finished)
/// This account will become a regular account, contract will be removed.
pub fn add_full_access_key(
&mut self,
new_public_key: PublicKey
) -> Promise {
self.assert_owner();
self.assert_transfers_enabled();
self.assert_no_staking_or_idle();
self.assert_no_termination();
require!(
self.get_locked_amount().0 == 0,
"Tokens are still locked/unvested"
);
env::log_str("Adding a full access key");
// We pass in PublicKey, not Base58PublicKey, so no need this.
// let new_public_key: PublicKey = new_public_key.into();
Promise::new(
env::current_account_id()
).add_full_access_key(new_public_key)
}
}
It mentioned "given extra amount", which just means anything that you haven't staked. It doesn't mean the rewards you earn per se! The rewards you earn are counted, but those that you deposit to the staking pool are also "extra amount" before you stake them.
And of course, to unstake them from the selected staking pool. Since we select staking pool first before choosing to stake/unstake, we don't need to pass in "stake_pool" as arguments to the function. If you aren't sure how this works, try to stake some amount (perhaps 0.1 NEAR) on a staking pool on NEAR wallet and play with it. You'll see that you first need to select the pool, then only you can say to stake or unstake on that pool with a specified amount (DON'T put MAX, because you need to pay Gas fee to withdraw later. Leave a small amount on your wallet depending on how much you stake. If you stake a lot, withdrawing might require more gas fee so you need more in your wallet.)
use crate::*;
use near_sdk::{near_bindgen, AccountId, Promise, PublicKey, require};
#[near_bindgen]
impl LockupContract {
fn repeated_assertions(&mut self) {
self.assert_owner();
self.assert_staking_pool_is_idle();
self.assert_no_termination();
}
/// OWNER'S METHOD
///
/// Requires 75 TGas
///
/// Select staking pool contract at the given account ID. The staking pool
/// first has to be checked against staking pool whitelist contract.
pub fn select_staking_pool(
&mut self,
staking_pool_account_id: AccountId
) -> Promise {
self.assert_owner();
require!(
env::is_valid_account_id(staking_pool_account_id.as_bytes()),
"The staking pool account ID is invalid"
);
self.assert_staking_pool_is_not_selected();
self.assert_no_termination();
env::log_str(
format!(
"Selecting staking pool @{}. Checking if this pool is whitelisted.",
staking_pool_account_id
).as_str(),
);
ext_whitelist::is_whitelisted(
staking_pool_account_id.clone(),
self.staking_pool_whitelist_account_id.clone(),
NO_DEPOSIT,
gas::whitelist::IS_WHITELISTED,
)
.then(
ext_self_owner::on_whitelist_is_whitelisted(
staking_pool_account_id,
env::current_account_id(),
NO_DEPOSIT,
gas::owner_callbacks::ON_WHITELIST_IS_WHITELISTED,
)
)
}
/// OWNER'S METHOD
///
/// Requires 25 TGas
///
/// Unselects the current(ly selected) staking pool.
/// It requires that there are no known deposits left on
/// the currently selected staking pool.
pub fn unselect_staking_pool(&mut self) {
// self.assert_owner();
// self.assert_staking_pool_is_idle();
// self.assert_no_termination();
self.repeated_assertions();
// This is best effort check. There may still be leftovers
// in the staking pool. Owner can choose to or not to
// unselect. The contract doesn't care about leftovers.
require!(
self.staking_information.as_ref().unwrap()
.deposit_amount.0 == 0,
"There is still deposit on staking pool."
);
env::log_str(
format!(
"Unselecting current staking pool @{}",
self.staking_information
.as_ref()
.unwrap()
.staking_pool_account_id
.clone()
).as_str(),
);
self.staking_information = None;
}
/// OWNER'S METHOD
///
/// Requires 100 TGas
///
/// Deposits the given extra amount to the staking pool.
pub fn deposit_to_staking_pool(
&mut self,
amount: WrappedBalance
) -> Promise {
self.repeated_assertions();
require!(
amount.0 > 0,
"Deposit amount should be positive."
);
require!(
self.get_account_balance().0 >= amount.0,
"Trying to stake more than you have is not possible."
);
env::log_str(
format!(
"Depositing {} to @{}",
amount.0,
self.staking_information
.as_ref()
.unwrap()
.staking_pool_account_id
.clone()
).as_str(),
);
self.set_staking_pool_status(TransactionStatus::Busy);
ext_staking_pool::deposit(
self.staking_information
.as_ref()
.unwrap()
.staking_pool_account_id
.clone(),
amount.0,
gas::staking_pool::DEPOSIT,
)
.then(
ext_self_owner::on_staking_pool_deposit(
amount,
env::current_account_id(),
NO_DEPOSIT,
gas::owner_callbacks::ON_STAKING_POOL_DEPOSIT,
)
)
}
/// OWNER'S METHOD
///
/// Requires 125 TGas
///
/// Deposits and stakes the given extra amount to the
/// selected staking pool.
pub fn deposit_and_stake(
&mut self,
amount: WrappedBalance
) -> Promise {
self.repeated_assertions();
require!(amount.0 > 0, "Deposit amount should be positive.");
require!(
self.get_account_balance().0 >= amount.0,
"Trying to stake more than you have is not possible."
);
env::log_str(
format!(
"Depositing and staking {} to @{}",
amount.0,
self.staking_information
.as_ref()
.unwrap()
.staking_pool_account_id
.clone()
).as_str(),
);
self.set_staking_pool_status(TransactionStatus::Busy);
ext_staking_pool::deposit_and_stake(
self.staking_information
.as_ref()
.unwrap()
.staking_pool_account_id
.clone(),
amount.0,
gas::staking_pool::DEPOSIT_AND_STAKE,
)
.then(
ext_self_owner::on_staking_pool_deposit_and_stake(
amount,
env::current_account_id(),
NO_DEPOSIT,
gas::owner_callbacks::ON_STAKING_POOL_DEPOSIT_AND_STAKE,
)
)
}
/// OWNER'S METHOD
///
/// Requires 75 TGas
///
/// Retrieves total balance from staking pool and save it
/// internally. Useful when owner wants to receive rewards
/// during unstaking for querying total balance in the pool
/// that could be withdrawn.
pub fn refresh_staking_pool_balance(&mut self) -> Promise {
self.repeated_assertions();
env::log_str(
format!(
"Fetching total balance from staking pool @{}",
self.staking_information
.as_ref()
.unwrap()
.staking_pool_account_id
.clone()
).as_str(),
);
self.set_staking_pool_status(TransactionStatus::Busy);
ext_staking_pool::get_account_total_balance(
env::current_account_id(),
self.staking_information
.as_ref()
.unwrap()
.staking_pool_account_id
.clone(),
NO_DEPOSIT,
gas::staking_pool::GET_ACCOUNT_TOTAL_BALANCE,
)
.then(
ext_self_owner::on_get_account_total_balance(
env::current_account_id(),
NO_DEPOSIT,
gas::owner_callbacks::ON_GET_ACCOUNT_TOTAL_BALANCE,
)
)
}
/// OWNER'S METHOD
///
/// Requires 125 TGas
///
/// Withdraws the given amount from the staking pool.
pub fn withdraw_from_staking_pool(
&mut self,
amount: WrappedBalance
) -> Promise {
self.repeated_assertions();
require!(
amount.0 > 0,
"Amount should be positive"
);
env::log_str(
format!(
"Withdrawing {} from @{}",
amount.0,
self.staking_information
.as_ref()
.unwrap()
.staking_pool_account_id
.clone()
).as_str(),
);
self.set_staking_pool_status(TransactionStatus::Busy);
ext_staking_pool::withdraw(
amount,
self.staking_information
.as_ref()
.unwrap()
.staking_pool_account_id
.clone(),
NO_DEPOSIT,
gas::staking_pool::WITHDRAW,
)
.then(
ext_self_owner::on_staking_pool_withdraw(
amount,
env::current_account_id(),
NO_DEPOSIT,
gas::owner_callbacks::ON_STAKING_POOL_WITHDRAW,
)
)
}
/// OWNER'S METHOD
///
/// Requires 175 TGas
///
/// Tries to withdraw all unstaked balance from staking pool.
pub fn withdraw_all_from_staking_pool(&mut self) -> Promise {
self.repeated_assertions();
env::log_str(
format!(
"Querying unstaked balance on @{} to withdraw everything.",
self.staking_information
.as_ref()
.unwrap()
.staking_pool_account_id
.clone()
).as_str(),
);
self.set_staking_pool_status(TransactionStatus::Busy);
ext_staking_pool::get_account_unstaked_balance(
env::current_account_id(),
self.staking_information
.as_ref()
.unwrap()
.staking_pool_account_id
.clone(),
NO_DEPOSIT,
gas::staking_pool::GET_ACCOUNT_UNSTAKED_BALANCE,
)
.then(
ext_self_owner::on_get_account_unstaked_balance_to_withdraw_by_owner(
env::current_account_id(),
NO_DEPOSIT,
gas::owner_callbacks::ON_GET_ACCOUNT_UNSTAKED_BALANCE_TO_WITHDRAW_BY_OWNER,
),
)
}
/// ONWER'S METHOD
///
/// Requires 125 TGas
///
/// Stakes the given extra amount at the staking pool.
pub fn stake(
&mut self,
amount: WrappedBalance
) -> Promise {
self.repeated_assertions();
require!(
amount.0 > 0,
"Amount should be positive."
);
env::log_str(
format!(
"Staking {} at @{}",
amount.0,
self.staking_information
.as_ref()
.unwrap()
.staking_pool_account_id
).as_str(),
);
self.set_staking_pool_status(TransactionStatus::Busy);
ext_staking_pool::stake(
amount,
self.staking_information
.as_ref()
.unwrap()
.staking_pool_account_id
.clone(),
NO_DEPOSIT,
gas::staking_pool::STAKE,
)
.then(
ext_self_owner::on_staking_pool_stake(
amount,
env::current_account_id(),
NO_DEPOSIT,
gas::owner_callbacks::ON_STAKING_POOL_STAKE,
)
)
}
/// OWNER'S METHOD
///
/// Requires 125 TGas
///
/// Unstakes the given amount at the staking pool.
pub fn unstake(
&mut self,
amount: WrappedBalance
) -> Promise {
self.repeated_assertions();
require!(
amount.0 > 0,
"Amount should be positive"
);
env::log_str(
format!(
"Unstaking {} from @{}",
amount.0,
self.staking_information
.as_ref()
.unwrap()
.staking_pool_account_id
.clone()
).as_str(),
);
self.set_staking_pool_status(TransactionStatus::Busy);
ext_staking_pool::unstake(
amount,
self.staking_information
.as_ref()
.unwrap()
.staking_pool_account_id
.clone(),
NO_DEPOSIT,
gas::staking_pool::UNSTAKE,
)
.then(
ext_self_owner::on_staking_pool_unstake(
amount,
env::current_account_id(),
NO_DEPOSIT,
gas::owner_callbacks::ON_STAKING_POOL_UNSTAKE,
)
)
}
/// OWNER'S METHOD
///
/// Requires 125 TGas
///
/// Unstakes all tokens from staking pool
pub fn unstake_all(&mut self) -> Promise {
self.repeated_assertions();
env::log_str(
format!(
"Unstaking all tokens from @{}",
self.staking_information
.as_ref()
.unwrap()
.staking_pool_account_id
.clone()
).as_str(),
);
self.set_staking_pool_status(TransactionStatus::Busy);
ext_staking_pool::unstake_all(
self.staking_information
.as_ref()
.unwrap()
.staking_pool_account_id
.clone(),
NO_DEPOSIT,
gas::staking_pool::UNSTAKE_ALL,
).then(
ext_self_owner::on_staking_pool_unstake_all(
env::current_account_id(),
NO_DEPOSIT,
gas::owner_callbacks::ON_STAKING_POOL_UNSTAKE_ALL,
)
)
}
/// Requires 75 TGas
/// Not intended to hand over the access to someone else
/// except the owner.
///
/// Calls voting contract to validate if the transfers were enabled by
/// voting. Once transfers are enabled, they can't be disabled anymore.
pub fn check_transfers_vote(&mut self) -> Promise {
self.assert_owner();
self.assert_transfers_disabled();
self.assert_no_termination();
let transfer_poll_account_id =
match &self.lockup_information.transfers_information
{
TransfersInformation::TransfersDisabled {
transfer_poll_account_id
} => transfer_poll_account_id,
_ => unreachable!(),
};
env::log_str(
format!(
"Checking that transfers are enabled at the transfer poll contract @{}",
&transfer_poll_account_id.clone()
).as_str(),
);
ext_transfer_poll::get_result(
transfer_poll_account_id.clone(),
NO_DEPOSIT,
gas::transfer_poll::GET_RESULT,
)
.then(
ext_self_owner::on_get_result_from_transfer_poll(
env::current_account_id(),
NO_DEPOSIT,
gas::owner_callbacks::ON_VOTING_GET_RESULT,
)
)
}
/// Requires 50 TGas
/// Not intended to hand over the access to someone else
/// except the owner.
///
/// Transfers the given amount to the given receiver
/// account ID. This requires transfers to be enabled
/// within the voting contract.
pub fn transfer(
&mut self,
amount: WrappedBalance,
receiver_id: AccountId
) -> Promise {
self.assert_owner();
require!(
env::is_valid_account_id(receiver_id.as_bytes()),
"The receiver account ID is invalid"
);
self.assert_transfers_enabled();
self.assert_no_staking_or_idle();
self.assert_no_termination();
require!(
self.get_liquid_owners_balance().0 >= amount.0,
format!(
concat!(
"The available liquid balance {} is smaller than ",
"the requested tranfer amount {}"),
self.get_liquid_owners_balance().0,
amount.0,
),
);
env::log_str(
format!(
"Transferring {} to @{}",
amount.0,
receiver_id
).as_str()
);
Promise::new(receiver_id).transfer(amount.0)
}
/// Requires 50 TGas
/// Not intended to hand over the access to someone else
/// except the owner.
///
/// Adds full access key with the given public key to the
/// account. You need:
/// - Fully-vested account
/// - Lockup duration expired.
/// - Transfers enabled.
/// - No termination in progress (either none or must be finished)
/// This account will become a regular account, contract will be removed.
pub fn add_full_access_key(
&mut self,
new_public_key: PublicKey
) -> Promise {
self.assert_owner();
self.assert_transfers_enabled();
self.assert_no_staking_or_idle();
self.assert_no_termination();
require!(
self.get_locked_amount().0 == 0,
"Tokens are still locked/unvested"
);
env::log_str("Adding a full access key");
// We pass in PublicKey, not Base58PublicKey, so no need this.
// let new_public_key: PublicKey = new_public_key.into();
Promise::new(
env::current_account_id()
).add_full_access_key(new_public_key)
}
}
You might have noticed one thing, we keep repeating this block of code, which means even if we call "deposit", "stake" we need to run this block of code 4 times (and that's just visible here, what says the Promises it called might also need the information):
self.staking_information
.as_ref()
.unwrap()
.staking_pool_account_id
.clone()
We could actually take it out and call it, that's not a problem. If you're caching the result within the "stake", "unstake", "deposit" block locally, fine. They might take up some Gas fee to store the variable, but that's not a big problem and might even save some computation time (which we didn't do here, and one suggest you to try it, but you need to check whether passing a variable instead of the actual code above to the Promise results the same or not). A problem is when we try to cache the result.
If you cache the result, we never know whether what we query may change after subsequent function calls, hence it's important that you never cache the result globally, only locally within the function. This is for security reasons, and might cause severe problems if you don't follow this advice. You might be depositing to a pool, in the middle the id changes for whatever reason, and continuing with the cache result would either panic the contract in the best case scenario (where your money could be returned with proper callbacks), or transferred to other staking pool or even get lost in the worse case scenario.
We will skip the unstake_all
here, you can refer to it in the References link, and CTRL+F your way to the function name.
Next, we look at check_transfers_vote
. Note previously we mentioned owner is no longer the caller at some point, here it is. Turn to next page for more information.