solana_builtins_default_costs/
lib.rs

1#![cfg_attr(feature = "frozen-abi", feature(min_specialization))]
2#![allow(clippy::arithmetic_side_effects)]
3use {
4    ahash::AHashMap,
5    lazy_static::lazy_static,
6    solana_sdk::{
7        address_lookup_table, bpf_loader, bpf_loader_deprecated, bpf_loader_upgradeable,
8        compute_budget, ed25519_program,
9        feature_set::{self, FeatureSet},
10        loader_v4,
11        pubkey::Pubkey,
12        secp256k1_program,
13    },
14};
15
16#[derive(Clone)]
17pub struct MigratingBuiltinCost {
18    native_cost: u64,
19    core_bpf_migration_feature: Pubkey,
20    // encoding positional information explicitly for migration feature item,
21    // its value must be correctly corresponding to this object's position
22    // in MIGRATING_BUILTINS_COSTS, otherwise a const validation
23    // `validate_position(MIGRATING_BUILTINS_COSTS)` will fail at compile time.
24    position: usize,
25}
26
27#[derive(Clone)]
28pub struct NotMigratingBuiltinCost {
29    native_cost: u64,
30}
31
32/// DEVELOPER: when a builtin is migrated to sbpf, please add its corresponding
33/// migration feature ID to BUILTIN_INSTRUCTION_COSTS, and move it from
34/// NON_MIGRATING_BUILTINS_COSTS to MIGRATING_BUILTINS_COSTS, so the builtin's
35/// default cost can be determined properly based on feature status.
36/// When migration completed, eg the feature gate is enabled everywhere, please
37/// remove that builtin entry from MIGRATING_BUILTINS_COSTS.
38#[derive(Clone)]
39pub enum BuiltinCost {
40    Migrating(MigratingBuiltinCost),
41    NotMigrating(NotMigratingBuiltinCost),
42}
43
44impl BuiltinCost {
45    pub fn native_cost(&self) -> u64 {
46        match self {
47            BuiltinCost::Migrating(MigratingBuiltinCost { native_cost, .. }) => *native_cost,
48            BuiltinCost::NotMigrating(NotMigratingBuiltinCost { native_cost }) => *native_cost,
49        }
50    }
51
52    pub fn core_bpf_migration_feature(&self) -> Option<&Pubkey> {
53        match self {
54            BuiltinCost::Migrating(MigratingBuiltinCost {
55                core_bpf_migration_feature,
56                ..
57            }) => Some(core_bpf_migration_feature),
58            BuiltinCost::NotMigrating(_) => None,
59        }
60    }
61
62    pub fn position(&self) -> Option<usize> {
63        match self {
64            BuiltinCost::Migrating(MigratingBuiltinCost { position, .. }) => Some(*position),
65            BuiltinCost::NotMigrating(_) => None,
66        }
67    }
68
69    fn has_migrated(&self, feature_set: &FeatureSet) -> bool {
70        match self {
71            BuiltinCost::Migrating(MigratingBuiltinCost {
72                core_bpf_migration_feature,
73                ..
74            }) => feature_set.is_active(core_bpf_migration_feature),
75            BuiltinCost::NotMigrating(_) => false,
76        }
77    }
78}
79
80lazy_static! {
81    /// Number of compute units for each built-in programs
82    ///
83    /// DEVELOPER WARNING: This map CANNOT be modified without causing a
84    /// consensus failure because this map is used to calculate the compute
85    /// limit for transactions that don't specify a compute limit themselves as
86    /// of https://github.com/anza-xyz/agave/issues/2212.  It's also used to
87    /// calculate the cost of a transaction which is used in replay to enforce
88    /// block cost limits as of
89    /// https://github.com/solana-labs/solana/issues/29595.
90    static ref BUILTIN_INSTRUCTION_COSTS: AHashMap<Pubkey, BuiltinCost> =
91        MIGRATING_BUILTINS_COSTS
92          .iter()
93          .chain(NON_MIGRATING_BUILTINS_COSTS.iter())
94          .cloned()
95          .collect();
96    // DO NOT ADD MORE ENTRIES TO THIS MAP
97}
98
99/// DEVELOPER WARNING: please do not add new entry into MIGRATING_BUILTINS_COSTS or
100/// NON_MIGRATING_BUILTINS_COSTS, do so will modify BUILTIN_INSTRUCTION_COSTS therefore
101/// cause consensus failure. However, when a builtin started being migrated to core bpf,
102/// it MUST be moved from NON_MIGRATING_BUILTINS_COSTS to MIGRATING_BUILTINS_COSTS, then
103/// correctly furnishing `core_bpf_migration_feature`.
104///
105#[allow(dead_code)]
106const TOTAL_COUNT_BUILTS: usize = 12;
107#[cfg(test)]
108static_assertions::const_assert_eq!(
109    MIGRATING_BUILTINS_COSTS.len() + NON_MIGRATING_BUILTINS_COSTS.len(),
110    TOTAL_COUNT_BUILTS
111);
112
113pub const MIGRATING_BUILTINS_COSTS: &[(Pubkey, BuiltinCost)] = &[
114    (
115        solana_stake_program::id(),
116        BuiltinCost::Migrating(MigratingBuiltinCost {
117            native_cost: solana_stake_program::stake_instruction::DEFAULT_COMPUTE_UNITS,
118            core_bpf_migration_feature: feature_set::migrate_stake_program_to_core_bpf::id(),
119            position: 0,
120        }),
121    ),
122    (
123        solana_config_program::id(),
124        BuiltinCost::Migrating(MigratingBuiltinCost {
125            native_cost: solana_config_program::config_processor::DEFAULT_COMPUTE_UNITS,
126            core_bpf_migration_feature: feature_set::migrate_config_program_to_core_bpf::id(),
127            position: 1,
128        }),
129    ),
130    (
131        address_lookup_table::program::id(),
132        BuiltinCost::Migrating(MigratingBuiltinCost {
133            native_cost: solana_address_lookup_table_program::processor::DEFAULT_COMPUTE_UNITS,
134            core_bpf_migration_feature:
135                feature_set::migrate_address_lookup_table_program_to_core_bpf::id(),
136            position: 2,
137        }),
138    ),
139];
140
141pub const NON_MIGRATING_BUILTINS_COSTS: &[(Pubkey, BuiltinCost)] = &[
142    (
143        solana_vote_program::id(),
144        BuiltinCost::NotMigrating(NotMigratingBuiltinCost {
145            native_cost: solana_vote_program::vote_processor::DEFAULT_COMPUTE_UNITS,
146        }),
147    ),
148    (
149        solana_system_program::id(),
150        BuiltinCost::NotMigrating(NotMigratingBuiltinCost {
151            native_cost: solana_system_program::system_processor::DEFAULT_COMPUTE_UNITS,
152        }),
153    ),
154    (
155        compute_budget::id(),
156        BuiltinCost::NotMigrating(NotMigratingBuiltinCost {
157            native_cost: solana_compute_budget_program::DEFAULT_COMPUTE_UNITS,
158        }),
159    ),
160    (
161        bpf_loader_upgradeable::id(),
162        BuiltinCost::NotMigrating(NotMigratingBuiltinCost {
163            native_cost: solana_bpf_loader_program::UPGRADEABLE_LOADER_COMPUTE_UNITS,
164        }),
165    ),
166    (
167        bpf_loader_deprecated::id(),
168        BuiltinCost::NotMigrating(NotMigratingBuiltinCost {
169            native_cost: solana_bpf_loader_program::DEPRECATED_LOADER_COMPUTE_UNITS,
170        }),
171    ),
172    (
173        bpf_loader::id(),
174        BuiltinCost::NotMigrating(NotMigratingBuiltinCost {
175            native_cost: solana_bpf_loader_program::DEFAULT_LOADER_COMPUTE_UNITS,
176        }),
177    ),
178    (
179        loader_v4::id(),
180        BuiltinCost::NotMigrating(NotMigratingBuiltinCost {
181            native_cost: solana_loader_v4_program::DEFAULT_COMPUTE_UNITS,
182        }),
183    ),
184    // Note: These are precompile, run directly in bank during sanitizing;
185    (
186        secp256k1_program::id(),
187        BuiltinCost::NotMigrating(NotMigratingBuiltinCost { native_cost: 0 }),
188    ),
189    (
190        ed25519_program::id(),
191        BuiltinCost::NotMigrating(NotMigratingBuiltinCost { native_cost: 0 }),
192    ),
193];
194
195lazy_static! {
196    /// A table of 256 booleans indicates whether the first `u8` of a Pubkey exists in
197    /// BUILTIN_INSTRUCTION_COSTS. If the value is true, the Pubkey might be a builtin key;
198    /// if false, it cannot be a builtin key. This table allows for quick filtering of
199    /// builtin program IDs without the need for hashing.
200    pub static ref MAYBE_BUILTIN_KEY: [bool; 256] = {
201        let mut temp_table: [bool; 256] = [false; 256];
202        BUILTIN_INSTRUCTION_COSTS
203            .keys()
204            .for_each(|key| temp_table[key.as_ref()[0] as usize] = true);
205        temp_table
206    };
207}
208
209pub fn get_builtin_instruction_cost<'a>(
210    program_id: &'a Pubkey,
211    feature_set: &'a FeatureSet,
212) -> Option<u64> {
213    BUILTIN_INSTRUCTION_COSTS
214        .get(program_id)
215        .filter(|builtin_cost| !builtin_cost.has_migrated(feature_set))
216        .map(|builtin_cost| builtin_cost.native_cost())
217}
218
219pub enum BuiltinMigrationFeatureIndex {
220    NotBuiltin,
221    BuiltinNoMigrationFeature,
222    BuiltinWithMigrationFeature(usize),
223}
224
225pub fn get_builtin_migration_feature_index(program_id: &Pubkey) -> BuiltinMigrationFeatureIndex {
226    BUILTIN_INSTRUCTION_COSTS.get(program_id).map_or(
227        BuiltinMigrationFeatureIndex::NotBuiltin,
228        |builtin_cost| {
229            builtin_cost.position().map_or(
230                BuiltinMigrationFeatureIndex::BuiltinNoMigrationFeature,
231                BuiltinMigrationFeatureIndex::BuiltinWithMigrationFeature,
232            )
233        },
234    )
235}
236
237/// const function validates `position` correctness at compile time.
238#[allow(dead_code)]
239const fn validate_position(migrating_builtins: &[(Pubkey, BuiltinCost)]) {
240    let mut index = 0;
241    while index < migrating_builtins.len() {
242        match migrating_builtins[index].1 {
243            BuiltinCost::Migrating(MigratingBuiltinCost { position, .. }) => assert!(
244                position == index,
245                "migration feture must exist and at correct position"
246            ),
247            BuiltinCost::NotMigrating(_) => {
248                panic!("migration feture must exist and at correct position")
249            }
250        }
251        index += 1;
252    }
253}
254const _: () = validate_position(MIGRATING_BUILTINS_COSTS);
255
256/// Helper function to return ref of migration feature Pubkey at position `index`
257/// from MIGRATING_BUILTINS_COSTS
258pub fn get_migration_feature_id(index: usize) -> &'static Pubkey {
259    MIGRATING_BUILTINS_COSTS
260        .get(index)
261        .expect("valid index of MIGRATING_BUILTINS_COSTS")
262        .1
263        .core_bpf_migration_feature()
264        .expect("migrating builtin")
265}
266
267#[cfg(feature = "dev-context-only-utils")]
268pub fn get_migration_feature_position(feature_id: &Pubkey) -> usize {
269    MIGRATING_BUILTINS_COSTS
270        .iter()
271        .position(|(_, c)| c.core_bpf_migration_feature().expect("migrating builtin") == feature_id)
272        .unwrap()
273}
274
275#[cfg(test)]
276mod test {
277    use super::*;
278
279    #[test]
280    fn test_const_builtin_cost_arrays() {
281        // sanity check to make sure built-ins are declared in the correct array
282        assert!(MIGRATING_BUILTINS_COSTS
283            .iter()
284            .enumerate()
285            .all(|(index, (_, c))| {
286                c.core_bpf_migration_feature().is_some() && c.position() == Some(index)
287            }));
288        assert!(NON_MIGRATING_BUILTINS_COSTS
289            .iter()
290            .all(|(_, c)| c.core_bpf_migration_feature().is_none()));
291    }
292
293    #[test]
294    fn test_get_builtin_instruction_cost() {
295        // use native cost if no migration planned
296        assert_eq!(
297            Some(solana_compute_budget_program::DEFAULT_COMPUTE_UNITS),
298            get_builtin_instruction_cost(&compute_budget::id(), &FeatureSet::all_enabled())
299        );
300
301        // use native cost if migration is planned but not activated
302        assert_eq!(
303            Some(solana_stake_program::stake_instruction::DEFAULT_COMPUTE_UNITS),
304            get_builtin_instruction_cost(&solana_stake_program::id(), &FeatureSet::default())
305        );
306
307        // None if migration is planned and activated, in which case, it's no longer builtin
308        assert!(get_builtin_instruction_cost(
309            &solana_stake_program::id(),
310            &FeatureSet::all_enabled()
311        )
312        .is_none());
313
314        // None if not builtin
315        assert!(
316            get_builtin_instruction_cost(&Pubkey::new_unique(), &FeatureSet::default()).is_none()
317        );
318        assert!(
319            get_builtin_instruction_cost(&Pubkey::new_unique(), &FeatureSet::all_enabled())
320                .is_none()
321        );
322    }
323
324    #[test]
325    fn test_get_builtin_migration_feature_index() {
326        assert!(matches!(
327            get_builtin_migration_feature_index(&Pubkey::new_unique()),
328            BuiltinMigrationFeatureIndex::NotBuiltin
329        ));
330        assert!(matches!(
331            get_builtin_migration_feature_index(&compute_budget::id()),
332            BuiltinMigrationFeatureIndex::BuiltinNoMigrationFeature,
333        ));
334        let feature_index = get_builtin_migration_feature_index(&solana_stake_program::id());
335        assert!(matches!(
336            feature_index,
337            BuiltinMigrationFeatureIndex::BuiltinWithMigrationFeature(_)
338        ));
339        let BuiltinMigrationFeatureIndex::BuiltinWithMigrationFeature(feature_index) =
340            feature_index
341        else {
342            panic!("expect migrating builtin")
343        };
344        assert_eq!(
345            get_migration_feature_id(feature_index),
346            &feature_set::migrate_stake_program_to_core_bpf::id()
347        );
348        let feature_index = get_builtin_migration_feature_index(&solana_config_program::id());
349        assert!(matches!(
350            feature_index,
351            BuiltinMigrationFeatureIndex::BuiltinWithMigrationFeature(_)
352        ));
353        let BuiltinMigrationFeatureIndex::BuiltinWithMigrationFeature(feature_index) =
354            feature_index
355        else {
356            panic!("expect migrating builtin")
357        };
358        assert_eq!(
359            get_migration_feature_id(feature_index),
360            &feature_set::migrate_config_program_to_core_bpf::id()
361        );
362        let feature_index =
363            get_builtin_migration_feature_index(&address_lookup_table::program::id());
364        assert!(matches!(
365            feature_index,
366            BuiltinMigrationFeatureIndex::BuiltinWithMigrationFeature(_)
367        ));
368        let BuiltinMigrationFeatureIndex::BuiltinWithMigrationFeature(feature_index) =
369            feature_index
370        else {
371            panic!("expect migrating builtin")
372        };
373        assert_eq!(
374            get_migration_feature_id(feature_index),
375            &feature_set::migrate_address_lookup_table_program_to_core_bpf::id()
376        );
377    }
378
379    #[test]
380    #[should_panic(expected = "valid index of MIGRATING_BUILTINS_COSTS")]
381    fn test_get_migration_feature_id_invalid_index() {
382        let _ = get_migration_feature_id(MIGRATING_BUILTINS_COSTS.len() + 1);
383    }
384}