Contract as the owner

Previously, we mentioned owner is the caller of the contract. Well, there are some misconceptions that one hide previously. So let's see what it is, and let's clarify what one means "caller of the contract". This is what you previously taught (or thought) it was:

original_idea

But look, we never mentioned anything about the lockup contract here, so where is it? It's actually what I mean by "the owner". So the updated definition is:

altered_idea

The reason it's called owner, it's not because "you" called it, but because the "lockup contract" called the "staking pool contract", hence the lockup contract is the owner here. To make it easy to understand, I refer the owner as you the caller. It's true in a way for staking, as you're the one calling the lockup contract which in turns call the staking pool contract. We are looking at the owner is you + lockup contract as an entity; now we split it up, we see it more clearly refers to the lockup contract.

This is important because the next function, check_transfers_vote, is called by the contract, not by you. The lockup contract wants to check whether transfers are enabled by voting, but it has nothing to do with end users except for the DAO that did the vote (if it is enabled by voting). Otherwise, the transfer date might have been set at the beginning of the vesting period when it'll start transfer.

You might still have query, but one must say that one don't understand how it works fully either. One also don't know what it means by transfer; as in, who is the receiver? One don't know. Tell me in the discussions, open up a new discussion, if you know more about this and explain to me.

One shall put the rest of the code here therefore, without specific explanation on its purposes.

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

There's also a final function that changes the contract account from only accessible via a function key to be accessible via a full access key. This function have to be careful when created; if hacker gets access to this function, the whole staking pool is confiscated.

Next, we shall made some of the callbacks.