Skip to content

Commit

Permalink
Merge #5018
Browse files Browse the repository at this point in the history
5018: Make it possible to query BidKind records in smart contracts r=igor-casper a=igor-casper

It's been found desirable for smart contracts to be able to query global state for BidKind records like such:
```rs
let key = Key::BidAddr(BidAddr::DelegatedPurse { validator, delegator });
let bid: BidKind = read_from_key(key);
```
The above snippet works under this PR. This was done by implementing ``CLTyped`` for ``BidKind``, and allowing ``CLValue`` to be constructed from ``StoredValue::BidKind`` using the ``TryFrom`` implementation.

Appropriate tests have also been created, extending the ``staking-stored`` contract with this new functionality.

Co-authored-by: igor-casper <igor@casper.network>
  • Loading branch information
casperlabs-bors-ng[bot] and igor-casper authored Dec 11, 2024
2 parents a7ce99c + 6d70edd commit 5d68b73
Show file tree
Hide file tree
Showing 4 changed files with 217 additions and 8 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,8 @@ use casper_engine_test_support::{
use casper_types::{
runtime_args,
system::auction::{
BidAddr, BidKind, BidsExt, DelegationRate, DelegatorKind, EraInfo, ValidatorBid,
ARG_AMOUNT, ARG_NEW_VALIDATOR, ARG_VALIDATOR,
BidAddr, BidKind, BidsExt, DelegationRate, DelegatorBid, DelegatorKind, EraInfo,
ValidatorBid, ARG_AMOUNT, ARG_NEW_VALIDATOR, ARG_VALIDATOR,
},
GenesisAccount, GenesisValidator, Key, Motes, PublicKey, SecretKey, StoredValue, U512,
};
Expand Down Expand Up @@ -62,6 +62,7 @@ fn should_support_contract_staking() {
let stake = "STAKE".to_string();
let unstake = "UNSTAKE".to_string();
let restake = "RESTAKE".to_string();
let get_staked_amount = "STAKED_AMOUNT".to_string();
let account = *DEFAULT_ACCOUNT_ADDR;
let seed_amount = U512::from(10_000_000_000_000_000_u64);
let delegate_amount = U512::from(5_000_000_000_000_000_u64);
Expand Down Expand Up @@ -153,6 +154,31 @@ fn should_support_contract_staking() {
let pre_delegation_balance = builder.get_purse_balance(contract_purse);
assert_eq!(pre_delegation_balance, seed_amount);

// check delegated amount from contract
builder
.exec(
ExecuteRequestBuilder::contract_call_by_name(
account,
&contract_name,
&entry_point_name,
runtime_args! {
ARG_ACTION => get_staked_amount.clone(),
ARG_VALIDATOR => validator_pk.clone(),
},
)
.build(),
)
.commit()
.expect_success();

let result = builder.get_last_exec_result().unwrap();
let staked_amount: U512 = result.ret().unwrap().to_owned().into_t().unwrap();
assert_eq!(
staked_amount,
U512::zero(),
"staked amount should be zero prior to staking"
);

// stake from contract
builder
.exec(
Expand Down Expand Up @@ -200,6 +226,30 @@ fn should_support_contract_staking() {
);
}

// check delegated amount from contract
builder
.exec(
ExecuteRequestBuilder::contract_call_by_name(
account,
&contract_name,
&entry_point_name,
runtime_args! {
ARG_ACTION => get_staked_amount.clone(),
ARG_VALIDATOR => validator_pk.clone(),
},
)
.build(),
)
.commit()
.expect_success();

let result = builder.get_last_exec_result().unwrap();
let staked_amount: U512 = result.ret().unwrap().to_owned().into_t().unwrap();
assert_eq!(
staked_amount, delegate_amount,
"staked amount should match delegation amount"
);

for _ in 0..=auction_delay {
// crank era
builder.run_auction(timestamp_millis, vec![]);
Expand Down Expand Up @@ -485,3 +535,121 @@ fn should_not_enforce_max_spending_when_main_purse_not_in_use() {
.query(None, delegation_key, &[])
.expect("should have delegation bid");
}

#[ignore]
#[test]
fn should_read_bid_with_vesting_schedule_populated() {
const ARG_ACTION: &str = "action";
let purse_name = "staking_purse".to_string();
let contract_name = "staking".to_string();
let entry_point_name = "run".to_string();
let get_staked_amount = "STAKED_AMOUNT".to_string();
let account = *DEFAULT_ACCOUNT_ADDR;
let seed_amount = U512::from(10_000_000_000_000_000_u64);
let validator_pk = &*DEFAULT_PROPOSER_PUBLIC_KEY;

let mut builder = LmdbWasmTestBuilder::default();
let mut genesis_request = LOCAL_GENESIS_REQUEST.clone();
genesis_request.set_enable_entity(false);
genesis_request.push_genesis_validator(
validator_pk,
GenesisValidator::new(
Motes::new(10_000_000_000_000_000_u64),
DelegationRate::zero(),
),
);
builder.run_genesis(genesis_request);

builder
.exec(
ExecuteRequestBuilder::standard(
account,
STORED_STAKING_CONTRACT_NAME,
runtime_args! {
ARG_AMOUNT => seed_amount
},
)
.build(),
)
.commit()
.expect_success();

let default_account = builder.get_account(account).expect("should have account");
let named_keys = default_account.named_keys();

let contract_key = named_keys
.get(&contract_name)
.expect("contract_name key should exist");

let stored_contract = builder
.query(None, *contract_key, &[])
.expect("should have stored value at contract key");

let contract = stored_contract
.as_contract()
.expect("stored value should be contract");

let contract_named_keys = contract.named_keys();

let contract_purse = contract_named_keys
.get(&purse_name)
.expect("purse_name key should exist")
.into_uref()
.expect("should be a uref");

// Create a mock bid with a vesting schedule initialized.
// This is only there to make sure size constraints are not a problem
// when trying to read this relatively large structure as a guest.
let mut mock_bid = DelegatorBid::locked(
DelegatorKind::Purse(contract_purse.addr()),
U512::from(100_000_000),
contract_purse,
validator_pk.clone(),
0,
);

mock_bid
.vesting_schedule_mut()
.unwrap()
.initialize_with_schedule(U512::from(100_000_000), 0);

let delegation_key = Key::BidAddr(BidAddr::DelegatedPurse {
validator: validator_pk.to_account_hash(),
delegator: contract_purse.addr(),
});

builder.write_data_and_commit(
[(
delegation_key,
StoredValue::BidKind(BidKind::Delegator(Box::new(mock_bid))),
)]
.iter()
.cloned(),
);

builder
.query(None, delegation_key, &[])
.expect("should have delegation bid")
.as_bid_kind()
.expect("should be bidkind")
.vesting_schedule()
.expect("should have vesting schedule")
.locked_amounts()
.expect("should have locked amounts");

builder
.exec(
ExecuteRequestBuilder::contract_call_by_name(
account,
&contract_name,
&entry_point_name,
runtime_args! {
ARG_ACTION => get_staked_amount.clone(),
ARG_VALIDATOR => validator_pk.clone(),
},
)
.build(),
)
.commit()
.expect_success();
}
40 changes: 37 additions & 3 deletions smart_contracts/contracts/test/staking/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,11 @@ use alloc::{
};

use casper_contract::{
contract_api::{runtime, runtime::revert, system},
contract_api::{
runtime::{self, revert},
storage::read_from_key,
system,
},
ext_ffi,
unwrap_or_revert::UnwrapOrRevert,
};
Expand All @@ -18,8 +22,8 @@ use casper_types::{
api_error,
bytesrepr::{self, ToBytes},
runtime_args,
system::auction,
ApiError, Key, PublicKey, URef, U512,
system::auction::{self, BidAddr, BidKind},
ApiError, CLValue, Key, PublicKey, URef, U512,
};

pub const STAKING_ID: &str = "staking_contract";
Expand Down Expand Up @@ -79,6 +83,8 @@ pub fn run() {
stake_all();
} else if action == *"RESTAKE".to_string() {
restake();
} else if action == *"STAKED_AMOUNT".to_string() {
read_staked_amount_gs();
} else {
revert(ApiError::User(StakingError::UnexpectedAction as u16));
}
Expand Down Expand Up @@ -136,6 +142,34 @@ fn stake_all() {
runtime::call_contract::<U512>(contract_hash, auction::METHOD_DELEGATE, args);
}

pub fn read_staked_amount_gs() {
let purse = get_uref_with_user_errors(
STAKING_PURSE,
StakingError::MissingStakingPurse,
StakingError::InvalidStakingPurse,
);

let validator = match runtime::try_get_named_arg::<PublicKey>(ARG_VALIDATOR) {
Some(validator_public_key) => validator_public_key,
None => revert(ApiError::User(StakingError::MissingValidator as u16)),
};

let key = Key::BidAddr(BidAddr::DelegatedPurse {
validator: validator.to_account_hash(),
delegator: purse.addr(),
});

let bid = read_from_key::<BidKind>(key);

let staked_amount = if let Ok(Some(BidKind::Delegator(delegator_bid))) = bid {
delegator_bid.staked_amount()
} else {
U512::zero()
};

runtime::ret(CLValue::from_t(staked_amount).unwrap_or_revert());
}

fn get_unstaking_args(is_restake: bool) -> casper_types::RuntimeArgs {
let staking_purse = get_uref_with_user_errors(
STAKING_PURSE,
Expand Down
2 changes: 2 additions & 0 deletions types/src/stored_value.rs
Original file line number Diff line number Diff line change
Expand Up @@ -566,6 +566,8 @@ impl TryFrom<StoredValue> for CLValue {
let type_name = stored_value.type_name();
match stored_value {
StoredValue::CLValue(cl_value) => Ok(cl_value),
StoredValue::BidKind(bid_kind) => Ok(CLValue::from_t(bid_kind)
.map_err(|_| TypeMismatch::new("BidKind".to_string(), type_name))?),
StoredValue::ContractPackage(contract_package) => Ok(CLValue::from_t(contract_package)
.map_err(|_error| TypeMismatch::new("ContractPackage".to_string(), type_name))?),
_ => Err(TypeMismatch::new("StoredValue".to_string(), type_name)),
Expand Down
11 changes: 8 additions & 3 deletions types/src/system/auction/bid_kind.rs
Original file line number Diff line number Diff line change
@@ -1,10 +1,9 @@
use crate::{
bytesrepr,
bytesrepr::{FromBytes, ToBytes, U8_SERIALIZED_LENGTH},
bytesrepr::{self, FromBytes, ToBytes, U8_SERIALIZED_LENGTH},
system::auction::{
bid::VestingSchedule, Bid, BidAddr, DelegatorBid, ValidatorBid, ValidatorCredit,
},
EraId, PublicKey, URef, U512,
CLType, CLTyped, EraId, PublicKey, URef, U512,
};

use crate::system::auction::{
Expand Down Expand Up @@ -332,6 +331,12 @@ impl BidKind {
}
}

impl CLTyped for BidKind {
fn cl_type() -> CLType {
CLType::Any
}
}

impl ToBytes for BidKind {
fn to_bytes(&self) -> Result<Vec<u8>, bytesrepr::Error> {
let mut result = bytesrepr::allocate_buffer(self)?;
Expand Down

0 comments on commit 5d68b73

Please sign in to comment.