solana_runtime/
non_circulating_supply.rs

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            // The program-id account index checks for Account owner on inclusion. However, due to
42            // the current AccountsDb implementation, an account may remain in storage as a
43            // zero-lamport Account::Default() after being wiped and reinitialized in later
44            // updates. We include the redundant filter here to avoid returning these accounts.
45            |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
85// Mainnet-beta accounts that should be considered non-circulating
86solana_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
202// Withdraw authority for autostaked accounts on mainnet-beta
203solana_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        // Advance bank an epoch, which should unlock stakes
318        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}