Skip to content

Commit d961d79

Browse files
committed
test: sa003
1 parent 7c43373 commit d961d79

File tree

3 files changed

+261
-0
lines changed

3 files changed

+261
-0
lines changed

wasm/Cargo.lock

Lines changed: 2 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

wasm/tx_become_validator/Cargo.toml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,5 +14,10 @@ namada_tx_prelude.workspace = true
1414
rlsf.workspace = true
1515
getrandom.workspace = true
1616

17+
[dev-dependencies]
18+
namada_tests = {path = "../../crates/tests"}
19+
20+
test-log = {version = "0.2.14", default-features = false, features = ["trace"]}
21+
1722
[lib]
1823
crate-type = ["cdylib"]

wasm/tx_become_validator/src/lib.rs

Lines changed: 254 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -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

Comments
 (0)