1use {
2 crate::{
3 accounts_index::{AccountIndex, IndexKey, ScanConfig, ScanResult},
4 bank::Bank,
5 },
6 log::*,
7 solana_sdk::{
8 account::ReadableAccount,
9 pubkey::Pubkey,
10 stake::{self, state::StakeState},
11 },
12 solana_stake_program::stake_state,
13 std::{collections::HashSet, sync::Arc},
14};
15
16pub struct NonCirculatingSupply {
17 pub lamports: u64,
18 pub accounts: Vec<Pubkey>,
19}
20
21pub fn calculate_non_circulating_supply(bank: &Arc<Bank>) -> ScanResult<NonCirculatingSupply> {
22 debug!("Updating Bank supply, epoch: {}", bank.epoch());
23 let mut non_circulating_accounts_set: HashSet<Pubkey> = HashSet::new();
24
25 for key in non_circulating_accounts() {
26 non_circulating_accounts_set.insert(key);
27 }
28 let withdraw_authority_list = withdraw_authority();
29
30 let clock = bank.clock();
31 let config = &ScanConfig::default();
32 let stake_accounts = if bank
33 .rc
34 .accounts
35 .accounts_db
36 .account_indexes
37 .contains(&AccountIndex::ProgramId)
38 {
39 bank.get_filtered_indexed_accounts(
40 &IndexKey::ProgramId(stake::program::id()),
41 |account| account.owner() == &stake::program::id(),
46 config,
47 None,
48 )?
49 } else {
50 bank.get_program_accounts(&stake::program::id(), config)?
51 };
52
53 for (pubkey, account) in stake_accounts.iter() {
54 let stake_account = stake_state::from(account).unwrap_or_default();
55 match stake_account {
56 StakeState::Initialized(meta) => {
57 if meta.lockup.is_in_force(&clock, None)
58 || withdraw_authority_list.contains(&meta.authorized.withdrawer)
59 {
60 non_circulating_accounts_set.insert(*pubkey);
61 }
62 }
63 StakeState::Stake(meta, _stake) => {
64 if meta.lockup.is_in_force(&clock, None)
65 || withdraw_authority_list.contains(&meta.authorized.withdrawer)
66 {
67 non_circulating_accounts_set.insert(*pubkey);
68 }
69 }
70 _ => {}
71 }
72 }
73
74 let lamports = non_circulating_accounts_set
75 .iter()
76 .map(|pubkey| bank.get_balance(pubkey))
77 .sum();
78
79 Ok(NonCirculatingSupply {
80 lamports,
81 accounts: non_circulating_accounts_set.into_iter().collect(),
82 })
83}
84
85solana_sdk::pubkeys!(
87 non_circulating_accounts,
88 [
89 "9huDUZfxoJ7wGMTffUE7vh1xePqef7gyrLJu9NApncqA",
90 "GK2zqSsXLA2rwVZk347RYhh6jJpRsCA69FjLW93ZGi3B",
91 "CWeRmXme7LmbaUWTZWFLt6FMnpzLCHaQLuR2TdgFn4Lq",
92 "HCV5dGFJXRrJ3jhDYA4DCeb9TEDTwGGYXtT3wHksu2Zr",
93 "14FUT96s9swbmH7ZjpDvfEDywnAYy9zaNhv4xvezySGu",
94 "HbZ5FfmKWNHC7uwk6TF1hVi6TCs7dtYfdjEcuPGgzFAg",
95 "C7C8odR8oashR5Feyrq2tJKaXL18id1dSj2zbkDGL2C2",
96 "Eyr9P5XsjK2NUKNCnfu39eqpGoiLFgVAv1LSQgMZCwiQ",
97 "DE1bawNcRJB9rVm3buyMVfr8mBEoyyu73NBovf2oXJsJ",
98 "CakcnaRDHka2gXyfbEd2d3xsvkJkqsLw2akB3zsN1D2S",
99 "7Np41oeYqPefeNQEHSv1UDhYrehxin3NStELsSKCT4K2",
100 "GdnSyH3YtwcxFvQrVVJMm1JhTS4QVX7MFsX56uJLUfiZ",
101 "4jAMsnkjcRapWdVaXMvJc1QMcD53t1tbqnwXQmdRWGRe",
102 "7cvkjYAkUYs4W8XcXsca7cBrEGFeSUjeZmKoNBvEwyri",
103 "AG3m2bAibcY8raMt4oXEGqRHwX4FWKPPJVjZxn1LySDX",
104 "5XdtyEDREHJXXW1CTtCsVjJRjBapAwK78ZquzvnNVRrV",
105 "6yKHERk8rsbmJxvMpPuwPs1ct3hRiP7xaJF2tvnGU6nK",
106 "CHmdL15akDcJgBkY6BP3hzs98Dqr6wbdDC5p8odvtSbq",
107 "FR84wZQy3Y3j2gWz6pgETUiUoJtreMEuWfbg6573UCj9",
108 "5q54XjQ7vDx4y6KphPeE97LUNiYGtP55spjvXAWPGBuf",
109 "3o6xgkJ9sTmDeQWyfj3sxwon18fXJB9PV5LDc8sfgR4a",
110 "GumSE5HsMV5HCwBTv2D2D81yy9x17aDkvobkqAfTRgmo",
111 "AzVV9ZZDxTgW4wWfJmsG6ytaHpQGSe1yz76Nyy84VbQF",
112 "ETb9UBuunEPA1RrwwZ9WrkJ4BJ1836ZUcE9UfRYnDQRK",
113 "CQDYc4ET2mbFhVpgj41gXahL6Exn5ZoPcGAzSHuYxwmE",
114 "5PLJZLJiRR9vf7d1JCCg7UuWjtyN9nkab9uok6TqSyuP",
115 "7xJ9CLtEAcEShw9kW2gSoZkRWL566Dg12cvgzANJwbTr",
116 "BuCEvc9ze8UoAQwwsQLy8d447C8sA4zeVtVpc6m5wQeS",
117 "8ndGYFjav6NDXvzYcxs449Aub3AxYv4vYpk89zRDwgj7",
118 "8W58E8JVJjH1jCy5CeHJQgvwFXTyAVyesuXRZGbcSUGG",
119 "GNiz4Mq886bTNDT3pijGsu2gbw6it7sqrwncro45USeB",
120 "GhsotwFMH6XUrRLJCxcx62h7748N2Uq8mf87hUGkmPhg",
121 "Fgyh8EeYGZtbW8sS33YmNQnzx54WXPrJ5KWNPkCfWPot",
122 "8UVjvYyoqP6sqcctTso3xpCdCfgTMiv3VRh7vraC2eJk",
123 "BhvLngiqqKeZ8rpxch2uGjeCiC88zzewoWPRuoxpp1aS",
124 "63DtkW7zuARcd185EmHAkfF44bDcC2SiTSEj2spLP3iA",
125 "GvpCiTgq9dmEeojCDBivoLoZqc4AkbUDACpqPMwYLWKh",
126 "7Y8smnoUrYKGGuDq2uaFKVxJYhojgg7DVixHyAtGTYEV",
127 "DUS1KxwUhUyDKB4A81E8vdnTe3hSahd92Abtn9CXsEcj",
128 "F9MWFw8cnYVwsRq8Am1PGfFL3cQUZV37mbGoxZftzLjN",
129 "8vqrX3H2BYLaXVintse3gorPEM4TgTwTFZNN1Fm9TdYs",
130 "CUageMFi49kzoDqtdU8NvQ4Bq3sbtJygjKDAXJ45nmAi",
131 "5smrYwb1Hr2T8XMnvsqccTgXxuqQs14iuE8RbHFYf2Cf",
132 "xQadXQiUTCCFhfHjvQx1hyJK6KVWr1w2fD6DT3cdwj7",
133 "8DE8fqPfv1fp9DHyGyDFFaMjpopMgDeXspzoi9jpBJjC",
134 "3itU5ME8L6FDqtMiRoUiT1F7PwbkTtHBbW51YWD5jtjm",
135 "AsrYX4FeLXnZcrjcZmrASY2Eq1jvEeQfwxtNTxS5zojA",
136 "8rT45mqpuDBR1vcnDc9kwP9DrZAXDR4ZeuKWw3u1gTGa",
137 "nGME7HgBT6tAJN1f6YuCCngpqT5cvSTndZUVLjQ4jwA",
138 "CzAHrrrHKx9Lxf6wdCMrsZkLvk74c7J2vGv8VYPUmY6v",
139 "AzHQ8Bia1grVVbcGyci7wzueSWkgvu7YZVZ4B9rkL5P6",
140 "FiWYY85b58zEEcPtxe3PuqzWPjqBJXqdwgZeqSBmT9Cn",
141 "GpxpMVhrBBBEYbEJxdR62w3daWz444V7m6dxYDZKH77D",
142 "3bTGcGB9F98XxnrBNftmmm48JGfPgi5sYxDEKiCjQYk3",
143 "8pNBEppa1VcFAsx4Hzq9CpdXUXZjUXbvQwLX2K7QsCwb",
144 "HKJgYGTTYYR2ZkfJKHbn58w676fKueQXmvbtpyvrSM3N",
145 "3jnknRabs7G2V9dKhxd2KP85pNWXKXiedYnYxtySnQMs",
146 "4sxwau4mdqZ8zEJsfryXq4QFYnMJSCp3HWuZQod8WU5k",
147 "Fg12tB1tz8w6zJSQ4ZAGotWoCztdMJF9hqK8R11pakog",
148 "GEWSkfWgHkpiLbeKaAnwvqnECGdRNf49at5nFccVey7c",
149 "CND6ZjRTzaCFVdX7pSSWgjTfHZuhxqFDoUBqWBJguNoA",
150 "2WWb1gRzuXDd5viZLQF7pNRR6Y7UiyeaPpaL35X6j3ve",
151 "BUnRE27mYXN9p8H1Ay24GXhJC88q2CuwLoNU2v2CrW4W",
152 "CsUqV42gVQLJwQsKyjWHqGkfHarxn9hcY4YeSjgaaeTd",
153 "5khMKAcvmsFaAhoKkdg3u5abvKsmjUQNmhTNP624WB1F",
154 "GpYnVDgB7dzvwSgsjQFeHznjG6Kt1DLBFYrKxjGU1LuD",
155 "DQQGPtj7pphPHCLzzBuEyDDQByUcKGrsJdsH7SP3hAug",
156 "FwfaykN7ACnsEUDHANzGHqTGQZMcGnUSsahAHUqbdPrz",
157 "JCwT5Ygmq3VeBEbDjL8s8E82Ra2rP9bq45QfZE7Xyaq7",
158 "H3Ni7vG1CsmJZdTvxF7RkAf9UM5qk4RsohJsmPvtZNnu",
159 "CVgyXrbEd1ctEuvq11QdpnCQVnPit8NLdhyqXQHLprM2",
160 "EAJJD6nDqtXcZ4DnQb19F9XEz8y8bRDHxbWbahatZNbL",
161 "6o5v1HC7WhBnLfRHp8mQTtCP2khdXXjhuyGyYEoy2Suy",
162 "3ZrsTmNM6AkMcqFfv3ryfhQ2jMfqP64RQbqVyAaxqhrQ",
163 "6zw7em7uQdmMpuS9fGz8Nq9TLHa5YQhEKKwPjo5PwDK4",
164 "CuatS6njAcfkFHnvai7zXCs7syA9bykXWsDCJEWfhjHG",
165 "Hz9nydgN1k15wnwffKX7CSmZp4VFTnTwLXAEdomFGNXy",
166 "Ep5Y58PaSyALPrdFxDVAdfKtVdP55vApvsWjb3jSmXsG",
167 "EziVYi3Sv5kJWxmU77PnbrT8jmkVuqwdiFLLzZpLVEn7",
168 "H1rt8KvXkNhQExTRfkY8r9wjZbZ8yCih6J4wQ5Fz9HGP",
169 "6nN69B4uZuESZYxr9nrLDjmKRtjDZQXrehwkfQTKw62U",
170 "Hm9JW7of5i9dnrboS8pCUCSeoQUPh7JsP1rkbJnW7An4",
171 "5D5NxsNVTgXHyVziwV7mDFwVDS6voaBsyyGxUbhQrhNW",
172 "EMAY24PrS6rWfvpqffFCsTsFJypeeYYmtUc26wdh3Wup",
173 "Br3aeVGapRb2xTq17RU2pYZCoJpWA7bq6TKBCcYtMSmt",
174 "BUjkdqUuH5Lz9XzcMcR4DdEMnFG6r8QzUMBm16Rfau96",
175 "Es13uD2p64UVPFpEWfDtd6SERdoNR2XVgqBQBZcZSLqW",
176 "AVYpwVou2BhdLivAwLxKPALZQsY7aZNkNmGbP2fZw7RU",
177 "DrKzW5koKSZp4mg4BdHLwr72MMXscd2kTiWgckCvvPXz",
178 "9hknftBZAQL4f48tWfk3bUEV5YSLcYYtDRqNmpNnhCWG",
179 "GLUmCeJpXB8veNcchPwibkRYwCwvQbKodex5mEjrgToi",
180 "9S2M3UYPpnPZTBtbcUvehYmiWFK3kBhwfzV2iWuwvaVy",
181 "HUAkU5psJXZuw54Lrg1ksbXzHv2fzczQ9sNbmisVMeJU",
182 "GK8R4uUmrawcREZ5xJy5dAzVV5V7aFvYg77id37pVTK",
183 "4vuWt1oHRqLMhf8Nv1zyEXZsYaeK7dipwrfKLoYU9Riq",
184 "EMhn1U3TMimW3bvWYbPUvN2eZnCfsuBN4LGWhzzYhiWR",
185 "BsKsunvENxAraBrL77UfAn1Gi7unVEmQAdCbhsjUN6tU",
186 "CTvhdUVf8KNyMbyEdnvRrBCHJjBKtQwkbj6zwoqcEssG",
187 "3fV2GaDKa3pZxyDcpMh5Vrh2FVAMUiWUKbYmnBFv8As3",
188 "4pV47TiPzZ7SSBPHmgUvSLmH9mMSe8tjyPhQZGbi1zPC",
189 "P8aKfWQPeRnsZtpBrwWTYzyAoRk74KMz56xc6NEpC4J",
190 "HuqDWJodFhAEWh6aWdsDVUqsjRket5DYXMYyDYtD8hdN",
191 "Ab1UcdsFXZVnkSt1Z3vcYU65GQk5MvCbs54SviaiaqHb",
192 "Dc2oHxFXQaC2QfLStuU7txtD3U5HZ82MrCSGDooWjbsv",
193 "3iPvAS4xdhYr6SkhVDHCLr7tJjMAFK4wvvHWJxFQVg15",
194 "GmyW1nqYcrw7P7JqrcyP9ivU9hYNbrgZ1r5SYJJH41Fs",
195 "E8jcgWvrvV7rwYHJThwfiBeQ8VAH4FgNEEMG9aAuCMAq",
196 "CY7X5o3Wi2eQhTocLmUS6JSWyx1NinBfW7AXRrkRCpi8",
197 "HQJtLqvEGGxgNYfRXUurfxV8E1swvCnsbC3456ik27HY",
198 "9xbcBZoGYFnfJZe81EDuDYKUm8xGkjzW8z4EgnVhNvsv",
199 ]
200);
201
202solana_sdk::pubkeys!(
204 withdraw_authority,
205 [
206 "ETb9UBuunEPA1RrwwZ9WrkJ4BJ1836ZUcE9UfRYnDQRK",
207 "3DSuNXv7qzvcB6TKAxzaREvW85vit7xw4m2NBWf75585",
208 "6DB5E4mEyFzr9n6jt2aew73cjfysiHegLbmHmC4xME3u",
209 "4e6KwQpyzGQPfgVr5Jn3g5jLjbXB4pKPa2jRLohEb1QA",
210 "FjiEiVKyMGzSLpqoB27QypukUfyWHrwzPcGNtopzZVdh",
211 "DwbVjia1mYeSGoJipzhaf4L5hfer2DJ1Ys681VzQm5YY",
212 "GeMGyvsTEsANVvcT5cme65Xq5MVU8fVVzMQ13KAZFNS2",
213 "Bj3aQ2oFnZYfNR1njzRjmWizzuhvfcYLckh76cqsbuBM",
214 "4ZJhPQAgUseCsWhKvJLTmmRRUV74fdoTpQLNfKoekbPY",
215 "HXdYQ5gixrY2H6Y9gqsD8kPM2JQKSaRiohDQtLbZkRWE",
216 ]
217);
218
219#[cfg(test)]
220mod tests {
221 use {
222 super::*,
223 crate::genesis_utils::genesis_sysvar_and_builtin_program_lamports,
224 solana_sdk::{
225 account::{Account, AccountSharedData},
226 epoch_schedule::EpochSchedule,
227 genesis_config::{ClusterType, GenesisConfig},
228 stake::state::{Authorized, Lockup, Meta},
229 },
230 std::{collections::BTreeMap, sync::Arc},
231 };
232
233 fn new_from_parent(parent: &Arc<Bank>) -> Bank {
234 Bank::new_from_parent(parent, &Pubkey::default(), parent.slot() + 1)
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_sdk::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_sdk::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 &StakeState::Initialized(meta),
268 StakeState::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}