Rpc: Add getCirculatingSupply endpoint, redux (#9953)
* Add Bank.clock() helper * Add non-circulating calculations * Plumb getSupply rpc endpoint * Add docs for getSupply, and remove getTotalSupply from docs * Add pubkeys! procedural macro * Use procedural macro in non_circulating_supply
This commit is contained in:
parent
cb50877bbf
commit
3ee702a922
|
@ -199,3 +199,12 @@ pub struct RpcAccountBalance {
|
||||||
pub address: String,
|
pub address: String,
|
||||||
pub lamports: u64,
|
pub lamports: u64,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Serialize, Deserialize, Clone, Debug)]
|
||||||
|
#[serde(rename_all = "camelCase")]
|
||||||
|
pub struct RpcSupply {
|
||||||
|
pub total: u64,
|
||||||
|
pub circulating: u64,
|
||||||
|
pub non_circulating: u64,
|
||||||
|
pub non_circulating_accounts: Vec<String>,
|
||||||
|
}
|
||||||
|
|
|
@ -30,6 +30,7 @@ pub mod gen_keys;
|
||||||
pub mod gossip_service;
|
pub mod gossip_service;
|
||||||
pub mod ledger_cleanup_service;
|
pub mod ledger_cleanup_service;
|
||||||
pub mod local_vote_signer_service;
|
pub mod local_vote_signer_service;
|
||||||
|
pub mod non_circulating_supply;
|
||||||
pub mod poh_recorder;
|
pub mod poh_recorder;
|
||||||
pub mod poh_service;
|
pub mod poh_service;
|
||||||
pub mod progress_map;
|
pub mod progress_map;
|
||||||
|
|
|
@ -0,0 +1,193 @@
|
||||||
|
use solana_runtime::bank::Bank;
|
||||||
|
use solana_sdk::pubkey::Pubkey;
|
||||||
|
use solana_stake_program::stake_state::StakeState;
|
||||||
|
use std::{collections::HashSet, sync::Arc};
|
||||||
|
|
||||||
|
pub struct NonCirculatingSupply {
|
||||||
|
pub lamports: u64,
|
||||||
|
pub accounts: Vec<Pubkey>,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn calculate_non_circulating_supply(bank: Arc<Bank>) -> NonCirculatingSupply {
|
||||||
|
debug!("Updating Bank supply, epoch: {}", bank.epoch());
|
||||||
|
let mut non_circulating_accounts_set: HashSet<Pubkey> = HashSet::new();
|
||||||
|
|
||||||
|
for key in non_circulating_accounts() {
|
||||||
|
non_circulating_accounts_set.insert(key);
|
||||||
|
}
|
||||||
|
|
||||||
|
let clock = bank.clock();
|
||||||
|
let stake_accounts = bank.get_program_accounts(Some(&solana_stake_program::id()));
|
||||||
|
for (pubkey, account) in stake_accounts.iter() {
|
||||||
|
let stake_account = StakeState::from(&account).unwrap_or_default();
|
||||||
|
match stake_account {
|
||||||
|
StakeState::Initialized(meta) => {
|
||||||
|
if meta.lockup.is_in_force(&clock, &HashSet::default())
|
||||||
|
|| meta.authorized.withdrawer == withdraw_authority()
|
||||||
|
{
|
||||||
|
non_circulating_accounts_set.insert(*pubkey);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
StakeState::Stake(meta, _stake) => {
|
||||||
|
if meta.lockup.is_in_force(&clock, &HashSet::default())
|
||||||
|
|| meta.authorized.withdrawer == withdraw_authority()
|
||||||
|
{
|
||||||
|
non_circulating_accounts_set.insert(*pubkey);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let lamports = non_circulating_accounts_set
|
||||||
|
.iter()
|
||||||
|
.fold(0, |acc, pubkey| acc + bank.get_balance(&pubkey));
|
||||||
|
|
||||||
|
NonCirculatingSupply {
|
||||||
|
lamports,
|
||||||
|
accounts: non_circulating_accounts_set.into_iter().collect(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Mainnet-beta accounts that should be considered non-circulating
|
||||||
|
solana_sdk::pubkeys!(
|
||||||
|
non_circulating_accounts,
|
||||||
|
[
|
||||||
|
"9huDUZfxoJ7wGMTffUE7vh1xePqef7gyrLJu9NApncqA",
|
||||||
|
"GK2zqSsXLA2rwVZk347RYhh6jJpRsCA69FjLW93ZGi3B",
|
||||||
|
"HCV5dGFJXRrJ3jhDYA4DCeb9TEDTwGGYXtT3wHksu2Zr",
|
||||||
|
"25odAafVXnd63L6Hq5Cx6xGmhKqkhE2y6UrLVuqUfWZj",
|
||||||
|
"14FUT96s9swbmH7ZjpDvfEDywnAYy9zaNhv4xvezySGu",
|
||||||
|
"HbZ5FfmKWNHC7uwk6TF1hVi6TCs7dtYfdjEcuPGgzFAg",
|
||||||
|
"C7C8odR8oashR5Feyrq2tJKaXL18id1dSj2zbkDGL2C2",
|
||||||
|
"APnSR52EC1eH676m7qTBHUJ1nrGpHYpV7XKPxgRDD8gX",
|
||||||
|
"9ibqedFVnu5k4wo1mJRbH6KJ5HLBCyjpA9omPYkDeeT5",
|
||||||
|
"FopBKzQkG9pkyQqjdMFBLMQ995pSkjy83ziR4aism4c6",
|
||||||
|
"AiUHvJhTbMCcgFE2K26Ea9qCe74y3sFwqUt38iD5sfoR",
|
||||||
|
"3DndE3W53QdHSfBJiSJgzDKGvKJBoQLVmRHvy5LtqYfG",
|
||||||
|
"Eyr9P5XsjK2NUKNCnfu39eqpGoiLFgVAv1LSQgMZCwiQ",
|
||||||
|
"DE1bawNcRJB9rVm3buyMVfr8mBEoyyu73NBovf2oXJsJ",
|
||||||
|
"CakcnaRDHka2gXyfbEd2d3xsvkJkqsLw2akB3zsN1D2S",
|
||||||
|
"7Np41oeYqPefeNQEHSv1UDhYrehxin3NStELsSKCT4K2",
|
||||||
|
"GdnSyH3YtwcxFvQrVVJMm1JhTS4QVX7MFsX56uJLUfiZ",
|
||||||
|
"Mc5XB47H3DKJHym5RLa9mPzWv5snERsF3KNv5AauXK8",
|
||||||
|
"7cvkjYAkUYs4W8XcXsca7cBrEGFeSUjeZmKoNBvEwyri",
|
||||||
|
"AG3m2bAibcY8raMt4oXEGqRHwX4FWKPPJVjZxn1LySDX",
|
||||||
|
"5XdtyEDREHJXXW1CTtCsVjJRjBapAwK78ZquzvnNVRrV",
|
||||||
|
"6yKHERk8rsbmJxvMpPuwPs1ct3hRiP7xaJF2tvnGU6nK",
|
||||||
|
"CHmdL15akDcJgBkY6BP3hzs98Dqr6wbdDC5p8odvtSbq",
|
||||||
|
"FR84wZQy3Y3j2gWz6pgETUiUoJtreMEuWfbg6573UCj9",
|
||||||
|
"5q54XjQ7vDx4y6KphPeE97LUNiYGtP55spjvXAWPGBuf",
|
||||||
|
]
|
||||||
|
);
|
||||||
|
|
||||||
|
// Withdraw authority for autostaked accounts on mainnet-beta
|
||||||
|
solana_sdk::pubkeys!(
|
||||||
|
withdraw_authority,
|
||||||
|
"8CUUMKYNGxdgYio5CLHRHyzMEhhVRMcqefgE6dLqnVRK"
|
||||||
|
);
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
use solana_sdk::{
|
||||||
|
account::Account, epoch_schedule::EpochSchedule, genesis_config::GenesisConfig,
|
||||||
|
};
|
||||||
|
use solana_stake_program::stake_state::{Authorized, Lockup, Meta, StakeState};
|
||||||
|
use std::{collections::BTreeMap, sync::Arc};
|
||||||
|
|
||||||
|
fn new_from_parent(parent: &Arc<Bank>) -> Bank {
|
||||||
|
Bank::new_from_parent(parent, &Pubkey::default(), parent.slot() + 1)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_calculate_non_circulating_supply() {
|
||||||
|
let mut accounts: BTreeMap<Pubkey, Account> = BTreeMap::new();
|
||||||
|
let balance = 10;
|
||||||
|
let num_genesis_accounts = 10;
|
||||||
|
for _ in 0..num_genesis_accounts {
|
||||||
|
accounts.insert(
|
||||||
|
Pubkey::new_rand(),
|
||||||
|
Account::new(balance, 0, &Pubkey::default()),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
let non_circulating_accounts = non_circulating_accounts();
|
||||||
|
let num_non_circulating_accounts = non_circulating_accounts.len() as u64;
|
||||||
|
for key in non_circulating_accounts.clone() {
|
||||||
|
accounts.insert(key, Account::new(balance, 0, &Pubkey::default()));
|
||||||
|
}
|
||||||
|
|
||||||
|
let num_stake_accounts = 3;
|
||||||
|
for _ in 0..num_stake_accounts {
|
||||||
|
let pubkey = Pubkey::new_rand();
|
||||||
|
let meta = Meta {
|
||||||
|
authorized: Authorized::auto(&pubkey),
|
||||||
|
lockup: Lockup {
|
||||||
|
epoch: 1,
|
||||||
|
..Lockup::default()
|
||||||
|
},
|
||||||
|
..Meta::default()
|
||||||
|
};
|
||||||
|
let stake_account = Account::new_data_with_space(
|
||||||
|
balance,
|
||||||
|
&StakeState::Initialized(meta),
|
||||||
|
std::mem::size_of::<StakeState>(),
|
||||||
|
&solana_stake_program::id(),
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
accounts.insert(pubkey, stake_account);
|
||||||
|
}
|
||||||
|
|
||||||
|
let slots_per_epoch = 32;
|
||||||
|
let genesis_config = GenesisConfig {
|
||||||
|
accounts,
|
||||||
|
epoch_schedule: EpochSchedule::new(slots_per_epoch),
|
||||||
|
..GenesisConfig::default()
|
||||||
|
};
|
||||||
|
let mut bank = Arc::new(Bank::new(&genesis_config));
|
||||||
|
assert_eq!(
|
||||||
|
bank.capitalization(),
|
||||||
|
(num_genesis_accounts + num_non_circulating_accounts + num_stake_accounts) * balance
|
||||||
|
);
|
||||||
|
|
||||||
|
let non_circulating_supply = calculate_non_circulating_supply(bank.clone());
|
||||||
|
assert_eq!(
|
||||||
|
non_circulating_supply.lamports,
|
||||||
|
(num_non_circulating_accounts + num_stake_accounts) * balance
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
non_circulating_supply.accounts.len(),
|
||||||
|
num_non_circulating_accounts as usize + num_stake_accounts as usize
|
||||||
|
);
|
||||||
|
|
||||||
|
bank = Arc::new(new_from_parent(&bank));
|
||||||
|
let new_balance = 11;
|
||||||
|
for key in non_circulating_accounts {
|
||||||
|
bank.store_account(&key, &Account::new(new_balance, 0, &Pubkey::default()));
|
||||||
|
}
|
||||||
|
let non_circulating_supply = calculate_non_circulating_supply(bank.clone());
|
||||||
|
assert_eq!(
|
||||||
|
non_circulating_supply.lamports,
|
||||||
|
(num_non_circulating_accounts * new_balance) + (num_stake_accounts * balance)
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
non_circulating_supply.accounts.len(),
|
||||||
|
num_non_circulating_accounts as usize + num_stake_accounts as usize
|
||||||
|
);
|
||||||
|
|
||||||
|
// Advance bank an epoch, which should unlock stakes
|
||||||
|
for _ in 0..slots_per_epoch {
|
||||||
|
bank = Arc::new(new_from_parent(&bank));
|
||||||
|
}
|
||||||
|
assert_eq!(bank.epoch(), 1);
|
||||||
|
let non_circulating_supply = calculate_non_circulating_supply(bank.clone());
|
||||||
|
assert_eq!(
|
||||||
|
non_circulating_supply.lamports,
|
||||||
|
num_non_circulating_accounts * new_balance
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
non_circulating_supply.accounts.len(),
|
||||||
|
num_non_circulating_accounts as usize
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
|
@ -4,6 +4,7 @@ use crate::{
|
||||||
cluster_info::ClusterInfo,
|
cluster_info::ClusterInfo,
|
||||||
commitment::{BlockCommitmentArray, BlockCommitmentCache},
|
commitment::{BlockCommitmentArray, BlockCommitmentCache},
|
||||||
contact_info::ContactInfo,
|
contact_info::ContactInfo,
|
||||||
|
non_circulating_supply::calculate_non_circulating_supply,
|
||||||
storage_stage::StorageState,
|
storage_stage::StorageState,
|
||||||
validator::ValidatorExit,
|
validator::ValidatorExit,
|
||||||
};
|
};
|
||||||
|
@ -302,6 +303,25 @@ impl JsonRpcRequestProcessor {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn get_supply(&self, commitment: Option<CommitmentConfig>) -> RpcResponse<RpcSupply> {
|
||||||
|
let bank = self.bank(commitment)?;
|
||||||
|
let non_circulating_supply = calculate_non_circulating_supply(bank.clone());
|
||||||
|
let total_supply = bank.capitalization();
|
||||||
|
new_response(
|
||||||
|
&bank,
|
||||||
|
RpcSupply {
|
||||||
|
total: total_supply,
|
||||||
|
circulating: total_supply - non_circulating_supply.lamports,
|
||||||
|
non_circulating: non_circulating_supply.lamports,
|
||||||
|
non_circulating_accounts: non_circulating_supply
|
||||||
|
.accounts
|
||||||
|
.iter()
|
||||||
|
.map(|pubkey| pubkey.to_string())
|
||||||
|
.collect(),
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
fn get_vote_accounts(
|
fn get_vote_accounts(
|
||||||
&self,
|
&self,
|
||||||
commitment: Option<CommitmentConfig>,
|
commitment: Option<CommitmentConfig>,
|
||||||
|
@ -786,6 +806,7 @@ pub trait RpcSol {
|
||||||
commitment: Option<CommitmentConfig>,
|
commitment: Option<CommitmentConfig>,
|
||||||
) -> Result<u64>;
|
) -> Result<u64>;
|
||||||
|
|
||||||
|
// DEPRECATED
|
||||||
#[rpc(meta, name = "getTotalSupply")]
|
#[rpc(meta, name = "getTotalSupply")]
|
||||||
fn get_total_supply(
|
fn get_total_supply(
|
||||||
&self,
|
&self,
|
||||||
|
@ -800,6 +821,13 @@ pub trait RpcSol {
|
||||||
commitment: Option<CommitmentConfig>,
|
commitment: Option<CommitmentConfig>,
|
||||||
) -> RpcResponse<Vec<RpcAccountBalance>>;
|
) -> RpcResponse<Vec<RpcAccountBalance>>;
|
||||||
|
|
||||||
|
#[rpc(meta, name = "getSupply")]
|
||||||
|
fn get_supply(
|
||||||
|
&self,
|
||||||
|
meta: Self::Metadata,
|
||||||
|
commitment: Option<CommitmentConfig>,
|
||||||
|
) -> RpcResponse<RpcSupply>;
|
||||||
|
|
||||||
#[rpc(meta, name = "requestAirdrop")]
|
#[rpc(meta, name = "requestAirdrop")]
|
||||||
fn request_airdrop(
|
fn request_airdrop(
|
||||||
&self,
|
&self,
|
||||||
|
@ -1213,6 +1241,18 @@ impl RpcSol for RpcSolImpl {
|
||||||
.get_largest_accounts(commitment)
|
.get_largest_accounts(commitment)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn get_supply(
|
||||||
|
&self,
|
||||||
|
meta: Self::Metadata,
|
||||||
|
commitment: Option<CommitmentConfig>,
|
||||||
|
) -> RpcResponse<RpcSupply> {
|
||||||
|
debug!("get_supply rpc request received");
|
||||||
|
meta.request_processor
|
||||||
|
.read()
|
||||||
|
.unwrap()
|
||||||
|
.get_supply(commitment)
|
||||||
|
}
|
||||||
|
|
||||||
fn request_airdrop(
|
fn request_airdrop(
|
||||||
&self,
|
&self,
|
||||||
meta: Self::Metadata,
|
meta: Self::Metadata,
|
||||||
|
|
|
@ -43,8 +43,8 @@ To interact with a Solana node inside a JavaScript application, use the [solana-
|
||||||
* [getStoragePubkeysForSlot](jsonrpc-api.md#getstoragepubkeysforslot)
|
* [getStoragePubkeysForSlot](jsonrpc-api.md#getstoragepubkeysforslot)
|
||||||
* [getStorageTurn](jsonrpc-api.md#getstorageturn)
|
* [getStorageTurn](jsonrpc-api.md#getstorageturn)
|
||||||
* [getStorageTurnRate](jsonrpc-api.md#getstorageturnrate)
|
* [getStorageTurnRate](jsonrpc-api.md#getstorageturnrate)
|
||||||
|
* [getSupply](jsonrpc-api.md#getsupply)
|
||||||
* [getTransactionCount](jsonrpc-api.md#gettransactioncount)
|
* [getTransactionCount](jsonrpc-api.md#gettransactioncount)
|
||||||
* [getTotalSupply](jsonrpc-api.md#gettotalsupply)
|
|
||||||
* [getVersion](jsonrpc-api.md#getversion)
|
* [getVersion](jsonrpc-api.md#getversion)
|
||||||
* [getVoteAccounts](jsonrpc-api.md#getvoteaccounts)
|
* [getVoteAccounts](jsonrpc-api.md#getvoteaccounts)
|
||||||
* [minimumLedgerSlot](jsonrpc-api.md#minimumledgerslot)
|
* [minimumLedgerSlot](jsonrpc-api.md#minimumledgerslot)
|
||||||
|
@ -944,6 +944,32 @@ curl -X POST -H "Content-Type: application/json" -d '{"jsonrpc":"2.0","id":1, "m
|
||||||
{"jsonrpc":"2.0","result":1024,"id":1}
|
{"jsonrpc":"2.0","result":1024,"id":1}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
### getSupply
|
||||||
|
|
||||||
|
Returns information about the current supply.
|
||||||
|
|
||||||
|
#### Parameters:
|
||||||
|
|
||||||
|
* `<object>` - (optional) [Commitment](jsonrpc-api.md#configuring-state-commitment)
|
||||||
|
|
||||||
|
#### Results:
|
||||||
|
|
||||||
|
The result will be an RpcResponse JSON object with `value` equal to a JSON object containing:
|
||||||
|
|
||||||
|
* `total: <u64>` - Total supply in lamports
|
||||||
|
* `circulating: <u64>` - Circulating supply in lamports
|
||||||
|
* `nonCirculating: <u64>` - Non-circulating supply in lamports
|
||||||
|
* `nonCirculatingAccounts: <array>` - an array of account addresses of non-circulating accounts, as strings
|
||||||
|
|
||||||
|
#### Example:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
// Request
|
||||||
|
curl -X POST -H "Content-Type: application/json" -d '{"jsonrpc":"2.0", "id":1, "method":"getCirculatingSupply"}' http://localhost:8899
|
||||||
|
// Result
|
||||||
|
{"jsonrpc":"2.0","result":{"context":{"slot":1114},"value":{"circulating":16000,"nonCirculating":1000000,"nonCirculatingAccounts":["FEy8pTbP5fEoqMV1GdTz83byuA8EKByqYat1PKDgVAq5","9huDUZfxoJ7wGMTffUE7vh1xePqef7gyrLJu9NApncqA","3mi1GmwEE3zo2jmfDuzvjSX9ovRXsDUKHvsntpkhuLJ9","BYxEJTDerkaRWBem3XgnVcdhppktBXa2HbkHPKj2Ui4Z],total:1016000}},"id":1}
|
||||||
|
```
|
||||||
|
|
||||||
### getTransactionCount
|
### getTransactionCount
|
||||||
|
|
||||||
Returns the current Transaction count from the ledger
|
Returns the current Transaction count from the ledger
|
||||||
|
@ -966,28 +992,6 @@ curl -X POST -H "Content-Type: application/json" -d '{"jsonrpc":"2.0","id":1, "m
|
||||||
{"jsonrpc":"2.0","result":268,"id":1}
|
{"jsonrpc":"2.0","result":268,"id":1}
|
||||||
```
|
```
|
||||||
|
|
||||||
### getTotalSupply
|
|
||||||
|
|
||||||
Returns the current total supply in lamports
|
|
||||||
|
|
||||||
#### Parameters:
|
|
||||||
|
|
||||||
* `<object>` - (optional) [Commitment](jsonrpc-api.md#configuring-state-commitment)
|
|
||||||
|
|
||||||
#### Results:
|
|
||||||
|
|
||||||
* `<u64>` - Total supply
|
|
||||||
|
|
||||||
#### Example:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
// Request
|
|
||||||
curl -X POST -H "Content-Type: application/json" -d '{"jsonrpc":"2.0","id":1, "method":"getTotalSupply"}' http://localhost:8899
|
|
||||||
|
|
||||||
// Result
|
|
||||||
{"jsonrpc":"2.0","result":10126,"id":1}
|
|
||||||
```
|
|
||||||
|
|
||||||
### getVersion
|
### getVersion
|
||||||
|
|
||||||
Returns the current solana versions running on the node
|
Returns the current solana versions running on the node
|
||||||
|
|
|
@ -567,16 +567,20 @@ impl Bank {
|
||||||
old_account.as_ref().map(|a| a.lamports).unwrap_or(1)
|
old_account.as_ref().map(|a| a.lamports).unwrap_or(1)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn clock(&self) -> sysvar::clock::Clock {
|
||||||
|
sysvar::clock::Clock {
|
||||||
|
slot: self.slot,
|
||||||
|
segment: get_segment_from_slot(self.slot, self.slots_per_segment),
|
||||||
|
epoch: self.epoch_schedule.get_epoch(self.slot),
|
||||||
|
leader_schedule_epoch: self.epoch_schedule.get_leader_schedule_epoch(self.slot),
|
||||||
|
unix_timestamp: self.unix_timestamp(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fn update_clock(&self) {
|
fn update_clock(&self) {
|
||||||
self.update_sysvar_account(&sysvar::clock::id(), |account| {
|
self.update_sysvar_account(&sysvar::clock::id(), |account| {
|
||||||
sysvar::clock::Clock {
|
self.clock()
|
||||||
slot: self.slot,
|
.create_account(self.inherit_sysvar_account_balance(account))
|
||||||
segment: get_segment_from_slot(self.slot, self.slots_per_segment),
|
|
||||||
epoch: self.epoch_schedule.get_epoch(self.slot),
|
|
||||||
leader_schedule_epoch: self.epoch_schedule.get_leader_schedule_epoch(self.slot),
|
|
||||||
unix_timestamp: self.unix_timestamp(),
|
|
||||||
}
|
|
||||||
.create_account(self.inherit_sysvar_account_balance(account))
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -9,8 +9,12 @@ use proc_macro2::Span;
|
||||||
use quote::{quote, ToTokens};
|
use quote::{quote, ToTokens};
|
||||||
use std::convert::TryFrom;
|
use std::convert::TryFrom;
|
||||||
use syn::{
|
use syn::{
|
||||||
|
bracketed,
|
||||||
parse::{Parse, ParseStream, Result},
|
parse::{Parse, ParseStream, Result},
|
||||||
parse_macro_input, Expr, LitByte, LitStr,
|
parse_macro_input,
|
||||||
|
punctuated::Punctuated,
|
||||||
|
token::Bracket,
|
||||||
|
Expr, Ident, LitByte, LitStr, Token,
|
||||||
};
|
};
|
||||||
|
|
||||||
struct Id(proc_macro2::TokenStream);
|
struct Id(proc_macro2::TokenStream);
|
||||||
|
@ -18,21 +22,7 @@ impl Parse for Id {
|
||||||
fn parse(input: ParseStream) -> Result<Self> {
|
fn parse(input: ParseStream) -> Result<Self> {
|
||||||
let token_stream = if input.peek(syn::LitStr) {
|
let token_stream = if input.peek(syn::LitStr) {
|
||||||
let id_literal: LitStr = input.parse()?;
|
let id_literal: LitStr = input.parse()?;
|
||||||
let id_vec = bs58::decode(id_literal.value())
|
parse_pubkey(&id_literal)?
|
||||||
.into_vec()
|
|
||||||
.map_err(|_| syn::Error::new_spanned(&id_literal, "failed to decode base58 id"))?;
|
|
||||||
let id_array = <[u8; 32]>::try_from(<&[u8]>::clone(&&id_vec[..])).map_err(|_| {
|
|
||||||
syn::Error::new_spanned(
|
|
||||||
&id_literal,
|
|
||||||
format!("id is not 32 bytes long: len={}", id_vec.len()),
|
|
||||||
)
|
|
||||||
})?;
|
|
||||||
let bytes = id_array.iter().map(|b| LitByte::new(*b, Span::call_site()));
|
|
||||||
quote! {
|
|
||||||
::solana_sdk::pubkey::Pubkey::new_from_array(
|
|
||||||
[#(#bytes,)*]
|
|
||||||
)
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
let expr: Expr = input.parse()?;
|
let expr: Expr = input.parse()?;
|
||||||
quote! { #expr }
|
quote! { #expr }
|
||||||
|
@ -75,3 +65,85 @@ pub fn declare_id(input: TokenStream) -> TokenStream {
|
||||||
let id = parse_macro_input!(input as Id);
|
let id = parse_macro_input!(input as Id);
|
||||||
TokenStream::from(quote! {#id})
|
TokenStream::from(quote! {#id})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn parse_pubkey(id_literal: &LitStr) -> Result<proc_macro2::TokenStream> {
|
||||||
|
let id_vec = bs58::decode(id_literal.value())
|
||||||
|
.into_vec()
|
||||||
|
.map_err(|_| syn::Error::new_spanned(&id_literal, "failed to decode base58 string"))?;
|
||||||
|
let id_array = <[u8; 32]>::try_from(<&[u8]>::clone(&&id_vec[..])).map_err(|_| {
|
||||||
|
syn::Error::new_spanned(
|
||||||
|
&id_literal,
|
||||||
|
format!("pubkey array is not 32 bytes long: len={}", id_vec.len()),
|
||||||
|
)
|
||||||
|
})?;
|
||||||
|
let bytes = id_array.iter().map(|b| LitByte::new(*b, Span::call_site()));
|
||||||
|
Ok(quote! {
|
||||||
|
::solana_sdk::pubkey::Pubkey::new_from_array(
|
||||||
|
[#(#bytes,)*]
|
||||||
|
)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
struct Pubkeys {
|
||||||
|
method: Ident,
|
||||||
|
num: usize,
|
||||||
|
pubkeys: proc_macro2::TokenStream,
|
||||||
|
}
|
||||||
|
impl Parse for Pubkeys {
|
||||||
|
fn parse(input: ParseStream) -> Result<Self> {
|
||||||
|
let method = input.parse()?;
|
||||||
|
let _comma: Token![,] = input.parse()?;
|
||||||
|
let (num, pubkeys) = if input.peek(syn::LitStr) {
|
||||||
|
let id_literal: LitStr = input.parse()?;
|
||||||
|
(1, parse_pubkey(&id_literal)?)
|
||||||
|
} else if input.peek(Bracket) {
|
||||||
|
let pubkey_strings;
|
||||||
|
bracketed!(pubkey_strings in input);
|
||||||
|
let punctuated: Punctuated<LitStr, Token![,]> =
|
||||||
|
Punctuated::parse_terminated(&pubkey_strings)?;
|
||||||
|
let mut pubkeys: Punctuated<proc_macro2::TokenStream, Token![,]> = Punctuated::new();
|
||||||
|
for string in punctuated.iter() {
|
||||||
|
pubkeys.push(parse_pubkey(string)?);
|
||||||
|
}
|
||||||
|
(pubkeys.len(), quote! {#pubkeys})
|
||||||
|
} else {
|
||||||
|
let stream: proc_macro2::TokenStream = input.parse()?;
|
||||||
|
return Err(syn::Error::new_spanned(stream, "unexpected token"));
|
||||||
|
};
|
||||||
|
|
||||||
|
Ok(Pubkeys {
|
||||||
|
method,
|
||||||
|
num,
|
||||||
|
pubkeys,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ToTokens for Pubkeys {
|
||||||
|
fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) {
|
||||||
|
let Pubkeys {
|
||||||
|
method,
|
||||||
|
num,
|
||||||
|
pubkeys,
|
||||||
|
} = self;
|
||||||
|
if *num == 1 {
|
||||||
|
tokens.extend(quote! {
|
||||||
|
pub fn #method() -> ::solana_sdk::pubkey::Pubkey {
|
||||||
|
#pubkeys
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
tokens.extend(quote! {
|
||||||
|
pub fn #method() -> ::std::vec::Vec<::solana_sdk::pubkey::Pubkey> {
|
||||||
|
vec![#pubkeys]
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[proc_macro]
|
||||||
|
pub fn pubkeys(input: TokenStream) -> TokenStream {
|
||||||
|
let pubkeys = parse_macro_input!(input as Pubkeys);
|
||||||
|
TokenStream::from(quote! {#pubkeys})
|
||||||
|
}
|
||||||
|
|
|
@ -57,6 +57,7 @@ pub mod timing;
|
||||||
/// assert_eq!(id(), my_id);
|
/// assert_eq!(id(), my_id);
|
||||||
/// ```
|
/// ```
|
||||||
pub use solana_sdk_macro::declare_id;
|
pub use solana_sdk_macro::declare_id;
|
||||||
|
pub use solana_sdk_macro::pubkeys;
|
||||||
|
|
||||||
// On-chain program specific modules
|
// On-chain program specific modules
|
||||||
pub mod account_info;
|
pub mod account_info;
|
||||||
|
|
Loading…
Reference in New Issue