Skip to content

Commit

Permalink
Merge pull request #3153 from dusk-network/mocello/3150-stake_tests
Browse files Browse the repository at this point in the history
stake-contract: Add tests
  • Loading branch information
moCello authored Dec 13, 2024
2 parents aac2727 + e4614b8 commit 2aacfcb
Show file tree
Hide file tree
Showing 7 changed files with 683 additions and 166 deletions.
1 change: 1 addition & 0 deletions contracts/stake/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ rusk-abi = { workspace = true, features = ["abi", "dlmalloc"] }
rusk-abi = { workspace = true, features = ["host"] }
execution-core = { workspace = true, features = ["zk"] }
rusk-prover = { workspace = true }
wallet-core = { workspace = true }
rand = { workspace = true }
ff = { workspace = true }
criterion = { workspace = true }
Expand Down
122 changes: 105 additions & 17 deletions contracts/stake/tests/common/assert.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,19 +5,24 @@
// Copyright (c) DUSK NETWORK. All rights reserved.

use dusk_bytes::Serializable;
use execution_core::signatures::bls::PublicKey as BlsPublicKey;
use execution_core::stake::{
Reward, SlashEvent, StakeData, StakeEvent, STAKE_CONTRACT,
};
use execution_core::transfer::moonlight::AccountData;
use execution_core::transfer::TRANSFER_CONTRACT;
use execution_core::Event;
use rkyv::{check_archived_root, Deserialize, Infallible};
use rusk_abi::Session;

use execution_core::{
signatures::bls::PublicKey as BlsPublicKey,
stake::{Reward, SlashEvent, StakeEvent},
Event,
};
use super::utils::GAS_LIMIT;

pub fn assert_event<S>(
pub fn assert_stake_event<S>(
events: &Vec<Event>,
topic: S,
should_pk: &BlsPublicKey,
should_amount: u64,
should_value: u64,
should_locked: u64,
) where
S: AsRef<str>,
{
Expand All @@ -27,25 +32,54 @@ pub fn assert_event<S>(
.find(|e| e.topic == topic)
.expect(&format!("event: {topic} should exist in the event list",));

if topic == "reward" {
let reward_event_data = rkyv::from_bytes::<Vec<Reward>>(&event.data)
.expect("Reward event data should deserialize correctly");

assert!(reward_event_data.iter().any(|reward| {
&reward.account == should_pk && reward.value == should_amount
}))
} else {
if topic == "stake" || topic == "unstake" || topic == "withdraw" {
let staking_event_data =
check_archived_root::<StakeEvent>(event.data.as_slice())
.expect("Stake event data should deserialize correctly");
let staking_event_data: StakeEvent = staking_event_data
.deserialize(&mut Infallible)
.expect("Infallible");
assert_eq!(staking_event_data.value, should_amount);
assert_eq!(
staking_event_data.value, should_value,
"Stake-event: value incorrect"
);
assert_eq!(
staking_event_data.locked, should_locked,
"Stake-event: locked incorrect"
);
assert_eq!(
staking_event_data.keys.account.to_bytes(),
should_pk.to_bytes()
should_pk.to_bytes(),
"Stake-event: stake key incorrect"
);
} else {
panic!("{topic} topic cannot be verified with assert_stake_event");
}
}

pub fn assert_reward_event<S>(
events: &Vec<Event>,
topic: S,
should_pk: &BlsPublicKey,
should_value: u64,
) where
S: AsRef<str>,
{
let topic = topic.as_ref();
let event = events
.iter()
.find(|e| e.topic == topic)
.expect(&format!("event: {topic} should exist in the event list",));

if topic == "reward" {
let reward_event_data = rkyv::from_bytes::<Vec<Reward>>(&event.data)
.expect("Reward event data should deserialize correctly");

assert!(reward_event_data.iter().any(|reward| {
&reward.account == should_pk && reward.value == should_value
}))
} else {
panic!("{topic} topic cannot be verified with assert_reward_event");
}
}

Expand Down Expand Up @@ -81,3 +115,57 @@ pub fn assert_slash_event<S, E: Into<Option<u64>>>(
panic!("{topic} topic cannot be verified with assert_slash_event");
}
}

pub fn assert_stake(
session: &mut Session,
stake_pk: &BlsPublicKey,
expected_total: u64,
expected_locked: u64,
expected_reward: u64,
) {
let stake_data: Option<StakeData> = session
.call(STAKE_CONTRACT, "get_stake", stake_pk, GAS_LIMIT)
.expect("Getting the stake should succeed")
.data;

if expected_total != 0 || expected_reward != 0 {
let stake_data =
stake_data.expect("There should be a stake for the given key");

let amount =
stake_data.amount.expect("There should be an amount staked");

assert_eq!(
amount.total_funds(),
expected_total,
"Total stake incorrect"
);
assert_eq!(amount.locked, expected_locked, "Locked stake incorrect");
assert_eq!(
stake_data.reward, expected_reward,
"Stake reward incorrect"
);
} else {
assert!(stake_data.is_none());
}
}

pub fn assert_moonlight(
session: &mut Session,
moonlight_pk: &BlsPublicKey,
expected_balance: u64,
expected_nonce: u64,
) {
let moonlight_account: AccountData = session
.call(TRANSFER_CONTRACT, "account", moonlight_pk, GAS_LIMIT)
.map(|r| r.data)
.expect("Getting the moonlight account should succeed");
assert_eq!(
moonlight_account.balance, expected_balance,
"Moonlight balance incorrect"
);
assert_eq!(
moonlight_account.nonce, expected_nonce,
"Moonlight nonce incorrect"
);
}
39 changes: 19 additions & 20 deletions contracts/stake/tests/common/init.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,24 +4,20 @@
//
// Copyright (c) DUSK NETWORK. All rights reserved.

use rand::{CryptoRng, RngCore};

use execution_core::{
stake::STAKE_CONTRACT,
transfer::{
phoenix::{Note, PublicKey as PhoenixPublicKey},
TRANSFER_CONTRACT,
},
JubJubScalar,
use execution_core::stake::STAKE_CONTRACT;
use execution_core::transfer::{
phoenix::{Note, PublicKey as PhoenixPublicKey},
TRANSFER_CONTRACT,
};
use execution_core::JubJubScalar;
use ff::Field;
use rand::{CryptoRng, RngCore};
use rusk_abi::{ContractData, Session, VM};

use crate::common::utils::update_root;
use crate::common::utils::{update_root, GAS_LIMIT};

const OWNER: [u8; 32] = [0; 32];
pub const CHAIN_ID: u8 = 0xFA;
const POINT_LIMIT: u64 = 0x100_000_000;

/// Instantiate the virtual machine with the transfer contract deployed, with a
/// single note owned by the given public spend key.
Expand All @@ -31,35 +27,37 @@ pub fn instantiate<Rng: RngCore + CryptoRng>(
pk: &PhoenixPublicKey,
genesis_value: u64,
) -> Session {
let mut session = rusk_abi::new_genesis_session(vm, CHAIN_ID);

// deploy transfer-contract
let transfer_bytecode = include_bytes!(
"../../../../target/dusk/wasm64-unknown-unknown/release/transfer_contract.wasm"
);
let stake_bytecode = include_bytes!(
"../../../../target/dusk/wasm32-unknown-unknown/release/stake_contract.wasm"
);

let mut session = rusk_abi::new_genesis_session(vm, CHAIN_ID);

session
.deploy(
transfer_bytecode,
ContractData::builder()
.owner(OWNER)
.contract_id(TRANSFER_CONTRACT),
POINT_LIMIT,
GAS_LIMIT,
)
.expect("Deploying the transfer contract should succeed");

// deploy stake-contract
let stake_bytecode = include_bytes!(
"../../../../target/dusk/wasm32-unknown-unknown/release/stake_contract.wasm"
);
session
.deploy(
stake_bytecode,
ContractData::builder()
.owner(OWNER)
.contract_id(STAKE_CONTRACT),
POINT_LIMIT,
GAS_LIMIT,
)
.expect("Deploying the stake contract should succeed");

// create genesis-note
let sender_blinder = [
JubJubScalar::random(&mut *rng),
JubJubScalar::random(&mut *rng),
Expand All @@ -73,10 +71,11 @@ pub fn instantiate<Rng: RngCore + CryptoRng>(
TRANSFER_CONTRACT,
"push_note",
&(0u64, genesis_note),
POINT_LIMIT,
GAS_LIMIT,
)
.expect("Pushing genesis note should succeed");

// update root of the tree of notes after genesis note has been pushed
update_root(&mut session).expect("Updating the root should succeed");

// sets the block height for all subsequent operations to 1
Expand Down
49 changes: 26 additions & 23 deletions contracts/stake/tests/common/utils.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,24 +6,21 @@

use std::sync::mpsc;

use rand::rngs::StdRng;

use execution_core::{
transfer::{
data::TransactionData,
phoenix::{
Note, NoteLeaf, NoteOpening, NoteTreeItem,
PublicKey as PhoenixPublicKey, SecretKey as PhoenixSecretKey,
Transaction as PhoenixTransaction, ViewKey as PhoenixViewKey,
},
Transaction, TRANSFER_CONTRACT,
},
BlsScalar, ContractError,
use execution_core::transfer::data::TransactionData;
use execution_core::transfer::phoenix::{
Note, NoteLeaf, NoteOpening, NoteTreeItem, PublicKey as PhoenixPublicKey,
SecretKey as PhoenixSecretKey, Transaction as PhoenixTransaction,
ViewKey as PhoenixViewKey,
};
use execution_core::transfer::{Transaction, TRANSFER_CONTRACT};
use execution_core::LUX;
use execution_core::{BlsScalar, ContractError};
use rand::rngs::StdRng;
use rusk_abi::{CallReceipt, PiecrustError, Session};
use rusk_prover::LocalProver;

const POINT_LIMIT: u64 = 0x100000000;
pub const GAS_LIMIT: u64 = 0x100_000_000;
pub const GAS_PRICE: u64 = LUX;

pub fn leaves_from_height(
session: &mut Session,
Expand Down Expand Up @@ -55,7 +52,7 @@ pub fn leaves_from_pos(
TRANSFER_CONTRACT,
"leaves_from_pos",
&pos,
POINT_LIMIT,
GAS_LIMIT,
feeder,
)?;

Expand All @@ -67,13 +64,13 @@ pub fn leaves_from_pos(

pub fn update_root(session: &mut Session) -> Result<(), PiecrustError> {
session
.call(TRANSFER_CONTRACT, "update_root", &(), POINT_LIMIT)
.call(TRANSFER_CONTRACT, "update_root", &(), GAS_LIMIT)
.map(|r| r.data)
}

pub fn root(session: &mut Session) -> Result<BlsScalar, PiecrustError> {
session
.call(TRANSFER_CONTRACT, "root", &(), POINT_LIMIT)
.call(TRANSFER_CONTRACT, "root", &(), GAS_LIMIT)
.map(|r| r.data)
}

Expand All @@ -82,13 +79,13 @@ pub fn opening(
pos: u64,
) -> Result<Option<NoteOpening>, PiecrustError> {
session
.call(TRANSFER_CONTRACT, "opening", &pos, POINT_LIMIT)
.call(TRANSFER_CONTRACT, "opening", &pos, GAS_LIMIT)
.map(|r| r.data)
}

pub fn chain_id(session: &mut Session) -> Result<u8, PiecrustError> {
session
.call(TRANSFER_CONTRACT, "chain_id", &(), POINT_LIMIT)
.call(TRANSFER_CONTRACT, "chain_id", &(), GAS_LIMIT)
.map(|r| r.data)
}

Expand Down Expand Up @@ -146,15 +143,21 @@ pub fn create_transaction<const I: usize>(
session: &mut Session,
sender_sk: &PhoenixSecretKey,
refund_pk: &PhoenixPublicKey,
receiver_pk: &PhoenixPublicKey,
gas_limit: u64,
gas_price: u64,
input_pos: [u64; I],
transfer_value: u64,
obfuscated_transaction: bool,
deposit: u64,
data: Option<impl Into<TransactionData>>,
) -> Transaction {
// in stake transactions the sender, receiver and refund keys are the same
let receiver_pk = refund_pk;

// in stake transactions there is no transfer value
let transfer_value = 0;

// in stake transactions the transfer-note is transparent
let obfuscate_transfer_note = false;

// Get the root of the tree of phoenix-notes.
let root = root(session).expect("Getting the anchor should be successful");

Expand Down Expand Up @@ -192,7 +195,7 @@ pub fn create_transaction<const I: usize>(
inputs,
root,
transfer_value,
obfuscated_transaction,
obfuscate_transfer_note,
deposit,
gas_limit,
gas_price,
Expand Down
6 changes: 3 additions & 3 deletions contracts/stake/tests/events.rs
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ use execution_core::{
};
use rusk_abi::PiecrustError;

use crate::common::assert::assert_event;
use crate::common::assert::assert_reward_event;
use crate::common::init::instantiate;

const GENESIS_VALUE: u64 = dusk(1_000_000.0);
Expand Down Expand Up @@ -83,7 +83,7 @@ fn reward_slash() -> Result<(), PiecrustError> {

let receipt =
session.call::<_, ()>(STAKE_CONTRACT, "reward", &rewards, u64::MAX)?;
assert_event(&receipt.events, "reward", &stake_pk, reward_amount);
assert_reward_event(&receipt.events, "reward", &stake_pk, reward_amount);

let receipt = session.call::<_, ()>(
STAKE_CONTRACT,
Expand Down Expand Up @@ -227,7 +227,7 @@ fn stake_hard_slash() -> Result<(), PiecrustError> {

let receipt =
session.call::<_, ()>(STAKE_CONTRACT, "reward", &rewards, u64::MAX)?;
assert_event(&receipt.events, "reward", &stake_pk, reward_amount);
assert_reward_event(&receipt.events, "reward", &stake_pk, reward_amount);

// Simple hard fault post-reward (slash 10%)
// Rewards should reset 'hard_faults'
Expand Down
Loading

0 comments on commit 2aacfcb

Please sign in to comment.