solana_runtime/
non_circulating_supply.rs

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            // The program-id account index checks for Account owner on inclusion. However, due to
40            // the current AccountsDb implementation, an account may remain in storage as a
41            // zero-lamport Account::Default() after being wiped and reinitialized in later
42            // updates. We include the redundant filter here to avoid returning these accounts.
43            |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
83// Mainnet-beta accounts that should be considered non-circulating
84solana_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
200// Withdraw authority for autostaked accounts on mainnet-beta
201solana_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        // 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}