In the internal_restake
function, why do we have to access the on_stake_action
function as if it's an external cross contract call when its a function thats a part of the calling contract? Couldn't we just do .then(self.on_stake_action())
instead? I'm assuming it has something to do with the face that it's a callback from stake()
promise call.
In what situation would you make an interface for the contract itself like ext_self
?
What does the #[ext_contract()] macro do in a nutshell?
-------- Staking Pool Contract Code Below --------------------
lib.rs lines 155~162
/// Interface for the contract itself.
#[ext_contract(ext_self)]
pub trait SelfContract {
/// A callback to check the result of the staking action.
fn on_stake_action(&mut self);
}
pub fn on_stake_action(&mut self) {
assert_eq!(
env::current_account_id(),
env::predecessor_account_id(),
"Can be called only as a callback"
);
assert_eq!(
env::promise_results_count(),
1,
"Contract expected a result on the callback"
);
let stake_action_succeeded = match env::promise_result(0) {
PromiseResult::Successful(_) => true,
_ => false,
};
// If the stake action failed and the current locked amount is positive, then the contract
// has to unstake.
if !stake_action_succeeded && env::account_locked_balance() > 0 {
Promise::new(env::current_account_id()).stake(0, self.stake_public_key.clone());
}
}
/// Restakes the current `total_staked_balance` again.
pub(crate) fn internal_restake(&mut self) {
if self.paused {
return;
}
// Stakes with the staking public key. If the public key is invalid the entire function
// call will be rolled back.
Promise::new(env::current_account_id())
.stake(self.total_staked_balance, self.stake_public_key.clone())
.then(ext_self::on_stake_action(
&env::current_account_id(),
NO_DEPOSIT,
ON_STAKE_ACTION_GAS,
));
}
The reason for the "external" interface for the contract is that the call to the method is external since it is "called" as part of a promise action.
/// Interface for the contract itself.
#[ext_contract(ext_self)]
pub trait SelfContract {
/// A callback to check the result of the staking action.
fn on_stake_action(&mut self);
}
Firstly in rust procedural macros a stream of tokens (pub
, trait
, SelfContract
, ...) in the input and output. In this case the output is not a trait but rather a module](https://doc.rust-lang.org/reference/items/modules.html) named ext_self
. Then a function on_stake_action
is added to the module and modified with the self argument removed and three new arguments added and returns a Promise
.
Promise::new(env::current_account_id())
.stake(self.total_staked_balance, self.stake_public_key.clone())
.then(ext_self::on_stake_action(
&env::current_account_id(),
NO_DEPOSIT,
ON_STAKE_ACTION_GAS,
));
Note ext_self
is the module ::
is the path separator to access on_stake_action
and &env::current_account_id()
is the receiver, NO_DEPOSIT
is the attached deposit, and ON_STAKE_ACTION_GAS
is the gas for the promise call. Furthermore the code for the implementation of the function is generated; it encodes the arguments to the function (in this case there aren't any) and creates a promise that calls the method on_stake_action
.
The reason the initial declaration for this is a trait is that it doesn't require an implementation and IDE's with good rust support will already expand this macro allowing you to use ext_self
as a module even though you can't see that that is what it is.
You raise a good point though that ext_contract
macro treats calls to the same contract the same as others. So perhaps a new useful feature would be to create a new macro that will already use env::current_account_id()
.