Skip to content

Commit

Permalink
chore(merge): added timelocked delegation RPC server tests
Browse files Browse the repository at this point in the history
  • Loading branch information
valeriyr authored May 16, 2024
2 parents 978bbb7 + a064bc2 commit 08c6efd
Show file tree
Hide file tree
Showing 11 changed files with 913 additions and 88 deletions.
169 changes: 143 additions & 26 deletions crates/sui-indexer/src/apis/governance_api.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,17 +11,22 @@ use cached::{proc_macro::cached, SizedCache};
use sui_json_rpc::{governance_api::ValidatorExchangeRates, SuiRpcModule};
use sui_json_rpc_api::GovernanceReadApiServer;
use sui_json_rpc_types::{
DelegatedStake, EpochInfo, StakeStatus, SuiCommittee, SuiObjectDataFilter, ValidatorApys,
DelegatedStake, DelegatedTimelockedStake, EpochInfo, StakeStatus, SuiCommittee,
SuiObjectDataFilter, ValidatorApys,
};
use sui_open_rpc::Module;
use sui_types::{
base_types::{MoveObjectType, ObjectID, SuiAddress},
committee::EpochId,
governance::StakedSui,
stardust::timelocked_staked_sui::TimelockedStakedSui,
sui_serde::BigInt,
sui_system_state::{sui_system_state_summary::SuiSystemStateSummary, PoolTokenExchangeRate},
};

/// Maximum amount of staked objects for querying.
const MAX_QUERY_STAKED_OBJECTS: usize = 1000;

#[derive(Clone)]
pub struct GovernanceReadApi {
inner: IndexerReader,
Expand Down Expand Up @@ -104,8 +109,7 @@ impl GovernanceReadApi {
MoveObjectType::staked_sui().into(),
)),
None,
// Allow querying for up to 1000 staked objects
1000,
MAX_QUERY_STAKED_OBJECTS,
)
.await?
{
Expand All @@ -117,6 +121,31 @@ impl GovernanceReadApi {
self.get_delegated_stakes(stakes).await
}

async fn get_timelocked_staked_by_owner(
&self,
owner: SuiAddress,
) -> Result<Vec<DelegatedTimelockedStake>, IndexerError> {
let mut stakes = vec![];
for stored_object in self
.inner
.get_owned_objects_in_blocking_task(
owner,
Some(SuiObjectDataFilter::StructType(
MoveObjectType::timelocked_staked_sui().into(),
)),
None,
MAX_QUERY_STAKED_OBJECTS,
)
.await?
{
let object = sui_types::object::Object::try_from(stored_object)?;
let stake_object = TimelockedStakedSui::try_from(&object)?;
stakes.push(stake_object);
}

self.get_delegated_timelocked_stakes(stakes).await
}

pub async fn get_delegated_stakes(
&self,
stakes: Vec<StakedSui>,
Expand Down Expand Up @@ -149,29 +178,14 @@ impl GovernanceReadApi {

let mut delegations = vec![];
for stake in stakes {
let status = if epoch >= stake.activation_epoch() {
let estimated_reward = if let Some(current_rate) = current_rate {
let stake_rate = rate_table
.rates
.iter()
.find_map(|(epoch, rate)| {
if *epoch == stake.activation_epoch() {
Some(rate.clone())
} else {
None
}
})
.unwrap_or_default();
let estimated_reward = ((stake_rate.rate() / current_rate.rate()) - 1.0)
* stake.principal() as f64;
std::cmp::max(0, estimated_reward.round() as u64)
} else {
0
};
StakeStatus::Active { estimated_reward }
} else {
StakeStatus::Pending
};
let status = stake_status(
epoch,
stake.activation_epoch(),
stake.principal(),
rate_table,
current_rate,
);

delegations.push(sui_json_rpc_types::Stake {
staked_sui_id: stake.id(),
// TODO: this might change when we implement warm up period.
Expand All @@ -189,6 +203,91 @@ impl GovernanceReadApi {
}
Ok(delegated_stakes)
}

pub async fn get_delegated_timelocked_stakes(
&self,
stakes: Vec<TimelockedStakedSui>,
) -> Result<Vec<DelegatedTimelockedStake>, IndexerError> {
let pools = stakes
.into_iter()
.fold(BTreeMap::<_, Vec<_>>::new(), |mut pools, stake| {
pools.entry(stake.pool_id()).or_default().push(stake);
pools
});

let system_state_summary = self.get_latest_sui_system_state().await?;
let epoch = system_state_summary.epoch;

let rates = exchange_rates(self, system_state_summary)
.await?
.into_iter()
.map(|rates| (rates.pool_id, rates))
.collect::<BTreeMap<_, _>>();

let mut delegated_stakes = vec![];
for (pool_id, stakes) in pools {
// Rate table and rate can be null when the pool is not active
let rate_table = rates.get(&pool_id).ok_or_else(|| {
IndexerError::InvalidArgumentError(
"Cannot find rates for staking pool {pool_id}".to_string(),
)
})?;
let current_rate = rate_table.rates.first().map(|(_, rate)| rate);

let mut delegations = vec![];
for stake in stakes {
let status = stake_status(
epoch,
stake.activation_epoch(),
stake.principal(),
rate_table,
current_rate,
);

delegations.push(sui_json_rpc_types::TimelockedStake {
timelocked_staked_sui_id: stake.id(),
// TODO: this might change when we implement warm up period.
stake_request_epoch: stake.activation_epoch().saturating_sub(1),
stake_active_epoch: stake.activation_epoch(),
principal: stake.principal(),
status,
expiration_timestamp_ms: stake.expiration_timestamp_ms(),
})
}
delegated_stakes.push(DelegatedTimelockedStake {
validator_address: rate_table.address,
staking_pool: pool_id,
stakes: delegations,
})
}
Ok(delegated_stakes)
}
}

fn stake_status(
epoch: u64,
activation_epoch: u64,
principal: u64,
rate_table: &ValidatorExchangeRates,
current_rate: Option<&PoolTokenExchangeRate>,
) -> StakeStatus {
if epoch >= activation_epoch {
let estimated_reward = if let Some(current_rate) = current_rate {
let stake_rate = rate_table
.rates
.iter()
.find_map(|(epoch, rate)| (*epoch == activation_epoch).then(|| rate.clone()))
.unwrap_or_default();
let estimated_reward =
((stake_rate.rate() / current_rate.rate()) - 1.0) * principal as f64;
std::cmp::max(0, estimated_reward.round() as u64)
} else {
0
};
StakeStatus::Active { estimated_reward }
} else {
StakeStatus::Pending
}
}

/// Cached exchange rates for validators for the given epoch, the cache size is 1, it will be cleared when the epoch changes.
Expand Down Expand Up @@ -310,6 +409,24 @@ impl GovernanceReadApiServer for GovernanceReadApi {
self.get_staked_by_owner(owner).await.map_err(Into::into)
}

async fn get_timelocked_stakes_by_ids(
&self,
timelocked_staked_sui_ids: Vec<ObjectID>,
) -> RpcResult<Vec<DelegatedTimelockedStake>> {
self.get_timelocked_stakes_by_ids(timelocked_staked_sui_ids)
.await
.map_err(Into::into)
}

async fn get_timelocked_stakes(
&self,
owner: SuiAddress,
) -> RpcResult<Vec<DelegatedTimelockedStake>> {
self.get_timelocked_staked_by_owner(owner)
.await
.map_err(Into::into)
}

async fn get_committee_info(&self, epoch: Option<BigInt<u64>>) -> RpcResult<SuiCommittee> {
let epoch = self.get_epoch_info(epoch.as_deref().copied()).await?;
Ok(epoch.committee().map_err(IndexerError::from)?.into())
Expand Down
16 changes: 15 additions & 1 deletion crates/sui-json-rpc-api/src/governance.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
use jsonrpsee::core::RpcResult;
use jsonrpsee::proc_macros::rpc;

use sui_json_rpc_types::{DelegatedStake, SuiCommittee, ValidatorApys};
use sui_json_rpc_types::{DelegatedStake, DelegatedTimelockedStake, SuiCommittee, ValidatorApys};
use sui_open_rpc_macros::open_rpc;
use sui_types::base_types::{ObjectID, SuiAddress};
use sui_types::sui_serde::BigInt;
Expand All @@ -24,6 +24,20 @@ pub trait GovernanceReadApi {
#[method(name = "getStakes")]
async fn get_stakes(&self, owner: SuiAddress) -> RpcResult<Vec<DelegatedStake>>;

/// Return one or more [DelegatedTimelockedStake]. If a Stake was withdrawn its status will be Unstaked.
#[method(name = "getTimelockedStakesByIds")]
async fn get_timelocked_stakes_by_ids(
&self,
timelocked_staked_sui_ids: Vec<ObjectID>,
) -> RpcResult<Vec<DelegatedTimelockedStake>>;

/// Return all [DelegatedTimelockedStake].
#[method(name = "getTimelockedStakes")]
async fn get_timelocked_stakes(
&self,
owner: SuiAddress,
) -> RpcResult<Vec<DelegatedTimelockedStake>>;

/// Return the committee information for the asked `epoch`.
#[method(name = "getCommitteeInfo")]
async fn get_committee_info(
Expand Down
Loading

0 comments on commit 08c6efd

Please sign in to comment.