1use {
2 crate::bank::Bank,
3 log::*,
4 solana_accounts_db::accounts_index::{AccountIndex, IndexKey, ScanConfig, ScanResult},
5 solana_sdk::{
6 account::ReadableAccount,
7 pubkey::Pubkey,
8 stake::{self, state::StakeStateV2},
9 },
10 solana_stake_program::stake_state,
11 std::collections::HashSet,
12};
13
14pub struct NonCirculatingSupply {
15 pub lamports: u64,
16 pub accounts: Vec<Pubkey>,
17}
18
19pub fn calculate_non_circulating_supply(bank: &Bank) -> ScanResult<NonCirculatingSupply> {
20 debug!("Updating Bank supply, epoch: {}", bank.epoch());
21 let mut non_circulating_accounts_set: HashSet<Pubkey> = HashSet::new();
22
23 for key in non_circulating_accounts() {
24 non_circulating_accounts_set.insert(key);
25 }
26 let withdraw_authority_list = withdraw_authority();
27
28 let clock = bank.clock();
29 let config = &ScanConfig::default();
30 let stake_accounts = if bank
31 .rc
32 .accounts
33 .accounts_db
34 .account_indexes
35 .contains(&AccountIndex::ProgramId)
36 {
37 bank.get_filtered_indexed_accounts(
38 &IndexKey::ProgramId(stake::program::id()),
39 |account| account.owner() == &stake::program::id(),
44 config,
45 None,
46 )?
47 } else {
48 bank.get_program_accounts(&stake::program::id(), config)?
49 };
50
51 for (pubkey, account) in stake_accounts.iter() {
52 let stake_account = stake_state::from(account).unwrap_or_default();
53 match stake_account {
54 StakeStateV2::Initialized(meta) => {
55 if meta.lockup.is_in_force(&clock, None)
56 || withdraw_authority_list.contains(&meta.authorized.withdrawer)
57 {
58 non_circulating_accounts_set.insert(*pubkey);
59 }
60 }
61 StakeStateV2::Stake(meta, _stake, _stake_flags) => {
62 if meta.lockup.is_in_force(&clock, None)
63 || withdraw_authority_list.contains(&meta.authorized.withdrawer)
64 {
65 non_circulating_accounts_set.insert(*pubkey);
66 }
67 }
68 _ => {}
69 }
70 }
71
72 let lamports = non_circulating_accounts_set
73 .iter()
74 .map(|pubkey| bank.get_balance(pubkey))
75 .sum();
76
77 Ok(NonCirculatingSupply {
78 lamports,
79 accounts: non_circulating_accounts_set.into_iter().collect(),
80 })
81}
82
83solana_sdk::pubkeys!(
85 non_circulating_accounts,
86 [
87 "9huDUZfxoJ7wGMTffUE7vh1xePqef7gyrLJu9NApncqA",
88 "GK2zqSsXLA2rwVZk347RYhh6jJpRsCA69FjLW93ZGi3B",
89 "CWeRmXme7LmbaUWTZWFLt6FMnpzLCHaQLuR2TdgFn4Lq",
90 "HCV5dGFJXRrJ3jhDYA4DCeb9TEDTwGGYXtT3wHksu2Zr",
91 "14FUT96s9swbmH7ZjpDvfEDywnAYy9zaNhv4xvezySGu",
92 "HbZ5FfmKWNHC7uwk6TF1hVi6TCs7dtYfdjEcuPGgzFAg",
93 "C7C8odR8oashR5Feyrq2tJKaXL18id1dSj2zbkDGL2C2",
94 "Eyr9P5XsjK2NUKNCnfu39eqpGoiLFgVAv1LSQgMZCwiQ",
95 "DE1bawNcRJB9rVm3buyMVfr8mBEoyyu73NBovf2oXJsJ",
96 "CakcnaRDHka2gXyfbEd2d3xsvkJkqsLw2akB3zsN1D2S",
97 "7Np41oeYqPefeNQEHSv1UDhYrehxin3NStELsSKCT4K2",
98 "GdnSyH3YtwcxFvQrVVJMm1JhTS4QVX7MFsX56uJLUfiZ",
99 "Mc5XB47H3DKJHym5RLa9mPzWv5snERsF3KNv5AauXK8",
100 "7cvkjYAkUYs4W8XcXsca7cBrEGFeSUjeZmKoNBvEwyri",
101 "AG3m2bAibcY8raMt4oXEGqRHwX4FWKPPJVjZxn1LySDX",
102 "5XdtyEDREHJXXW1CTtCsVjJRjBapAwK78ZquzvnNVRrV",
103 "6yKHERk8rsbmJxvMpPuwPs1ct3hRiP7xaJF2tvnGU6nK",
104 "CHmdL15akDcJgBkY6BP3hzs98Dqr6wbdDC5p8odvtSbq",
105 "FR84wZQy3Y3j2gWz6pgETUiUoJtreMEuWfbg6573UCj9",
106 "5q54XjQ7vDx4y6KphPeE97LUNiYGtP55spjvXAWPGBuf",
107 "3o6xgkJ9sTmDeQWyfj3sxwon18fXJB9PV5LDc8sfgR4a",
108 "GumSE5HsMV5HCwBTv2D2D81yy9x17aDkvobkqAfTRgmo",
109 "AzVV9ZZDxTgW4wWfJmsG6ytaHpQGSe1yz76Nyy84VbQF",
110 "8CUUMKYNGxdgYio5CLHRHyzMEhhVRMcqefgE6dLqnVRK",
111 "CQDYc4ET2mbFhVpgj41gXahL6Exn5ZoPcGAzSHuYxwmE",
112 "5PLJZLJiRR9vf7d1JCCg7UuWjtyN9nkab9uok6TqSyuP",
113 "7xJ9CLtEAcEShw9kW2gSoZkRWL566Dg12cvgzANJwbTr",
114 "BuCEvc9ze8UoAQwwsQLy8d447C8sA4zeVtVpc6m5wQeS",
115 "8ndGYFjav6NDXvzYcxs449Aub3AxYv4vYpk89zRDwgj7",
116 "8W58E8JVJjH1jCy5CeHJQgvwFXTyAVyesuXRZGbcSUGG",
117 "GNiz4Mq886bTNDT3pijGsu2gbw6it7sqrwncro45USeB",
118 "GhsotwFMH6XUrRLJCxcx62h7748N2Uq8mf87hUGkmPhg",
119 "Fgyh8EeYGZtbW8sS33YmNQnzx54WXPrJ5KWNPkCfWPot",
120 "8UVjvYyoqP6sqcctTso3xpCdCfgTMiv3VRh7vraC2eJk",
121 "BhvLngiqqKeZ8rpxch2uGjeCiC88zzewoWPRuoxpp1aS",
122 "63DtkW7zuARcd185EmHAkfF44bDcC2SiTSEj2spLP3iA",
123 "GvpCiTgq9dmEeojCDBivoLoZqc4AkbUDACpqPMwYLWKh",
124 "7Y8smnoUrYKGGuDq2uaFKVxJYhojgg7DVixHyAtGTYEV",
125 "DUS1KxwUhUyDKB4A81E8vdnTe3hSahd92Abtn9CXsEcj",
126 "F9MWFw8cnYVwsRq8Am1PGfFL3cQUZV37mbGoxZftzLjN",
127 "8vqrX3H2BYLaXVintse3gorPEM4TgTwTFZNN1Fm9TdYs",
128 "CUageMFi49kzoDqtdU8NvQ4Bq3sbtJygjKDAXJ45nmAi",
129 "5smrYwb1Hr2T8XMnvsqccTgXxuqQs14iuE8RbHFYf2Cf",
130 "xQadXQiUTCCFhfHjvQx1hyJK6KVWr1w2fD6DT3cdwj7",
131 "8DE8fqPfv1fp9DHyGyDFFaMjpopMgDeXspzoi9jpBJjC",
132 "3itU5ME8L6FDqtMiRoUiT1F7PwbkTtHBbW51YWD5jtjm",
133 "AsrYX4FeLXnZcrjcZmrASY2Eq1jvEeQfwxtNTxS5zojA",
134 "8rT45mqpuDBR1vcnDc9kwP9DrZAXDR4ZeuKWw3u1gTGa",
135 "nGME7HgBT6tAJN1f6YuCCngpqT5cvSTndZUVLjQ4jwA",
136 "CzAHrrrHKx9Lxf6wdCMrsZkLvk74c7J2vGv8VYPUmY6v",
137 "AzHQ8Bia1grVVbcGyci7wzueSWkgvu7YZVZ4B9rkL5P6",
138 "FiWYY85b58zEEcPtxe3PuqzWPjqBJXqdwgZeqSBmT9Cn",
139 "GpxpMVhrBBBEYbEJxdR62w3daWz444V7m6dxYDZKH77D",
140 "3bTGcGB9F98XxnrBNftmmm48JGfPgi5sYxDEKiCjQYk3",
141 "8pNBEppa1VcFAsx4Hzq9CpdXUXZjUXbvQwLX2K7QsCwb",
142 "HKJgYGTTYYR2ZkfJKHbn58w676fKueQXmvbtpyvrSM3N",
143 "3jnknRabs7G2V9dKhxd2KP85pNWXKXiedYnYxtySnQMs",
144 "4sxwau4mdqZ8zEJsfryXq4QFYnMJSCp3HWuZQod8WU5k",
145 "Fg12tB1tz8w6zJSQ4ZAGotWoCztdMJF9hqK8R11pakog",
146 "GEWSkfWgHkpiLbeKaAnwvqnECGdRNf49at5nFccVey7c",
147 "CND6ZjRTzaCFVdX7pSSWgjTfHZuhxqFDoUBqWBJguNoA",
148 "2WWb1gRzuXDd5viZLQF7pNRR6Y7UiyeaPpaL35X6j3ve",
149 "BUnRE27mYXN9p8H1Ay24GXhJC88q2CuwLoNU2v2CrW4W",
150 "CsUqV42gVQLJwQsKyjWHqGkfHarxn9hcY4YeSjgaaeTd",
151 "5khMKAcvmsFaAhoKkdg3u5abvKsmjUQNmhTNP624WB1F",
152 "GpYnVDgB7dzvwSgsjQFeHznjG6Kt1DLBFYrKxjGU1LuD",
153 "DQQGPtj7pphPHCLzzBuEyDDQByUcKGrsJdsH7SP3hAug",
154 "FwfaykN7ACnsEUDHANzGHqTGQZMcGnUSsahAHUqbdPrz",
155 "JCwT5Ygmq3VeBEbDjL8s8E82Ra2rP9bq45QfZE7Xyaq7",
156 "H3Ni7vG1CsmJZdTvxF7RkAf9UM5qk4RsohJsmPvtZNnu",
157 "CVgyXrbEd1ctEuvq11QdpnCQVnPit8NLdhyqXQHLprM2",
158 "EAJJD6nDqtXcZ4DnQb19F9XEz8y8bRDHxbWbahatZNbL",
159 "6o5v1HC7WhBnLfRHp8mQTtCP2khdXXjhuyGyYEoy2Suy",
160 "3ZrsTmNM6AkMcqFfv3ryfhQ2jMfqP64RQbqVyAaxqhrQ",
161 "6zw7em7uQdmMpuS9fGz8Nq9TLHa5YQhEKKwPjo5PwDK4",
162 "CuatS6njAcfkFHnvai7zXCs7syA9bykXWsDCJEWfhjHG",
163 "Hz9nydgN1k15wnwffKX7CSmZp4VFTnTwLXAEdomFGNXy",
164 "Ep5Y58PaSyALPrdFxDVAdfKtVdP55vApvsWjb3jSmXsG",
165 "EziVYi3Sv5kJWxmU77PnbrT8jmkVuqwdiFLLzZpLVEn7",
166 "H1rt8KvXkNhQExTRfkY8r9wjZbZ8yCih6J4wQ5Fz9HGP",
167 "6nN69B4uZuESZYxr9nrLDjmKRtjDZQXrehwkfQTKw62U",
168 "Hm9JW7of5i9dnrboS8pCUCSeoQUPh7JsP1rkbJnW7An4",
169 "5D5NxsNVTgXHyVziwV7mDFwVDS6voaBsyyGxUbhQrhNW",
170 "EMAY24PrS6rWfvpqffFCsTsFJypeeYYmtUc26wdh3Wup",
171 "Br3aeVGapRb2xTq17RU2pYZCoJpWA7bq6TKBCcYtMSmt",
172 "BUjkdqUuH5Lz9XzcMcR4DdEMnFG6r8QzUMBm16Rfau96",
173 "Es13uD2p64UVPFpEWfDtd6SERdoNR2XVgqBQBZcZSLqW",
174 "AVYpwVou2BhdLivAwLxKPALZQsY7aZNkNmGbP2fZw7RU",
175 "DrKzW5koKSZp4mg4BdHLwr72MMXscd2kTiWgckCvvPXz",
176 "9hknftBZAQL4f48tWfk3bUEV5YSLcYYtDRqNmpNnhCWG",
177 "GLUmCeJpXB8veNcchPwibkRYwCwvQbKodex5mEjrgToi",
178 "9S2M3UYPpnPZTBtbcUvehYmiWFK3kBhwfzV2iWuwvaVy",
179 "HUAkU5psJXZuw54Lrg1ksbXzHv2fzczQ9sNbmisVMeJU",
180 "GK8R4uUmrawcREZ5xJy5dAzVV5V7aFvYg77id37pVTK",
181 "4vuWt1oHRqLMhf8Nv1zyEXZsYaeK7dipwrfKLoYU9Riq",
182 "EMhn1U3TMimW3bvWYbPUvN2eZnCfsuBN4LGWhzzYhiWR",
183 "BsKsunvENxAraBrL77UfAn1Gi7unVEmQAdCbhsjUN6tU",
184 "CTvhdUVf8KNyMbyEdnvRrBCHJjBKtQwkbj6zwoqcEssG",
185 "3fV2GaDKa3pZxyDcpMh5Vrh2FVAMUiWUKbYmnBFv8As3",
186 "4pV47TiPzZ7SSBPHmgUvSLmH9mMSe8tjyPhQZGbi1zPC",
187 "P8aKfWQPeRnsZtpBrwWTYzyAoRk74KMz56xc6NEpC4J",
188 "HuqDWJodFhAEWh6aWdsDVUqsjRket5DYXMYyDYtD8hdN",
189 "Ab1UcdsFXZVnkSt1Z3vcYU65GQk5MvCbs54SviaiaqHb",
190 "Dc2oHxFXQaC2QfLStuU7txtD3U5HZ82MrCSGDooWjbsv",
191 "3iPvAS4xdhYr6SkhVDHCLr7tJjMAFK4wvvHWJxFQVg15",
192 "GmyW1nqYcrw7P7JqrcyP9ivU9hYNbrgZ1r5SYJJH41Fs",
193 "E8jcgWvrvV7rwYHJThwfiBeQ8VAH4FgNEEMG9aAuCMAq",
194 "CY7X5o3Wi2eQhTocLmUS6JSWyx1NinBfW7AXRrkRCpi8",
195 "HQJtLqvEGGxgNYfRXUurfxV8E1swvCnsbC3456ik27HY",
196 "9xbcBZoGYFnfJZe81EDuDYKUm8xGkjzW8z4EgnVhNvsv",
197 ]
198);
199
200solana_sdk::pubkeys!(
202 withdraw_authority,
203 [
204 "8CUUMKYNGxdgYio5CLHRHyzMEhhVRMcqefgE6dLqnVRK",
205 "3FFaheyqtyAXZSYxDzsr5CVKvJuvZD1WE1VEsBtDbRqB",
206 "FdGYQdiRky8NZzN9wZtczTBcWLYYRXrJ3LMDhqDPn5rM",
207 "4e6KwQpyzGQPfgVr5Jn3g5jLjbXB4pKPa2jRLohEb1QA",
208 "FjiEiVKyMGzSLpqoB27QypukUfyWHrwzPcGNtopzZVdh",
209 "DwbVjia1mYeSGoJipzhaf4L5hfer2DJ1Ys681VzQm5YY",
210 "GeMGyvsTEsANVvcT5cme65Xq5MVU8fVVzMQ13KAZFNS2",
211 "Bj3aQ2oFnZYfNR1njzRjmWizzuhvfcYLckh76cqsbuBM",
212 "4ZJhPQAgUseCsWhKvJLTmmRRUV74fdoTpQLNfKoekbPY",
213 "HXdYQ5gixrY2H6Y9gqsD8kPM2JQKSaRiohDQtLbZkRWE",
214 ]
215);
216
217#[cfg(test)]
218mod tests {
219 use {
220 super::*,
221 crate::genesis_utils::genesis_sysvar_and_builtin_program_lamports,
222 solana_sdk::{
223 account::{Account, AccountSharedData},
224 epoch_schedule::EpochSchedule,
225 genesis_config::{ClusterType, GenesisConfig},
226 stake::state::{Authorized, Lockup, Meta},
227 },
228 std::{collections::BTreeMap, sync::Arc},
229 };
230
231 fn new_from_parent(parent: Arc<Bank>) -> Bank {
232 let slot = parent.slot() + 1;
233 let collector_id = Pubkey::default();
234 Bank::new_from_parent(parent, &collector_id, slot)
235 }
236
237 #[test]
238 fn test_calculate_non_circulating_supply() {
239 let mut accounts: BTreeMap<Pubkey, Account> = BTreeMap::new();
240 let balance = 10;
241 let num_genesis_accounts = 10;
242 for _ in 0..num_genesis_accounts {
243 accounts.insert(
244 solana_pubkey::new_rand(),
245 Account::new(balance, 0, &Pubkey::default()),
246 );
247 }
248 let non_circulating_accounts = non_circulating_accounts();
249 let num_non_circulating_accounts = non_circulating_accounts.len() as u64;
250 for key in non_circulating_accounts.clone() {
251 accounts.insert(key, Account::new(balance, 0, &Pubkey::default()));
252 }
253
254 let num_stake_accounts = 3;
255 for _ in 0..num_stake_accounts {
256 let pubkey = solana_pubkey::new_rand();
257 let meta = Meta {
258 authorized: Authorized::auto(&pubkey),
259 lockup: Lockup {
260 epoch: 1,
261 ..Lockup::default()
262 },
263 ..Meta::default()
264 };
265 let stake_account = Account::new_data_with_space(
266 balance,
267 &StakeStateV2::Initialized(meta),
268 StakeStateV2::size_of(),
269 &stake::program::id(),
270 )
271 .unwrap();
272 accounts.insert(pubkey, stake_account);
273 }
274
275 let slots_per_epoch = 32;
276 let genesis_config = GenesisConfig {
277 accounts,
278 epoch_schedule: EpochSchedule::new(slots_per_epoch),
279 cluster_type: ClusterType::MainnetBeta,
280 ..GenesisConfig::default()
281 };
282 let mut bank = Arc::new(Bank::new_for_tests(&genesis_config));
283 assert_eq!(
284 bank.capitalization(),
285 (num_genesis_accounts + num_non_circulating_accounts + num_stake_accounts) * balance
286 + genesis_sysvar_and_builtin_program_lamports(),
287 );
288
289 let non_circulating_supply = calculate_non_circulating_supply(&bank).unwrap();
290 assert_eq!(
291 non_circulating_supply.lamports,
292 (num_non_circulating_accounts + num_stake_accounts) * balance
293 );
294 assert_eq!(
295 non_circulating_supply.accounts.len(),
296 num_non_circulating_accounts as usize + num_stake_accounts as usize
297 );
298
299 bank = Arc::new(new_from_parent(bank));
300 let new_balance = 11;
301 for key in non_circulating_accounts {
302 bank.store_account(
303 &key,
304 &AccountSharedData::new(new_balance, 0, &Pubkey::default()),
305 );
306 }
307 let non_circulating_supply = calculate_non_circulating_supply(&bank).unwrap();
308 assert_eq!(
309 non_circulating_supply.lamports,
310 (num_non_circulating_accounts * new_balance) + (num_stake_accounts * balance)
311 );
312 assert_eq!(
313 non_circulating_supply.accounts.len(),
314 num_non_circulating_accounts as usize + num_stake_accounts as usize
315 );
316
317 for _ in 0..slots_per_epoch {
319 bank = Arc::new(new_from_parent(bank));
320 }
321 assert_eq!(bank.epoch(), 1);
322 let non_circulating_supply = calculate_non_circulating_supply(&bank).unwrap();
323 assert_eq!(
324 non_circulating_supply.lamports,
325 num_non_circulating_accounts * new_balance
326 );
327 assert_eq!(
328 non_circulating_supply.accounts.len(),
329 num_non_circulating_accounts as usize
330 );
331 }
332}