@@ -36,3 +36,257 @@ fn apply_tx(ctx: &mut Ctx, tx_data: BatchedTx) -> TxResult {
3636 debug_log ! ( "Created validator {validator_address}" ) ;
3737 Ok ( ( ) )
3838}
39+
40+ #[ cfg( test) ]
41+ mod tests {
42+ use std:: cell:: RefCell ;
43+
44+ use namada_tests:: log:: test;
45+ use namada_tests:: native_vp:: pos:: init_pos;
46+ use namada_tests:: native_vp:: TestNativeVpEnv ;
47+ use namada_tests:: tx:: * ;
48+ use namada_tests:: validation:: PosVp ;
49+ use namada_tx_prelude:: account:: AccountPublicKeysMap ;
50+ use namada_tx_prelude:: address:: testing:: {
51+ established_address_1, established_address_2,
52+ } ;
53+ use namada_tx_prelude:: chain:: ChainId ;
54+ use namada_tx_prelude:: dec:: { Dec , POS_DECIMAL_PRECISION } ;
55+ use namada_tx_prelude:: gas:: VpGasMeter ;
56+ use namada_tx_prelude:: key:: { common, RefTo } ;
57+ use namada_tx_prelude:: proof_of_stake:: parameters:: OwnedPosParams ;
58+ use namada_tx_prelude:: proof_of_stake:: types:: GenesisValidator ;
59+
60+ use super :: * ;
61+
62+ /// Test that a valid signed tx_become_validator is accepted by PoS VP
63+ #[ test]
64+ fn test_valid_become_validator_accepted ( ) {
65+ init_tx_env_with_pos ( ) ;
66+
67+ let validator = established_address_2 ( ) ;
68+ tx_host_env:: with ( |tx_env| {
69+ tx_env. spawn_accounts ( [ validator. clone ( ) ] ) ;
70+ } ) ;
71+ let account_key = key:: testing:: keypair_1 ( ) ;
72+ let consensus_key = key:: testing:: keypair_2 ( ) ;
73+ let protocol_key = key:: testing:: keypair_3 ( ) ;
74+
75+ let eth_hot_key =
76+ key:: testing:: gen_keypair :: < key:: secp256k1:: SigScheme > ( ) ;
77+ let eth_cold_key =
78+ key:: testing:: gen_keypair :: < key:: secp256k1:: SigScheme > ( ) ;
79+ let become_validator = BecomeValidator {
80+ address : validator. clone ( ) ,
81+ consensus_key : consensus_key. to_public ( ) ,
82+ eth_cold_key : eth_cold_key. ref_to ( ) ,
83+ eth_hot_key : eth_hot_key. ref_to ( ) ,
84+ protocol_key : protocol_key. to_public ( ) ,
85+ commission_rate : Dec :: new ( 5 , 2 ) . expect ( "Cannot fail" ) ,
86+ max_commission_rate_change : Dec :: new ( 1 , 2 ) . expect ( "Cannot fail" ) ,
87+ email : "[email protected] " . to_owned ( ) , 88+ description : None ,
89+ website : None ,
90+ discord_handle : None ,
91+ avatar : None ,
92+ name : None ,
93+ } ;
94+
95+ apply_become_validator_tx (
96+ become_validator,
97+ account_key,
98+ vec ! [
99+ consensus_key,
100+ protocol_key,
101+ common:: SecretKey :: Secp256k1 ( eth_hot_key) ,
102+ common:: SecretKey :: Secp256k1 ( eth_cold_key) ,
103+ ] ,
104+ )
105+ . unwrap ( ) ;
106+
107+ let result = run_pos_vp ( ) ;
108+ assert ! (
109+ result. is_ok( ) ,
110+ "PoS Validity predicate must accept this transaction, but got \
111+ {result:?}",
112+ ) ;
113+ }
114+
115+ /// Test that tx_become_validator missing a signature for one of its keys
116+ /// fails
117+ #[ test]
118+ fn test_become_validator_missing_sig_fails ( ) {
119+ // Remove one of the 4 other keys used for the validator from tx auth
120+ for removed_key_ix in 0 ..4 {
121+ init_tx_env_with_pos ( ) ;
122+
123+ let validator = established_address_2 ( ) ;
124+ tx_host_env:: with ( |tx_env| {
125+ tx_env. spawn_accounts ( [ validator. clone ( ) ] ) ;
126+ } ) ;
127+ let account_key = key:: testing:: keypair_1 ( ) ;
128+ let consensus_key = key:: testing:: keypair_2 ( ) ;
129+ let protocol_key = key:: testing:: keypair_3 ( ) ;
130+
131+ let eth_hot_key =
132+ key:: testing:: gen_keypair :: < key:: secp256k1:: SigScheme > ( ) ;
133+ let eth_cold_key =
134+ key:: testing:: gen_keypair :: < key:: secp256k1:: SigScheme > ( ) ;
135+ let become_validator = BecomeValidator {
136+ address : validator. clone ( ) ,
137+ consensus_key : consensus_key. to_public ( ) ,
138+ eth_cold_key : eth_cold_key. ref_to ( ) ,
139+ eth_hot_key : eth_hot_key. ref_to ( ) ,
140+ protocol_key : protocol_key. to_public ( ) ,
141+ commission_rate : Dec :: new ( 5 , 2 ) . unwrap ( ) ,
142+ max_commission_rate_change : Dec :: new ( 1 , 2 ) . unwrap ( ) ,
143+ email : "[email protected] " . to_owned ( ) , 144+ description : None ,
145+ website : None ,
146+ discord_handle : None ,
147+ avatar : None ,
148+ name : None ,
149+ } ;
150+
151+ let mut other_keys = vec ! [
152+ consensus_key,
153+ protocol_key,
154+ common:: SecretKey :: Secp256k1 ( eth_hot_key) ,
155+ common:: SecretKey :: Secp256k1 ( eth_cold_key) ,
156+ ] ;
157+ other_keys. remove ( removed_key_ix) ;
158+
159+ let result = apply_become_validator_tx (
160+ become_validator,
161+ account_key,
162+ other_keys,
163+ ) ;
164+
165+ assert ! ( result. is_err( ) , "Tx should fail, but got {result:?}" , ) ;
166+ }
167+ }
168+
169+ /// Check that invalid commission rates are rejected by PoS VP.
170+ #[ test]
171+ fn test_invalid_commission_rate_rejected ( ) {
172+ for commission_rate in
173+ [ -Dec :: one ( ) , -( Dec :: new ( 1 , POS_DECIMAL_PRECISION ) . unwrap ( ) ) ]
174+ {
175+ init_tx_env_with_pos ( ) ;
176+
177+ let validator = established_address_2 ( ) ;
178+ tx_host_env:: with ( |tx_env| {
179+ tx_env. spawn_accounts ( [ validator. clone ( ) ] ) ;
180+ } ) ;
181+ let account_key = key:: testing:: keypair_1 ( ) ;
182+ let consensus_key = key:: testing:: keypair_2 ( ) ;
183+ let protocol_key = key:: testing:: keypair_3 ( ) ;
184+
185+ let eth_hot_key =
186+ key:: testing:: gen_keypair :: < key:: secp256k1:: SigScheme > ( ) ;
187+ let eth_cold_key =
188+ key:: testing:: gen_keypair :: < key:: secp256k1:: SigScheme > ( ) ;
189+ let become_validator = BecomeValidator {
190+ address : validator. clone ( ) ,
191+ consensus_key : consensus_key. to_public ( ) ,
192+ eth_cold_key : eth_cold_key. ref_to ( ) ,
193+ eth_hot_key : eth_hot_key. ref_to ( ) ,
194+ protocol_key : protocol_key. to_public ( ) ,
195+ commission_rate,
196+ max_commission_rate_change : Dec :: one ( ) ,
197+ email : "[email protected] " . to_owned ( ) , 198+ description : None ,
199+ website : None ,
200+ discord_handle : None ,
201+ avatar : None ,
202+ name : None ,
203+ } ;
204+
205+ apply_become_validator_tx (
206+ become_validator,
207+ account_key,
208+ vec ! [
209+ consensus_key,
210+ protocol_key,
211+ common:: SecretKey :: Secp256k1 ( eth_hot_key) ,
212+ common:: SecretKey :: Secp256k1 ( eth_cold_key) ,
213+ ] ,
214+ )
215+ . unwrap ( ) ;
216+
217+ let result = run_pos_vp ( ) ;
218+ assert ! (
219+ result. is_err( ) ,
220+ "PoS Validity predicate must reject this transaction, but got \
221+ {result:?}",
222+ ) ;
223+ }
224+ }
225+
226+ /// Init tx env with a single genesis PoS validator
227+ fn init_tx_env_with_pos ( ) {
228+ tx_host_env:: init ( ) ;
229+
230+ let pos_params = OwnedPosParams :: default ( ) ;
231+ let genesis_validators = [ GenesisValidator {
232+ address : established_address_1 ( ) ,
233+ tokens : pos_params. validator_stake_threshold ,
234+ consensus_key : key:: testing:: keypair_1 ( ) . ref_to ( ) ,
235+ protocol_key : key:: testing:: keypair_2 ( ) . ref_to ( ) ,
236+ commission_rate : Dec :: new ( 5 , 2 ) . unwrap ( ) ,
237+ max_commission_rate_change : Dec :: new ( 1 , 2 ) . unwrap ( ) ,
238+ eth_cold_key : key:: testing:: keypair_3 ( ) . ref_to ( ) ,
239+ eth_hot_key : key:: testing:: keypair_4 ( ) . ref_to ( ) ,
240+ metadata : Default :: default ( ) ,
241+ } ] ;
242+
243+ let _pos_params =
244+ init_pos ( & genesis_validators[ ..] , & pos_params, Epoch ( 0 ) ) ;
245+ }
246+
247+ /// Apply the become_validator tx in `tx_host_env`
248+ fn apply_become_validator_tx (
249+ become_validator : BecomeValidator ,
250+ account_key : common:: SecretKey ,
251+ other_keys : Vec < common:: SecretKey > ,
252+ ) -> TxResult {
253+ let tx_data = become_validator. serialize_to_vec ( ) ;
254+ let mut tx = Tx :: new ( ChainId :: default ( ) , None ) ;
255+
256+ let tx_code = vec ! [ ] ;
257+ tx. add_code ( tx_code, None ) . add_serialized_data ( tx_data) ;
258+
259+ let pks_map = AccountPublicKeysMap :: from_iter (
260+ other_keys
261+ . iter ( )
262+ . map ( common:: SecretKey :: to_public)
263+ . collect :: < Vec < _ > > ( ) ,
264+ ) ;
265+ tx. sign_raw ( other_keys, pks_map, None ) ;
266+
267+ tx. sign_wrapper ( account_key. clone ( ) ) ;
268+
269+ let tx = tx. batch_first_tx ( ) ;
270+ // Put the tx inside the tx_env - it's needed for sig verification
271+ tx_host_env:: with ( |tx_env| {
272+ tx_env. batched_tx = tx. clone ( ) ;
273+ } ) ;
274+ apply_tx ( ctx ( ) , tx)
275+ }
276+
277+ /// Use the `tx_host_env` to run PoS VP
278+ fn run_pos_vp ( ) -> TxResult {
279+ let tx_env = tx_host_env:: take ( ) ;
280+ let gas_meter = RefCell :: new ( VpGasMeter :: new_from_tx_meter (
281+ & tx_env. gas_meter . borrow ( ) ,
282+ ) ) ;
283+ let vp_env = TestNativeVpEnv :: from_tx_env ( tx_env, address:: POS ) ;
284+ let ctx = vp_env. ctx ( & gas_meter) ;
285+ PosVp :: validate_tx (
286+ & ctx,
287+ & vp_env. tx_env . batched_tx . to_ref ( ) ,
288+ & vp_env. keys_changed ,
289+ & vp_env. verifiers ,
290+ )
291+ }
292+ }
0 commit comments