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