solana_builtins_default_costs/
lib.rs

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