diff --git a/Cargo.lock b/Cargo.lock index 423053745b..43d4a1a1c4 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -415,6 +415,14 @@ version = "3.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a3e2c3daef883ecc1b5d58c15adae93470a91d425f3532ba1695849656af3fc1" +[[package]] +name = "burn" +version = "0.1.0" +dependencies = [ + "casper-contract", + "casper-types", +] + [[package]] name = "byteorder" version = "1.4.3" diff --git a/binary_port/src/key_prefix.rs b/binary_port/src/key_prefix.rs new file mode 100644 index 0000000000..c8eed0270f --- /dev/null +++ b/binary_port/src/key_prefix.rs @@ -0,0 +1,171 @@ +#[cfg(any(feature = "testing", test))] +use casper_types::testing::TestRng; +use casper_types::{ + account::AccountHash, + bytesrepr::{self, FromBytes, ToBytes, U8_SERIALIZED_LENGTH}, + contract_messages::TopicNameHash, + system::{auction::BidAddrTag, mint::BalanceHoldAddrTag}, + EntityAddr, KeyTag, URefAddr, +}; +#[cfg(any(feature = "testing", test))] +use rand::Rng; + +/// Key prefixes used for querying the global state. +#[derive(Debug, PartialEq, Eq, Clone, PartialOrd, Ord, Hash)] +pub enum KeyPrefix { + /// Retrieves all delegator bid addresses for a given validator. + DelegatorBidAddrsByValidator(AccountHash), + /// Retrieves all messages for a given entity. + MessagesByEntity(EntityAddr), + /// Retrieves all messages for a given entity and topic. + MessagesByEntityAndTopic(EntityAddr, TopicNameHash), + /// Retrieves all named keys for a given entity. + NamedKeysByEntity(EntityAddr), + /// Retrieves all gas balance holds for a given purse. + GasBalanceHoldsByPurse(URefAddr), + /// Retrieves all processing balance holds for a given purse. + ProcessingBalanceHoldsByPurse(URefAddr), +} + +impl KeyPrefix { + /// Returns a random `KeyPrefix`. + #[cfg(any(feature = "testing", test))] + pub fn random(rng: &mut TestRng) -> Self { + match rng.gen_range(0..6) { + 0 => KeyPrefix::DelegatorBidAddrsByValidator(rng.gen()), + 1 => KeyPrefix::MessagesByEntity(rng.gen()), + 2 => KeyPrefix::MessagesByEntityAndTopic(rng.gen(), rng.gen()), + 3 => KeyPrefix::NamedKeysByEntity(rng.gen()), + 4 => KeyPrefix::GasBalanceHoldsByPurse(rng.gen()), + 5 => KeyPrefix::ProcessingBalanceHoldsByPurse(rng.gen()), + _ => unreachable!(), + } + } +} + +impl ToBytes for KeyPrefix { + fn to_bytes(&self) -> Result, bytesrepr::Error> { + let mut result = bytesrepr::unchecked_allocate_buffer(self); + self.write_bytes(&mut result)?; + Ok(result) + } + + fn write_bytes(&self, writer: &mut Vec) -> Result<(), bytesrepr::Error> { + match self { + KeyPrefix::DelegatorBidAddrsByValidator(validator) => { + writer.push(KeyTag::BidAddr as u8); + writer.push(BidAddrTag::Delegator as u8); + validator.write_bytes(writer)?; + } + KeyPrefix::MessagesByEntity(entity) => { + writer.push(KeyTag::Message as u8); + entity.write_bytes(writer)?; + } + KeyPrefix::MessagesByEntityAndTopic(entity, topic) => { + writer.push(KeyTag::Message as u8); + entity.write_bytes(writer)?; + topic.write_bytes(writer)?; + } + KeyPrefix::NamedKeysByEntity(entity) => { + writer.push(KeyTag::NamedKey as u8); + entity.write_bytes(writer)?; + } + KeyPrefix::GasBalanceHoldsByPurse(uref) => { + writer.push(KeyTag::BalanceHold as u8); + writer.push(BalanceHoldAddrTag::Gas as u8); + uref.write_bytes(writer)?; + } + KeyPrefix::ProcessingBalanceHoldsByPurse(uref) => { + writer.push(KeyTag::BalanceHold as u8); + writer.push(BalanceHoldAddrTag::Processing as u8); + uref.write_bytes(writer)?; + } + } + Ok(()) + } + + fn serialized_length(&self) -> usize { + U8_SERIALIZED_LENGTH + + match self { + KeyPrefix::DelegatorBidAddrsByValidator(validator) => { + U8_SERIALIZED_LENGTH + validator.serialized_length() + } + KeyPrefix::MessagesByEntity(entity) => entity.serialized_length(), + KeyPrefix::MessagesByEntityAndTopic(entity, topic) => { + entity.serialized_length() + topic.serialized_length() + } + KeyPrefix::NamedKeysByEntity(entity) => entity.serialized_length(), + KeyPrefix::GasBalanceHoldsByPurse(uref) => { + U8_SERIALIZED_LENGTH + uref.serialized_length() + } + KeyPrefix::ProcessingBalanceHoldsByPurse(uref) => { + U8_SERIALIZED_LENGTH + uref.serialized_length() + } + } + } +} + +impl FromBytes for KeyPrefix { + fn from_bytes(bytes: &[u8]) -> Result<(Self, &[u8]), bytesrepr::Error> { + let (tag, remainder) = u8::from_bytes(bytes)?; + let result = match tag { + tag if tag == KeyTag::BidAddr as u8 => { + let (bid_addr_tag, remainder) = u8::from_bytes(remainder)?; + match bid_addr_tag { + tag if tag == BidAddrTag::Delegator as u8 => { + let (validator, remainder) = AccountHash::from_bytes(remainder)?; + ( + KeyPrefix::DelegatorBidAddrsByValidator(validator), + remainder, + ) + } + _ => return Err(bytesrepr::Error::Formatting), + } + } + tag if tag == KeyTag::Message as u8 => { + let (entity, remainder) = EntityAddr::from_bytes(remainder)?; + if remainder.is_empty() { + (KeyPrefix::MessagesByEntity(entity), remainder) + } else { + let (topic, remainder) = TopicNameHash::from_bytes(remainder)?; + ( + KeyPrefix::MessagesByEntityAndTopic(entity, topic), + remainder, + ) + } + } + tag if tag == KeyTag::NamedKey as u8 => { + let (entity, remainder) = EntityAddr::from_bytes(remainder)?; + (KeyPrefix::NamedKeysByEntity(entity), remainder) + } + tag if tag == KeyTag::BalanceHold as u8 => { + let (balance_hold_addr_tag, remainder) = u8::from_bytes(remainder)?; + let (uref, remainder) = URefAddr::from_bytes(remainder)?; + match balance_hold_addr_tag { + tag if tag == BalanceHoldAddrTag::Gas as u8 => { + (KeyPrefix::GasBalanceHoldsByPurse(uref), remainder) + } + tag if tag == BalanceHoldAddrTag::Processing as u8 => { + (KeyPrefix::ProcessingBalanceHoldsByPurse(uref), remainder) + } + _ => return Err(bytesrepr::Error::Formatting), + } + } + _ => return Err(bytesrepr::Error::Formatting), + }; + Ok(result) + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn bytesrepr_roundtrip() { + let rng = &mut TestRng::new(); + + let key_prefix = KeyPrefix::random(rng); + bytesrepr::test_serialization_roundtrip(&key_prefix); + } +} diff --git a/binary_port/src/lib.rs b/binary_port/src/lib.rs index 36b7582cb5..f510dfc49b 100644 --- a/binary_port/src/lib.rs +++ b/binary_port/src/lib.rs @@ -12,6 +12,7 @@ mod error_code; mod get_request; mod global_state_query_result; mod information_request; +mod key_prefix; mod minimal_block_info; mod node_status; mod payload_type; @@ -33,6 +34,7 @@ pub use error_code::ErrorCode; pub use get_request::GetRequest; pub use global_state_query_result::GlobalStateQueryResult; pub use information_request::{InformationRequest, InformationRequestTag}; +pub use key_prefix::KeyPrefix; pub use minimal_block_info::MinimalBlockInfo; pub use node_status::NodeStatus; pub use payload_type::{PayloadEntity, PayloadType}; diff --git a/execution_engine/src/runtime/mint_internal.rs b/execution_engine/src/runtime/mint_internal.rs index ed98c899e0..ae77487561 100644 --- a/execution_engine/src/runtime/mint_internal.rs +++ b/execution_engine/src/runtime/mint_internal.rs @@ -97,6 +97,11 @@ where fn allow_unrestricted_transfers(&self) -> bool { self.context.engine_config().allow_unrestricted_transfers() } + + /// Validate URef against context access rights. + fn is_valid_uref(&self, uref: &URef) -> bool { + self.context.access_rights().has_access_rights_to_uref(uref) + } } // TODO: update Mint + StorageProvider to better handle errors diff --git a/execution_engine/src/runtime/mod.rs b/execution_engine/src/runtime/mod.rs index a3df8b951e..1f4f6930b9 100644 --- a/execution_engine/src/runtime/mod.rs +++ b/execution_engine/src/runtime/mod.rs @@ -607,6 +607,14 @@ where let result: Result<(), mint::Error> = mint_runtime.reduce_total_supply(amount); CLValue::from_t(result).map_err(Self::reverter) })(), + mint::METHOD_BURN => (|| { + mint_runtime.charge_system_contract_call(mint_costs.burn)?; + + let purse: URef = Self::get_named_argument(runtime_args, mint::ARG_PURSE)?; + let amount: U512 = Self::get_named_argument(runtime_args, mint::ARG_AMOUNT)?; + let result: Result<(), mint::Error> = mint_runtime.burn(purse, amount); + CLValue::from_t(result).map_err(Self::reverter) + })(), // Type: `fn create() -> URef` mint::METHOD_CREATE => (|| { mint_runtime.charge_system_contract_call(mint_costs.create)?; diff --git a/execution_engine/src/runtime_context/mod.rs b/execution_engine/src/runtime_context/mod.rs index 77a6152e48..b73cf2284a 100644 --- a/execution_engine/src/runtime_context/mod.rs +++ b/execution_engine/src/runtime_context/mod.rs @@ -653,7 +653,7 @@ where } /// Validates whether keys used in the `value` are not forged. - fn validate_value(&self, value: &StoredValue) -> Result<(), ExecError> { + pub(crate) fn validate_value(&self, value: &StoredValue) -> Result<(), ExecError> { match value { StoredValue::CLValue(cl_value) => self.validate_cl_value(cl_value), StoredValue::NamedKey(named_key_value) => { @@ -727,7 +727,7 @@ where } /// Validates if a [`Key`] refers to a [`URef`] and has a write bit set. - fn validate_writeable(&self, key: &Key) -> Result<(), ExecError> { + pub(crate) fn validate_writeable(&self, key: &Key) -> Result<(), ExecError> { if self.is_writeable(key) { Ok(()) } else { diff --git a/execution_engine_testing/test_support/src/wasm_test_builder.rs b/execution_engine_testing/test_support/src/wasm_test_builder.rs index fafd7115df..13e2ea6e21 100644 --- a/execution_engine_testing/test_support/src/wasm_test_builder.rs +++ b/execution_engine_testing/test_support/src/wasm_test_builder.rs @@ -705,8 +705,8 @@ where /// Panics if the total supply can't be found. pub fn total_supply( &self, - maybe_post_state: Option, protocol_version: ProtocolVersion, + maybe_post_state: Option, ) -> U512 { let post_state = maybe_post_state .or(self.post_state_hash) @@ -759,7 +759,7 @@ where let post_state = maybe_post_state .or(self.post_state_hash) .expect("builder must have a post-state hash"); - let total_supply = self.total_supply(Some(post_state), protocol_version); + let total_supply = self.total_supply(protocol_version, Some(post_state)); let rate = self.round_seigniorage_rate(Some(post_state), protocol_version); rate.checked_mul(&Ratio::from(total_supply)) .map(|ratio| ratio.to_integer()) diff --git a/execution_engine_testing/tests/src/test/private_chain/burn_fees_and_refund.rs b/execution_engine_testing/tests/src/test/private_chain/burn_fees_and_refund.rs index 95cb758ab4..b5907c8a36 100644 --- a/execution_engine_testing/tests/src/test/private_chain/burn_fees_and_refund.rs +++ b/execution_engine_testing/tests/src/test/private_chain/burn_fees_and_refund.rs @@ -121,7 +121,7 @@ fn test_burning_fees( .expect("should have rewards purse"); let rewards_purse_uref = rewards_purse_key.into_uref().expect("should be uref"); assert_eq!(builder.get_purse_balance(rewards_purse_uref), U512::zero()); - let total_supply_before = builder.total_supply(None, protocol_version); + let total_supply_before = builder.total_supply(protocol_version, None); // TODO: reevaluate this test, considering fee / refund / pricing modes // let exec_request_1 = ExecuteRequestBuilder::module_bytes( // *DEFAULT_ADMIN_ACCOUNT_ADDR, @@ -139,7 +139,7 @@ fn test_burning_fees( // U512::zero(), // "proposer should not receive anything", // ); - let total_supply_after = builder.total_supply(None, protocol_version); + let total_supply_after = builder.total_supply(protocol_version, None); assert_eq!( total_supply_before - total_supply_after, expected_burn_amount, @@ -149,11 +149,11 @@ fn test_burning_fees( TransferRequestBuilder::new(MINIMUM_ACCOUNT_CREATION_BALANCE, *ACCOUNT_1_ADDR) .with_initiator(*DEFAULT_ADMIN_ACCOUNT_ADDR) .build(); - let total_supply_before = builder.total_supply(None, protocol_version); + let total_supply_before = builder.total_supply(protocol_version, None); builder .transfer_and_commit(transfer_request) .expect_success(); - let total_supply_after = builder.total_supply(None, protocol_version); + let total_supply_after = builder.total_supply(protocol_version, None); match fee_handling { FeeHandling::PayToProposer | FeeHandling::Accumulate | FeeHandling::NoFee => { diff --git a/execution_engine_testing/tests/src/test/private_chain/restricted_auction.rs b/execution_engine_testing/tests/src/test/private_chain/restricted_auction.rs index 7173cf6eb5..b2eee1ecc0 100644 --- a/execution_engine_testing/tests/src/test/private_chain/restricted_auction.rs +++ b/execution_engine_testing/tests/src/test/private_chain/restricted_auction.rs @@ -19,7 +19,7 @@ fn should_not_distribute_rewards_but_compute_next_set() { let protocol_version = DEFAULT_PROTOCOL_VERSION; // initial token supply - let initial_supply = builder.total_supply(None, protocol_version); + let initial_supply = builder.total_supply(protocol_version, None); for _ in 0..3 { builder.distribute( @@ -101,7 +101,7 @@ fn should_not_distribute_rewards_but_compute_next_set() { era_info ); - let total_supply_after_distribution = builder.total_supply(None, protocol_version); + let total_supply_after_distribution = builder.total_supply(protocol_version, None); assert_eq!( initial_supply, total_supply_after_distribution, "total supply of tokens should not increase after an auction is ran" diff --git a/execution_engine_testing/tests/src/test/system_contracts/auction/distribute.rs b/execution_engine_testing/tests/src/test/system_contracts/auction/distribute.rs index ebf3ce49ee..3144f4900b 100644 --- a/execution_engine_testing/tests/src/test/system_contracts/auction/distribute.rs +++ b/execution_engine_testing/tests/src/test/system_contracts/auction/distribute.rs @@ -96,8 +96,8 @@ fn withdraw_bid( ) { let auction = builder.get_auction_contract_hash(); let withdraw_bid_args = runtime_args! { - auction::ARG_PUBLIC_KEY => validator, - auction::ARG_AMOUNT => amount, + ARG_PUBLIC_KEY => validator, + ARG_AMOUNT => amount, }; let withdraw_bid_request = ExecuteRequestBuilder::contract_call_by_hash( sender, @@ -118,9 +118,9 @@ fn undelegate( ) { let auction = builder.get_auction_contract_hash(); let undelegate_args = runtime_args! { - auction::ARG_DELEGATOR => delegator, - auction::ARG_VALIDATOR => validator, - auction::ARG_AMOUNT => amount, + ARG_DELEGATOR => delegator, + ARG_VALIDATOR => validator, + ARG_AMOUNT => amount, }; let undelegate_request = ExecuteRequestBuilder::contract_call_by_hash( sender, @@ -256,7 +256,7 @@ fn should_distribute_delegation_rate_zero() { let protocol_version = DEFAULT_PROTOCOL_VERSION; // initial token supply - let initial_supply = builder.total_supply(None, protocol_version); + let initial_supply = builder.total_supply(protocol_version, None); let total_payout = builder.base_round_reward(None, protocol_version); let expected_total_reward = *GENESIS_ROUND_SEIGNIORAGE_RATE * initial_supply; let expected_total_reward_integer = expected_total_reward.to_integer(); @@ -524,7 +524,7 @@ fn should_withdraw_bids_after_distribute() { let total_payout = builder.base_round_reward(None, protocol_version); // initial token supply - let initial_supply = builder.total_supply(None, protocol_version); + let initial_supply = builder.total_supply(protocol_version, None); let rate = builder.round_seigniorage_rate(None, protocol_version); let expected_total_reward = rate * initial_supply; @@ -830,7 +830,7 @@ fn should_distribute_rewards_after_restaking_delegated_funds() { let protocol_version = DEFAULT_PROTOCOL_VERSION; // initial token supply - let initial_supply = builder.total_supply(None, protocol_version); + let initial_supply = builder.total_supply(protocol_version, None); let initial_rate = builder.round_seigniorage_rate(None, protocol_version); let initial_round_reward = builder.base_round_reward(None, protocol_version); @@ -978,7 +978,7 @@ fn should_distribute_rewards_after_restaking_delegated_funds() { )); // Next round of rewards - let updated_supply = builder.total_supply(None, protocol_version); + let updated_supply = builder.total_supply(protocol_version, None); assert!(updated_supply > total_supply); total_supply = updated_supply; @@ -1151,7 +1151,7 @@ fn should_distribute_delegation_rate_half() { let protocol_version = DEFAULT_PROTOCOL_VERSION; // initial token supply - let initial_supply = builder.total_supply(None, protocol_version); + let initial_supply = builder.total_supply(protocol_version, None); let total_payout = builder.base_round_reward(None, protocol_version); let expected_total_reward = *GENESIS_ROUND_SEIGNIORAGE_RATE * initial_supply; let expected_total_reward_integer = expected_total_reward.to_integer(); @@ -1383,7 +1383,7 @@ fn should_distribute_delegation_rate_full() { let protocol_version = DEFAULT_PROTOCOL_VERSION; // initial token supply - let initial_supply = builder.total_supply(None, protocol_version); + let initial_supply = builder.total_supply(protocol_version, None); let expected_total_reward = *GENESIS_ROUND_SEIGNIORAGE_RATE * initial_supply; let expected_total_reward_integer = expected_total_reward.to_integer(); @@ -1565,7 +1565,7 @@ fn should_distribute_uneven_delegation_rate_zero() { let protocol_version = DEFAULT_PROTOCOL_VERSION; // initial token supply - let initial_supply = builder.total_supply(None, protocol_version); + let initial_supply = builder.total_supply(protocol_version, None); let total_payout = builder.base_round_reward(None, protocol_version); let expected_total_reward = *GENESIS_ROUND_SEIGNIORAGE_RATE * initial_supply; let expected_total_reward_integer = expected_total_reward.to_integer(); @@ -1868,7 +1868,7 @@ fn should_distribute_with_multiple_validators_and_delegators() { let protocol_version = DEFAULT_PROTOCOL_VERSION; // initial token supply - let initial_supply = builder.total_supply(None, protocol_version); + let initial_supply = builder.total_supply(protocol_version, None); let total_payout = builder.base_round_reward(None, protocol_version); let expected_total_reward = *GENESIS_ROUND_SEIGNIORAGE_RATE * initial_supply; let expected_total_reward_integer = expected_total_reward.to_integer(); @@ -2200,7 +2200,7 @@ fn should_distribute_with_multiple_validators_and_shared_delegator() { let protocol_version = DEFAULT_PROTOCOL_VERSION; // initial token supply - let initial_supply = builder.total_supply(None, protocol_version); + let initial_supply = builder.total_supply(protocol_version, None); let total_payout = builder.base_round_reward(None, protocol_version); let expected_total_reward = *GENESIS_ROUND_SEIGNIORAGE_RATE * initial_supply; let expected_total_reward_integer = expected_total_reward.to_integer(); @@ -2557,13 +2557,13 @@ fn should_increase_total_supply_after_distribute() { let protocol_version = DEFAULT_PROTOCOL_VERSION; // initial token supply - let initial_supply = builder.total_supply(None, protocol_version); + let initial_supply = builder.total_supply(protocol_version, None); for request in post_genesis_requests { builder.exec(request).commit().expect_success(); } - let post_genesis_supply = builder.total_supply(None, protocol_version); + let post_genesis_supply = builder.total_supply(protocol_version, None); assert_eq!( initial_supply, post_genesis_supply, @@ -2576,7 +2576,7 @@ fn should_increase_total_supply_after_distribute() { timestamp_millis += TIMESTAMP_MILLIS_INCREMENT; } - let post_auction_supply = builder.total_supply(None, protocol_version); + let post_auction_supply = builder.total_supply(protocol_version, None); assert_eq!( initial_supply, post_auction_supply, "total supply should remain unchanged regardless of auction" @@ -2603,7 +2603,7 @@ fn should_increase_total_supply_after_distribute() { builder.exec(distribute_request).expect_success().commit(); - let post_distribute_supply = builder.total_supply(None, protocol_version); + let post_distribute_supply = builder.total_supply(protocol_version, None); assert!( initial_supply < post_distribute_supply, "total supply should increase after distribute ({} >= {})", @@ -2737,13 +2737,13 @@ fn should_not_create_purses_during_distribute() { let protocol_version = DEFAULT_PROTOCOL_VERSION; // initial token supply - let initial_supply = builder.total_supply(None, protocol_version); + let initial_supply = builder.total_supply(protocol_version, None); for request in post_genesis_requests { builder.exec(request).commit().expect_success(); } - let post_genesis_supply = builder.total_supply(None, protocol_version); + let post_genesis_supply = builder.total_supply(protocol_version, None); assert_eq!( initial_supply, post_genesis_supply, @@ -2756,7 +2756,7 @@ fn should_not_create_purses_during_distribute() { timestamp_millis += TIMESTAMP_MILLIS_INCREMENT; } - let post_auction_supply = builder.total_supply(None, protocol_version); + let post_auction_supply = builder.total_supply(protocol_version, None); assert_eq!( initial_supply, post_auction_supply, "total supply should remain unchanged regardless of auction" @@ -2789,7 +2789,7 @@ fn should_not_create_purses_during_distribute() { number_of_purses_before_distribute ); - let post_distribute_supply = builder.total_supply(None, protocol_version); + let post_distribute_supply = builder.total_supply(protocol_version, None); assert!( initial_supply < post_distribute_supply, "total supply should increase after distribute ({} >= {})", @@ -2899,7 +2899,7 @@ fn should_distribute_delegation_rate_full_after_upgrading() { let protocol_version = DEFAULT_PROTOCOL_VERSION; // initial token supply - let initial_supply = builder.total_supply(None, protocol_version); + let initial_supply = builder.total_supply(protocol_version, None); let expected_total_reward_before = *GENESIS_ROUND_SEIGNIORAGE_RATE * initial_supply; let expected_total_reward_integer = expected_total_reward_before.to_integer(); @@ -2985,7 +2985,7 @@ fn should_distribute_delegation_rate_full_after_upgrading() { builder.upgrade(&mut upgrade_request); - let initial_supply = builder.total_supply(None, protocol_version); + let initial_supply = builder.total_supply(protocol_version, None); for _ in 0..5 { builder.advance_era(); diff --git a/execution_engine_testing/tests/src/test/system_contracts/genesis.rs b/execution_engine_testing/tests/src/test/system_contracts/genesis.rs index f0b5bebb7f..32e16308dc 100644 --- a/execution_engine_testing/tests/src/test/system_contracts/genesis.rs +++ b/execution_engine_testing/tests/src/test/system_contracts/genesis.rs @@ -151,7 +151,7 @@ fn should_track_total_token_supply_in_mint() { builder.run_genesis(genesis_request); - let total_supply = builder.total_supply(None, protocol_version); + let total_supply = builder.total_supply(protocol_version, None); let expected_balance: U512 = accounts.iter().map(|item| item.balance().value()).sum(); let expected_staked_amount: U512 = accounts diff --git a/execution_engine_testing/tests/src/test/system_contracts/mint.rs b/execution_engine_testing/tests/src/test/system_contracts/mint.rs new file mode 100644 index 0000000000..98489d8b74 --- /dev/null +++ b/execution_engine_testing/tests/src/test/system_contracts/mint.rs @@ -0,0 +1,235 @@ +use casper_engine_test_support::{ + ExecuteRequestBuilder, LmdbWasmTestBuilder, DEFAULT_ACCOUNT_ADDR, LOCAL_GENESIS_REQUEST, +}; +use casper_types::{runtime_args, ProtocolVersion, URef, U512}; + +use casper_storage::data_access_layer::BalanceIdentifier; +use tempfile::TempDir; + +// const TEST_DELEGATOR_INITIAL_ACCOUNT_BALANCE: u64 = 1_000_000 * 1_000_000_000; + +const CONTRACT_BURN: &str = "burn.wasm"; +const CONTRACT_TRANSFER_TO_NAMED_PURSE: &str = "transfer_to_named_purse.wasm"; + +const ARG_AMOUNT: &str = "amount"; + +const ARG_PURSE_NAME: &str = "purse_name"; + +#[ignore] +#[test] +fn should_empty_purse_when_burning_above_balance() { + let data_dir = TempDir::new().expect("should create temp dir"); + let mut builder = LmdbWasmTestBuilder::new(data_dir.as_ref()); + let source = *DEFAULT_ACCOUNT_ADDR; + + builder.run_genesis(LOCAL_GENESIS_REQUEST.clone()); + + // let delegator_keys = auction::generate_public_keys(1); + // let validator_keys = auction::generate_public_keys(1); + + // run_genesis_and_create_initial_accounts( + // &mut builder, + // &validator_keys, + // delegator_keys + // .iter() + // .map(|public_key| public_key.to_account_hash()) + // .collect::>(), + // U512::from(TEST_DELEGATOR_INITIAL_ACCOUNT_BALANCE), + // ); + + let initial_supply = builder.total_supply(ProtocolVersion::V2_0_0, None); + let purse_name = "purse"; + let purse_amount = U512::from(10_000_000_000u64); + + // Create purse and transfer tokens to it + let exec_request = ExecuteRequestBuilder::standard( + source, + CONTRACT_TRANSFER_TO_NAMED_PURSE, + runtime_args! { + ARG_PURSE_NAME => purse_name, + ARG_AMOUNT => purse_amount, + }, + ) + .build(); + + builder.exec(exec_request).expect_success().commit(); + + let account = builder + .get_entity_with_named_keys_by_account_hash(source) + .expect("should have account"); + + let purse_uref: URef = account + .named_keys() + .get(purse_name) + .unwrap() + .into_uref() + .expect("should be uref"); + + assert_eq!( + builder + .get_purse_balance_result_with_proofs( + ProtocolVersion::V2_0_0, + BalanceIdentifier::Purse(purse_uref) + ) + .total_balance() + .cloned() + .unwrap(), + purse_amount + ); + + // Burn part of tokens in a purse + let num_of_tokens_to_burn = U512::from(2_000_000_000u64); + let num_of_tokens_after_burn = U512::from(8_000_000_000u64); + + let exec_request = ExecuteRequestBuilder::standard( + source, + CONTRACT_BURN, + runtime_args! { + ARG_PURSE_NAME => purse_name, + ARG_AMOUNT => num_of_tokens_to_burn, + }, + ) + .build(); + + builder.exec(exec_request).expect_success().commit(); + + assert_eq!( + builder + .get_purse_balance_result_with_proofs( + ProtocolVersion::V2_0_0, + BalanceIdentifier::Purse(purse_uref) + ) + .total_balance() + .cloned() + .unwrap(), + num_of_tokens_after_burn + ); + + // Burn rest of tokens in a purse + let num_of_tokens_to_burn = U512::from(8_000_000_000u64); + let num_of_tokens_after_burn = U512::zero(); + + let exec_request = ExecuteRequestBuilder::standard( + source, + CONTRACT_BURN, + runtime_args! { + ARG_PURSE_NAME => purse_name, + ARG_AMOUNT => num_of_tokens_to_burn, + }, + ) + .build(); + + builder.exec(exec_request).expect_success().commit(); + + assert_eq!( + builder + .get_purse_balance_result_with_proofs( + ProtocolVersion::V2_0_0, + BalanceIdentifier::Purse(purse_uref) + ) + .total_balance() + .cloned() + .unwrap(), + num_of_tokens_after_burn + ); + + let supply_after_burns = builder.total_supply(ProtocolVersion::V2_0_0, None); + let expected_supply_after_burns = initial_supply - U512::from(10_000_000_000u64); + + assert_eq!(supply_after_burns, expected_supply_after_burns); +} + +#[ignore] +#[test] +fn should_not_burn_excess_tokens() { + let data_dir = TempDir::new().expect("should create temp dir"); + let mut builder = LmdbWasmTestBuilder::new(data_dir.as_ref()); + let source = *DEFAULT_ACCOUNT_ADDR; + + builder.run_genesis(LOCAL_GENESIS_REQUEST.clone()); + // let delegator_keys = auction::generate_public_keys(1); + // let validator_keys = auction::generate_public_keys(1); + // + // run_genesis_and_create_initial_accounts( + // &mut builder, + // &validator_keys, + // delegator_keys + // .iter() + // .map(|public_key| public_key.to_account_hash()) + // .collect::>(), + // U512::from(TEST_DELEGATOR_INITIAL_ACCOUNT_BALANCE), + // ); + + let initial_supply = builder.total_supply(ProtocolVersion::V2_0_0, None); + let purse_name = "purse"; + let purse_amount = U512::from(10_000_000_000u64); + + // Create purse and transfer tokens to it + let exec_request = ExecuteRequestBuilder::standard( + source, + CONTRACT_TRANSFER_TO_NAMED_PURSE, + runtime_args! { + ARG_PURSE_NAME => purse_name, + ARG_AMOUNT => purse_amount, + }, + ) + .build(); + + builder.exec(exec_request).expect_success().commit(); + + let account = builder + .get_entity_with_named_keys_by_account_hash(source) + .expect("should have account"); + + let purse_uref: URef = account + .named_keys() + .get(purse_name) + .unwrap() + .into_uref() + .expect("should be uref"); + + assert_eq!( + builder + .get_purse_balance_result_with_proofs( + ProtocolVersion::V2_0_0, + BalanceIdentifier::Purse(purse_uref) + ) + .total_balance() + .cloned() + .unwrap(), + purse_amount + ); + + // Try to burn more then in a purse + let num_of_tokens_to_burn = U512::MAX; + let num_of_tokens_after_burn = U512::zero(); + + let exec_request = ExecuteRequestBuilder::standard( + source, + CONTRACT_BURN, + runtime_args! { + ARG_PURSE_NAME => purse_name, + ARG_AMOUNT => num_of_tokens_to_burn, + }, + ) + .build(); + + builder.exec(exec_request).expect_success().commit(); + + assert_eq!( + builder + .get_purse_balance_result_with_proofs( + ProtocolVersion::V2_0_0, + BalanceIdentifier::Purse(purse_uref) + ) + .total_balance() + .cloned() + .unwrap(), + num_of_tokens_after_burn, + ); + + let supply_after_burns = builder.total_supply(ProtocolVersion::V2_0_0, None); + let expected_supply_after_burns = initial_supply - U512::from(10_000_000_000u64); + + assert_eq!(supply_after_burns, expected_supply_after_burns); +} diff --git a/execution_engine_testing/tests/src/test/system_contracts/mod.rs b/execution_engine_testing/tests/src/test/system_contracts/mod.rs index 9a75a324de..a2fe0ef6ef 100644 --- a/execution_engine_testing/tests/src/test/system_contracts/mod.rs +++ b/execution_engine_testing/tests/src/test/system_contracts/mod.rs @@ -2,5 +2,6 @@ mod auction; mod auction_bidding; mod genesis; mod handle_payment; +mod mint; mod standard_payment; mod upgrade; diff --git a/resources/local/chainspec.toml.in b/resources/local/chainspec.toml.in index b2ec6852c3..5e46e1c180 100644 --- a/resources/local/chainspec.toml.in +++ b/resources/local/chainspec.toml.in @@ -343,6 +343,7 @@ mint = 2_500_000_000 reduce_total_supply = 10_000 create = 2_500_000_000 balance = 10_000 +burn = 10_000 transfer = 10_000 read_base_round_reward = 10_000 mint_into_existing_purse = 2_500_000_000 @@ -361,4 +362,4 @@ pay = 10_000 upper_threshold = 90 lower_threshold = 50 max_gas_price = 3 -min_gas_price = 1 \ No newline at end of file +min_gas_price = 1 diff --git a/resources/production/chainspec.toml b/resources/production/chainspec.toml index 9219a51809..9cd4abd59a 100644 --- a/resources/production/chainspec.toml +++ b/resources/production/chainspec.toml @@ -353,6 +353,7 @@ mint = 2_500_000_000 reduce_total_supply = 10_000 create = 2_500_000_000 balance = 10_000 +burn = 10_000 transfer = 100_000_000 read_base_round_reward = 10_000 mint_into_existing_purse = 2_500_000_000 @@ -391,4 +392,4 @@ pay = 10_000 upper_threshold = 90 lower_threshold = 50 max_gas_price = 3 -min_gas_price = 1 \ No newline at end of file +min_gas_price = 1 diff --git a/smart_contracts/contracts/client/burn/Cargo.toml b/smart_contracts/contracts/client/burn/Cargo.toml new file mode 100644 index 0000000000..f9949db688 --- /dev/null +++ b/smart_contracts/contracts/client/burn/Cargo.toml @@ -0,0 +1,16 @@ +[package] +name = "burn" +version = "0.1.0" +authors = ["Igor Bunar ", "Jan Hoffmann "] +edition = "2021" + +[[bin]] +name = "burn" +path = "src/main.rs" +bench = false +doctest = false +test = false + +[dependencies] +casper-contract = { path = "../../../contract" } +casper-types = { path = "../../../../types" } diff --git a/smart_contracts/contracts/client/burn/src/main.rs b/smart_contracts/contracts/client/burn/src/main.rs new file mode 100644 index 0000000000..545fedafd3 --- /dev/null +++ b/smart_contracts/contracts/client/burn/src/main.rs @@ -0,0 +1,89 @@ +#![no_std] +#![no_main] + +extern crate alloc; +use alloc::{string::String, vec::Vec}; + +use casper_contract::{ + contract_api::{account, alloc_bytes, runtime, system}, + ext_ffi, + unwrap_or_revert::UnwrapOrRevert, +}; +use casper_types::{api_error, bytesrepr, runtime_args, system::mint, ApiError, Key, URef, U512}; + +const ARG_PURSE_NAME: &str = "purse_name"; + +fn burn(uref: URef, amount: U512) -> Result<(), mint::Error> { + let contract_hash = system::get_mint(); + let args = runtime_args! { + mint::ARG_PURSE => uref, + mint::ARG_AMOUNT => amount, + }; + runtime::call_contract(contract_hash, mint::METHOD_BURN, args) +} + +#[no_mangle] +pub extern "C" fn call() { + let purse_uref = match get_named_arg_option::(ARG_PURSE_NAME) { + Some(name) => { + // if a key was provided and there is no value under it we revert + // to prevent user from accidentaly burning tokens from the main purse + // eg. if they make a typo + let Some(Key::URef(purse_uref)) = runtime::get_key(&name) else { + runtime::revert(ApiError::InvalidPurseName) + }; + purse_uref + } + None => account::get_main_purse(), + }; + let amount: U512 = runtime::get_named_arg(mint::ARG_AMOUNT); + + burn(purse_uref, amount).unwrap_or_revert(); +} + +fn get_named_arg_size(name: &str) -> Option { + let mut arg_size: usize = 0; + let ret = unsafe { + ext_ffi::casper_get_named_arg_size( + name.as_bytes().as_ptr(), + name.len(), + &mut arg_size as *mut usize, + ) + }; + match api_error::result_from(ret) { + Ok(_) => Some(arg_size), + Err(ApiError::MissingArgument) => None, + Err(e) => runtime::revert(e), + } +} + +fn get_named_arg_option(name: &str) -> Option { + let arg_size = get_named_arg_size(name).unwrap_or_revert_with(ApiError::MissingArgument); + let arg_bytes = if arg_size > 0 { + let res = { + let data_non_null_ptr = alloc_bytes(arg_size); + let ret = unsafe { + ext_ffi::casper_get_named_arg( + name.as_bytes().as_ptr(), + name.len(), + data_non_null_ptr.as_ptr(), + arg_size, + ) + }; + let data = + unsafe { Vec::from_raw_parts(data_non_null_ptr.as_ptr(), arg_size, arg_size) }; + if ret != 0 { + return None; + } + data + }; + res + } else { + // Avoids allocation with 0 bytes and a call to get_named_arg + Vec::new() + }; + + let deserialized_data = + bytesrepr::deserialize(arg_bytes).unwrap_or_revert_with(ApiError::InvalidArgument); + Some(deserialized_data) +} diff --git a/storage/src/data_access_layer.rs b/storage/src/data_access_layer.rs index e9feea1da8..b011df9d64 100644 --- a/storage/src/data_access_layer.rs +++ b/storage/src/data_access_layer.rs @@ -20,6 +20,7 @@ mod flush; mod genesis; pub mod handle_fee; mod handle_refund; +mod key_prefix; pub mod mint; mod protocol_upgrade; pub mod prune; @@ -54,6 +55,7 @@ pub use flush::{FlushRequest, FlushResult}; pub use genesis::{GenesisRequest, GenesisResult}; pub use handle_fee::{HandleFeeMode, HandleFeeRequest, HandleFeeResult}; pub use handle_refund::{HandleRefundMode, HandleRefundRequest, HandleRefundResult}; +pub use key_prefix::KeyPrefix; pub use mint::{TransferRequest, TransferResult}; pub use protocol_upgrade::{ProtocolUpgradeRequest, ProtocolUpgradeResult}; pub use prune::{PruneRequest, PruneResult}; diff --git a/storage/src/data_access_layer/key_prefix.rs b/storage/src/data_access_layer/key_prefix.rs new file mode 100644 index 0000000000..4090f1217e --- /dev/null +++ b/storage/src/data_access_layer/key_prefix.rs @@ -0,0 +1,226 @@ +#[cfg(any(feature = "testing", test))] +use casper_types::testing::TestRng; +use casper_types::{ + account::AccountHash, + bytesrepr::{self, FromBytes, ToBytes, U8_SERIALIZED_LENGTH}, + contract_messages::TopicNameHash, + system::{auction::BidAddrTag, mint::BalanceHoldAddrTag}, + EntityAddr, KeyTag, URefAddr, +}; +#[cfg(any(feature = "testing", test))] +use rand::Rng; + +/// Key prefixes used for querying the global state. +#[derive(Debug, PartialEq, Eq, Clone, PartialOrd, Ord, Hash)] +pub enum KeyPrefix { + /// Retrieves all delegator bid addresses for a given validator. + DelegatorBidAddrsByValidator(AccountHash), + /// Retrieves all messages for a given entity. + MessagesByEntity(EntityAddr), + /// Retrieves all messages for a given entity and topic. + MessagesByEntityAndTopic(EntityAddr, TopicNameHash), + /// Retrieves all named keys for a given entity. + NamedKeysByEntity(EntityAddr), + /// Retrieves all gas balance holds for a given purse. + GasBalanceHoldsByPurse(URefAddr), + /// Retrieves all processing balance holds for a given purse. + ProcessingBalanceHoldsByPurse(URefAddr), +} + +impl ToBytes for KeyPrefix { + fn to_bytes(&self) -> Result, bytesrepr::Error> { + let mut result = bytesrepr::unchecked_allocate_buffer(self); + self.write_bytes(&mut result)?; + Ok(result) + } + + fn write_bytes(&self, writer: &mut Vec) -> Result<(), bytesrepr::Error> { + match self { + KeyPrefix::DelegatorBidAddrsByValidator(validator) => { + writer.push(KeyTag::BidAddr as u8); + writer.push(BidAddrTag::Delegator as u8); + validator.write_bytes(writer)?; + } + KeyPrefix::MessagesByEntity(entity) => { + writer.push(KeyTag::Message as u8); + entity.write_bytes(writer)?; + } + KeyPrefix::MessagesByEntityAndTopic(entity, topic) => { + writer.push(KeyTag::Message as u8); + entity.write_bytes(writer)?; + topic.write_bytes(writer)?; + } + KeyPrefix::NamedKeysByEntity(entity) => { + writer.push(KeyTag::NamedKey as u8); + entity.write_bytes(writer)?; + } + KeyPrefix::GasBalanceHoldsByPurse(uref) => { + writer.push(KeyTag::BalanceHold as u8); + writer.push(BalanceHoldAddrTag::Gas as u8); + uref.write_bytes(writer)?; + } + KeyPrefix::ProcessingBalanceHoldsByPurse(uref) => { + writer.push(KeyTag::BalanceHold as u8); + writer.push(BalanceHoldAddrTag::Processing as u8); + uref.write_bytes(writer)?; + } + } + Ok(()) + } + + fn serialized_length(&self) -> usize { + U8_SERIALIZED_LENGTH + + match self { + KeyPrefix::DelegatorBidAddrsByValidator(validator) => { + U8_SERIALIZED_LENGTH + validator.serialized_length() + } + KeyPrefix::MessagesByEntity(entity) => entity.serialized_length(), + KeyPrefix::MessagesByEntityAndTopic(entity, topic) => { + entity.serialized_length() + topic.serialized_length() + } + KeyPrefix::NamedKeysByEntity(entity) => entity.serialized_length(), + KeyPrefix::GasBalanceHoldsByPurse(uref) => { + U8_SERIALIZED_LENGTH + uref.serialized_length() + } + KeyPrefix::ProcessingBalanceHoldsByPurse(uref) => { + U8_SERIALIZED_LENGTH + uref.serialized_length() + } + } + } +} + +impl FromBytes for KeyPrefix { + fn from_bytes(bytes: &[u8]) -> Result<(Self, &[u8]), bytesrepr::Error> { + let (tag, remainder) = u8::from_bytes(bytes)?; + let result = match tag { + tag if tag == KeyTag::BidAddr as u8 => { + let (bid_addr_tag, remainder) = u8::from_bytes(remainder)?; + match bid_addr_tag { + tag if tag == BidAddrTag::Delegator as u8 => { + let (validator, remainder) = AccountHash::from_bytes(remainder)?; + ( + KeyPrefix::DelegatorBidAddrsByValidator(validator), + remainder, + ) + } + _ => return Err(bytesrepr::Error::Formatting), + } + } + tag if tag == KeyTag::Message as u8 => { + let (entity, remainder) = EntityAddr::from_bytes(remainder)?; + if remainder.is_empty() { + (KeyPrefix::MessagesByEntity(entity), remainder) + } else { + let (topic, remainder) = TopicNameHash::from_bytes(remainder)?; + ( + KeyPrefix::MessagesByEntityAndTopic(entity, topic), + remainder, + ) + } + } + tag if tag == KeyTag::NamedKey as u8 => { + let (entity, remainder) = EntityAddr::from_bytes(remainder)?; + (KeyPrefix::NamedKeysByEntity(entity), remainder) + } + tag if tag == KeyTag::BalanceHold as u8 => { + let (balance_hold_addr_tag, remainder) = u8::from_bytes(remainder)?; + let (uref, remainder) = URefAddr::from_bytes(remainder)?; + match balance_hold_addr_tag { + tag if tag == BalanceHoldAddrTag::Gas as u8 => { + (KeyPrefix::GasBalanceHoldsByPurse(uref), remainder) + } + tag if tag == BalanceHoldAddrTag::Processing as u8 => { + (KeyPrefix::ProcessingBalanceHoldsByPurse(uref), remainder) + } + _ => return Err(bytesrepr::Error::Formatting), + } + } + _ => return Err(bytesrepr::Error::Formatting), + }; + Ok(result) + } +} + +#[cfg(test)] +mod tests { + use casper_types::{ + addressable_entity::NamedKeyAddr, + contract_messages::MessageAddr, + gens::{account_hash_arb, entity_addr_arb, topic_name_hash_arb, u8_slice_32}, + system::{auction::BidAddr, mint::BalanceHoldAddr}, + BlockTime, Key, + }; + + use super::*; + use proptest::prelude::*; + + pub fn key_prefix_arb() -> impl Strategy { + prop_oneof![ + account_hash_arb().prop_map(KeyPrefix::DelegatorBidAddrsByValidator), + entity_addr_arb().prop_map(KeyPrefix::MessagesByEntity), + (entity_addr_arb(), topic_name_hash_arb()) + .prop_map(|(entity, topic)| KeyPrefix::MessagesByEntityAndTopic(entity, topic)), + entity_addr_arb().prop_map(KeyPrefix::NamedKeysByEntity), + u8_slice_32().prop_map(KeyPrefix::GasBalanceHoldsByPurse), + u8_slice_32().prop_map(KeyPrefix::ProcessingBalanceHoldsByPurse), + ] + } + + proptest! { + #[test] + fn bytesrepr_roundtrip(key_prefix in key_prefix_arb()) { + bytesrepr::test_serialization_roundtrip(&key_prefix); + } + } + + #[test] + fn key_serializer_compat() { + // This test ensures that the `KeyPrefix` deserializer is compatible with the `Key` + // serializer. Combined with the `bytesrepr_roundtrip` test, this ensures that + // `KeyPrefix` is binary compatible with `Key`. + + let rng = &mut TestRng::new(); + + let hash1 = rng.gen(); + let hash2 = rng.gen(); + + for (key, prefix) in [ + ( + Key::BidAddr(BidAddr::new_delegator_addr((hash1, hash2))), + KeyPrefix::DelegatorBidAddrsByValidator(AccountHash::new(hash1)), + ), + ( + Key::Message(MessageAddr::new_message_addr( + EntityAddr::Account(hash1), + TopicNameHash::new(hash2), + 0, + )), + KeyPrefix::MessagesByEntityAndTopic( + EntityAddr::Account(hash1), + TopicNameHash::new(hash2), + ), + ), + ( + Key::NamedKey(NamedKeyAddr::new_named_key_entry( + EntityAddr::Account(hash1), + hash2, + )), + KeyPrefix::NamedKeysByEntity(EntityAddr::Account(hash1)), + ), + ( + Key::BalanceHold(BalanceHoldAddr::new_gas(hash1, BlockTime::new(0))), + KeyPrefix::GasBalanceHoldsByPurse(hash1), + ), + ( + Key::BalanceHold(BalanceHoldAddr::new_processing(hash1, BlockTime::new(0))), + KeyPrefix::ProcessingBalanceHoldsByPurse(hash1), + ), + ] { + let key_bytes = key.to_bytes().expect("should serialize key"); + let (parsed_key_prefix, remainder) = + KeyPrefix::from_bytes(&key_bytes).expect("should deserialize key prefix"); + assert_eq!(parsed_key_prefix, prefix, "key: {:?}", key); + assert!(!remainder.is_empty(), "key: {:?}", key); + } + } +} diff --git a/storage/src/global_state/state/mod.rs b/storage/src/global_state/state/mod.rs index 7f44420fae..823ea8df89 100644 --- a/storage/src/global_state/state/mod.rs +++ b/storage/src/global_state/state/mod.rs @@ -1332,7 +1332,7 @@ pub trait StateProvider { )); } }; - match runtime.burn(source_purse, burn_amount) { + match runtime.payment_burn(source_purse, burn_amount) { Ok(_) => Ok(burn_amount), Err(err) => Err(err), } @@ -1424,7 +1424,7 @@ pub trait StateProvider { Ok(value) => value, Err(tce) => return HandleFeeResult::Failure(tce), }; - runtime.burn(source_purse, amount) + runtime.payment_burn(source_purse, amount) } }; diff --git a/storage/src/lib.rs b/storage/src/lib.rs index 6f0b27c1c4..be19b4cc84 100644 --- a/storage/src/lib.rs +++ b/storage/src/lib.rs @@ -16,6 +16,7 @@ pub mod system; pub mod tracking_copy; pub use address_generator::{AddressGenerator, AddressGeneratorBuilder}; +pub use data_access_layer::KeyPrefix; pub use tracking_copy::TrackingCopy; pub use block_store::{ diff --git a/storage/src/system/auction/auction_native.rs b/storage/src/system/auction/auction_native.rs index f7dcacd9c3..91cf15d659 100644 --- a/storage/src/system/auction/auction_native.rs +++ b/storage/src/system/auction/auction_native.rs @@ -66,15 +66,10 @@ where fn delegator_count(&mut self, bid_addr: &BidAddr) -> Result { let prefix = bid_addr.delegators_prefix()?; - let keys = self - .tracking_copy() - .borrow_mut() - .reader() - .keys_with_prefix(&prefix) - .map_err(|err| { - error!("RuntimeProvider::delegator_count {:?}", err); - Error::Storage - })?; + let keys = self.get_keys_by_prefix(&prefix).map_err(|err| { + error!("RuntimeProvider::delegator_count {:?}", err); + Error::Storage + })?; Ok(keys.len()) } diff --git a/storage/src/system/auction/detail.rs b/storage/src/system/auction/detail.rs index 2b5dac2645..effd549671 100644 --- a/storage/src/system/auction/detail.rs +++ b/storage/src/system/auction/detail.rs @@ -665,7 +665,6 @@ where .delegators_prefix() .map_err(|_| Error::Serialization)?, )?; - for delegator_bid_key in delegator_bid_keys { let delegator_bid = read_delegator_bid(provider, &delegator_bid_key)?; ret.push(*delegator_bid); diff --git a/storage/src/system/handle_payment.rs b/storage/src/system/handle_payment.rs index c952d8fcfe..b9854da1ed 100644 --- a/storage/src/system/handle_payment.rs +++ b/storage/src/system/handle_payment.rs @@ -6,9 +6,10 @@ pub mod storage_provider; use casper_types::{ system::handle_payment::{Error, REFUND_PURSE_KEY}, - AccessRights, URef, U512, + AccessRights, PublicKey, URef, U512, }; use num_rational::Ratio; +use tracing::error; use crate::system::handle_payment::{ mint_provider::MintProvider, runtime_provider::RuntimeProvider, @@ -43,6 +44,11 @@ pub trait HandlePayment: MintProvider + RuntimeProvider + StorageProvider + Size /// Clear refund purse. fn clear_refund_purse(&mut self) -> Result<(), Error> { + if self.get_caller() != PublicKey::System.to_account_hash() { + error!("invalid caller to clear refund purse"); + return Err(Error::InvalidCaller); + } + self.remove_key(REFUND_PURSE_KEY) } @@ -57,6 +63,11 @@ pub trait HandlePayment: MintProvider + RuntimeProvider + StorageProvider + Size source_purse: URef, refund_ratio: Ratio, ) -> Result<(U512, U512), Error> { + if self.get_caller() != PublicKey::System.to_account_hash() { + error!("invalid caller to calculate overpayment and fee"); + return Err(Error::InvalidCaller); + } + let available_balance = match self.available_balance(source_purse)? { Some(balance) => balance, None => return Err(Error::PaymentPurseBalanceNotFound), @@ -77,11 +88,21 @@ pub trait HandlePayment: MintProvider + RuntimeProvider + StorageProvider + Size source_uref: URef, amount: Option, ) -> Result<(), Error> { + if self.get_caller() != PublicKey::System.to_account_hash() { + error!("invalid caller to distribute accumulated fee"); + return Err(Error::InvalidCaller); + } + internal::distribute_accumulated_fees(self, source_uref, amount) } /// Burns the imputed amount from the imputed purse. - fn burn(&mut self, source_uref: URef, amount: Option) -> Result<(), Error> { - internal::burn(self, source_uref, amount) + fn payment_burn(&mut self, source_uref: URef, amount: Option) -> Result<(), Error> { + if self.get_caller() != PublicKey::System.to_account_hash() { + error!("invalid caller to payment burn"); + return Err(Error::InvalidCaller); + } + + internal::payment_burn(self, source_uref, amount) } } diff --git a/storage/src/system/handle_payment/internal.rs b/storage/src/system/handle_payment/internal.rs index ef6313e159..b6dbf33dba 100644 --- a/storage/src/system/handle_payment/internal.rs +++ b/storage/src/system/handle_payment/internal.rs @@ -4,7 +4,7 @@ use super::{ }; use casper_types::{ system::handle_payment::{Error, PAYMENT_PURSE_KEY, REFUND_PURSE_KEY}, - Key, Phase, PublicKey, URef, U512, + Key, Phase, URef, U512, }; use num::CheckedMul; use num_rational::Ratio; @@ -112,23 +112,22 @@ pub fn calculate_overpayment_and_fee( Ok((adjusted_refund, fee)) } -pub fn burn( +pub fn payment_burn( provider: &mut P, purse: URef, amount: Option, ) -> Result<(), Error> { - // get the purse total balance (without holds) - let total_balance = match provider.available_balance(purse)? { + let available_balance = match provider.available_balance(purse)? { Some(balance) => balance, None => return Err(Error::PaymentPurseBalanceNotFound), }; - let burn_amount = amount.unwrap_or(total_balance); + let burn_amount = amount.unwrap_or(available_balance); if burn_amount.is_zero() { // nothing to burn == noop return Ok(()); } // Reduce the source purse and total supply by the refund amount - let adjusted_balance = total_balance + let adjusted_balance = available_balance .checked_sub(burn_amount) .ok_or(Error::ArithmeticOverflow)?; provider.write_balance(purse, adjusted_balance)?; @@ -152,10 +151,6 @@ where return Err(Error::IncompatiblePaymentSettings); } - if provider.get_caller() != PublicKey::System.to_account_hash() { - return Err(Error::SystemFunctionCalledByUserAccount); - } - let administrative_accounts = provider.administrative_accounts(); let reward_recipients = U512::from(administrative_accounts.len()); diff --git a/storage/src/system/mint.rs b/storage/src/system/mint.rs index 322733dedb..a9c4387a72 100644 --- a/storage/src/system/mint.rs +++ b/storage/src/system/mint.rs @@ -1,3 +1,4 @@ +pub(crate) mod detail; mod mint_native; pub mod runtime_provider; pub mod storage_provider; @@ -51,6 +52,31 @@ pub trait Mint: RuntimeProvider + StorageProvider + SystemProvider { Ok(purse_uref) } + /// Burns native tokens. + fn burn(&mut self, purse: URef, amount: U512) -> Result<(), Error> { + if !purse.is_writeable() { + return Err(Error::InvalidAccessRights); + } + if !self.is_valid_uref(&purse) { + return Err(Error::ForgedReference); + } + + let source_available_balance: U512 = match self.balance(purse)? { + Some(source_balance) => source_balance, + None => return Err(Error::PurseNotFound), + }; + + let new_balance = match source_available_balance.checked_sub(amount) { + Some(value) => value, + None => U512::zero(), + }; + // change balance + self.write_balance(purse, new_balance)?; + // reduce total supply AFTER changing balance in case changing balance errors + let burned_amount = source_available_balance.saturating_sub(new_balance); + detail::reduce_total_supply_unsafe(self, burned_amount) + } + /// Reduce total supply by `amount`. Returns unit on success, otherwise /// an error. fn reduce_total_supply(&mut self, amount: U512) -> Result<(), Error> { @@ -60,29 +86,7 @@ pub trait Mint: RuntimeProvider + StorageProvider + SystemProvider { return Err(Error::InvalidTotalSupplyReductionAttempt); } - if amount.is_zero() { - return Ok(()); // no change to supply - } - - // get total supply or error - let total_supply_uref = match self.get_key(TOTAL_SUPPLY_KEY) { - Some(Key::URef(uref)) => uref, - Some(_) => return Err(Error::MissingKey), // TODO - None => return Err(Error::MissingKey), - }; - let total_supply: U512 = self - .read(total_supply_uref)? - .ok_or(Error::TotalSupplyNotFound)?; - - // decrease total supply - let reduced_total_supply = total_supply - .checked_sub(amount) - .ok_or(Error::ArithmeticOverflow)?; - - // update total supply - self.write_amount(total_supply_uref, reduced_total_supply)?; - - Ok(()) + detail::reduce_total_supply_unsafe(self, amount) } /// Read balance of given `purse`. diff --git a/storage/src/system/mint/detail.rs b/storage/src/system/mint/detail.rs new file mode 100644 index 0000000000..cc102629e2 --- /dev/null +++ b/storage/src/system/mint/detail.rs @@ -0,0 +1,39 @@ +use casper_types::{ + system::{ + mint, + mint::{Error, TOTAL_SUPPLY_KEY}, + }, + Key, U512, +}; + +use crate::system::mint::Mint; + +// Please do not expose this to the user! +pub(crate) fn reduce_total_supply_unsafe

(mint: &mut P, amount: U512) -> Result<(), mint::Error> +where + P: Mint + ?Sized, +{ + if amount.is_zero() { + return Ok(()); // no change to supply + } + + // get total supply or error + let total_supply_uref = match mint.get_key(TOTAL_SUPPLY_KEY) { + Some(Key::URef(uref)) => uref, + Some(_) => return Err(Error::MissingKey), // TODO + None => return Err(Error::MissingKey), + }; + let total_supply: U512 = mint + .read(total_supply_uref)? + .ok_or(Error::TotalSupplyNotFound)?; + + // decrease total supply + let reduced_total_supply = total_supply + .checked_sub(amount) + .ok_or(Error::ArithmeticOverflow)?; + + // update total supply + mint.write_amount(total_supply_uref, reduced_total_supply)?; + + Ok(()) +} diff --git a/storage/src/system/mint/mint_native.rs b/storage/src/system/mint/mint_native.rs index 8b308084ff..ead72fe9db 100644 --- a/storage/src/system/mint/mint_native.rs +++ b/storage/src/system/mint/mint_native.rs @@ -102,6 +102,10 @@ where fn allow_unrestricted_transfers(&self) -> bool { self.transfer_config().allow_unrestricted_transfers() } + + fn is_valid_uref(&self, uref: &URef) -> bool { + self.access_rights().has_access_rights_to_uref(uref) + } } impl StorageProvider for RuntimeNative diff --git a/storage/src/system/mint/runtime_provider.rs b/storage/src/system/mint/runtime_provider.rs index b20b46491b..fbebcfa1a3 100644 --- a/storage/src/system/mint/runtime_provider.rs +++ b/storage/src/system/mint/runtime_provider.rs @@ -43,4 +43,7 @@ pub trait RuntimeProvider { /// Checks if users can perform unrestricted transfers. This option is valid only for private /// chains. fn allow_unrestricted_transfers(&self) -> bool; + + /// Validate URef against context access rights. + fn is_valid_uref(&self, uref: &URef) -> bool; } diff --git a/storage/src/system/runtime_native.rs b/storage/src/system/runtime_native.rs index a29f320f43..36a96b1443 100644 --- a/storage/src/system/runtime_native.rs +++ b/storage/src/system/runtime_native.rs @@ -409,7 +409,7 @@ where &mut self.named_keys } - pub fn access_rights(&mut self) -> &ContextAccessRights { + pub fn access_rights(&self) -> &ContextAccessRights { &self.access_rights } diff --git a/storage/src/tracking_copy/ext.rs b/storage/src/tracking_copy/ext.rs index 060e0f10c1..cecd910534 100644 --- a/storage/src/tracking_copy/ext.rs +++ b/storage/src/tracking_copy/ext.rs @@ -10,10 +10,12 @@ use crate::{ }, global_state::{error::Error as GlobalStateError, state::StateReader}, tracking_copy::{TrackingCopy, TrackingCopyError}, + KeyPrefix, }; use casper_types::{ account::AccountHash, addressable_entity::NamedKeys, + bytesrepr::ToBytes, global_state::TrieMerkleProof, system::{ mint::{ @@ -247,16 +249,15 @@ where ) -> Result, Self::Error> { let tagged_keys = { let mut ret: Vec = vec![]; - let tag = BalanceHoldAddrTag::Gas; - let gas_prefix = tag.purse_prefix_by_tag(purse_addr)?; + let gas_prefix = KeyPrefix::GasBalanceHoldsByPurse(purse_addr).to_bytes()?; for key in self.keys_with_prefix(&gas_prefix)? { let addr = key .as_balance_hold() .ok_or(Self::Error::UnexpectedKeyVariant(key))?; ret.push(*addr); } - let tag = BalanceHoldAddrTag::Processing; - let processing_prefix = tag.purse_prefix_by_tag(purse_addr)?; + let processing_prefix = + KeyPrefix::ProcessingBalanceHoldsByPurse(purse_addr).to_bytes()?; for key in self.keys_with_prefix(&processing_prefix)? { let addr = key .as_balance_hold() @@ -378,9 +379,14 @@ where filter: Vec<(BalanceHoldAddrTag, HoldsEpoch)>, ) -> Result<(), Self::Error> { for (tag, holds_epoch) in filter { - let prefix = tag.purse_prefix_by_tag(purse_addr)?; + let prefix = match tag { + BalanceHoldAddrTag::Gas => KeyPrefix::GasBalanceHoldsByPurse(purse_addr), + BalanceHoldAddrTag::Processing => { + KeyPrefix::ProcessingBalanceHoldsByPurse(purse_addr) + } + }; let immut: &_ = self; - let hold_keys = immut.keys_with_prefix(&prefix)?; + let hold_keys = immut.keys_with_prefix(&prefix.to_bytes()?)?; for hold_key in hold_keys { let balance_hold_addr = hold_key .as_balance_hold() @@ -539,8 +545,8 @@ where } fn get_named_keys(&self, entity_addr: EntityAddr) -> Result { - let prefix = entity_addr - .named_keys_prefix() + let prefix = KeyPrefix::NamedKeysByEntity(entity_addr) + .to_bytes() .map_err(Self::Error::BytesRepr)?; let mut ret: BTreeSet = BTreeSet::new(); diff --git a/types/src/addressable_entity.rs b/types/src/addressable_entity.rs index cfc9470c2d..2b40dbd8f8 100644 --- a/types/src/addressable_entity.rs +++ b/types/src/addressable_entity.rs @@ -67,7 +67,7 @@ use crate::{ system::SystemEntityType, uref::{self, URef}, AccessRights, ApiError, CLType, CLTyped, CLValue, CLValueError, ContextAccessRights, Group, - HashAddr, Key, KeyTag, PackageHash, ProtocolVersion, PublicKey, Tagged, BLAKE2B_DIGEST_LENGTH, + HashAddr, Key, PackageHash, ProtocolVersion, PublicKey, Tagged, BLAKE2B_DIGEST_LENGTH, KEY_HASH_LENGTH, }; @@ -973,14 +973,6 @@ impl EntityAddr { } } - /// Returns the common prefix of all NamedKey entries. - pub fn named_keys_prefix(&self) -> Result, bytesrepr::Error> { - let mut ret = Vec::with_capacity(self.serialized_length() + 1); - ret.push(KeyTag::NamedKey as u8); - self.write_bytes(&mut ret)?; - Ok(ret) - } - /// Returns the formatted String representation of the [`EntityAddr`]. pub fn to_formatted_string(&self) -> String { match self { diff --git a/types/src/chainspec/vm_config/mint_costs.rs b/types/src/chainspec/vm_config/mint_costs.rs index c32df42088..0e286d5cb2 100644 --- a/types/src/chainspec/vm_config/mint_costs.rs +++ b/types/src/chainspec/vm_config/mint_costs.rs @@ -14,6 +14,8 @@ use crate::bytesrepr::{self, FromBytes, ToBytes}; pub const DEFAULT_MINT_COST: u32 = 2_500_000_000; /// Default cost of the `reduce_total_supply` mint entry point. pub const DEFAULT_REDUCE_TOTAL_SUPPLY_COST: u32 = 10_000; +/// Default cost of the `burn` mint entry point. +pub const DEFAULT_BURN_COST: u32 = 10_000; /// Default cost of the `create` mint entry point. pub const DEFAULT_CREATE_COST: u32 = 2_500_000_000; /// Default cost of the `balance` mint entry point. @@ -34,6 +36,8 @@ pub struct MintCosts { pub mint: u32, /// Cost of calling the `reduce_total_supply` entry point. pub reduce_total_supply: u32, + /// Cost of calling the `burn` entry point. + pub burn: u32, /// Cost of calling the `create` entry point. pub create: u32, /// Cost of calling the `balance` entry point. @@ -51,6 +55,7 @@ impl Default for MintCosts { Self { mint: DEFAULT_MINT_COST, reduce_total_supply: DEFAULT_REDUCE_TOTAL_SUPPLY_COST, + burn: DEFAULT_BURN_COST, create: DEFAULT_CREATE_COST, balance: DEFAULT_BALANCE_COST, transfer: DEFAULT_TRANSFER_COST, @@ -67,6 +72,7 @@ impl ToBytes for MintCosts { let Self { mint, reduce_total_supply, + burn, create, balance, transfer, @@ -81,6 +87,7 @@ impl ToBytes for MintCosts { ret.append(&mut transfer.to_bytes()?); ret.append(&mut read_base_round_reward.to_bytes()?); ret.append(&mut mint_into_existing_purse.to_bytes()?); + ret.append(&mut burn.to_bytes()?); Ok(ret) } @@ -89,6 +96,7 @@ impl ToBytes for MintCosts { let Self { mint, reduce_total_supply, + burn, create, balance, transfer, @@ -98,6 +106,7 @@ impl ToBytes for MintCosts { mint.serialized_length() + reduce_total_supply.serialized_length() + + burn.serialized_length() + create.serialized_length() + balance.serialized_length() + transfer.serialized_length() @@ -115,11 +124,13 @@ impl FromBytes for MintCosts { let (transfer, rem) = FromBytes::from_bytes(rem)?; let (read_base_round_reward, rem) = FromBytes::from_bytes(rem)?; let (mint_into_existing_purse, rem) = FromBytes::from_bytes(rem)?; + let (burn, rem) = FromBytes::from_bytes(rem)?; Ok(( Self { mint, reduce_total_supply, + burn, create, balance, transfer, @@ -136,6 +147,7 @@ impl Distribution for Standard { fn sample(&self, rng: &mut R) -> MintCosts { MintCosts { mint: rng.gen(), + burn: rng.gen(), reduce_total_supply: rng.gen(), create: rng.gen(), balance: rng.gen(), @@ -157,6 +169,7 @@ pub mod gens { pub fn mint_costs_arb()( mint in num::u32::ANY, reduce_total_supply in num::u32::ANY, + burn in num::u32::ANY, create in num::u32::ANY, balance in num::u32::ANY, transfer in num::u32::ANY, @@ -166,6 +179,7 @@ pub mod gens { MintCosts { mint, reduce_total_supply, + burn, create, balance, transfer, diff --git a/types/src/gens.rs b/types/src/gens.rs index 08520239fd..f272c13b41 100644 --- a/types/src/gens.rs +++ b/types/src/gens.rs @@ -50,9 +50,9 @@ use crate::{ TransferAddr, }, AccessRights, AddressableEntity, AddressableEntityHash, BlockTime, ByteCode, CLType, CLValue, - Digest, EntityKind, EntryPoint, EntryPointAccess, EntryPointType, EntryPoints, EraId, Group, - Key, NamedArg, Package, Parameter, Phase, ProtocolVersion, SemVer, StoredValue, URef, U128, - U256, U512, + Digest, EntityAddr, EntityKind, EntryPoint, EntryPointAccess, EntryPointType, EntryPoints, + EraId, Group, Key, NamedArg, Package, Parameter, Phase, ProtocolVersion, SemVer, StoredValue, + URef, U128, U256, U512, }; pub fn u8_slice_32() -> impl Strategy { @@ -138,6 +138,18 @@ pub fn account_hash_arb() -> impl Strategy { u8_slice_32().prop_map(AccountHash::new) } +pub fn entity_addr_arb() -> impl Strategy { + prop_oneof![ + u8_slice_32().prop_map(EntityAddr::System), + u8_slice_32().prop_map(EntityAddr::Account), + u8_slice_32().prop_map(EntityAddr::SmartContract), + ] +} + +pub fn topic_name_hash_arb() -> impl Strategy { + u8_slice_32().prop_map(TopicNameHash::new) +} + pub fn bid_addr_validator_arb() -> impl Strategy { u8_slice_32().prop_map(BidAddr::new_validator_addr) } diff --git a/types/src/key.rs b/types/src/key.rs index 3e411a37e2..dda1451fe1 100644 --- a/types/src/key.rs +++ b/types/src/key.rs @@ -1448,7 +1448,7 @@ impl ToBytes for Key { + BLOCK_GLOBAL_PADDING_BYTES.len() } Key::BalanceHold(balance_hold_addr) => { - U8_SERIALIZED_LENGTH + balance_hold_addr.serialized_length() + KEY_ID_SERIALIZED_LENGTH + balance_hold_addr.serialized_length() } } } diff --git a/types/src/system/handle_payment/error.rs b/types/src/system/handle_payment/error.rs index 7eb8ea5910..b31e509c05 100644 --- a/types/src/system/handle_payment/error.rs +++ b/types/src/system/handle_payment/error.rs @@ -156,13 +156,12 @@ pub enum Error { /// assert_eq!(21, Error::StakesDeserializationFailed as u8); /// ``` StakesDeserializationFailed = 21, - /// The invoked Handle Payment function can only be called by system contracts, but was called - /// by a user contract. + /// Raised when caller is not the system account. /// ``` /// # use casper_types::system::handle_payment::Error; - /// assert_eq!(22, Error::SystemFunctionCalledByUserAccount as u8); + /// assert_eq!(22, Error::InvalidCaller as u8); /// ``` - SystemFunctionCalledByUserAccount = 22, + InvalidCaller = 22, /// Internal error: while finalizing payment, the amount spent exceeded the amount available. /// ``` /// # use casper_types::system::handle_payment::Error; @@ -308,7 +307,7 @@ impl Display for Error { Error::StakesDeserializationFailed => { formatter.write_str("Failed to deserialize stake's balance") } - Error::SystemFunctionCalledByUserAccount => { + Error::InvalidCaller => { formatter.write_str("System function was called by user account") } Error::InsufficientPaymentForAmountSpent => { @@ -387,9 +386,7 @@ impl TryFrom for Error { v if v == Error::StakesDeserializationFailed as u8 => { Error::StakesDeserializationFailed } - v if v == Error::SystemFunctionCalledByUserAccount as u8 => { - Error::SystemFunctionCalledByUserAccount - } + v if v == Error::InvalidCaller as u8 => Error::InvalidCaller, v if v == Error::InsufficientPaymentForAmountSpent as u8 => { Error::InsufficientPaymentForAmountSpent } diff --git a/types/src/system/mint/balance_hold.rs b/types/src/system/mint/balance_hold.rs index 4ef183249d..ef1542d1da 100644 --- a/types/src/system/mint/balance_hold.rs +++ b/types/src/system/mint/balance_hold.rs @@ -20,11 +20,10 @@ use schemars::JsonSchema; use serde::{Deserialize, Serialize}; use crate::{ - bytesrepr, - bytesrepr::{FromBytes, ToBytes}, + bytesrepr::{self, FromBytes, ToBytes}, checksummed_hex, key::FromStrError, - BlockTime, Key, KeyTag, Timestamp, URefAddr, BLOCKTIME_SERIALIZED_LENGTH, UREF_ADDR_LENGTH, + BlockTime, Key, Timestamp, URefAddr, BLOCKTIME_SERIALIZED_LENGTH, UREF_ADDR_LENGTH, }; const GAS_TAG: u8 = 0; @@ -60,15 +59,6 @@ impl BalanceHoldAddrTag { } None } - - /// Returns key prefix for a purse by balance hold addr tag. - pub fn purse_prefix_by_tag(&self, purse_addr: URefAddr) -> Result, bytesrepr::Error> { - let mut ret = Vec::with_capacity(purse_addr.serialized_length() + 2); - ret.push(KeyTag::BalanceHold as u8); - ret.push(*self as u8); - purse_addr.write_bytes(&mut ret)?; - Ok(ret) - } } impl Display for BalanceHoldAddrTag { @@ -137,7 +127,7 @@ impl BalanceHoldAddr { + BLOCKTIME_SERIALIZED_LENGTH; /// Creates a Gas variant instance of [`BalanceHoldAddr`]. - pub(crate) const fn new_gas(purse_addr: URefAddr, block_time: BlockTime) -> BalanceHoldAddr { + pub const fn new_gas(purse_addr: URefAddr, block_time: BlockTime) -> BalanceHoldAddr { BalanceHoldAddr::Gas { purse_addr, block_time, @@ -145,10 +135,7 @@ impl BalanceHoldAddr { } /// Creates a Processing variant instance of [`BalanceHoldAddr`]. - pub(crate) const fn new_processing( - purse_addr: URefAddr, - block_time: BlockTime, - ) -> BalanceHoldAddr { + pub const fn new_processing(purse_addr: URefAddr, block_time: BlockTime) -> BalanceHoldAddr { BalanceHoldAddr::Processing { purse_addr, block_time, diff --git a/types/src/system/mint/constants.rs b/types/src/system/mint/constants.rs index e8e7d63989..a44aa1fcbf 100644 --- a/types/src/system/mint/constants.rs +++ b/types/src/system/mint/constants.rs @@ -17,6 +17,8 @@ pub const ARG_ROUND_SEIGNIORAGE_RATE: &str = "round_seigniorage_rate"; pub const METHOD_MINT: &str = "mint"; /// Named constant for method `reduce_total_supply`. pub const METHOD_REDUCE_TOTAL_SUPPLY: &str = "reduce_total_supply"; +/// Named constant for method `burn`. +pub const METHOD_BURN: &str = "burn"; /// Named constant for (synthetic) method `create` pub const METHOD_CREATE: &str = "create"; /// Named constant for method `balance`. diff --git a/types/src/system/mint/entry_points.rs b/types/src/system/mint/entry_points.rs index bc22fcec78..d6ffd1bca4 100644 --- a/types/src/system/mint/entry_points.rs +++ b/types/src/system/mint/entry_points.rs @@ -3,7 +3,7 @@ use alloc::boxed::Box; use crate::{ addressable_entity::Parameters, system::mint::{ - ARG_AMOUNT, ARG_ID, ARG_PURSE, ARG_SOURCE, ARG_TARGET, ARG_TO, METHOD_BALANCE, + ARG_AMOUNT, ARG_ID, ARG_PURSE, ARG_SOURCE, ARG_TARGET, ARG_TO, METHOD_BALANCE, METHOD_BURN, METHOD_CREATE, METHOD_MINT, METHOD_MINT_INTO_EXISTING_PURSE, METHOD_READ_BASE_ROUND_REWARD, METHOD_REDUCE_TOTAL_SUPPLY, METHOD_TRANSFER, }, @@ -38,6 +38,21 @@ pub fn mint_entry_points() -> EntryPoints { ); entry_points.add_entry_point(entry_point); + let entry_point = EntryPoint::new( + METHOD_BURN, + vec![ + Parameter::new(ARG_PURSE, CLType::URef), + Parameter::new(ARG_AMOUNT, CLType::U512), + ], + CLType::Result { + ok: Box::new(CLType::Unit), + err: Box::new(CLType::U8), + }, + EntryPointAccess::Public, + EntryPointType::Called, + ); + entry_points.add_entry_point(entry_point); + let entry_point = EntryPoint::new( METHOD_CREATE, Parameters::new(), diff --git a/utils/global-state-update-gen/src/admins.rs b/utils/global-state-update-gen/src/admins.rs index 4a6e08f802..c6833f5be9 100644 --- a/utils/global-state-update-gen/src/admins.rs +++ b/utils/global-state-update-gen/src/admins.rs @@ -35,7 +35,7 @@ pub(crate) fn generate_admins(matches: &ArgMatches<'_>) { let admin_values = matches.values_of("admin").expect("at least one argument"); let protocol_version = DEFAULT_PROTOCOL_VERSION; - let mut total_supply = test_builder.total_supply(Some(post_state_hash), protocol_version); + let mut total_supply = test_builder.total_supply(protocol_version, Some(post_state_hash)); let total_supply_before = total_supply; for value in admin_values {