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