tempo_precompiles/validator_config/
dispatch.rs1use super::ValidatorConfig;
4use crate::{
5 Precompile, SelectorSchedule, charge_input_cost, dispatch_call, error::TempoPrecompileError,
6 mutate_void, view,
7};
8use alloy::{
9 primitives::Address,
10 sol_types::{SolCall, SolInterface},
11};
12use revm::precompile::PrecompileResult;
13use tempo_chainspec::hardfork::TempoHardfork;
14use tempo_contracts::precompiles::IValidatorConfig::{self, IValidatorConfigCalls};
15
16const T1_ADDED: &[[u8; 4]] = &[IValidatorConfig::changeValidatorStatusByIndexCall::SELECTOR];
17
18impl Precompile for ValidatorConfig {
19 fn call(&mut self, calldata: &[u8], msg_sender: Address) -> PrecompileResult {
20 if let Some(err) = charge_input_cost(&mut self.storage, calldata) {
21 return err;
22 }
23
24 dispatch_call(
25 calldata,
26 &[SelectorSchedule::new(TempoHardfork::T1).with_added(T1_ADDED)],
27 IValidatorConfigCalls::abi_decode,
28 |call| match call {
29 IValidatorConfigCalls::owner(call) => view(call, |_| self.owner()),
31 IValidatorConfigCalls::getValidators(call) => view(call, |_| self.get_validators()),
32 IValidatorConfigCalls::getNextFullDkgCeremony(call) => {
33 view(call, |_| self.get_next_full_dkg_ceremony())
34 }
35 IValidatorConfigCalls::validatorsArray(call) => view(call, |c| {
36 let index =
37 u64::try_from(c.index).map_err(|_| TempoPrecompileError::array_oob())?;
38 self.validators_array(index)
39 }),
40 IValidatorConfigCalls::validators(call) => {
41 view(call, |c| self.validators(c.validator))
42 }
43 IValidatorConfigCalls::validatorCount(call) => {
44 view(call, |_| self.validator_count())
45 }
46
47 IValidatorConfigCalls::addValidator(call) => {
49 mutate_void(call, msg_sender, |s, c| self.add_validator(s, c))
50 }
51 IValidatorConfigCalls::updateValidator(call) => {
52 mutate_void(call, msg_sender, |s, c| self.update_validator(s, c))
53 }
54 IValidatorConfigCalls::changeValidatorStatus(call) => {
55 mutate_void(call, msg_sender, |s, c| self.change_validator_status(s, c))
56 }
57 IValidatorConfigCalls::changeValidatorStatusByIndex(call) => {
58 mutate_void(call, msg_sender, |s, c| {
59 self.change_validator_status_by_index(s, c)
60 })
61 }
62 IValidatorConfigCalls::changeOwner(call) => {
63 mutate_void(call, msg_sender, |s, c| self.change_owner(s, c))
64 }
65 IValidatorConfigCalls::setNextFullDkgCeremony(call) => {
66 mutate_void(call, msg_sender, |s, c| {
67 self.set_next_full_dkg_ceremony(s, c)
68 })
69 }
70 },
71 )
72 }
73}
74
75#[cfg(test)]
76mod tests {
77 use super::*;
78 use crate::{
79 expect_precompile_revert,
80 storage::{StorageCtx, hashmap::HashMapStorageProvider},
81 test_util::{assert_full_coverage, check_selector_coverage},
82 };
83 use alloy::{
84 primitives::{Address, FixedBytes},
85 sol_types::{SolCall, SolValue},
86 };
87
88 use tempo_chainspec::hardfork::TempoHardfork;
89 use tempo_contracts::precompiles::{
90 IValidatorConfig, IValidatorConfig::IValidatorConfigCalls, ValidatorConfigError,
91 };
92
93 #[test]
94 fn test_function_selector_dispatch() -> eyre::Result<()> {
95 let sender = Address::random();
96 let owner = Address::random();
97
98 let mut storage = HashMapStorageProvider::new_with_spec(1, TempoHardfork::T1);
100 StorageCtx::enter(&mut storage, || -> eyre::Result<()> {
101 let mut validator_config = ValidatorConfig::new();
102 validator_config.initialize(owner)?;
103
104 let result = validator_config.call(&[0x12, 0x34, 0x56, 0x78], sender)?;
105 assert!(result.is_revert());
106
107 let result = validator_config.call(&[0x12, 0x34], sender)?;
109 assert!(result.is_revert());
110
111 Ok(())
112 })?;
113
114 let mut storage = HashMapStorageProvider::new_with_spec(1, TempoHardfork::T0);
116 StorageCtx::enter(&mut storage, || {
117 let mut validator_config = ValidatorConfig::new();
118 validator_config.initialize(owner)?;
119
120 let result = validator_config.call(&[0x12, 0x34], sender);
121 let output = result.expect("expected Ok(halt) for short calldata");
122 assert!(output.is_halt());
123
124 Ok(())
125 })
126 }
127
128 #[test]
129 fn test_owner_view_dispatch() -> eyre::Result<()> {
130 let mut storage = HashMapStorageProvider::new(1);
131 let sender = Address::random();
132 let owner = Address::random();
133 StorageCtx::enter(&mut storage, || {
134 let mut validator_config = ValidatorConfig::new();
135
136 validator_config.initialize(owner)?;
138
139 let owner_call = IValidatorConfig::ownerCall {};
141 let calldata = owner_call.abi_encode();
142
143 let result = validator_config.call(&calldata, sender)?;
144 assert_eq!(result.gas_used, 0);
146
147 let decoded = Address::abi_decode(&result.bytes)?;
149 assert_eq!(decoded, owner);
150
151 Ok(())
152 })
153 }
154
155 #[test]
156 fn test_add_validator_dispatch() -> eyre::Result<()> {
157 let mut storage = HashMapStorageProvider::new(1);
158 let owner = Address::random();
159 let validator_addr = Address::random();
160 StorageCtx::enter(&mut storage, || {
161 let mut validator_config = ValidatorConfig::new();
162
163 validator_config.initialize(owner)?;
165
166 let public_key = FixedBytes::<32>::from([0x42; 32]);
168 let add_call = IValidatorConfig::addValidatorCall {
169 newValidatorAddress: validator_addr,
170 publicKey: public_key,
171 active: true,
172 inboundAddress: "192.168.1.1:8000".to_string(),
173 outboundAddress: "192.168.1.1:9000".to_string(),
174 };
175 let calldata = add_call.abi_encode();
176
177 let result = validator_config.call(&calldata, owner)?;
178
179 assert_eq!(result.gas_used, 0);
181
182 let validators = validator_config.get_validators()?;
184 assert_eq!(validators.len(), 1);
185 assert_eq!(validators[0].validatorAddress, validator_addr);
186 assert_eq!(validators[0].publicKey, public_key);
187 assert_eq!(validators[0].inboundAddress, "192.168.1.1:8000");
188 assert_eq!(validators[0].outboundAddress, "192.168.1.1:9000");
189 assert!(validators[0].active);
190
191 Ok(())
192 })
193 }
194
195 #[test]
196 fn test_unauthorized_add_validator_dispatch() -> eyre::Result<()> {
197 let mut storage = HashMapStorageProvider::new(1);
198 let owner = Address::random();
199 let non_owner = Address::random();
200 let validator_addr = Address::random();
201 StorageCtx::enter(&mut storage, || {
202 let mut validator_config = ValidatorConfig::new();
203
204 validator_config.initialize(owner)?;
206
207 let public_key = FixedBytes::<32>::from([0x42; 32]);
209 let add_call = IValidatorConfig::addValidatorCall {
210 newValidatorAddress: validator_addr,
211 publicKey: public_key,
212 active: true,
213 inboundAddress: "192.168.1.1:8000".to_string(),
214 outboundAddress: "192.168.1.1:9000".to_string(),
215 };
216 let calldata = add_call.abi_encode();
217
218 let result = validator_config.call(&calldata, non_owner);
219 expect_precompile_revert(&result, ValidatorConfigError::unauthorized());
220
221 Ok(())
222 })
223 }
224
225 #[test]
226 fn test_selector_coverage() -> eyre::Result<()> {
227 let mut storage = HashMapStorageProvider::new_with_spec(1, TempoHardfork::T1);
228 StorageCtx::enter(&mut storage, || {
229 let mut validator_config = ValidatorConfig::new();
230
231 let unsupported = check_selector_coverage(
232 &mut validator_config,
233 IValidatorConfigCalls::SELECTORS,
234 "IValidatorConfig",
235 IValidatorConfigCalls::name_by_selector,
236 );
237
238 assert_full_coverage([unsupported]);
239
240 Ok(())
241 })
242 }
243
244 #[test]
245 fn test_change_validator_status_by_index_t1_gating() -> eyre::Result<()> {
246 use alloy::sol_types::SolError;
247 use tempo_contracts::precompiles::UnknownFunctionSelector;
248
249 let owner = Address::random();
250 let validator = Address::random();
251 let public_key = FixedBytes::<32>::from([0x42; 32]);
252
253 let mut storage = HashMapStorageProvider::new_with_spec(1, TempoHardfork::T0);
255 StorageCtx::enter(&mut storage, || -> eyre::Result<()> {
256 let mut validator_config = ValidatorConfig::new();
257 validator_config.initialize(owner)?;
258
259 validator_config.add_validator(
261 owner,
262 IValidatorConfig::addValidatorCall {
263 newValidatorAddress: validator,
264 publicKey: public_key,
265 active: true,
266 inboundAddress: "192.168.1.1:8000".to_string(),
267 outboundAddress: "192.168.1.1:9000".to_string(),
268 },
269 )?;
270
271 let call = IValidatorConfig::changeValidatorStatusByIndexCall {
273 index: 0,
274 active: false,
275 };
276 let calldata = call.abi_encode();
277 let result = validator_config.call(&calldata, owner)?;
278
279 assert!(result.is_revert());
280 let decoded = UnknownFunctionSelector::abi_decode(&result.bytes)?;
281 assert_eq!(
282 decoded.selector.0,
283 IValidatorConfig::changeValidatorStatusByIndexCall::SELECTOR
284 );
285
286 Ok(())
287 })?;
288
289 let mut storage = HashMapStorageProvider::new_with_spec(1, TempoHardfork::T1);
291 StorageCtx::enter(&mut storage, || -> eyre::Result<()> {
292 let mut validator_config = ValidatorConfig::new();
293 validator_config.initialize(owner)?;
294
295 validator_config.add_validator(
297 owner,
298 IValidatorConfig::addValidatorCall {
299 newValidatorAddress: validator,
300 publicKey: public_key,
301 active: true,
302 inboundAddress: "192.168.1.1:8000".to_string(),
303 outboundAddress: "192.168.1.1:9000".to_string(),
304 },
305 )?;
306
307 let call = IValidatorConfig::changeValidatorStatusByIndexCall {
309 index: 0,
310 active: false,
311 };
312 let calldata = call.abi_encode();
313 let result = validator_config.call(&calldata, owner)?;
314
315 assert!(
316 !result.is_revert(),
317 "changeValidatorStatusByIndex should succeed in T1"
318 );
319
320 let validators = validator_config.get_validators()?;
322 assert!(!validators[0].active, "Validator should be inactive");
323
324 Ok(())
325 })
326 }
327}