306 lines
12 KiB
Rust
306 lines
12 KiB
Rust
// file: kb-onchain-transport/src/standard_http_economics.rs
|
|
// version: 2
|
|
|
|
//! Configurable standard inflation, supply and stake-economics HTTP requests.
|
|
|
|
/// Inflation governor parameters.
|
|
#[derive(Clone, Copy, Debug, PartialEq, serde::Deserialize, serde::Serialize)]
|
|
#[serde(rename_all = "camelCase")]
|
|
pub struct RpcInflationGovernor {
|
|
/// Initial inflation rate.
|
|
pub initial: f64,
|
|
/// Terminal inflation rate.
|
|
pub terminal: f64,
|
|
/// Annual taper rate.
|
|
pub taper: f64,
|
|
/// Foundation allocation rate.
|
|
pub foundation: f64,
|
|
/// Foundation allocation term in years.
|
|
pub foundation_term: f64,
|
|
}
|
|
|
|
/// Inflation rates for the current epoch.
|
|
#[derive(Clone, Copy, Debug, PartialEq, serde::Deserialize, serde::Serialize)]
|
|
#[serde(rename_all = "camelCase")]
|
|
pub struct RpcInflationRate {
|
|
/// Total inflation rate.
|
|
pub total: f64,
|
|
/// Validator inflation rate.
|
|
pub validator: f64,
|
|
/// Foundation inflation rate.
|
|
pub foundation: f64,
|
|
/// Epoch represented by the rates.
|
|
pub epoch: u64,
|
|
}
|
|
|
|
/// Inflation reward credited to one requested address.
|
|
#[derive(Clone, Copy, Debug, Eq, PartialEq, serde::Deserialize, serde::Serialize)]
|
|
#[serde(rename_all = "camelCase")]
|
|
pub struct RpcInflationReward {
|
|
/// Reward epoch.
|
|
pub epoch: u64,
|
|
/// First effective slot of the rewarded epoch.
|
|
pub effective_slot: u64,
|
|
/// Reward amount in lamports.
|
|
pub amount: u64,
|
|
/// Account balance after the reward.
|
|
pub post_balance: u64,
|
|
/// Legacy vote commission percentage when applicable.
|
|
pub commission: std::option::Option<u8>,
|
|
/// Vote commission in basis points when exposed by the node.
|
|
#[serde(default, skip_serializing_if = "std::option::Option::is_none")]
|
|
pub commission_bps: std::option::Option<u16>,
|
|
}
|
|
|
|
/// Current lamport supply breakdown.
|
|
#[derive(Clone, Debug, Eq, PartialEq, serde::Deserialize, serde::Serialize)]
|
|
#[serde(rename_all = "camelCase")]
|
|
pub struct RpcSupply {
|
|
/// Total lamport supply.
|
|
pub total: u64,
|
|
/// Circulating lamport supply.
|
|
pub circulating: u64,
|
|
/// Non-circulating lamport supply.
|
|
pub non_circulating: u64,
|
|
/// Non-circulating account list when requested.
|
|
#[serde(default)]
|
|
pub non_circulating_accounts: std::vec::Vec<std::string::String>,
|
|
}
|
|
|
|
/// Optional commitment accepted by `getInflationGovernor`.
|
|
#[derive(Clone, Copy, Debug, Default, Eq, PartialEq, serde::Deserialize, serde::Serialize)]
|
|
#[serde(rename_all = "camelCase")]
|
|
pub struct RpcInflationGovernorConfig {
|
|
/// Optional commitment level.
|
|
#[serde(skip_serializing_if = "std::option::Option::is_none")]
|
|
pub commitment: std::option::Option<crate::RpcCommitmentLevel>,
|
|
}
|
|
|
|
/// Epoch and contextual options accepted by `getInflationReward`.
|
|
#[derive(Clone, Copy, Debug, Default, Eq, PartialEq, serde::Deserialize, serde::Serialize)]
|
|
#[serde(rename_all = "camelCase")]
|
|
pub struct RpcEpochConfig {
|
|
/// Optional reward epoch.
|
|
#[serde(skip_serializing_if = "std::option::Option::is_none")]
|
|
pub epoch: std::option::Option<u64>,
|
|
/// Optional commitment level.
|
|
#[serde(skip_serializing_if = "std::option::Option::is_none")]
|
|
pub commitment: std::option::Option<crate::RpcCommitmentLevel>,
|
|
/// Optional minimum context slot.
|
|
#[serde(skip_serializing_if = "std::option::Option::is_none")]
|
|
pub min_context_slot: std::option::Option<u64>,
|
|
}
|
|
|
|
/// Options accepted by `getSupply`.
|
|
#[derive(Clone, Copy, Debug, Default, Eq, PartialEq, serde::Deserialize, serde::Serialize)]
|
|
#[serde(rename_all = "camelCase")]
|
|
pub struct RpcSupplyConfig {
|
|
/// Optional commitment level.
|
|
#[serde(skip_serializing_if = "std::option::Option::is_none")]
|
|
pub commitment: std::option::Option<crate::RpcCommitmentLevel>,
|
|
/// Optional omission of the potentially large non-circulating account list.
|
|
#[serde(skip_serializing_if = "std::option::Option::is_none")]
|
|
pub exclude_non_circulating_accounts_list: std::option::Option<bool>,
|
|
}
|
|
|
|
/// Typed `getInflationGovernor` request.
|
|
#[derive(Clone, Debug, Default, Eq, PartialEq)]
|
|
pub struct GetInflationGovernorRequest {
|
|
/// Optional commitment configuration.
|
|
pub config: std::option::Option<crate::RpcInflationGovernorConfig>,
|
|
}
|
|
|
|
impl crate::StandardHttpRequest for crate::GetInflationGovernorRequest {
|
|
type Response = crate::RpcInflationGovernor;
|
|
|
|
const METHOD: &'static str = "getInflationGovernor";
|
|
|
|
fn params(&self) -> kb_core::Result<std::vec::Vec<serde_json::Value>> {
|
|
if let std::option::Option::Some(config) = &self.config {
|
|
let value = match crate::serialize_parameter(Self::METHOD, config) {
|
|
std::result::Result::Ok(value) => value,
|
|
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
|
};
|
|
return std::result::Result::Ok(std::vec![value]);
|
|
}
|
|
return std::result::Result::Ok(std::vec::Vec::new());
|
|
}
|
|
}
|
|
|
|
/// Typed `getInflationRate` request.
|
|
#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
|
|
pub struct GetInflationRateRequest;
|
|
|
|
impl crate::StandardHttpRequest for crate::GetInflationRateRequest {
|
|
type Response = crate::RpcInflationRate;
|
|
|
|
const METHOD: &'static str = "getInflationRate";
|
|
|
|
fn params(&self) -> kb_core::Result<std::vec::Vec<serde_json::Value>> {
|
|
return std::result::Result::Ok(std::vec::Vec::new());
|
|
}
|
|
}
|
|
|
|
/// Typed `getInflationReward` request.
|
|
#[derive(Clone, Debug, Eq, PartialEq)]
|
|
pub struct GetInflationRewardRequest {
|
|
/// Account public keys whose rewards are requested, in response order.
|
|
pub addresses: std::vec::Vec<std::string::String>,
|
|
/// Optional epoch, commitment and minimum-context options.
|
|
pub config: std::option::Option<crate::RpcEpochConfig>,
|
|
}
|
|
|
|
impl crate::StandardHttpRequest for crate::GetInflationRewardRequest {
|
|
type Response = std::vec::Vec<std::option::Option<crate::RpcInflationReward>>;
|
|
|
|
const METHOD: &'static str = "getInflationReward";
|
|
|
|
fn params(&self) -> kb_core::Result<std::vec::Vec<serde_json::Value>> {
|
|
let validation_result =
|
|
crate::validate_pubkey_list(&self.addresses, "getInflationReward address", usize::MAX);
|
|
if let std::result::Result::Err(error) = validation_result {
|
|
return std::result::Result::Err(error);
|
|
}
|
|
let addresses = match crate::serialize_parameter(Self::METHOD, &self.addresses) {
|
|
std::result::Result::Ok(value) => value,
|
|
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
|
};
|
|
let mut params = std::vec![addresses];
|
|
if let std::option::Option::Some(config) = &self.config {
|
|
let value = match crate::serialize_parameter(Self::METHOD, config) {
|
|
std::result::Result::Ok(value) => value,
|
|
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
|
};
|
|
params.push(value);
|
|
}
|
|
return std::result::Result::Ok(params);
|
|
}
|
|
}
|
|
|
|
/// Typed `getStakeMinimumDelegation` request.
|
|
#[derive(Clone, Debug, Default, Eq, PartialEq)]
|
|
pub struct GetStakeMinimumDelegationRequest {
|
|
/// Optional commitment and minimum-context options.
|
|
pub config: std::option::Option<crate::RpcContextConfig>,
|
|
}
|
|
|
|
impl crate::StandardHttpRequest for crate::GetStakeMinimumDelegationRequest {
|
|
type Response = crate::RpcResponse<u64>;
|
|
|
|
const METHOD: &'static str = "getStakeMinimumDelegation";
|
|
|
|
fn params(&self) -> kb_core::Result<std::vec::Vec<serde_json::Value>> {
|
|
if let std::option::Option::Some(config) = &self.config {
|
|
let value = match crate::serialize_parameter(Self::METHOD, config) {
|
|
std::result::Result::Ok(value) => value,
|
|
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
|
};
|
|
return std::result::Result::Ok(std::vec![value]);
|
|
}
|
|
return std::result::Result::Ok(std::vec::Vec::new());
|
|
}
|
|
}
|
|
|
|
/// Typed `getSupply` request.
|
|
#[derive(Clone, Debug, Default, Eq, PartialEq)]
|
|
pub struct GetSupplyRequest {
|
|
/// Optional commitment and non-circulating-list options.
|
|
pub config: std::option::Option<crate::RpcSupplyConfig>,
|
|
}
|
|
|
|
impl crate::StandardHttpRequest for crate::GetSupplyRequest {
|
|
type Response = crate::RpcResponse<crate::RpcSupply>;
|
|
|
|
const METHOD: &'static str = "getSupply";
|
|
|
|
fn params(&self) -> kb_core::Result<std::vec::Vec<serde_json::Value>> {
|
|
if let std::option::Option::Some(config) = &self.config {
|
|
let value = match crate::serialize_parameter(Self::METHOD, config) {
|
|
std::result::Result::Ok(value) => value,
|
|
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
|
};
|
|
return std::result::Result::Ok(std::vec![value]);
|
|
}
|
|
return std::result::Result::Ok(std::vec::Vec::new());
|
|
}
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod tests {
|
|
fn pubkey(seed: u8) -> std::string::String {
|
|
return bs58::encode([seed; 32]).into_string();
|
|
}
|
|
|
|
#[test]
|
|
fn supply_distinguishes_omitted_and_explicit_false_option() {
|
|
let omitted = crate::GetSupplyRequest {
|
|
config: std::option::Option::Some(crate::RpcSupplyConfig {
|
|
commitment: std::option::Option::Some(crate::RpcCommitmentLevel::Finalized),
|
|
exclude_non_circulating_accounts_list: std::option::Option::None,
|
|
}),
|
|
};
|
|
let explicit = crate::GetSupplyRequest {
|
|
config: std::option::Option::Some(crate::RpcSupplyConfig {
|
|
commitment: std::option::Option::Some(crate::RpcCommitmentLevel::Finalized),
|
|
exclude_non_circulating_accounts_list: std::option::Option::Some(false),
|
|
}),
|
|
};
|
|
let omitted_params = match crate::StandardHttpRequest::params(&omitted) {
|
|
std::result::Result::Ok(params) => params,
|
|
std::result::Result::Err(error) => panic!("params failed: {error}"),
|
|
};
|
|
let explicit_params = match crate::StandardHttpRequest::params(&explicit) {
|
|
std::result::Result::Ok(params) => params,
|
|
std::result::Result::Err(error) => panic!("params failed: {error}"),
|
|
};
|
|
assert!(omitted_params[0].get("excludeNonCirculatingAccountsList").is_none());
|
|
assert_eq!(
|
|
explicit_params[0]["excludeNonCirculatingAccountsList"],
|
|
serde_json::Value::Bool(false)
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn inflation_reward_preserves_epoch_commitment_and_minimum_context() {
|
|
let request = crate::GetInflationRewardRequest {
|
|
addresses: std::vec![pubkey(1), pubkey(2)],
|
|
config: std::option::Option::Some(crate::RpcEpochConfig {
|
|
epoch: std::option::Option::Some(44),
|
|
commitment: std::option::Option::Some(crate::RpcCommitmentLevel::Confirmed),
|
|
min_context_slot: std::option::Option::Some(99),
|
|
}),
|
|
};
|
|
let params = match crate::StandardHttpRequest::params(&request) {
|
|
std::result::Result::Ok(params) => params,
|
|
std::result::Result::Err(error) => panic!("params failed: {error}"),
|
|
};
|
|
assert_eq!(params[1]["epoch"], serde_json::Value::from(44_u64));
|
|
assert_eq!(params[1]["commitment"], serde_json::Value::String("confirmed".to_string()));
|
|
assert_eq!(params[1]["minContextSlot"], serde_json::Value::from(99_u64));
|
|
|
|
let reward = match serde_json::from_value::<crate::RpcInflationReward>(serde_json::json!({
|
|
"epoch": 44,
|
|
"effectiveSlot": 100,
|
|
"amount": 200,
|
|
"postBalance": 300,
|
|
"commission": 5,
|
|
"commissionBps": 575
|
|
})) {
|
|
std::result::Result::Ok(value) => value,
|
|
std::result::Result::Err(error) => panic!("inflation reward parsing failed: {error}"),
|
|
};
|
|
assert_eq!(reward.commission, std::option::Option::Some(5));
|
|
assert_eq!(reward.commission_bps, std::option::Option::Some(575));
|
|
|
|
let empty = crate::GetInflationRewardRequest {
|
|
addresses: std::vec::Vec::new(),
|
|
config: std::option::Option::None,
|
|
};
|
|
let empty_params = match crate::StandardHttpRequest::params(&empty) {
|
|
std::result::Result::Ok(params) => params,
|
|
std::result::Result::Err(error) => panic!("empty params failed: {error}"),
|
|
};
|
|
assert_eq!(empty_params, std::vec![serde_json::json!([])]);
|
|
}
|
|
}
|