clone_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    clone_agave_feature_set::{self as feature_set, FeatureSet},
8    clone_solana_pubkey::Pubkey,
9    clone_solana_sdk_ids::{
10        address_lookup_table, bpf_loader, bpf_loader_deprecated, bpf_loader_upgradeable,
11        compute_budget, config, ed25519_program, loader_v4, secp256k1_program, stake,
12        system_program, vote,
13    },
14    lazy_static::lazy_static,
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: clone_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: clone_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:
141                clone_solana_address_lookup_table_program::processor::DEFAULT_COMPUTE_UNITS,
142            core_bpf_migration_feature:
143                feature_set::migrate_address_lookup_table_program_to_core_bpf::id(),
144            position: 2,
145        }),
146    ),
147];
148
149const NON_MIGRATING_BUILTINS_COSTS: &[(Pubkey, BuiltinCost)] = &[
150    (
151        vote::id(),
152        BuiltinCost::NotMigrating(NotMigratingBuiltinCost {
153            native_cost: clone_solana_vote_program::vote_processor::DEFAULT_COMPUTE_UNITS,
154        }),
155    ),
156    (
157        system_program::id(),
158        BuiltinCost::NotMigrating(NotMigratingBuiltinCost {
159            native_cost: clone_solana_system_program::system_processor::DEFAULT_COMPUTE_UNITS,
160        }),
161    ),
162    (
163        compute_budget::id(),
164        BuiltinCost::NotMigrating(NotMigratingBuiltinCost {
165            native_cost: clone_solana_compute_budget_program::DEFAULT_COMPUTE_UNITS,
166        }),
167    ),
168    (
169        bpf_loader_upgradeable::id(),
170        BuiltinCost::NotMigrating(NotMigratingBuiltinCost {
171            native_cost: clone_solana_bpf_loader_program::UPGRADEABLE_LOADER_COMPUTE_UNITS,
172        }),
173    ),
174    (
175        bpf_loader_deprecated::id(),
176        BuiltinCost::NotMigrating(NotMigratingBuiltinCost {
177            native_cost: clone_solana_bpf_loader_program::DEPRECATED_LOADER_COMPUTE_UNITS,
178        }),
179    ),
180    (
181        bpf_loader::id(),
182        BuiltinCost::NotMigrating(NotMigratingBuiltinCost {
183            native_cost: clone_solana_bpf_loader_program::DEFAULT_LOADER_COMPUTE_UNITS,
184        }),
185    ),
186    (
187        loader_v4::id(),
188        BuiltinCost::NotMigrating(NotMigratingBuiltinCost {
189            native_cost: clone_solana_loader_v4_program::DEFAULT_COMPUTE_UNITS,
190        }),
191    ),
192    // Note: These are precompile, run directly in bank during sanitizing;
193    (
194        secp256k1_program::id(),
195        BuiltinCost::NotMigrating(NotMigratingBuiltinCost { native_cost: 0 }),
196    ),
197    (
198        ed25519_program::id(),
199        BuiltinCost::NotMigrating(NotMigratingBuiltinCost { native_cost: 0 }),
200    ),
201];
202
203lazy_static! {
204    /// A table of 256 booleans indicates whether the first `u8` of a Pubkey exists in
205    /// BUILTIN_INSTRUCTION_COSTS. If the value is true, the Pubkey might be a builtin key;
206    /// if false, it cannot be a builtin key. This table allows for quick filtering of
207    /// builtin program IDs without the need for hashing.
208    pub static ref MAYBE_BUILTIN_KEY: [bool; 256] = {
209        let mut temp_table: [bool; 256] = [false; 256];
210        BUILTIN_INSTRUCTION_COSTS
211            .keys()
212            .for_each(|key| temp_table[key.as_ref()[0] as usize] = true);
213        temp_table
214    };
215}
216
217pub fn get_builtin_instruction_cost<'a>(
218    program_id: &'a Pubkey,
219    feature_set: &'a FeatureSet,
220) -> Option<u64> {
221    BUILTIN_INSTRUCTION_COSTS
222        .get(program_id)
223        .filter(|builtin_cost| !builtin_cost.has_migrated(feature_set))
224        .map(|builtin_cost| builtin_cost.native_cost())
225}
226
227#[cfg(feature = "svm-internal")]
228#[cfg_attr(feature = "svm-internal", qualifiers(pub))]
229enum BuiltinMigrationFeatureIndex {
230    NotBuiltin,
231    BuiltinNoMigrationFeature,
232    BuiltinWithMigrationFeature(usize),
233}
234
235#[cfg(feature = "svm-internal")]
236#[cfg_attr(feature = "svm-internal", qualifiers(pub))]
237fn get_builtin_migration_feature_index(program_id: &Pubkey) -> BuiltinMigrationFeatureIndex {
238    BUILTIN_INSTRUCTION_COSTS.get(program_id).map_or(
239        BuiltinMigrationFeatureIndex::NotBuiltin,
240        |builtin_cost| {
241            builtin_cost.position().map_or(
242                BuiltinMigrationFeatureIndex::BuiltinNoMigrationFeature,
243                BuiltinMigrationFeatureIndex::BuiltinWithMigrationFeature,
244            )
245        },
246    )
247}
248
249/// const function validates `position` correctness at compile time.
250#[allow(dead_code)]
251const fn validate_position(migrating_builtins: &[(Pubkey, BuiltinCost)]) {
252    let mut index = 0;
253    while index < migrating_builtins.len() {
254        match migrating_builtins[index].1 {
255            BuiltinCost::Migrating(MigratingBuiltinCost { position, .. }) => assert!(
256                position == index,
257                "migration feture must exist and at correct position"
258            ),
259            BuiltinCost::NotMigrating(_) => {
260                panic!("migration feture must exist and at correct position")
261            }
262        }
263        index += 1;
264    }
265}
266const _: () = validate_position(MIGRATING_BUILTINS_COSTS);
267
268/// Helper function to return ref of migration feature Pubkey at position `index`
269/// from MIGRATING_BUILTINS_COSTS
270#[cfg(feature = "svm-internal")]
271#[cfg_attr(feature = "svm-internal", qualifiers(pub))]
272pub(crate) fn get_migration_feature_id(index: usize) -> &'static Pubkey {
273    MIGRATING_BUILTINS_COSTS
274        .get(index)
275        .expect("valid index of MIGRATING_BUILTINS_COSTS")
276        .1
277        .core_bpf_migration_feature()
278        .expect("migrating builtin")
279}
280
281#[cfg(feature = "dev-context-only-utils")]
282pub fn get_migration_feature_position(feature_id: &Pubkey) -> usize {
283    MIGRATING_BUILTINS_COSTS
284        .iter()
285        .position(|(_, c)| c.core_bpf_migration_feature().expect("migrating builtin") == feature_id)
286        .unwrap()
287}
288
289#[cfg(test)]
290mod test {
291    use super::*;
292
293    #[test]
294    fn test_const_builtin_cost_arrays() {
295        // sanity check to make sure built-ins are declared in the correct array
296        assert!(MIGRATING_BUILTINS_COSTS
297            .iter()
298            .enumerate()
299            .all(|(index, (_, c))| {
300                c.core_bpf_migration_feature().is_some() && c.position() == Some(index)
301            }));
302        assert!(NON_MIGRATING_BUILTINS_COSTS
303            .iter()
304            .all(|(_, c)| c.core_bpf_migration_feature().is_none()));
305    }
306
307    #[test]
308    fn test_get_builtin_instruction_cost() {
309        // use native cost if no migration planned
310        assert_eq!(
311            Some(clone_solana_compute_budget_program::DEFAULT_COMPUTE_UNITS),
312            get_builtin_instruction_cost(&compute_budget::id(), &FeatureSet::all_enabled())
313        );
314
315        // use native cost if migration is planned but not activated
316        assert_eq!(
317            Some(clone_solana_stake_program::stake_instruction::DEFAULT_COMPUTE_UNITS),
318            get_builtin_instruction_cost(&stake::id(), &FeatureSet::default())
319        );
320
321        // None if migration is planned and activated, in which case, it's no longer builtin
322        assert!(get_builtin_instruction_cost(&stake::id(), &FeatureSet::all_enabled()).is_none());
323
324        // None if not builtin
325        assert!(
326            get_builtin_instruction_cost(&Pubkey::new_unique(), &FeatureSet::default()).is_none()
327        );
328        assert!(
329            get_builtin_instruction_cost(&Pubkey::new_unique(), &FeatureSet::all_enabled())
330                .is_none()
331        );
332    }
333
334    #[test]
335    fn test_get_builtin_migration_feature_index() {
336        assert!(matches!(
337            get_builtin_migration_feature_index(&Pubkey::new_unique()),
338            BuiltinMigrationFeatureIndex::NotBuiltin
339        ));
340        assert!(matches!(
341            get_builtin_migration_feature_index(&compute_budget::id()),
342            BuiltinMigrationFeatureIndex::BuiltinNoMigrationFeature,
343        ));
344        let feature_index = get_builtin_migration_feature_index(&stake::id());
345        assert!(matches!(
346            feature_index,
347            BuiltinMigrationFeatureIndex::BuiltinWithMigrationFeature(_)
348        ));
349        let BuiltinMigrationFeatureIndex::BuiltinWithMigrationFeature(feature_index) =
350            feature_index
351        else {
352            panic!("expect migrating builtin")
353        };
354        assert_eq!(
355            get_migration_feature_id(feature_index),
356            &feature_set::migrate_stake_program_to_core_bpf::id()
357        );
358        let feature_index = get_builtin_migration_feature_index(&config::id());
359        assert!(matches!(
360            feature_index,
361            BuiltinMigrationFeatureIndex::BuiltinWithMigrationFeature(_)
362        ));
363        let BuiltinMigrationFeatureIndex::BuiltinWithMigrationFeature(feature_index) =
364            feature_index
365        else {
366            panic!("expect migrating builtin")
367        };
368        assert_eq!(
369            get_migration_feature_id(feature_index),
370            &feature_set::migrate_config_program_to_core_bpf::id()
371        );
372        let feature_index = get_builtin_migration_feature_index(&address_lookup_table::id());
373        assert!(matches!(
374            feature_index,
375            BuiltinMigrationFeatureIndex::BuiltinWithMigrationFeature(_)
376        ));
377        let BuiltinMigrationFeatureIndex::BuiltinWithMigrationFeature(feature_index) =
378            feature_index
379        else {
380            panic!("expect migrating builtin")
381        };
382        assert_eq!(
383            get_migration_feature_id(feature_index),
384            &feature_set::migrate_address_lookup_table_program_to_core_bpf::id()
385        );
386    }
387
388    #[test]
389    #[should_panic(expected = "valid index of MIGRATING_BUILTINS_COSTS")]
390    fn test_get_migration_feature_id_invalid_index() {
391        let _ = get_migration_feature_id(MIGRATING_BUILTINS_COSTS.len() + 1);
392    }
393}