tempo_precompiles/validator_config_v2/
dispatch.rs1use super::*;
4use crate::{Precompile, charge_input_cost, dispatch_call, mutate, mutate_void, view};
5use alloy::{primitives::Address, sol_types::SolInterface};
6use revm::precompile::PrecompileResult;
7use tempo_contracts::precompiles::IValidatorConfigV2::IValidatorConfigV2Calls;
8
9impl Precompile for ValidatorConfigV2 {
10 fn call(&mut self, calldata: &[u8], msg_sender: Address) -> PrecompileResult {
11 if let Some(err) = charge_input_cost(&mut self.storage, calldata) {
12 return err;
13 }
14
15 if !self.storage.spec().is_t2() {
17 return Ok(self.storage.success_output(Default::default()));
18 }
19
20 dispatch_call(
21 calldata,
22 &[],
23 IValidatorConfigV2Calls::abi_decode,
24 |call| match call {
25 IValidatorConfigV2Calls::owner(call) => view(call, |_| self.owner()),
26 IValidatorConfigV2Calls::getActiveValidators(call) => {
27 view(call, |_| self.get_active_validators())
28 }
29 IValidatorConfigV2Calls::getInitializedAtHeight(call) => {
30 view(call, |_| self.get_initialized_at_height())
31 }
32 IValidatorConfigV2Calls::validatorCount(call) => {
33 view(call, |_| self.validator_count())
34 }
35 IValidatorConfigV2Calls::validatorByIndex(call) => {
36 view(call, |c| self.validator_by_index(c.index))
37 }
38 IValidatorConfigV2Calls::validatorByAddress(call) => {
39 view(call, |c| self.validator_by_address(c.validatorAddress))
40 }
41 IValidatorConfigV2Calls::validatorByPublicKey(call) => {
42 view(call, |c| self.validator_by_public_key(c.publicKey))
43 }
44 IValidatorConfigV2Calls::getNextNetworkIdentityRotationEpoch(call) => {
45 view(call, |_| self.get_next_network_identity_rotation_epoch())
46 }
47 IValidatorConfigV2Calls::isInitialized(call) => {
48 view(call, |_| self.is_initialized())
49 }
50
51 IValidatorConfigV2Calls::addValidator(call) => {
52 mutate(call, msg_sender, |s, c| self.add_validator(s, c))
53 }
54 IValidatorConfigV2Calls::deactivateValidator(call) => {
55 mutate_void(call, msg_sender, |s, c| self.deactivate_validator(s, c))
56 }
57 IValidatorConfigV2Calls::rotateValidator(call) => {
58 mutate_void(call, msg_sender, |s, c| self.rotate_validator(s, c))
59 }
60 IValidatorConfigV2Calls::setFeeRecipient(call) => {
61 mutate_void(call, msg_sender, |s, c| self.set_fee_recipient(s, c))
62 }
63 IValidatorConfigV2Calls::setIpAddresses(call) => {
64 mutate_void(call, msg_sender, |s, c| self.set_ip_addresses(s, c))
65 }
66 IValidatorConfigV2Calls::transferValidatorOwnership(call) => {
67 mutate_void(call, msg_sender, |s, c| {
68 self.transfer_validator_ownership(s, c)
69 })
70 }
71 IValidatorConfigV2Calls::transferOwnership(call) => {
72 mutate_void(call, msg_sender, |s, c| self.transfer_ownership(s, c))
73 }
74 IValidatorConfigV2Calls::setNetworkIdentityRotationEpoch(call) => {
75 mutate_void(call, msg_sender, |s, c| {
76 self.set_network_identity_rotation_epoch(s, c)
77 })
78 }
79 IValidatorConfigV2Calls::migrateValidator(call) => {
80 mutate_void(call, msg_sender, |s, c| self.migrate_validator(s, c))
81 }
82 IValidatorConfigV2Calls::initializeIfMigrated(call) => {
83 mutate_void(call, msg_sender, |s, _| self.initialize_if_migrated(s))
84 }
85 },
86 )
87 }
88}
89
90#[cfg(test)]
91mod tests {
92 use super::*;
93 use crate::{
94 expect_precompile_revert,
95 storage::{StorageCtx, hashmap::HashMapStorageProvider},
96 test_util::{assert_full_coverage, check_selector_coverage},
97 };
98 use alloy::{
99 primitives::{Address, FixedBytes},
100 sol_types::{SolCall, SolValue},
101 };
102 use tempo_chainspec::hardfork::TempoHardfork;
103 use tempo_contracts::precompiles::{
104 IValidatorConfigV2, IValidatorConfigV2::IValidatorConfigV2Calls, ValidatorConfigV2Error,
105 };
106
107 #[test]
108 fn test_pre_t2_returns_empty_success() -> eyre::Result<()> {
109 let owner = Address::random();
110
111 let mut storage = HashMapStorageProvider::new_with_spec(1, TempoHardfork::T1);
113 StorageCtx::enter(&mut storage, || -> eyre::Result<()> {
114 let mut vc = ValidatorConfigV2::new();
115 vc.initialize(owner)?;
116
117 let owner_call = IValidatorConfigV2::ownerCall {};
119 let calldata = owner_call.abi_encode();
120 let result = vc.call(&calldata, owner)?;
121
122 assert!(!result.is_revert(), "Pre-T2 call should not revert");
123 assert!(
124 result.bytes.is_empty(),
125 "Pre-T2 call should return empty bytes"
126 );
127
128 Ok(())
129 })?;
130
131 let mut storage = HashMapStorageProvider::new_with_spec(1, TempoHardfork::T0);
133 StorageCtx::enter(&mut storage, || -> eyre::Result<()> {
134 let mut vc = ValidatorConfigV2::new();
135 vc.initialize(owner)?;
136
137 let calldata = IValidatorConfigV2::ownerCall {}.abi_encode();
138 let result = vc.call(&calldata, owner)?;
139
140 assert!(!result.is_revert());
141 assert!(result.bytes.is_empty());
142
143 let result = vc.call(&[], owner)?;
145 assert!(!result.is_revert());
146 assert!(result.bytes.is_empty());
147
148 Ok(())
149 })?;
150
151 Ok(())
152 }
153
154 #[test]
155 fn test_t2_dispatch_works() -> eyre::Result<()> {
156 let owner = Address::random();
157
158 let mut storage = HashMapStorageProvider::new_with_spec(1, TempoHardfork::T2);
159 StorageCtx::enter(&mut storage, || -> eyre::Result<()> {
160 let mut vc = ValidatorConfigV2::new();
161 vc.initialize(owner)?;
162
163 let calldata = IValidatorConfigV2::ownerCall {}.abi_encode();
165 let result = vc.call(&calldata, owner)?;
166
167 assert!(!result.is_revert());
168 let decoded = Address::abi_decode(&result.bytes)?;
169 assert_eq!(decoded, owner);
170
171 Ok(())
172 })
173 }
174
175 #[test]
176 fn test_add_validator_dispatch() -> eyre::Result<()> {
177 use commonware_codec::Encode;
178 use commonware_cryptography::{Signer, ed25519::PrivateKey};
179
180 let mut storage = HashMapStorageProvider::new_with_spec(1, TempoHardfork::T2);
181 let owner = Address::random();
182 let validator_addr = Address::random();
183 StorageCtx::enter(&mut storage, || {
184 let mut vc = ValidatorConfigV2::new();
185 vc.initialize(owner)?;
186
187 let seed = rand_08::random::<u64>();
189 let private_key = PrivateKey::from_seed(seed);
190 let public_key_obj = private_key.public_key();
191
192 let mut msg_data = Vec::new();
194 msg_data.extend_from_slice(&1u64.to_be_bytes());
195 msg_data.extend_from_slice(
196 tempo_contracts::precompiles::VALIDATOR_CONFIG_V2_ADDRESS.as_slice(),
197 );
198 msg_data.extend_from_slice(validator_addr.as_slice());
199 msg_data.push(u8::try_from("192.168.1.1:8000".len()).unwrap());
200 msg_data.extend_from_slice(b"192.168.1.1:8000");
201 msg_data.push(u8::try_from("192.168.1.1".len()).unwrap());
202 msg_data.extend_from_slice(b"192.168.1.1");
203 msg_data.extend_from_slice(validator_addr.as_slice());
204 let message = alloy::primitives::keccak256(&msg_data);
205
206 let signature = private_key.sign(VALIDATOR_NS_ADD, message.as_slice());
208
209 let pubkey_bytes = public_key_obj.encode();
211 let mut pubkey_array = [0u8; 32];
212 pubkey_array.copy_from_slice(&pubkey_bytes);
213 let public_key = FixedBytes::<32>::from(pubkey_array);
214
215 let add_call = IValidatorConfigV2::addValidatorCall {
216 validatorAddress: validator_addr,
217 publicKey: public_key,
218 ingress: "192.168.1.1:8000".to_string(),
219 egress: "192.168.1.1".to_string(),
220 feeRecipient: validator_addr,
221 signature: signature.encode().to_vec().into(),
222 };
223 let calldata = add_call.abi_encode();
224
225 let result = vc.call(&calldata, owner)?;
226 assert!(!result.is_revert());
227
228 assert_eq!(vc.validator_count()?, 1);
229 let v = vc.validator_by_index(0)?;
230 assert_eq!(v.validatorAddress, validator_addr);
231 assert_eq!(v.publicKey, public_key);
232
233 Ok(())
234 })
235 }
236
237 #[test]
238 fn test_unauthorized_add_validator_dispatch() -> eyre::Result<()> {
239 let mut storage = HashMapStorageProvider::new_with_spec(1, TempoHardfork::T2);
240 let owner = Address::random();
241 let non_owner = Address::random();
242 let validator_addr = Address::random();
243 StorageCtx::enter(&mut storage, || {
244 let mut vc = ValidatorConfigV2::new();
245 vc.initialize(owner)?;
246
247 let add_call = IValidatorConfigV2::addValidatorCall {
248 validatorAddress: validator_addr,
249 publicKey: FixedBytes::<32>::from([0x42; 32]),
250 ingress: "192.168.1.1:8000".to_string(),
251 egress: "192.168.1.1".to_string(),
252 feeRecipient: validator_addr,
253 signature: vec![0u8; 64].into(),
254 };
255 let calldata = add_call.abi_encode();
256
257 let result = vc.call(&calldata, non_owner);
258 expect_precompile_revert(&result, ValidatorConfigV2Error::unauthorized());
259
260 Ok(())
261 })
262 }
263
264 #[test]
265 fn test_function_selector_dispatch() -> eyre::Result<()> {
266 let sender = Address::random();
267 let owner = Address::random();
268
269 let mut storage = HashMapStorageProvider::new_with_spec(1, TempoHardfork::T2);
271 StorageCtx::enter(&mut storage, || -> eyre::Result<()> {
272 let mut vc = ValidatorConfigV2::new();
273 vc.initialize(owner)?;
274
275 let result = vc.call(&[0x12, 0x34, 0x56, 0x78], sender)?;
276 assert!(result.is_revert());
277
278 let result = vc.call(&[0x12, 0x34], sender)?;
280 assert!(result.is_revert());
281
282 Ok(())
283 })
284 }
285
286 #[test]
287 fn test_selector_coverage() -> eyre::Result<()> {
288 let mut storage = HashMapStorageProvider::new_with_spec(1, TempoHardfork::T2);
289 StorageCtx::enter(&mut storage, || {
290 let mut vc = ValidatorConfigV2::new();
291
292 let unsupported = check_selector_coverage(
293 &mut vc,
294 IValidatorConfigV2Calls::SELECTORS,
295 "IValidatorConfigV2",
296 IValidatorConfigV2Calls::name_by_selector,
297 );
298
299 assert_full_coverage([unsupported]);
300
301 Ok(())
302 })
303 }
304}