tempo_precompiles/validator_config/
dispatch.rs

1use super::{IValidatorConfig, ValidatorConfig};
2use crate::{Precompile, fill_precompile_output, input_cost, mutate_void, unknown_selector, view};
3use alloy::{primitives::Address, sol_types::SolCall};
4use revm::precompile::{PrecompileError, PrecompileResult};
5
6impl Precompile for ValidatorConfig {
7    fn call(&mut self, calldata: &[u8], msg_sender: Address) -> PrecompileResult {
8        self.storage
9            .deduct_gas(input_cost(calldata.len()))
10            .map_err(|_| PrecompileError::OutOfGas)?;
11
12        let selector: [u8; 4] = calldata
13            .get(..4)
14            .ok_or_else(|| {
15                PrecompileError::Other("Invalid input: missing function selector".into())
16            })?
17            .try_into()
18            .map_err(|_| PrecompileError::Other("Invalid function selector length".into()))?;
19
20        let result = match selector {
21            // View functions
22            IValidatorConfig::ownerCall::SELECTOR => {
23                view::<IValidatorConfig::ownerCall>(calldata, |_call| self.owner())
24            }
25            IValidatorConfig::getValidatorsCall::SELECTOR => {
26                view::<IValidatorConfig::getValidatorsCall>(calldata, |_call| self.get_validators())
27            }
28
29            // Mutate functions
30            IValidatorConfig::addValidatorCall::SELECTOR => {
31                mutate_void::<IValidatorConfig::addValidatorCall>(
32                    calldata,
33                    msg_sender,
34                    |s, call| self.add_validator(s, call),
35                )
36            }
37            IValidatorConfig::updateValidatorCall::SELECTOR => {
38                mutate_void::<IValidatorConfig::updateValidatorCall>(
39                    calldata,
40                    msg_sender,
41                    |s, call| self.update_validator(s, call),
42                )
43            }
44            IValidatorConfig::changeValidatorStatusCall::SELECTOR => {
45                mutate_void::<IValidatorConfig::changeValidatorStatusCall>(
46                    calldata,
47                    msg_sender,
48                    |s, call| self.change_validator_status(s, call),
49                )
50            }
51            IValidatorConfig::changeOwnerCall::SELECTOR => {
52                mutate_void::<IValidatorConfig::changeOwnerCall>(calldata, msg_sender, |s, call| {
53                    self.change_owner(s, call)
54                })
55            }
56
57            _ => unknown_selector(selector, self.storage.gas_used(), self.storage.spec()),
58        };
59
60        result.map(|res| fill_precompile_output(res, &mut self.storage))
61    }
62}
63
64#[cfg(test)]
65mod tests {
66    use super::*;
67    use crate::{
68        expect_precompile_revert,
69        storage::{StorageCtx, hashmap::HashMapStorageProvider},
70        test_util::{assert_full_coverage, check_selector_coverage},
71    };
72    use alloy::{
73        primitives::{Address, FixedBytes},
74        sol_types::SolValue,
75    };
76    use tempo_chainspec::hardfork::TempoHardfork;
77    use tempo_contracts::precompiles::{
78        IValidatorConfig::IValidatorConfigCalls, ValidatorConfigError,
79    };
80
81    #[test]
82    fn test_function_selector_dispatch() -> eyre::Result<()> {
83        let mut storage = HashMapStorageProvider::new(1).with_spec(TempoHardfork::Moderato);
84        let sender = Address::random();
85        let owner = Address::random();
86        StorageCtx::enter(&mut storage, || {
87            let mut validator_config = ValidatorConfig::new();
88
89            // Initialize with owner
90            validator_config.initialize(owner)?;
91
92            // Test invalid selector - should return Ok with reverted status
93            let result = validator_config.call(&[0x12, 0x34, 0x56, 0x78], sender)?;
94            assert!(result.reverted);
95
96            // Test insufficient calldata
97            let result = validator_config.call(&[0x12, 0x34], sender);
98            assert!(matches!(result, Err(PrecompileError::Other(_))));
99
100            Ok(())
101        })
102    }
103
104    #[test]
105    fn test_owner_view_dispatch() -> eyre::Result<()> {
106        let mut storage = HashMapStorageProvider::new(1);
107        let sender = Address::random();
108        let owner = Address::random();
109        StorageCtx::enter(&mut storage, || {
110            let mut validator_config = ValidatorConfig::new();
111
112            // Initialize with owner
113            validator_config.initialize(owner)?;
114
115            // Call owner() via dispatch
116            let owner_call = IValidatorConfig::ownerCall {};
117            let calldata = owner_call.abi_encode();
118
119            let result = validator_config.call(&calldata, sender)?;
120            // HashMapStorageProvider does not do gas accounting, so we expect 0 here.
121            assert_eq!(result.gas_used, 0);
122
123            // Verify we get the correct owner
124            let decoded = Address::abi_decode(&result.bytes)?;
125            assert_eq!(decoded, owner);
126
127            Ok(())
128        })
129    }
130
131    #[test]
132    fn test_add_validator_dispatch() -> eyre::Result<()> {
133        let mut storage = HashMapStorageProvider::new(1);
134        let owner = Address::random();
135        let validator_addr = Address::random();
136        StorageCtx::enter(&mut storage, || {
137            let mut validator_config = ValidatorConfig::new();
138
139            // Initialize with owner
140            validator_config.initialize(owner)?;
141
142            // Add validator via dispatch
143            let public_key = FixedBytes::<32>::from([0x42; 32]);
144            let add_call = IValidatorConfig::addValidatorCall {
145                newValidatorAddress: validator_addr,
146                publicKey: public_key,
147                active: true,
148                inboundAddress: "192.168.1.1:8000".to_string(),
149                outboundAddress: "192.168.1.1:9000".to_string(),
150            };
151            let calldata = add_call.abi_encode();
152
153            let result = validator_config.call(&calldata, owner)?;
154
155            // HashMapStorageProvider does not have gas accounting, so we expect 0
156            assert_eq!(result.gas_used, 0);
157
158            // Verify validator was added by calling getValidators
159            let validators = validator_config.get_validators()?;
160            assert_eq!(validators.len(), 1);
161            assert_eq!(validators[0].validatorAddress, validator_addr);
162            assert_eq!(validators[0].publicKey, public_key);
163            assert_eq!(validators[0].inboundAddress, "192.168.1.1:8000");
164            assert_eq!(validators[0].outboundAddress, "192.168.1.1:9000");
165            assert!(validators[0].active);
166
167            Ok(())
168        })
169    }
170
171    #[test]
172    fn test_unauthorized_add_validator_dispatch() -> eyre::Result<()> {
173        let mut storage = HashMapStorageProvider::new(1);
174        let owner = Address::random();
175        let non_owner = Address::random();
176        let validator_addr = Address::random();
177        StorageCtx::enter(&mut storage, || {
178            let mut validator_config = ValidatorConfig::new();
179
180            // Initialize with owner
181            validator_config.initialize(owner)?;
182
183            // Try to add validator as non-owner
184            let public_key = FixedBytes::<32>::from([0x42; 32]);
185            let add_call = IValidatorConfig::addValidatorCall {
186                newValidatorAddress: validator_addr,
187                publicKey: public_key,
188                active: true,
189                inboundAddress: "192.168.1.1:8000".to_string(),
190                outboundAddress: "192.168.1.1:9000".to_string(),
191            };
192            let calldata = add_call.abi_encode();
193
194            let result = validator_config.call(&calldata, non_owner);
195            expect_precompile_revert(&result, ValidatorConfigError::unauthorized());
196
197            Ok(())
198        })
199    }
200
201    #[test]
202    fn test_selector_coverage() -> eyre::Result<()> {
203        let mut storage = HashMapStorageProvider::new(1);
204        StorageCtx::enter(&mut storage, || {
205            let mut validator_config = ValidatorConfig::new();
206
207            let unsupported = check_selector_coverage(
208                &mut validator_config,
209                IValidatorConfigCalls::SELECTORS,
210                "IValidatorConfig",
211                IValidatorConfigCalls::name_by_selector,
212            );
213
214            assert_full_coverage([unsupported]);
215
216            Ok(())
217        })
218    }
219}