From b46d1c78a3d3a07e4b40f84a0f79783675103027 Mon Sep 17 00:00:00 2001 From: Deuszex Date: Thu, 25 Apr 2024 17:48:33 +0200 Subject: [PATCH 01/27] WIP --- Cargo.toml | 15 -- Makefile | 21 +-- cep18-test-contract/Cargo.toml | 13 +- .../rust-toolchain | 0 .../rustfmt.toml | 2 +- cep18-test-contract/src/main.rs | 84 +++++---- cep18/Cargo.toml | 14 +- cep18/rust-toolchain | 1 + cep18/rustfmt.toml | 4 + cep18/src/constants.rs | 1 + cep18/src/entry_points.rs | 30 ++-- cep18/src/error.rs | 1 + cep18/src/events.rs | 78 ++++---- cep18/src/main.rs | 78 ++++++-- cep18/src/modalities.rs | 6 +- cep18/src/utils.rs | 16 +- docs/4-tests.md | 14 +- tests/Cargo.toml | 14 +- tests/rust-toolchain | 1 + tests/src/allowance.rs | 23 +-- tests/src/install.rs | 21 ++- tests/src/migration.rs | 2 +- tests/src/mint_and_burn.rs | 52 +++--- tests/src/transfer.rs | 46 +++-- .../src/utility/installer_request_builders.rs | 169 +++++++++--------- 25 files changed, 391 insertions(+), 315 deletions(-) delete mode 100644 Cargo.toml rename rust-toolchain => cep18-test-contract/rust-toolchain (100%) rename rustfmt.toml => cep18-test-contract/rustfmt.toml (80%) create mode 100755 cep18/rust-toolchain create mode 100644 cep18/rustfmt.toml create mode 100755 tests/rust-toolchain diff --git a/Cargo.toml b/Cargo.toml deleted file mode 100644 index b67d7ff..0000000 --- a/Cargo.toml +++ /dev/null @@ -1,15 +0,0 @@ -[workspace] -members = [ - "cep18", - "cep18-test-contract", - "tests", -] -default-members = [ - "cep18", - "cep18-test-contract", - "tests", -] - -[profile.release] -codegen-units = 1 -lto = true diff --git a/Makefile b/Makefile index 3f13ad3..0a3648f 100644 --- a/Makefile +++ b/Makefile @@ -1,21 +1,17 @@ -PINNED_TOOLCHAIN := $(shell cat rust-toolchain) - prepare: rustup target add wasm32-unknown-unknown - rustup component add clippy --toolchain ${PINNED_TOOLCHAIN} - rustup component add rustfmt --toolchain ${PINNED_TOOLCHAIN} .PHONY: build-contract build-contract: - cargo build --release --target wasm32-unknown-unknown -p cep18 - cargo build --release --target wasm32-unknown-unknown -p cep18-test-contract - wasm-strip target/wasm32-unknown-unknown/release/cep18.wasm - wasm-strip target/wasm32-unknown-unknown/release/cep18_test_contract.wasm + cd cep18 && cargo build --release --target wasm32-unknown-unknown + cd cep18-test-contract && cargo build --release --target wasm32-unknown-unknown + wasm-strip ./cep18/target/wasm32-unknown-unknown/release/cep18.wasm + wasm-strip ./cep18-test-contract/target/wasm32-unknown-unknown/release/cep18_test_contract.wasm setup-test: build-contract mkdir -p tests/wasm - cp ./target/wasm32-unknown-unknown/release/cep18.wasm tests/wasm - cp ./target/wasm32-unknown-unknown/release/cep18_test_contract.wasm tests/wasm + cp ./cep18/target/wasm32-unknown-unknown/release/cep18.wasm tests/wasm + cp ./cep18-test-contract/target/wasm32-unknown-unknown/release/cep18_test_contract.wasm tests/wasm test: setup-test cd tests && cargo test @@ -25,6 +21,11 @@ clippy: cd cep18-test-contract && cargo clippy --all-targets -- -D warnings cd tests && cargo clippy --all-targets -- -D warnings +format: + cd cep18 && cargo fmt + cd cep18-test-contract && cargo fmt + cd tests && cargo fmt + check-lint: clippy cd cep18 && cargo fmt -- --check cd cep18-test-contract && cargo fmt -- --check diff --git a/cep18-test-contract/Cargo.toml b/cep18-test-contract/Cargo.toml index 080ccb0..dc052de 100644 --- a/cep18-test-contract/Cargo.toml +++ b/cep18-test-contract/Cargo.toml @@ -1,8 +1,7 @@ [package] name = "cep18-test-contract" -version = "1.2.0" -authors = ["Michał Papierski "] -edition = "2018" +version = "2.0.0" +edition = "2021" [[bin]] name = "cep18_test_contract" @@ -12,5 +11,9 @@ doctest = false test = false [dependencies] -casper-contract = "3.0.0" -casper-types = "3.0.0" +casper-types = { git = "https://github.com/casper-network/casper-node.git", branch = "release-2.0.0-rc1" } +casper-contract = { git = "https://github.com/casper-network/casper-node.git", branch = "release-2.0.0-rc1" } + +[profile.release] +codegen-units = 1 +lto = true diff --git a/rust-toolchain b/cep18-test-contract/rust-toolchain similarity index 100% rename from rust-toolchain rename to cep18-test-contract/rust-toolchain diff --git a/rustfmt.toml b/cep18-test-contract/rustfmt.toml similarity index 80% rename from rustfmt.toml rename to cep18-test-contract/rustfmt.toml index 3d2e76e..b16fb7d 100644 --- a/rustfmt.toml +++ b/cep18-test-contract/rustfmt.toml @@ -1,4 +1,4 @@ wrap_comments = true comment_width = 100 imports_granularity = "Crate" -edition = "2018" +edition = "2021" diff --git a/cep18-test-contract/src/main.rs b/cep18-test-contract/src/main.rs index 77ec354..32efbdd 100644 --- a/cep18-test-contract/src/main.rs +++ b/cep18-test-contract/src/main.rs @@ -15,7 +15,7 @@ use casper_contract::{ }; use casper_types::{ - bytesrepr::ToBytes, runtime_args, CLTyped, ContractHash, EntryPoint, EntryPointAccess, + bytesrepr::ToBytes, runtime_args, AddressableEntityHash, CLTyped, EntryPoint, EntryPointAccess, EntryPointType, EntryPoints, Key, Parameter, RuntimeArgs, U256, }; @@ -54,11 +54,10 @@ fn store_result(result: T) { #[no_mangle] extern "C" fn check_total_supply() { - let token_contract: ContractHash = ContractHash::new( + let token_contract: AddressableEntityHash = runtime::get_named_arg::(TOKEN_CONTRACT_RUNTIME_ARG_NAME) - .into_hash() - .unwrap_or_revert(), - ); + .into_entity_hash() + .unwrap_or_revert(); let total_supply: U256 = runtime::call_contract( token_contract, TOTAL_SUPPLY_ENTRY_POINT_NAME, @@ -69,11 +68,10 @@ extern "C" fn check_total_supply() { #[no_mangle] extern "C" fn check_balance_of() { - let token_contract: ContractHash = ContractHash::new( + let token_contract: AddressableEntityHash = runtime::get_named_arg::(TOKEN_CONTRACT_RUNTIME_ARG_NAME) - .into_hash() - .unwrap_or_revert(), - ); + .into_entity_hash() + .unwrap_or_revert(); let address: Key = runtime::get_named_arg(ADDRESS_RUNTIME_ARG_NAME); let balance_args = runtime_args! { @@ -87,11 +85,10 @@ extern "C" fn check_balance_of() { #[no_mangle] extern "C" fn check_allowance_of() { - let token_contract: ContractHash = ContractHash::new( + let token_contract: AddressableEntityHash = runtime::get_named_arg::(TOKEN_CONTRACT_RUNTIME_ARG_NAME) - .into_hash() - .unwrap_or_revert(), - ); + .into_entity_hash() + .unwrap_or_revert(); let owner: Key = runtime::get_named_arg(OWNER_RUNTIME_ARG_NAME); let spender: Key = runtime::get_named_arg(SPENDER_RUNTIME_ARG_NAME); @@ -107,11 +104,10 @@ extern "C" fn check_allowance_of() { #[no_mangle] extern "C" fn transfer_as_stored_contract() { - let token_contract: ContractHash = ContractHash::new( + let token_contract: AddressableEntityHash = runtime::get_named_arg::(TOKEN_CONTRACT_RUNTIME_ARG_NAME) - .into_hash() - .unwrap_or_revert(), - ); + .into_entity_hash() + .unwrap_or_revert(); let recipient: Key = runtime::get_named_arg(RECIPIENT_RUNTIME_ARG_NAME); let amount: U256 = runtime::get_named_arg(AMOUNT_RUNTIME_ARG_NAME); @@ -125,11 +121,10 @@ extern "C" fn transfer_as_stored_contract() { #[no_mangle] extern "C" fn transfer_from_as_stored_contract() { - let token_contract: ContractHash = ContractHash::new( + let token_contract: AddressableEntityHash = runtime::get_named_arg::(TOKEN_CONTRACT_RUNTIME_ARG_NAME) - .into_hash() - .unwrap_or_revert(), - ); + .into_entity_hash() + .unwrap_or_revert(); let owner: Key = runtime::get_named_arg(OWNER_RUNTIME_ARG_NAME); let recipient: Key = runtime::get_named_arg(RECIPIENT_RUNTIME_ARG_NAME); let amount: U256 = runtime::get_named_arg(AMOUNT_RUNTIME_ARG_NAME); @@ -149,11 +144,10 @@ extern "C" fn transfer_from_as_stored_contract() { #[no_mangle] extern "C" fn approve_as_stored_contract() { - let token_contract: ContractHash = ContractHash::new( + let token_contract: AddressableEntityHash = runtime::get_named_arg::(TOKEN_CONTRACT_RUNTIME_ARG_NAME) - .into_hash() - .unwrap_or_revert(), - ); + .into_entity_hash() + .unwrap_or_revert(); let spender: Key = runtime::get_named_arg(SPENDER_RUNTIME_ARG_NAME); let amount: U256 = runtime::get_named_arg(AMOUNT_RUNTIME_ARG_NAME); @@ -172,69 +166,84 @@ pub extern "C" fn call() { String::from(CHECK_TOTAL_SUPPLY_ENTRY_POINT_NAME), vec![Parameter::new( TOKEN_CONTRACT_RUNTIME_ARG_NAME, - ContractHash::cl_type(), + AddressableEntityHash::cl_type(), )], <()>::cl_type(), EntryPointAccess::Public, - EntryPointType::Contract, + EntryPointType::Called, ); let check_balance_of_entrypoint = EntryPoint::new( String::from(CHECK_BALANCE_OF_ENTRY_POINT_NAME), vec![ - Parameter::new(TOKEN_CONTRACT_RUNTIME_ARG_NAME, ContractHash::cl_type()), + Parameter::new( + TOKEN_CONTRACT_RUNTIME_ARG_NAME, + AddressableEntityHash::cl_type(), + ), Parameter::new(ADDRESS_RUNTIME_ARG_NAME, Key::cl_type()), ], <()>::cl_type(), EntryPointAccess::Public, - EntryPointType::Contract, + EntryPointType::Called, ); let check_allowance_of_entrypoint = EntryPoint::new( String::from(CHECK_ALLOWANCE_OF_ENTRY_POINT_NAME), vec![ - Parameter::new(TOKEN_CONTRACT_RUNTIME_ARG_NAME, ContractHash::cl_type()), + Parameter::new( + TOKEN_CONTRACT_RUNTIME_ARG_NAME, + AddressableEntityHash::cl_type(), + ), Parameter::new(OWNER_RUNTIME_ARG_NAME, Key::cl_type()), Parameter::new(SPENDER_RUNTIME_ARG_NAME, Key::cl_type()), ], <()>::cl_type(), EntryPointAccess::Public, - EntryPointType::Contract, + EntryPointType::Called, ); let transfer_as_stored_contract_entrypoint = EntryPoint::new( String::from(TRANSFER_AS_STORED_CONTRACT_ENTRY_POINT_NAME), vec![ - Parameter::new(TOKEN_CONTRACT_RUNTIME_ARG_NAME, ContractHash::cl_type()), + Parameter::new( + TOKEN_CONTRACT_RUNTIME_ARG_NAME, + AddressableEntityHash::cl_type(), + ), Parameter::new(RECIPIENT_RUNTIME_ARG_NAME, Key::cl_type()), Parameter::new(AMOUNT_RUNTIME_ARG_NAME, U256::cl_type()), ], <()>::cl_type(), EntryPointAccess::Public, - EntryPointType::Contract, + EntryPointType::Called, ); let approve_as_stored_contract_entrypoint = EntryPoint::new( String::from(APPROVE_AS_STORED_CONTRACT_ENTRY_POINT_NAME), vec![ - Parameter::new(TOKEN_CONTRACT_RUNTIME_ARG_NAME, ContractHash::cl_type()), + Parameter::new( + TOKEN_CONTRACT_RUNTIME_ARG_NAME, + AddressableEntityHash::cl_type(), + ), Parameter::new(SPENDER_RUNTIME_ARG_NAME, Key::cl_type()), Parameter::new(AMOUNT_RUNTIME_ARG_NAME, U256::cl_type()), ], <()>::cl_type(), EntryPointAccess::Public, - EntryPointType::Contract, + EntryPointType::Called, ); let transfer_from_as_stored_contract_entrypoint = EntryPoint::new( String::from(TRANSFER_FROM_AS_STORED_CONTRACT_ENTRY_POINT_NAME), vec![ - Parameter::new(TOKEN_CONTRACT_RUNTIME_ARG_NAME, ContractHash::cl_type()), + Parameter::new( + TOKEN_CONTRACT_RUNTIME_ARG_NAME, + AddressableEntityHash::cl_type(), + ), Parameter::new(OWNER_RUNTIME_ARG_NAME, Key::cl_type()), Parameter::new(RECIPIENT_RUNTIME_ARG_NAME, Key::cl_type()), Parameter::new(AMOUNT_RUNTIME_ARG_NAME, U256::cl_type()), ], <()>::cl_type(), EntryPointAccess::Public, - EntryPointType::Contract, + EntryPointType::Called, ); entry_points.add_entry_point(check_total_supply_entrypoint); @@ -249,5 +258,6 @@ pub extern "C" fn call() { None, Some(CEP18_TEST_CALL_KEY.to_string()), None, + None, ); } diff --git a/cep18/Cargo.toml b/cep18/Cargo.toml index 358d281..1db64b3 100644 --- a/cep18/Cargo.toml +++ b/cep18/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "cep18" -version = "1.2.0" -edition = "2018" +version = "2.0.0" +edition = "2021" description = "A library for developing CEP-18 tokens for the Casper network." readme = "README.md" documentation = "https://docs.rs/casper-cep18" @@ -18,8 +18,12 @@ test = false [dependencies] base64 = { version = "0.20.0", default-features = false, features = ["alloc"] } -casper-contract = "3.0.0" -casper-types = "3.0.0" +casper-types = { git = "https://github.com/casper-network/casper-node.git", branch = "release-2.0.0-rc1" } +casper-contract = { git = "https://github.com/casper-network/casper-node.git", branch = "release-2.0.0-rc1" } hex = { version = "0.4.3", default-features = false } once_cell = { version = "1.16.0", default-features = false } -casper-event-standard = { version = "0.4.1", default-features = false } +# casper-event-standard = { version = "0.4.1", default-features = false } + +[profile.release] +codegen-units = 1 +lto = true diff --git a/cep18/rust-toolchain b/cep18/rust-toolchain new file mode 100755 index 0000000..f9e5e5e --- /dev/null +++ b/cep18/rust-toolchain @@ -0,0 +1 @@ +nightly-2023-03-25 diff --git a/cep18/rustfmt.toml b/cep18/rustfmt.toml new file mode 100644 index 0000000..b16fb7d --- /dev/null +++ b/cep18/rustfmt.toml @@ -0,0 +1,4 @@ +wrap_comments = true +comment_width = 100 +imports_granularity = "Crate" +edition = "2021" diff --git a/cep18/src/constants.rs b/cep18/src/constants.rs index c99251e..c8eb846 100644 --- a/cep18/src/constants.rs +++ b/cep18/src/constants.rs @@ -12,6 +12,7 @@ pub const BALANCES: &str = "balances"; pub const ALLOWANCES: &str = "allowances"; /// Name of named-key for `total_supply` pub const TOTAL_SUPPLY: &str = "total_supply"; +pub const EVENTS: &str = "events"; pub const HASH_KEY_NAME_PREFIX: &str = "cep18_contract_package_"; pub const ACCESS_KEY_NAME_PREFIX: &str = "cep18_contract_package_access_"; diff --git a/cep18/src/entry_points.rs b/cep18/src/entry_points.rs index 7c62c73..532b2f5 100644 --- a/cep18/src/entry_points.rs +++ b/cep18/src/entry_points.rs @@ -22,7 +22,7 @@ pub fn name() -> EntryPoint { Vec::new(), String::cl_type(), EntryPointAccess::Public, - EntryPointType::Contract, + EntryPointType::Called, ) } @@ -33,7 +33,7 @@ pub fn symbol() -> EntryPoint { Vec::new(), String::cl_type(), EntryPointAccess::Public, - EntryPointType::Contract, + EntryPointType::Called, ) } @@ -48,7 +48,7 @@ pub fn transfer_from() -> EntryPoint { ], CLType::Unit, EntryPointAccess::Public, - EntryPointType::Contract, + EntryPointType::Called, ) } @@ -62,7 +62,7 @@ pub fn allowance() -> EntryPoint { ], U256::cl_type(), EntryPointAccess::Public, - EntryPointType::Contract, + EntryPointType::Called, ) } @@ -76,7 +76,7 @@ pub fn approve() -> EntryPoint { ], CLType::Unit, EntryPointAccess::Public, - EntryPointType::Contract, + EntryPointType::Called, ) } @@ -90,7 +90,7 @@ pub fn increase_allowance() -> EntryPoint { ], CLType::Unit, EntryPointAccess::Public, - EntryPointType::Contract, + EntryPointType::Called, ) } @@ -104,7 +104,7 @@ pub fn decrease_allowance() -> EntryPoint { ], CLType::Unit, EntryPointAccess::Public, - EntryPointType::Contract, + EntryPointType::Called, ) } @@ -118,7 +118,7 @@ pub fn transfer() -> EntryPoint { ], CLType::Unit, EntryPointAccess::Public, - EntryPointType::Contract, + EntryPointType::Called, ) } @@ -129,7 +129,7 @@ pub fn balance_of() -> EntryPoint { vec![Parameter::new(ADDRESS, Key::cl_type())], U256::cl_type(), EntryPointAccess::Public, - EntryPointType::Contract, + EntryPointType::Called, ) } @@ -140,7 +140,7 @@ pub fn total_supply() -> EntryPoint { Vec::new(), U256::cl_type(), EntryPointAccess::Public, - EntryPointType::Contract, + EntryPointType::Called, ) } @@ -151,7 +151,7 @@ pub fn decimals() -> EntryPoint { Vec::new(), u8::cl_type(), EntryPointAccess::Public, - EntryPointType::Contract, + EntryPointType::Called, ) } @@ -165,7 +165,7 @@ pub fn burn() -> EntryPoint { ], CLType::Unit, EntryPointAccess::Public, - EntryPointType::Contract, + EntryPointType::Called, ) } @@ -179,7 +179,7 @@ pub fn mint() -> EntryPoint { ], CLType::Unit, EntryPointAccess::Public, - EntryPointType::Contract, + EntryPointType::Called, ) } @@ -199,7 +199,7 @@ pub fn change_security() -> EntryPoint { ], CLType::Unit, EntryPointAccess::Public, - EntryPointType::Contract, + EntryPointType::Called, ) } @@ -210,7 +210,7 @@ pub fn init() -> EntryPoint { Vec::new(), CLType::Unit, EntryPointAccess::Public, - EntryPointType::Contract, + EntryPointType::Called, ) } diff --git a/cep18/src/error.rs b/cep18/src/error.rs index 0a43771..0199dcc 100644 --- a/cep18/src/error.rs +++ b/cep18/src/error.rs @@ -48,6 +48,7 @@ pub enum Cep18Error { CannotTargetSelfUser = 60017, InvalidBurnTarget = 60018, MissingPackageHashForUpgrade = 60019, + MissingContractHashForUpgrade = 60020, } impl From for ApiError { diff --git a/cep18/src/events.rs b/cep18/src/events.rs index de70023..9b00b16 100644 --- a/cep18/src/events.rs +++ b/cep18/src/events.rs @@ -1,16 +1,16 @@ use core::convert::TryFrom; -use alloc::collections::BTreeMap; -use casper_contract::unwrap_or_revert::UnwrapOrRevert; +use alloc::{collections::BTreeMap, format}; +use casper_contract::{contract_api::runtime, unwrap_or_revert::UnwrapOrRevert}; use casper_types::{Key, U256}; use crate::{ - constants::EVENTS_MODE, + constants::{EVENTS, EVENTS_MODE}, modalities::EventsMode, utils::{read_from, SecurityBadge}, }; -use casper_event_standard::{emit, Event, Schemas}; +// use casper_event_standard::{emit, Event, Schemas}; pub fn record_event_dictionary(event: Event) { let events_mode: EventsMode = @@ -18,10 +18,14 @@ pub fn record_event_dictionary(event: Event) { match events_mode { EventsMode::NoEvents => {} - EventsMode::CES => ces(event), + // EventsMode::CES => ces(event), + EventsMode::Native => { + runtime::emit_message(EVENTS, &format!("{event:?}").into()).unwrap_or_revert() + } } } +#[derive(Debug)] pub enum Event { Mint(Mint), Burn(Burn), @@ -33,26 +37,26 @@ pub enum Event { ChangeSecurity(ChangeSecurity), } -#[derive(Event, Debug, PartialEq, Eq)] +#[derive(Debug, PartialEq, Eq)] pub struct Mint { pub recipient: Key, pub amount: U256, } -#[derive(Event, Debug, PartialEq, Eq)] +#[derive(Debug, PartialEq, Eq)] pub struct Burn { pub owner: Key, pub amount: U256, } -#[derive(Event, Debug, PartialEq, Eq)] +#[derive(Debug, PartialEq, Eq)] pub struct SetAllowance { pub owner: Key, pub spender: Key, pub allowance: U256, } -#[derive(Event, Debug, PartialEq, Eq)] +#[derive(Debug, PartialEq, Eq)] pub struct IncreaseAllowance { pub owner: Key, pub spender: Key, @@ -60,7 +64,7 @@ pub struct IncreaseAllowance { pub inc_by: U256, } -#[derive(Event, Debug, PartialEq, Eq)] +#[derive(Debug, PartialEq, Eq)] pub struct DecreaseAllowance { pub owner: Key, pub spender: Key, @@ -68,14 +72,14 @@ pub struct DecreaseAllowance { pub decr_by: U256, } -#[derive(Event, Debug, PartialEq, Eq)] +#[derive(Debug, PartialEq, Eq)] pub struct Transfer { pub sender: Key, pub recipient: Key, pub amount: U256, } -#[derive(Event, Debug, PartialEq, Eq)] +#[derive(Debug, PartialEq, Eq)] pub struct TransferFrom { pub spender: Key, pub owner: Key, @@ -83,39 +87,39 @@ pub struct TransferFrom { pub amount: U256, } -#[derive(Event, Debug, PartialEq, Eq)] +#[derive(Debug, PartialEq, Eq)] pub struct ChangeSecurity { pub admin: Key, pub sec_change_map: BTreeMap, } -fn ces(event: Event) { - match event { - Event::Mint(ev) => emit(ev), - Event::Burn(ev) => emit(ev), - Event::SetAllowance(ev) => emit(ev), - Event::IncreaseAllowance(ev) => emit(ev), - Event::DecreaseAllowance(ev) => emit(ev), - Event::Transfer(ev) => emit(ev), - Event::TransferFrom(ev) => emit(ev), - Event::ChangeSecurity(ev) => emit(ev), - } -} +// fn ces(event: Event) { +// match event { +// Event::Mint(ev) => emit(ev), +// Event::Burn(ev) => emit(ev), +// Event::SetAllowance(ev) => emit(ev), +// Event::IncreaseAllowance(ev) => emit(ev), +// Event::DecreaseAllowance(ev) => emit(ev), +// Event::Transfer(ev) => emit(ev), +// Event::TransferFrom(ev) => emit(ev), +// Event::ChangeSecurity(ev) => emit(ev), +// } +// } pub fn init_events() { let events_mode: EventsMode = EventsMode::try_from(read_from::(EVENTS_MODE)).unwrap_or_revert(); - if events_mode == EventsMode::CES { - let schemas = Schemas::new() - .with::() - .with::() - .with::() - .with::() - .with::() - .with::() - .with::() - .with::(); - casper_event_standard::init(schemas); - } + // if events_mode == EventsMode::CES { + // let schemas = Schemas::new() + // .with::() + // .with::() + // .with::() + // .with::() + // .with::() + // .with::() + // .with::() + // .with::(); + // casper_event_standard::init(schemas); + // } } diff --git a/cep18/src/main.rs b/cep18/src/main.rs index 87ef439..cbbcd08 100644 --- a/cep18/src/main.rs +++ b/cep18/src/main.rs @@ -26,27 +26,31 @@ use entry_points::generate_entry_points; use casper_contract::{ contract_api::{ - runtime::{self, get_caller, get_key, get_named_arg, put_key, revert}, + runtime::{self, get_call_stack, get_caller, get_key, get_named_arg, put_key, revert}, storage::{self, dictionary_put}, }, unwrap_or_revert::UnwrapOrRevert, }; use casper_types::{ - bytesrepr::ToBytes, contracts::NamedKeys, runtime_args, CLValue, ContractHash, - ContractPackageHash, Key, RuntimeArgs, U256, + addressable_entity::{EntityKindTag, NamedKeys}, + bytesrepr::ToBytes, + contract_messages::MessageTopicOperation, + contracts::{ContractHash, ContractPackageHash}, + runtime_args, CLValue, Key, PackageHash, U256, }; use constants::{ ACCESS_KEY_NAME_PREFIX, ADDRESS, ADMIN_LIST, ALLOWANCES, AMOUNT, BALANCES, - CONTRACT_NAME_PREFIX, CONTRACT_VERSION_PREFIX, DECIMALS, ENABLE_MINT_BURN, EVENTS_MODE, + CONTRACT_NAME_PREFIX, CONTRACT_VERSION_PREFIX, DECIMALS, ENABLE_MINT_BURN, EVENTS, EVENTS_MODE, HASH_KEY_NAME_PREFIX, INIT_ENTRY_POINT_NAME, MINTER_LIST, NAME, NONE_LIST, OWNER, PACKAGE_HASH, RECIPIENT, SECURITY_BADGES, SPENDER, SYMBOL, TOTAL_SUPPLY, }; pub use error::Cep18Error; use events::{ - init_events, Burn, ChangeSecurity, DecreaseAllowance, Event, IncreaseAllowance, Mint, - SetAllowance, Transfer, TransferFrom, + Burn, ChangeSecurity, DecreaseAllowance, Event, IncreaseAllowance, Mint, SetAllowance, + Transfer, TransferFrom, }; +use modalities::EventsMode; use utils::{ get_immediate_caller_address, get_total_supply_uref, read_from, read_total_supply_from, sec_check, write_total_supply_to, SecurityBadge, @@ -289,7 +293,7 @@ pub extern "C" fn init() { let minter_list: Option> = utils::get_optional_named_arg_with_user_errors(MINTER_LIST, Cep18Error::InvalidMinterList); - init_events(); + // init_events(); if let Some(minter_list) = minter_list { for minter in minter_list { @@ -363,26 +367,56 @@ pub extern "C" fn change_security() { pub fn upgrade(name: &str) { let entry_points = generate_entry_points(); - let contract_package_hash = runtime::get_key(&format!("{HASH_KEY_NAME_PREFIX}{name}")) + let old_contract_package_hash = runtime::get_key(&format!("{HASH_KEY_NAME_PREFIX}{name}")) .unwrap_or_revert() - .into_hash() - .map(ContractPackageHash::new) + .into_entity_hash() .unwrap_or_revert_with(Cep18Error::MissingPackageHashForUpgrade); + let contract_package_hash = PackageHash::new(old_contract_package_hash.value()); let previous_contract_hash = runtime::get_key(&format!("{CONTRACT_NAME_PREFIX}{name}")) .unwrap_or_revert() - .into_hash() - .map(ContractHash::new) - .unwrap_or_revert_with(Cep18Error::MissingPackageHashForUpgrade); + .into_entity_hash() + .unwrap_or_revert_with(Cep18Error::MissingContractHashForUpgrade); + + let events_mode: EventsMode = EventsMode::try_from( + utils::get_optional_named_arg_with_user_errors(EVENTS_MODE, Cep18Error::InvalidEventsMode) + .unwrap_or(0u8), + ) + .unwrap_or(EventsMode::NoEvents); + + // If event mode is native append the Add operation to the contract + let mut message_topics = BTreeMap::new(); + if EventsMode::Native != events_mode { + message_topics.insert(EVENTS.to_string(), MessageTopicOperation::Add); + }; + + let mut named_keys = NamedKeys::new(); + named_keys.insert( + EVENTS_MODE.to_string(), + storage::new_uref(events_mode as u8).into(), + ); - let (contract_hash, contract_version) = - storage::add_contract_version(contract_package_hash, entry_points, NamedKeys::new()); + let (contract_hash, contract_version) = storage::add_contract_version( + contract_package_hash, + entry_points, + named_keys, + message_topics, + ); storage::disable_contract_version(contract_package_hash, previous_contract_hash) .unwrap_or_revert(); + + // migrate old ContractPackageHash as PackageHash so it's stored in a uniform format with the + // new `new_contract` implementation + runtime::put_key( + &format!("{HASH_KEY_NAME_PREFIX}{name}"), + contract_package_hash.into(), + ); + + // ContractHash in previous versions, now AddressableEntityHash runtime::put_key( &format!("{CONTRACT_NAME_PREFIX}{name}"), - contract_hash.into(), + Key::addressable_entity_key(EntityKindTag::SmartContract, contract_hash), ); runtime::put_key( &format!("{CONTRACT_VERSION_PREFIX}{name}"), @@ -427,20 +461,28 @@ pub fn install_contract(name: &str) { ); let entry_points = generate_entry_points(); - let hash_key_name = format!("{HASH_KEY_NAME_PREFIX}{name}"); + let message_topics = if events_mode == 2 { + let mut message_topics = BTreeMap::new(); + message_topics.insert(EVENTS.to_string(), MessageTopicOperation::Add); + Some(message_topics) + } else { + None + }; + let hash_key_name = format!("{HASH_KEY_NAME_PREFIX}{name}"); let (contract_hash, contract_version) = storage::new_contract( entry_points, Some(named_keys), Some(hash_key_name.clone()), Some(format!("{ACCESS_KEY_NAME_PREFIX}{name}")), + message_topics, ); let package_hash = runtime::get_key(&hash_key_name).unwrap_or_revert(); // Store contract_hash and contract_version under the keys CONTRACT_NAME and CONTRACT_VERSION runtime::put_key( &format!("{CONTRACT_NAME_PREFIX}{name}"), - contract_hash.into(), + Key::addressable_entity_key(EntityKindTag::SmartContract, contract_hash), ); runtime::put_key( &format!("{CONTRACT_VERSION_PREFIX}{name}"), diff --git a/cep18/src/modalities.rs b/cep18/src/modalities.rs index f4352f7..7b2d46f 100644 --- a/cep18/src/modalities.rs +++ b/cep18/src/modalities.rs @@ -7,7 +7,8 @@ use crate::Cep18Error; #[allow(clippy::upper_case_acronyms)] pub enum EventsMode { NoEvents = 0, - CES = 1, + // CES = 1, + Native = 2, } impl TryFrom for EventsMode { @@ -16,7 +17,8 @@ impl TryFrom for EventsMode { fn try_from(value: u8) -> Result { match value { 0 => Ok(EventsMode::NoEvents), - 1 => Ok(EventsMode::CES), + // 1 => Ok(EventsMode::CES), + 2 => Ok(EventsMode::Native), _ => Err(Cep18Error::InvalidEventsMode), } } diff --git a/cep18/src/utils.rs b/cep18/src/utils.rs index bb6d74a..1442151 100644 --- a/cep18/src/utils.rs +++ b/cep18/src/utils.rs @@ -14,7 +14,7 @@ use casper_contract::{ use casper_types::{ api_error, bytesrepr::{self, FromBytes, ToBytes}, - system::CallStackElement, + system::Caller, ApiError, CLTyped, Key, URef, U256, }; @@ -45,18 +45,10 @@ where /// /// For `Session` and `StoredSession` variants it will return account hash, and for `StoredContract` /// case it will use contract package hash as the address. -fn call_stack_element_to_address(call_stack_element: CallStackElement) -> Key { +fn call_stack_element_to_address(call_stack_element: Caller) -> Key { match call_stack_element { - CallStackElement::Session { account_hash } => Key::from(account_hash), - CallStackElement::StoredSession { account_hash, .. } => { - // Stored session code acts in account's context, so if stored session wants to interact - // with an CEP-18 token caller's address will be used. - Key::from(account_hash) - } - CallStackElement::StoredContract { - contract_package_hash, - .. - } => Key::from(contract_package_hash), + Caller::Initiator { account_hash } => Key::from(account_hash), + Caller::Entity { package_hash, .. } => Key::from(package_hash), } } diff --git a/docs/4-tests.md b/docs/4-tests.md index cbf7242..c613bb8 100644 --- a/docs/4-tests.md +++ b/docs/4-tests.md @@ -76,8 +76,8 @@ Expand the example below to see a subset of the required constants for this proj // File https://github.com/casper-ecosystem/cep18/blob/dev/tests/src/utility/installer_request_builders.rs use casper_engine_test_support::{ - ExecuteRequestBuilder, InMemoryWasmTestBuilder, DEFAULT_ACCOUNT_ADDR, - MINIMUM_ACCOUNT_CREATION_BALANCE, PRODUCTION_RUN_GENESIS_REQUEST, + ExecuteRequestBuilder, LmdbWasmTestBuilder, DEFAULT_ACCOUNT_ADDR, + MINIMUM_ACCOUNT_CREATION_BALANCE, LOCAL_GENESIS_REQUEST, }; use casper_execution_engine::core::engine_state::ExecuteRequest; use casper_types::{ @@ -121,7 +121,7 @@ pub(crate) cep18_test_contract_package: ContractPackageHash, // Setting up the test instance of CEP-18. -pub(crate) fn setup() -> (InMemoryWasmTestBuilder, TestContext) { +pub(crate) fn setup() -> (LmdbWasmTestBuilder, TestContext) { setup_with_args(runtime_args! { ARG_NAME => TOKEN_NAME, ARG_SYMBOL => TOKEN_SYMBOL, @@ -132,9 +132,9 @@ pub(crate) fn setup() -> (InMemoryWasmTestBuilder, TestContext) { // Establishing test accounts. -pub(crate) fn setup_with_args(install_args: RuntimeArgs) -> (InMemoryWasmTestBuilder, TestContext) { - let mut builder = InMemoryWasmTestBuilder::default(); - builder.run_genesis(&PRODUCTION_RUN_GENESIS_REQUEST); +pub(crate) fn setup_with_args(install_args: RuntimeArgs) -> (LmdbWasmTestBuilder, TestContext) { + let mut builder = LmdbWasmTestBuilder::default(); + builder.run_genesis(&LOCAL_GENESIS_REQUEST); let id: Option = None; let transfer_1_args = runtime_args! { @@ -219,7 +219,7 @@ The following code snippet is an example function that tests the ability to tran // File https://github.com/casper-ecosystem/cep18/blob/dev/tests/src/utility/installer_request_builders.rs pub(crate) fn test_cep18_transfer( - builder: &mut InMemoryWasmTestBuilder, + builder: &mut LmdbWasmTestBuilder, test_context: &TestContext, sender1: Key, recipient1: Key, diff --git a/tests/Cargo.toml b/tests/Cargo.toml index 870b86b..a36af90 100644 --- a/tests/Cargo.toml +++ b/tests/Cargo.toml @@ -1,16 +1,16 @@ [package] name = "tests" -version = "1.2.0" -edition = "2018" -authors = ["Michał Papierski "] +version = "2.0.0" +edition = "2021" [dependencies] -casper-types = "3.0.0" -casper-engine-test-support = "5.0.0" -casper-execution-engine = "5.0.0" +casper-types = { git = "https://github.com/casper-network/casper-node.git", branch = "release-2.0.0-rc1", default-features = false} +casper-engine-test-support = { git = "https://github.com/casper-network/casper-node.git", branch = "release-2.0.0-rc1", default-features = false} +casper-execution-engine = { git = "https://github.com/casper-network/casper-node.git", branch = "release-2.0.0-rc1", default-features = false} +casper-storage = { git = "https://github.com/casper-network/casper-node.git", branch = "release-2.0.0-rc1", default-features = false} once_cell = "1.16.0" [lib] name = "tests" bench = false -doctest = false +doctest = false \ No newline at end of file diff --git a/tests/rust-toolchain b/tests/rust-toolchain new file mode 100755 index 0000000..870bbe4 --- /dev/null +++ b/tests/rust-toolchain @@ -0,0 +1 @@ +stable \ No newline at end of file diff --git a/tests/src/allowance.rs b/tests/src/allowance.rs index b1c9d55..5b6258a 100644 --- a/tests/src/allowance.rs +++ b/tests/src/allowance.rs @@ -1,5 +1,5 @@ use casper_engine_test_support::{ExecuteRequestBuilder, DEFAULT_ACCOUNT_ADDR}; -use casper_types::{runtime_args, ApiError, Key, RuntimeArgs, U256}; +use casper_types::{addressable_entity::EntityKindTag, runtime_args, AddressableEntityHash, ApiError, Key, U256}; use crate::utility::{ constants::{ @@ -11,9 +11,7 @@ use crate::utility::{ cep18_check_allowance_of, make_cep18_approve_request, setup, test_approve_for, TestContext, }, }; -use casper_execution_engine::core::{ - engine_state::Error as CoreError, execution::Error as ExecError, -}; +use casper_execution_engine::{engine_state::Error as CoreError, execution::ExecError}; #[test] fn should_approve_funds_contract_to_account() { @@ -26,8 +24,8 @@ fn should_approve_funds_contract_to_account() { test_approve_for( &mut builder, &test_context, - Key::Hash(cep18_test_contract_package.value()), - Key::Hash(cep18_test_contract_package.value()), + Key::addressable_entity_key(EntityKindTag::SmartContract, AddressableEntityHash::new(cep18_test_contract_package.value())), + Key::addressable_entity_key(EntityKindTag::SmartContract, AddressableEntityHash::new(cep18_test_contract_package.value())), Key::Account(*DEFAULT_ACCOUNT_ADDR), ); } @@ -78,6 +76,8 @@ fn should_approve_funds_account_to_contract() { fn should_not_transfer_from_without_enough_allowance() { let (mut builder, TestContext { cep18_token, .. }) = setup(); + let addressable_cep18_token = AddressableEntityHash::new(cep18_token.value()); + let allowance_amount_1 = U256::from(ALLOWANCE_AMOUNT_1); let transfer_from_amount_1 = allowance_amount_1 + U256::one(); @@ -102,7 +102,7 @@ fn should_not_transfer_from_without_enough_allowance() { let approve_request_1 = ExecuteRequestBuilder::contract_call_by_hash( sender, - cep18_token, + addressable_cep18_token, METHOD_APPROVE, cep18_approve_args, ) @@ -110,7 +110,7 @@ fn should_not_transfer_from_without_enough_allowance() { let transfer_from_request_1 = ExecuteRequestBuilder::contract_call_by_hash( sender, - cep18_token, + addressable_cep18_token, METHOD_TRANSFER_FROM, cep18_transfer_from_args, ) @@ -135,6 +135,9 @@ fn should_not_transfer_from_without_enough_allowance() { #[test] fn test_decrease_allowance() { let (mut builder, TestContext { cep18_token, .. }) = setup(); + + let addressable_cep18_token = AddressableEntityHash::new(cep18_token.value()); + let sender = Key::Account(*DEFAULT_ACCOUNT_ADDR); let owner = Key::Account(*DEFAULT_ACCOUNT_ADDR); let spender = Key::Hash([42; 32]); @@ -148,7 +151,7 @@ fn test_decrease_allowance() { make_cep18_approve_request(sender, &cep18_token, spender, allowance_amount_1); let decrease_allowance_request = ExecuteRequestBuilder::contract_call_by_hash( sender.into_account().unwrap(), - cep18_token, + addressable_cep18_token, DECREASE_ALLOWANCE, runtime_args! { ARG_SPENDER => spender, @@ -158,7 +161,7 @@ fn test_decrease_allowance() { .build(); let increase_allowance_request = ExecuteRequestBuilder::contract_call_by_hash( sender.into_account().unwrap(), - cep18_token, + addressable_cep18_token, INCREASE_ALLOWANCE, runtime_args! { ARG_SPENDER => spender, diff --git a/tests/src/install.rs b/tests/src/install.rs index 4015360..520bc33 100644 --- a/tests/src/install.rs +++ b/tests/src/install.rs @@ -1,5 +1,5 @@ use casper_engine_test_support::DEFAULT_ACCOUNT_ADDR; -use casper_types::{Key, U256}; +use casper_types::{EntityAddr, Key, U256}; use crate::utility::{ constants::{ @@ -15,16 +15,18 @@ use crate::utility::{ fn should_have_queryable_properties() { let (mut builder, TestContext { cep18_token, .. }) = setup(); - let name: String = builder.get_value(cep18_token, NAME_KEY); + let cep18_entity_addr = EntityAddr::new_smart_contract(cep18_token.value()); + + let name: String = builder.get_value(cep18_entity_addr, NAME_KEY); assert_eq!(name, TOKEN_NAME); - let symbol: String = builder.get_value(cep18_token, SYMBOL_KEY); + let symbol: String = builder.get_value(cep18_entity_addr, SYMBOL_KEY); assert_eq!(symbol, TOKEN_SYMBOL); - let decimals: u8 = builder.get_value(cep18_token, DECIMALS_KEY); + let decimals: u8 = builder.get_value(cep18_entity_addr, DECIMALS_KEY); assert_eq!(decimals, TOKEN_DECIMALS); - let total_supply: U256 = builder.get_value(cep18_token, TOTAL_SUPPLY_KEY); + let total_supply: U256 = builder.get_value(cep18_entity_addr, TOTAL_SUPPLY_KEY); assert_eq!(total_supply, U256::from(TOKEN_TOTAL_SUPPLY)); let owner_key = Key::Account(*DEFAULT_ACCOUNT_ADDR); @@ -48,11 +50,8 @@ fn should_have_queryable_properties() { fn should_not_store_balances_or_allowances_under_account_after_install() { let (builder, _contract_hash) = setup(); - let account = builder - .get_account(*DEFAULT_ACCOUNT_ADDR) - .expect("should have account"); + let named_keys = builder.get_named_keys_by_account_hash(*DEFAULT_ACCOUNT_ADDR); - let named_keys = account.named_keys(); - assert!(!named_keys.contains_key(BALANCES_KEY), "{:?}", named_keys); - assert!(!named_keys.contains_key(ALLOWANCES_KEY), "{:?}", named_keys); + assert!(!named_keys.contains(BALANCES_KEY), "{:?}", named_keys); + assert!(!named_keys.contains(ALLOWANCES_KEY), "{:?}", named_keys); } diff --git a/tests/src/migration.rs b/tests/src/migration.rs index bb60f8b..a502218 100644 --- a/tests/src/migration.rs +++ b/tests/src/migration.rs @@ -1,5 +1,5 @@ use casper_engine_test_support::{ExecuteRequestBuilder, DEFAULT_ACCOUNT_ADDR}; -use casper_types::{runtime_args, Key, RuntimeArgs, U256}; +use casper_types::{runtime_args, Key, U256}; use crate::utility::{ constants::{ diff --git a/tests/src/mint_and_burn.rs b/tests/src/mint_and_burn.rs index e655e4b..0bdc54a 100644 --- a/tests/src/mint_and_burn.rs +++ b/tests/src/mint_and_burn.rs @@ -1,5 +1,5 @@ use casper_engine_test_support::{ExecuteRequestBuilder, DEFAULT_ACCOUNT_ADDR}; -use casper_types::{runtime_args, ApiError, Key, RuntimeArgs, U256}; +use casper_types::{runtime_args, AddressableEntityHash, ApiError, Key, U256}; use crate::utility::{ constants::{ @@ -14,9 +14,7 @@ use crate::utility::{ }, }; -use casper_execution_engine::core::{ - engine_state::Error as CoreError, execution::Error as ExecError, -}; +use casper_execution_engine::{engine_state::Error as CoreError, execution::ExecError}; #[test] fn test_mint_and_burn_tokens() { @@ -29,9 +27,10 @@ fn test_mint_and_burn_tokens() { ARG_TOTAL_SUPPLY => U256::from(TOKEN_TOTAL_SUPPLY), ENABLE_MINT_BURN => true, }); + let addressable_cep18_token = AddressableEntityHash::new(cep18_token.value()); let mint_request = ExecuteRequestBuilder::contract_call_by_hash( *DEFAULT_ACCOUNT_ADDR, - cep18_token, + addressable_cep18_token, METHOD_MINT, runtime_args! {OWNER => TOKEN_OWNER_ADDRESS_1, AMOUNT => U256::from(TOKEN_OWNER_AMOUNT_1)}, ) @@ -39,7 +38,7 @@ fn test_mint_and_burn_tokens() { builder.exec(mint_request).expect_success().commit(); let mint_request_2 = ExecuteRequestBuilder::contract_call_by_hash( *DEFAULT_ACCOUNT_ADDR, - cep18_token, + addressable_cep18_token, METHOD_MINT, runtime_args! {OWNER => TOKEN_OWNER_ADDRESS_2, AMOUNT => U256::from(TOKEN_OWNER_AMOUNT_2)}, ) @@ -65,7 +64,7 @@ fn test_mint_and_burn_tokens() { let mint_request = ExecuteRequestBuilder::contract_call_by_hash( *DEFAULT_ACCOUNT_ADDR, - cep18_token, + addressable_cep18_token, METHOD_MINT, runtime_args! { ARG_OWNER => TOKEN_OWNER_ADDRESS_1, @@ -94,7 +93,7 @@ fn test_mint_and_burn_tokens() { let burn_request = ExecuteRequestBuilder::contract_call_by_hash( *DEFAULT_ACCOUNT_ADDR, - cep18_token, + addressable_cep18_token, METHOD_BURN, runtime_args! { ARG_OWNER => Key::Account(*DEFAULT_ACCOUNT_ADDR), @@ -138,9 +137,10 @@ fn test_should_not_mint_above_limits() { "enable_mint_burn" => true, }); + let addressable_cep18_token = AddressableEntityHash::new(cep18_token.value()); let mint_request = ExecuteRequestBuilder::contract_call_by_hash( *DEFAULT_ACCOUNT_ADDR, - cep18_token, + addressable_cep18_token, METHOD_MINT, runtime_args! {OWNER => TOKEN_OWNER_ADDRESS_1, AMOUNT => U256::from(TOKEN_OWNER_AMOUNT_1)}, ) @@ -148,7 +148,7 @@ fn test_should_not_mint_above_limits() { builder.exec(mint_request).expect_success().commit(); let mint_request_2 = ExecuteRequestBuilder::contract_call_by_hash( *DEFAULT_ACCOUNT_ADDR, - cep18_token, + addressable_cep18_token, METHOD_MINT, runtime_args! {OWNER => TOKEN_OWNER_ADDRESS_2, AMOUNT => U256::from(TOKEN_OWNER_AMOUNT_2)}, ) @@ -161,7 +161,7 @@ fn test_should_not_mint_above_limits() { let mint_request = ExecuteRequestBuilder::contract_call_by_hash( *DEFAULT_ACCOUNT_ADDR, - cep18_token, + addressable_cep18_token, METHOD_MINT, runtime_args! { ARG_OWNER => TOKEN_OWNER_ADDRESS_1, @@ -190,9 +190,10 @@ fn test_should_not_burn_above_balance() { "enable_mint_burn" => true, }); + let addressable_cep18_token = AddressableEntityHash::new(cep18_token.value()); let burn_request = ExecuteRequestBuilder::contract_call_by_hash( *DEFAULT_ACCOUNT_ADDR, - cep18_token, + addressable_cep18_token, METHOD_BURN, runtime_args! { ARG_OWNER => Key::Account(*DEFAULT_ACCOUNT_ADDR), @@ -223,9 +224,10 @@ fn test_should_not_mint_or_burn_with_entrypoint_disabled() { ENABLE_MINT_BURN => false, }); + let addressable_cep18_token = AddressableEntityHash::new(cep18_token.value()); let mint_request = ExecuteRequestBuilder::contract_call_by_hash( *DEFAULT_ACCOUNT_ADDR, - cep18_token, + addressable_cep18_token, METHOD_MINT, runtime_args! { ARG_OWNER => TOKEN_OWNER_ADDRESS_1, @@ -245,7 +247,7 @@ fn test_should_not_mint_or_burn_with_entrypoint_disabled() { let burn_request = ExecuteRequestBuilder::contract_call_by_hash( *DEFAULT_ACCOUNT_ADDR, - cep18_token, + addressable_cep18_token, METHOD_BURN, runtime_args! { ARG_OWNER => TOKEN_OWNER_ADDRESS_1, @@ -276,9 +278,10 @@ fn test_security_no_rights() { ENABLE_MINT_BURN => true, }); + let addressable_cep18_token = AddressableEntityHash::new(cep18_token.value()); let mint_request = ExecuteRequestBuilder::contract_call_by_hash( *ACCOUNT_1_ADDR, - cep18_token, + addressable_cep18_token, METHOD_MINT, runtime_args! { ARG_OWNER => Key::Account(*ACCOUNT_1_ADDR), @@ -298,7 +301,7 @@ fn test_security_no_rights() { let passing_admin_mint_request = ExecuteRequestBuilder::contract_call_by_hash( *DEFAULT_ACCOUNT_ADDR, - cep18_token, + addressable_cep18_token, METHOD_MINT, runtime_args! { ARG_OWNER => Key::Account(*ACCOUNT_1_ADDR), @@ -314,7 +317,7 @@ fn test_security_no_rights() { let burn_request = ExecuteRequestBuilder::contract_call_by_hash( *ACCOUNT_1_ADDR, - cep18_token, + addressable_cep18_token, METHOD_BURN, runtime_args! { ARG_OWNER => Key::Account(*ACCOUNT_1_ADDR), @@ -339,9 +342,10 @@ fn test_security_minter_rights() { MINTER_LIST => vec![Key::Account(*ACCOUNT_1_ADDR)] }); + let addressable_cep18_token = AddressableEntityHash::new(cep18_token.value()); let mint_request = ExecuteRequestBuilder::contract_call_by_hash( *ACCOUNT_1_ADDR, - cep18_token, + addressable_cep18_token, METHOD_MINT, runtime_args! { ARG_OWNER => TOKEN_OWNER_ADDRESS_1, @@ -365,9 +369,10 @@ fn test_security_burner_rights() { ENABLE_MINT_BURN => true, }); + let addressable_cep18_token = AddressableEntityHash::new(cep18_token.value()); let mint_request = ExecuteRequestBuilder::contract_call_by_hash( *ACCOUNT_1_ADDR, - cep18_token, + addressable_cep18_token, METHOD_MINT, runtime_args! { ARG_OWNER => TOKEN_OWNER_ADDRESS_1, @@ -388,7 +393,7 @@ fn test_security_burner_rights() { // mint by admin let working_mint_request = ExecuteRequestBuilder::contract_call_by_hash( *DEFAULT_ACCOUNT_ADDR, - cep18_token, + addressable_cep18_token, METHOD_MINT, runtime_args! { ARG_OWNER => Key::Account(*DEFAULT_ACCOUNT_ADDR), @@ -402,7 +407,7 @@ fn test_security_burner_rights() { // any user can burn let burn_request = ExecuteRequestBuilder::contract_call_by_hash( *DEFAULT_ACCOUNT_ADDR, - cep18_token, + addressable_cep18_token, METHOD_BURN, runtime_args! { ARG_OWNER => Key::Account(*DEFAULT_ACCOUNT_ADDR), @@ -427,9 +432,10 @@ fn test_change_security() { ADMIN_LIST => vec![Key::Account(*ACCOUNT_1_ADDR)] }); + let addressable_cep18_token = AddressableEntityHash::new(cep18_token.value()); let change_security_request = ExecuteRequestBuilder::contract_call_by_hash( *ACCOUNT_1_ADDR, - cep18_token, + addressable_cep18_token, CHANGE_SECURITY, runtime_args! { NONE_LIST => vec![Key::Account(*DEFAULT_ACCOUNT_ADDR)], @@ -444,7 +450,7 @@ fn test_change_security() { let mint_request = ExecuteRequestBuilder::contract_call_by_hash( *DEFAULT_ACCOUNT_ADDR, - cep18_token, + addressable_cep18_token, METHOD_MINT, runtime_args! { ARG_OWNER => TOKEN_OWNER_ADDRESS_1, diff --git a/tests/src/transfer.rs b/tests/src/transfer.rs index 997acc3..ff1a594 100644 --- a/tests/src/transfer.rs +++ b/tests/src/transfer.rs @@ -1,5 +1,8 @@ use casper_engine_test_support::{ExecuteRequestBuilder, DEFAULT_ACCOUNT_ADDR}; -use casper_types::{runtime_args, ApiError, Key, RuntimeArgs, U256}; +use casper_types::{ + addressable_entity::EntityKindTag, runtime_args, AddressableEntityHash, ApiError, EntityAddr, + Key, U256, +}; use crate::utility::{ constants::{ @@ -14,14 +17,13 @@ use crate::utility::{ }, }; -use casper_execution_engine::core::{ - engine_state::Error as CoreError, execution::Error as ExecError, -}; +use casper_execution_engine::{engine_state::Error as CoreError, execution::ExecError}; #[test] fn should_transfer_full_owned_amount() { let (mut builder, TestContext { cep18_token, .. }) = setup(); + let addressable_cep18_token = AddressableEntityHash::new(cep18_token.value()); let initial_supply = U256::from(TOKEN_TOTAL_SUPPLY); let transfer_amount_1 = initial_supply; @@ -44,7 +46,7 @@ fn should_transfer_full_owned_amount() { let token_transfer_request_1 = ExecuteRequestBuilder::contract_call_by_hash( transfer_1_sender, - cep18_token, + addressable_cep18_token, METHOD_TRANSFER, cep18_transfer_1_args, ) @@ -66,7 +68,10 @@ fn should_transfer_full_owned_amount() { ); assert_eq!(owner_balance_after, U256::zero()); - let total_supply: U256 = builder.get_value(cep18_token, TOTAL_SUPPLY_KEY); + let total_supply: U256 = builder.get_value( + EntityAddr::new_smart_contract(cep18_token.value()), + TOTAL_SUPPLY_KEY, + ); assert_eq!(total_supply, initial_supply); } @@ -74,6 +79,8 @@ fn should_transfer_full_owned_amount() { fn should_not_transfer_more_than_owned_balance() { let (mut builder, TestContext { cep18_token, .. }) = setup(); + let addressable_cep18_token = AddressableEntityHash::new(cep18_token.value()); + let initial_supply = U256::from(TOKEN_TOTAL_SUPPLY); let transfer_amount = initial_supply + U256::one(); @@ -99,7 +106,7 @@ fn should_not_transfer_more_than_owned_balance() { let token_transfer_request_1 = ExecuteRequestBuilder::contract_call_by_hash( transfer_1_sender, - cep18_token, + addressable_cep18_token, METHOD_TRANSFER, cep18_transfer_1_args, ) @@ -125,7 +132,10 @@ fn should_not_transfer_more_than_owned_balance() { cep18_check_balance_of(&mut builder, &cep18_token, Key::Account(transfer_1_sender)); assert_eq!(owner_balance_after, initial_supply); - let total_supply: U256 = builder.get_value(cep18_token, TOTAL_SUPPLY_KEY); + let total_supply: U256 = builder.get_value( + EntityAddr::new_smart_contract(cep18_token.value()), + TOTAL_SUPPLY_KEY, + ); assert_eq!(total_supply, initial_supply); } @@ -133,6 +143,8 @@ fn should_not_transfer_more_than_owned_balance() { fn should_transfer_from_from_account_to_account() { let (mut builder, TestContext { cep18_token, .. }) = setup(); + let addressable_cep18_token = AddressableEntityHash::new(cep18_token.value()); + let initial_supply = U256::from(TOKEN_TOTAL_SUPPLY); let allowance_amount_1 = U256::from(ALLOWANCE_AMOUNT_1); let transfer_from_amount_1 = allowance_amount_1; @@ -157,7 +169,7 @@ fn should_transfer_from_from_account_to_account() { let approve_request_1 = ExecuteRequestBuilder::contract_call_by_hash( owner, - cep18_token, + addressable_cep18_token, METHOD_APPROVE, cep18_approve_args, ) @@ -165,7 +177,7 @@ fn should_transfer_from_from_account_to_account() { let transfer_from_request_1 = ExecuteRequestBuilder::contract_call_by_hash( spender, - cep18_token, + addressable_cep18_token, METHOD_TRANSFER_FROM, cep18_transfer_from_args, ) @@ -212,6 +224,8 @@ fn should_transfer_from_account_by_contract() { }, ) = setup(); + let addressable_cep18_token = AddressableEntityHash::new(cep18_token.value()); + let initial_supply = U256::from(TOKEN_TOTAL_SUPPLY); let allowance_amount_1 = U256::from(ALLOWANCE_AMOUNT_1); let transfer_from_amount_1 = allowance_amount_1; @@ -227,7 +241,7 @@ fn should_transfer_from_account_by_contract() { ARG_AMOUNT => allowance_amount_1, }; let cep18_transfer_from_args = runtime_args! { - ARG_TOKEN_CONTRACT => Key::from(cep18_token), + ARG_TOKEN_CONTRACT => Key::addressable_entity_key(EntityKindTag::SmartContract, cep18_token), ARG_OWNER => Key::Account(owner), ARG_RECIPIENT => recipient, ARG_AMOUNT => transfer_from_amount_1, @@ -239,7 +253,7 @@ fn should_transfer_from_account_by_contract() { let approve_request_1 = ExecuteRequestBuilder::contract_call_by_hash( owner, - cep18_token, + addressable_cep18_token, METHOD_APPROVE, cep18_approve_args, ) @@ -315,6 +329,8 @@ fn should_not_be_able_to_own_transfer() { fn should_not_be_able_to_own_transfer_from() { let (mut builder, TestContext { cep18_token, .. }) = setup(); + let addressable_cep18_token = AddressableEntityHash::new(cep18_token.value()); + let owner = Key::Account(*DEFAULT_ACCOUNT_ADDR); let spender = Key::Account(*DEFAULT_ACCOUNT_ADDR); let sender = Key::Account(*DEFAULT_ACCOUNT_ADDR); @@ -348,7 +364,7 @@ fn should_not_be_able_to_own_transfer_from() { }; ExecuteRequestBuilder::contract_call_by_hash( sender.into_account().unwrap(), - cep18_token, + addressable_cep18_token, METHOD_TRANSFER_FROM, cep18_transfer_from_args, ) @@ -396,6 +412,8 @@ fn should_verify_zero_amount_transfer_is_noop() { fn should_verify_zero_amount_transfer_from_is_noop() { let (mut builder, TestContext { cep18_token, .. }) = setup(); + let addressable_cep18_token = AddressableEntityHash::new(cep18_token.value()); + let owner = Key::Account(*DEFAULT_ACCOUNT_ADDR); let spender = Key::Account(*ACCOUNT_1_ADDR); let recipient = Key::Account(*ACCOUNT_2_ADDR); @@ -421,7 +439,7 @@ fn should_verify_zero_amount_transfer_from_is_noop() { }; ExecuteRequestBuilder::contract_call_by_hash( owner.into_account().unwrap(), - cep18_token, + addressable_cep18_token, METHOD_TRANSFER_FROM, cep18_transfer_from_args, ) diff --git a/tests/src/utility/installer_request_builders.rs b/tests/src/utility/installer_request_builders.rs index a500a3a..62aa3e4 100644 --- a/tests/src/utility/installer_request_builders.rs +++ b/tests/src/utility/installer_request_builders.rs @@ -1,20 +1,21 @@ use casper_engine_test_support::{ - ExecuteRequestBuilder, InMemoryWasmTestBuilder, DEFAULT_ACCOUNT_ADDR, - MINIMUM_ACCOUNT_CREATION_BALANCE, PRODUCTION_RUN_GENESIS_REQUEST, + utils::create_run_genesis_request, ExecuteRequest, ExecuteRequestBuilder, LmdbWasmTestBuilder, + DEFAULT_ACCOUNT_ADDR, DEFAULT_ACCOUNT_PUBLIC_KEY, }; -use casper_execution_engine::core::engine_state::ExecuteRequest; use casper_types::{ - account::AccountHash, bytesrepr::FromBytes, runtime_args, system::mint, CLTyped, ContractHash, - ContractPackageHash, Key, RuntimeArgs, U256, + account::AccountHash, addressable_entity::EntityKindTag, bytesrepr::FromBytes, + runtime_args, AddressableEntityHash, CLTyped, EntityAddr, + GenesisAccount, Key, Motes, PackageHash, RuntimeArgs, U256, U512, }; use crate::utility::constants::{ - ALLOWANCE_AMOUNT_1, ALLOWANCE_AMOUNT_2, TOTAL_SUPPLY_KEY, TRANSFER_AMOUNT_1, TRANSFER_AMOUNT_2, + ALLOWANCE_AMOUNT_1, ALLOWANCE_AMOUNT_2, TOTAL_SUPPLY_KEY, + TRANSFER_AMOUNT_1, TRANSFER_AMOUNT_2, }; use super::constants::{ - ACCOUNT_1_ADDR, ACCOUNT_2_ADDR, ARG_ADDRESS, ARG_AMOUNT, ARG_DECIMALS, ARG_NAME, ARG_OWNER, - ARG_RECIPIENT, ARG_SPENDER, ARG_SYMBOL, ARG_TOKEN_CONTRACT, ARG_TOTAL_SUPPLY, + ACCOUNT_1_PUBLIC_KEY, ACCOUNT_2_PUBLIC_KEY, ARG_ADDRESS, ARG_AMOUNT, ARG_DECIMALS, ARG_NAME, + ARG_OWNER, ARG_RECIPIENT, ARG_SPENDER, ARG_SYMBOL, ARG_TOKEN_CONTRACT, ARG_TOTAL_SUPPLY, CEP18_CONTRACT_WASM, CEP18_TEST_CONTRACT_KEY, CEP18_TEST_CONTRACT_WASM, CEP18_TOKEN_CONTRACT_KEY, CHECK_ALLOWANCE_OF_ENTRYPOINT, CHECK_BALANCE_OF_ENTRYPOINT, CHECK_TOTAL_SUPPLY_ENTRYPOINT, METHOD_APPROVE, METHOD_APPROVE_AS_STORED_CONTRACT, @@ -36,11 +37,11 @@ pub(crate) fn invert_cep18_address(address: Key) -> Key { #[derive(Copy, Clone)] pub(crate) struct TestContext { - pub(crate) cep18_token: ContractHash, - pub(crate) cep18_test_contract_package: ContractPackageHash, + pub(crate) cep18_token: AddressableEntityHash, + pub(crate) cep18_test_contract_package: PackageHash, } -pub(crate) fn setup() -> (InMemoryWasmTestBuilder, TestContext) { +pub(crate) fn setup() -> (LmdbWasmTestBuilder, TestContext) { setup_with_args(runtime_args! { ARG_NAME => TOKEN_NAME, ARG_SYMBOL => TOKEN_SYMBOL, @@ -49,26 +50,25 @@ pub(crate) fn setup() -> (InMemoryWasmTestBuilder, TestContext) { }) } -pub(crate) fn setup_with_args(install_args: RuntimeArgs) -> (InMemoryWasmTestBuilder, TestContext) { - let mut builder = InMemoryWasmTestBuilder::default(); - builder.run_genesis(&PRODUCTION_RUN_GENESIS_REQUEST); - - let id: Option = None; - let transfer_1_args = runtime_args! { - mint::ARG_TARGET => *ACCOUNT_1_ADDR, - mint::ARG_AMOUNT => MINIMUM_ACCOUNT_CREATION_BALANCE, - mint::ARG_ID => id, - }; - let transfer_2_args = runtime_args! { - mint::ARG_TARGET => *ACCOUNT_2_ADDR, - mint::ARG_AMOUNT => MINIMUM_ACCOUNT_CREATION_BALANCE, - mint::ARG_ID => id, - }; - - let transfer_request_1 = - ExecuteRequestBuilder::transfer(*DEFAULT_ACCOUNT_ADDR, transfer_1_args).build(); - let transfer_request_2 = - ExecuteRequestBuilder::transfer(*DEFAULT_ACCOUNT_ADDR, transfer_2_args).build(); +pub(crate) fn setup_with_args(install_args: RuntimeArgs) -> (LmdbWasmTestBuilder, TestContext) { + let mut builder = LmdbWasmTestBuilder::default(); + builder.run_genesis(create_run_genesis_request(vec![ + GenesisAccount::Account { + public_key: DEFAULT_ACCOUNT_PUBLIC_KEY.clone(), + balance: Motes::new(U512::from(5_000_000_000_000_u64)), + validator: None, + }, + GenesisAccount::Account { + public_key: ACCOUNT_1_PUBLIC_KEY.clone(), + balance: Motes::new(U512::from(5_000_000_000_000_u64)), + validator: None, + }, + GenesisAccount::Account { + public_key: ACCOUNT_2_PUBLIC_KEY.clone(), + balance: Motes::new(U512::from(5_000_000_000_000_u64)), + validator: None, + }, + ])); let install_request_1 = ExecuteRequestBuilder::standard(*DEFAULT_ACCOUNT_ADDR, CEP18_CONTRACT_WASM, install_args) @@ -81,28 +81,26 @@ pub(crate) fn setup_with_args(install_args: RuntimeArgs) -> (InMemoryWasmTestBui ) .build(); - builder.exec(transfer_request_1).expect_success().commit(); - builder.exec(transfer_request_2).expect_success().commit(); builder.exec(install_request_1).expect_success().commit(); builder.exec(install_request_2).expect_success().commit(); - let account = builder - .get_account(*DEFAULT_ACCOUNT_ADDR) - .expect("should have account"); + let account = + builder.get_entity_with_named_keys_by_account_hash(*DEFAULT_ACCOUNT_ADDR).unwrap(); + let account_named_keys = account.named_keys(); + // println!("{account_named_keys:?}"); - let cep18_token = account - .named_keys() + // let account_named_keys_2 = builder.get_named_keys(EntityAddr::new_account(DEFAULT_ACCOUNT_ADDR.value())); + // println!("{account_named_keys_2:?}"); + + let cep18_token = account_named_keys .get(CEP18_TOKEN_CONTRACT_KEY) - .and_then(|key| key.into_hash()) - .map(ContractHash::new) + .and_then(|key| key.into_entity_hash()) .expect("should have contract hash"); - let cep18_test_contract_package = account - .named_keys() + let cep18_test_contract_package = account_named_keys .get(CEP18_TEST_CONTRACT_KEY) - .and_then(|key| key.into_hash()) - .map(ContractPackageHash::new) - .expect("should have contract package hash"); + .and_then(|key| key.into_package_hash()) + .expect("should have package hash"); let test_context = TestContext { cep18_token, @@ -113,22 +111,21 @@ pub(crate) fn setup_with_args(install_args: RuntimeArgs) -> (InMemoryWasmTestBui } pub(crate) fn cep18_check_total_supply( - builder: &mut InMemoryWasmTestBuilder, - cep18_contract_hash: &ContractHash, + builder: &mut LmdbWasmTestBuilder, + cep18_contract_hash: &AddressableEntityHash, ) -> U256 { let account = builder - .get_account(*DEFAULT_ACCOUNT_ADDR) + .get_entity_with_named_keys_by_account_hash(*DEFAULT_ACCOUNT_ADDR) .expect("should have account"); let cep18_test_contract_package = account .named_keys() .get(CEP18_TEST_CONTRACT_KEY) - .and_then(|key| key.into_hash()) - .map(ContractPackageHash::new) + .and_then(|key| key.into_package_hash()) .expect("should have test contract hash"); let check_total_supply_args = runtime_args! { - ARG_TOKEN_CONTRACT => Key::from(*cep18_contract_hash), + ARG_TOKEN_CONTRACT => Key::addressable_entity_key(EntityKindTag::SmartContract, *cep18_contract_hash), }; let exec_request = ExecuteRequestBuilder::versioned_contract_call_by_hash( @@ -145,40 +142,38 @@ pub(crate) fn cep18_check_total_supply( } pub(crate) fn get_test_result( - builder: &mut InMemoryWasmTestBuilder, - cep18_test_contract_package: ContractPackageHash, + builder: &mut LmdbWasmTestBuilder, + cep18_test_contract_package: PackageHash, ) -> T { let contract_package = builder - .get_contract_package(cep18_test_contract_package) + .get_package(cep18_test_contract_package) .expect("should have contract package"); let enabled_versions = contract_package.enabled_versions(); - let (_version, contract_hash) = enabled_versions - .iter() - .rev() - .next() + let contract_hash = enabled_versions + .contract_hashes() + .last() .expect("should have latest version"); - - builder.get_value(*contract_hash, RESULT_KEY) + let entity_addr = EntityAddr::new_smart_contract(contract_hash.value()); + builder.get_value(entity_addr, RESULT_KEY) } pub(crate) fn cep18_check_balance_of( - builder: &mut InMemoryWasmTestBuilder, - cep18_contract_hash: &ContractHash, + builder: &mut LmdbWasmTestBuilder, + cep18_contract_hash: &AddressableEntityHash, address: Key, ) -> U256 { let account = builder - .get_account(*DEFAULT_ACCOUNT_ADDR) + .get_entity_with_named_keys_by_account_hash(*DEFAULT_ACCOUNT_ADDR) .expect("should have account"); let cep18_test_contract_package = account .named_keys() .get(CEP18_TEST_CONTRACT_KEY) - .and_then(|key| key.into_hash()) - .map(ContractPackageHash::new) + .and_then(|key| key.into_package_hash()) .expect("should have test contract hash"); let check_balance_args = runtime_args! { - ARG_TOKEN_CONTRACT => Key::from(*cep18_contract_hash), + ARG_TOKEN_CONTRACT => Key::addressable_entity_key(EntityKindTag::SmartContract, *cep18_contract_hash), ARG_ADDRESS => address, }; let exec_request = ExecuteRequestBuilder::versioned_contract_call_by_hash( @@ -195,28 +190,26 @@ pub(crate) fn cep18_check_balance_of( } pub(crate) fn cep18_check_allowance_of( - builder: &mut InMemoryWasmTestBuilder, + builder: &mut LmdbWasmTestBuilder, owner: Key, spender: Key, ) -> U256 { let account = builder - .get_account(*DEFAULT_ACCOUNT_ADDR) + .get_entity_with_named_keys_by_account_hash(*DEFAULT_ACCOUNT_ADDR) .expect("should have account"); let cep18_contract_hash = account .named_keys() .get(CEP18_TOKEN_CONTRACT_KEY) - .and_then(|key| key.into_hash()) - .map(ContractHash::new) + .and_then(|key| key.into_entity_hash()) .expect("should have test contract hash"); let cep18_test_contract_package = account .named_keys() .get(CEP18_TEST_CONTRACT_KEY) - .and_then(|key| key.into_hash()) - .map(ContractPackageHash::new) + .and_then(|key| key.into_package_hash()) .expect("should have test contract hash"); let check_balance_args = runtime_args! { - ARG_TOKEN_CONTRACT => Key::from(cep18_contract_hash), + ARG_TOKEN_CONTRACT => Key::addressable_entity_key(EntityKindTag::SmartContract, cep18_contract_hash), ARG_OWNER => owner, ARG_SPENDER => spender, }; @@ -234,7 +227,7 @@ pub(crate) fn cep18_check_allowance_of( } pub(crate) fn test_cep18_transfer( - builder: &mut InMemoryWasmTestBuilder, + builder: &mut LmdbWasmTestBuilder, test_context: &TestContext, sender1: Key, recipient1: Key, @@ -298,14 +291,14 @@ pub(crate) fn test_cep18_transfer( pub(crate) fn make_cep18_transfer_request( sender: Key, - cep18_token: &ContractHash, + cep18_token: &AddressableEntityHash, recipient: Key, amount: U256, ) -> ExecuteRequest { match sender { Key::Account(sender) => ExecuteRequestBuilder::contract_call_by_hash( sender, - *cep18_token, + AddressableEntityHash::new(cep18_token.value()), METHOD_TRANSFER, runtime_args! { ARG_AMOUNT => amount, @@ -315,11 +308,11 @@ pub(crate) fn make_cep18_transfer_request( .build(), Key::Hash(contract_package_hash) => ExecuteRequestBuilder::versioned_contract_call_by_hash( *DEFAULT_ACCOUNT_ADDR, - ContractPackageHash::new(contract_package_hash), + PackageHash::new(contract_package_hash), None, METHOD_TRANSFER_AS_STORED_CONTRACT, runtime_args! { - ARG_TOKEN_CONTRACT => Key::from(*cep18_token), + ARG_TOKEN_CONTRACT => Key::addressable_entity_key(EntityKindTag::SmartContract, *cep18_token), ARG_AMOUNT => amount, ARG_RECIPIENT => recipient, }, @@ -331,14 +324,14 @@ pub(crate) fn make_cep18_transfer_request( pub(crate) fn make_cep18_approve_request( sender: Key, - cep18_token: &ContractHash, + cep18_token: &AddressableEntityHash, spender: Key, amount: U256, ) -> ExecuteRequest { match sender { Key::Account(sender) => ExecuteRequestBuilder::contract_call_by_hash( sender, - *cep18_token, + AddressableEntityHash::new(cep18_token.value()), METHOD_APPROVE, runtime_args! { ARG_SPENDER => spender, @@ -348,11 +341,11 @@ pub(crate) fn make_cep18_approve_request( .build(), Key::Hash(contract_package_hash) => ExecuteRequestBuilder::versioned_contract_call_by_hash( *DEFAULT_ACCOUNT_ADDR, - ContractPackageHash::new(contract_package_hash), + PackageHash::new(contract_package_hash), None, METHOD_APPROVE_AS_STORED_CONTRACT, runtime_args! { - ARG_TOKEN_CONTRACT => Key::from(*cep18_token), + ARG_TOKEN_CONTRACT => Key::addressable_entity_key(EntityKindTag::SmartContract, *cep18_token), ARG_SPENDER => spender, ARG_AMOUNT => amount, }, @@ -363,7 +356,7 @@ pub(crate) fn make_cep18_approve_request( } pub(crate) fn test_approve_for( - builder: &mut InMemoryWasmTestBuilder, + builder: &mut LmdbWasmTestBuilder, test_context: &TestContext, sender: Key, owner: Key, @@ -388,7 +381,10 @@ pub(crate) fn test_approve_for( let account_1_allowance_after = cep18_check_allowance_of(builder, owner, spender); assert_eq!(account_1_allowance_after, allowance_amount_1); - let total_supply: U256 = builder.get_value(*cep18_token, TOTAL_SUPPLY_KEY); + let total_supply: U256 = builder.get_value( + EntityAddr::new_smart_contract(cep18_token.value()), + TOTAL_SUPPLY_KEY, + ); assert_eq!(total_supply, initial_supply); } @@ -405,6 +401,9 @@ pub(crate) fn test_approve_for( let inverted_spender_allowance = cep18_check_allowance_of(builder, owner, inverted_spender_key); assert_eq!(inverted_spender_allowance, U256::zero()); - let total_supply: U256 = builder.get_value(*cep18_token, TOTAL_SUPPLY_KEY); + let total_supply: U256 = builder.get_value( + EntityAddr::new_smart_contract(cep18_token.value()), + TOTAL_SUPPLY_KEY, + ); assert_eq!(total_supply, initial_supply); } From e2b0b851023572469f7604394bda29a2abb040e8 Mon Sep 17 00:00:00 2001 From: Deuszex Date: Thu, 9 May 2024 17:14:50 +0200 Subject: [PATCH 02/27] WIP --- cep18/src/constants.rs | 1 + cep18/src/main.rs | 17 +++++++++-------- cep18/src/utils.rs | 4 ++-- tests/src/allowance.rs | 6 +++--- tests/src/utility/installer_request_builders.rs | 14 +++++++------- 5 files changed, 22 insertions(+), 20 deletions(-) diff --git a/cep18/src/constants.rs b/cep18/src/constants.rs index c8eb846..edf1d5a 100644 --- a/cep18/src/constants.rs +++ b/cep18/src/constants.rs @@ -60,6 +60,7 @@ pub const AMOUNT: &str = "amount"; /// Name of `recipient` runtime argument. pub const RECIPIENT: &str = "recipient"; pub const PACKAGE_HASH: &str = "package_hash"; +pub const CONTRACT_HASH: &str = "contract_hash"; pub const EVENTS_MODE: &str = "events_mode"; pub const SECURITY_BADGES: &str = "security_badges"; pub const ADMIN_LIST: &str = "admin_list"; diff --git a/cep18/src/main.rs b/cep18/src/main.rs index cbbcd08..0e67e2a 100644 --- a/cep18/src/main.rs +++ b/cep18/src/main.rs @@ -26,7 +26,7 @@ use entry_points::generate_entry_points; use casper_contract::{ contract_api::{ - runtime::{self, get_call_stack, get_caller, get_key, get_named_arg, put_key, revert}, + runtime::{self, get_caller, get_key, get_named_arg, put_key, revert}, storage::{self, dictionary_put}, }, unwrap_or_revert::UnwrapOrRevert, @@ -35,12 +35,11 @@ use casper_types::{ addressable_entity::{EntityKindTag, NamedKeys}, bytesrepr::ToBytes, contract_messages::MessageTopicOperation, - contracts::{ContractHash, ContractPackageHash}, - runtime_args, CLValue, Key, PackageHash, U256, + runtime_args, CLValue, Key, U256, }; use constants::{ - ACCESS_KEY_NAME_PREFIX, ADDRESS, ADMIN_LIST, ALLOWANCES, AMOUNT, BALANCES, + ACCESS_KEY_NAME_PREFIX, ADDRESS, ADMIN_LIST, ALLOWANCES, AMOUNT, BALANCES, CONTRACT_HASH, CONTRACT_NAME_PREFIX, CONTRACT_VERSION_PREFIX, DECIMALS, ENABLE_MINT_BURN, EVENTS, EVENTS_MODE, HASH_KEY_NAME_PREFIX, INIT_ENTRY_POINT_NAME, MINTER_LIST, NAME, NONE_LIST, OWNER, PACKAGE_HASH, RECIPIENT, SECURITY_BADGES, SPENDER, SYMBOL, TOTAL_SUPPLY, @@ -275,6 +274,8 @@ pub extern "C" fn init() { } let package_hash = get_named_arg::(PACKAGE_HASH); put_key(PACKAGE_HASH, package_hash); + let contract_hash = get_named_arg::(CONTRACT_HASH); + put_key(CONTRACT_HASH, contract_hash); storage::new_dictionary(ALLOWANCES).unwrap_or_revert(); let balances_uref = storage::new_dictionary(BALANCES).unwrap_or_revert(); let initial_supply = runtime::get_named_arg(TOTAL_SUPPLY); @@ -364,14 +365,14 @@ pub extern "C" fn change_security() { sec_change_map: badge_map, })); } + pub fn upgrade(name: &str) { let entry_points = generate_entry_points(); - let old_contract_package_hash = runtime::get_key(&format!("{HASH_KEY_NAME_PREFIX}{name}")) + let contract_package_hash = runtime::get_key(&format!("{HASH_KEY_NAME_PREFIX}{name}")) .unwrap_or_revert() - .into_entity_hash() + .into_package_hash() .unwrap_or_revert_with(Cep18Error::MissingPackageHashForUpgrade); - let contract_package_hash = PackageHash::new(old_contract_package_hash.value()); let previous_contract_hash = runtime::get_key(&format!("{CONTRACT_NAME_PREFIX}{name}")) .unwrap_or_revert() @@ -489,7 +490,7 @@ pub fn install_contract(name: &str) { storage::new_uref(contract_version).into(), ); // Call contract to initialize it - let mut init_args = runtime_args! {TOTAL_SUPPLY => total_supply, PACKAGE_HASH => package_hash}; + let mut init_args = runtime_args! {TOTAL_SUPPLY => total_supply, PACKAGE_HASH => package_hash, CONTRACT_HASH => Key::addressable_entity_key(EntityKindTag::SmartContract, contract_hash)}; if let Some(admin_list) = admin_list { init_args.insert(ADMIN_LIST, admin_list).unwrap_or_revert(); diff --git a/cep18/src/utils.rs b/cep18/src/utils.rs index 1442151..2fdf2a0 100644 --- a/cep18/src/utils.rs +++ b/cep18/src/utils.rs @@ -47,8 +47,8 @@ where /// case it will use contract package hash as the address. fn call_stack_element_to_address(call_stack_element: Caller) -> Key { match call_stack_element { - Caller::Initiator { account_hash } => Key::from(account_hash), - Caller::Entity { package_hash, .. } => Key::from(package_hash), + Caller::Initiator { account_hash } => Key::Account(account_hash), + Caller::Entity { package_hash, .. } => Key::Hash(package_hash.value()), } } diff --git a/tests/src/allowance.rs b/tests/src/allowance.rs index 5b6258a..5050af3 100644 --- a/tests/src/allowance.rs +++ b/tests/src/allowance.rs @@ -1,5 +1,5 @@ use casper_engine_test_support::{ExecuteRequestBuilder, DEFAULT_ACCOUNT_ADDR}; -use casper_types::{addressable_entity::EntityKindTag, runtime_args, AddressableEntityHash, ApiError, Key, U256}; +use casper_types::{runtime_args, AddressableEntityHash, ApiError, Key, U256}; use crate::utility::{ constants::{ @@ -24,8 +24,8 @@ fn should_approve_funds_contract_to_account() { test_approve_for( &mut builder, &test_context, - Key::addressable_entity_key(EntityKindTag::SmartContract, AddressableEntityHash::new(cep18_test_contract_package.value())), - Key::addressable_entity_key(EntityKindTag::SmartContract, AddressableEntityHash::new(cep18_test_contract_package.value())), + Key::Hash(cep18_test_contract_package.value()), + Key::Hash(cep18_test_contract_package.value()), Key::Account(*DEFAULT_ACCOUNT_ADDR), ); } diff --git a/tests/src/utility/installer_request_builders.rs b/tests/src/utility/installer_request_builders.rs index 62aa3e4..85eec35 100644 --- a/tests/src/utility/installer_request_builders.rs +++ b/tests/src/utility/installer_request_builders.rs @@ -3,14 +3,13 @@ use casper_engine_test_support::{ DEFAULT_ACCOUNT_ADDR, DEFAULT_ACCOUNT_PUBLIC_KEY, }; use casper_types::{ - account::AccountHash, addressable_entity::EntityKindTag, bytesrepr::FromBytes, - runtime_args, AddressableEntityHash, CLTyped, EntityAddr, - GenesisAccount, Key, Motes, PackageHash, RuntimeArgs, U256, U512, + account::AccountHash, addressable_entity::EntityKindTag, bytesrepr::FromBytes, runtime_args, + AddressableEntityHash, CLTyped, EntityAddr, GenesisAccount, Key, Motes, PackageHash, + RuntimeArgs, U256, U512, }; use crate::utility::constants::{ - ALLOWANCE_AMOUNT_1, ALLOWANCE_AMOUNT_2, TOTAL_SUPPLY_KEY, - TRANSFER_AMOUNT_1, TRANSFER_AMOUNT_2, + ALLOWANCE_AMOUNT_1, ALLOWANCE_AMOUNT_2, TOTAL_SUPPLY_KEY, TRANSFER_AMOUNT_1, TRANSFER_AMOUNT_2, }; use super::constants::{ @@ -84,8 +83,9 @@ pub(crate) fn setup_with_args(install_args: RuntimeArgs) -> (LmdbWasmTestBuilder builder.exec(install_request_1).expect_success().commit(); builder.exec(install_request_2).expect_success().commit(); - let account = - builder.get_entity_with_named_keys_by_account_hash(*DEFAULT_ACCOUNT_ADDR).unwrap(); + let account = builder + .get_entity_with_named_keys_by_account_hash(*DEFAULT_ACCOUNT_ADDR) + .unwrap(); let account_named_keys = account.named_keys(); // println!("{account_named_keys:?}"); From 223619664e4b02bd1a7957514151fed88a1dc31e Mon Sep 17 00:00:00 2001 From: gRoussac Date: Thu, 30 May 2024 18:20:37 +0200 Subject: [PATCH 03/27] Commit toolchain for ci/cd --- rust-toolchain | 1 + 1 file changed, 1 insertion(+) create mode 100755 rust-toolchain diff --git a/rust-toolchain b/rust-toolchain new file mode 100755 index 0000000..f9e5e5e --- /dev/null +++ b/rust-toolchain @@ -0,0 +1 @@ +nightly-2023-03-25 From e1803bbd77598b25927d0f0219def8e9777c353d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rafa=C5=82=20Chabowski?= <88321181+rafal-ch@users.noreply.github.com> Date: Thu, 30 May 2024 16:03:49 +0200 Subject: [PATCH 04/27] Disable integer sign extensions (#139) * Update to recent (post 1.69) nightly * Build without LLVM sign extensions * Satisfy clippy --- Makefile | 11 +++++++---- rust-toolchain | 2 +- tests/src/utility/installer_request_builders.rs | 6 ++++++ 3 files changed, 14 insertions(+), 5 deletions(-) diff --git a/Makefile b/Makefile index 0a3648f..9c957f2 100644 --- a/Makefile +++ b/Makefile @@ -1,12 +1,15 @@ prepare: rustup target add wasm32-unknown-unknown + rustup component add clippy --toolchain ${PINNED_TOOLCHAIN} + rustup component add rustfmt --toolchain ${PINNED_TOOLCHAIN} + rustup component add rust-src --toolchain ${PINNED_TOOLCHAIN} .PHONY: build-contract build-contract: - cd cep18 && cargo build --release --target wasm32-unknown-unknown - cd cep18-test-contract && cargo build --release --target wasm32-unknown-unknown - wasm-strip ./cep18/target/wasm32-unknown-unknown/release/cep18.wasm - wasm-strip ./cep18-test-contract/target/wasm32-unknown-unknown/release/cep18_test_contract.wasm + RUSTFLAGS="-C target-cpu=mvp" cargo build --release --target wasm32-unknown-unknown -Z build-std=std,panic_abort -p cep18 + RUSTFLAGS="-C target-cpu=mvp" cargo build --release --target wasm32-unknown-unknown -Z build-std=std,panic_abort -p cep18-test-contract + wasm-strip target/wasm32-unknown-unknown/release/cep18.wasm + wasm-strip target/wasm32-unknown-unknown/release/cep18_test_contract.wasm setup-test: build-contract mkdir -p tests/wasm diff --git a/rust-toolchain b/rust-toolchain index f9e5e5e..579ba54 100755 --- a/rust-toolchain +++ b/rust-toolchain @@ -1 +1 @@ -nightly-2023-03-25 +nightly-2024-05-28 diff --git a/tests/src/utility/installer_request_builders.rs b/tests/src/utility/installer_request_builders.rs index 85eec35..122c9d6 100644 --- a/tests/src/utility/installer_request_builders.rs +++ b/tests/src/utility/installer_request_builders.rs @@ -149,9 +149,15 @@ pub(crate) fn get_test_result( .get_package(cep18_test_contract_package) .expect("should have contract package"); let enabled_versions = contract_package.enabled_versions(); +<<<<<<< HEAD let contract_hash = enabled_versions .contract_hashes() .last() +======= + let (_version, contract_hash) = enabled_versions + .iter() + .next_back() +>>>>>>> d6348eb (Disable integer sign extensions (#139)) .expect("should have latest version"); let entity_addr = EntityAddr::new_smart_contract(contract_hash.value()); builder.get_value(entity_addr, RESULT_KEY) From e0eef4e3d7d3b226a0bc4be3c3bf1b442116118e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rafa=C5=82=20Chabowski?= <88321181+rafal-ch@users.noreply.github.com> Date: Thu, 30 May 2024 16:03:49 +0200 Subject: [PATCH 05/27] Disable integer sign extensions (#139) * Update to recent (post 1.69) nightly * Build without LLVM sign extensions * Satisfy clippy --- tests/src/utility/installer_request_builders.rs | 6 ------ 1 file changed, 6 deletions(-) diff --git a/tests/src/utility/installer_request_builders.rs b/tests/src/utility/installer_request_builders.rs index 122c9d6..85eec35 100644 --- a/tests/src/utility/installer_request_builders.rs +++ b/tests/src/utility/installer_request_builders.rs @@ -149,15 +149,9 @@ pub(crate) fn get_test_result( .get_package(cep18_test_contract_package) .expect("should have contract package"); let enabled_versions = contract_package.enabled_versions(); -<<<<<<< HEAD let contract_hash = enabled_versions .contract_hashes() .last() -======= - let (_version, contract_hash) = enabled_versions - .iter() - .next_back() ->>>>>>> d6348eb (Disable integer sign extensions (#139)) .expect("should have latest version"); let entity_addr = EntityAddr::new_smart_contract(contract_hash.value()); builder.get_value(entity_addr, RESULT_KEY) From f32678553e576a910891863b34c0b624443d3f8e Mon Sep 17 00:00:00 2001 From: Deuszex Date: Thu, 25 Apr 2024 17:48:33 +0200 Subject: [PATCH 06/27] WIP --- cep18/src/main.rs | 12 +-- cep18/src/utils.rs | 6 +- tests/src/allowance.rs | 16 +++- .../src/utility/installer_request_builders.rs | 86 +++++++++++++++++-- 4 files changed, 103 insertions(+), 17 deletions(-) diff --git a/cep18/src/main.rs b/cep18/src/main.rs index 0e67e2a..2c1a6a6 100644 --- a/cep18/src/main.rs +++ b/cep18/src/main.rs @@ -26,7 +26,7 @@ use entry_points::generate_entry_points; use casper_contract::{ contract_api::{ - runtime::{self, get_caller, get_key, get_named_arg, put_key, revert}, + runtime::{self, get_call_stack, get_caller, get_key, get_named_arg, put_key, revert}, storage::{self, dictionary_put}, }, unwrap_or_revert::UnwrapOrRevert, @@ -35,11 +35,12 @@ use casper_types::{ addressable_entity::{EntityKindTag, NamedKeys}, bytesrepr::ToBytes, contract_messages::MessageTopicOperation, - runtime_args, CLValue, Key, U256, + contracts::{ContractHash, ContractPackageHash}, + runtime_args, CLValue, Key, PackageHash, U256, }; use constants::{ - ACCESS_KEY_NAME_PREFIX, ADDRESS, ADMIN_LIST, ALLOWANCES, AMOUNT, BALANCES, CONTRACT_HASH, + ACCESS_KEY_NAME_PREFIX, ADDRESS, ADMIN_LIST, ALLOWANCES, AMOUNT, BALANCES, CONTRACT_NAME_PREFIX, CONTRACT_VERSION_PREFIX, DECIMALS, ENABLE_MINT_BURN, EVENTS, EVENTS_MODE, HASH_KEY_NAME_PREFIX, INIT_ENTRY_POINT_NAME, MINTER_LIST, NAME, NONE_LIST, OWNER, PACKAGE_HASH, RECIPIENT, SECURITY_BADGES, SPENDER, SYMBOL, TOTAL_SUPPLY, @@ -369,10 +370,11 @@ pub extern "C" fn change_security() { pub fn upgrade(name: &str) { let entry_points = generate_entry_points(); - let contract_package_hash = runtime::get_key(&format!("{HASH_KEY_NAME_PREFIX}{name}")) + let old_contract_package_hash = runtime::get_key(&format!("{HASH_KEY_NAME_PREFIX}{name}")) .unwrap_or_revert() - .into_package_hash() + .into_entity_hash() .unwrap_or_revert_with(Cep18Error::MissingPackageHashForUpgrade); + let contract_package_hash = PackageHash::new(old_contract_package_hash.value()); let previous_contract_hash = runtime::get_key(&format!("{CONTRACT_NAME_PREFIX}{name}")) .unwrap_or_revert() diff --git a/cep18/src/utils.rs b/cep18/src/utils.rs index 2fdf2a0..4d94699 100644 --- a/cep18/src/utils.rs +++ b/cep18/src/utils.rs @@ -15,6 +15,7 @@ use casper_types::{ api_error, bytesrepr::{self, FromBytes, ToBytes}, system::Caller, + system::Caller, ApiError, CLTyped, Key, URef, U256, }; @@ -45,10 +46,11 @@ where /// /// For `Session` and `StoredSession` variants it will return account hash, and for `StoredContract` /// case it will use contract package hash as the address. +fn call_stack_element_to_address(call_stack_element: Caller) -> Key { fn call_stack_element_to_address(call_stack_element: Caller) -> Key { match call_stack_element { - Caller::Initiator { account_hash } => Key::Account(account_hash), - Caller::Entity { package_hash, .. } => Key::Hash(package_hash.value()), + Caller::Initiator { account_hash } => Key::from(account_hash), + Caller::Entity { package_hash, .. } => Key::from(package_hash), } } diff --git a/tests/src/allowance.rs b/tests/src/allowance.rs index 5050af3..db5364b 100644 --- a/tests/src/allowance.rs +++ b/tests/src/allowance.rs @@ -1,5 +1,5 @@ use casper_engine_test_support::{ExecuteRequestBuilder, DEFAULT_ACCOUNT_ADDR}; -use casper_types::{runtime_args, AddressableEntityHash, ApiError, Key, U256}; +use casper_types::{addressable_entity::EntityKindTag, runtime_args, AddressableEntityHash, ApiError, Key, U256}; use crate::utility::{ constants::{ @@ -12,6 +12,7 @@ use crate::utility::{ }, }; use casper_execution_engine::{engine_state::Error as CoreError, execution::ExecError}; +use casper_execution_engine::{engine_state::Error as CoreError, execution::ExecError}; #[test] fn should_approve_funds_contract_to_account() { @@ -24,8 +25,8 @@ fn should_approve_funds_contract_to_account() { test_approve_for( &mut builder, &test_context, - Key::Hash(cep18_test_contract_package.value()), - Key::Hash(cep18_test_contract_package.value()), + Key::addressable_entity_key(EntityKindTag::SmartContract, AddressableEntityHash::new(cep18_test_contract_package.value())), + Key::addressable_entity_key(EntityKindTag::SmartContract, AddressableEntityHash::new(cep18_test_contract_package.value())), Key::Account(*DEFAULT_ACCOUNT_ADDR), ); } @@ -78,6 +79,8 @@ fn should_not_transfer_from_without_enough_allowance() { let addressable_cep18_token = AddressableEntityHash::new(cep18_token.value()); + let addressable_cep18_token = AddressableEntityHash::new(cep18_token.value()); + let allowance_amount_1 = U256::from(ALLOWANCE_AMOUNT_1); let transfer_from_amount_1 = allowance_amount_1 + U256::one(); @@ -103,6 +106,7 @@ fn should_not_transfer_from_without_enough_allowance() { let approve_request_1 = ExecuteRequestBuilder::contract_call_by_hash( sender, addressable_cep18_token, + addressable_cep18_token, METHOD_APPROVE, cep18_approve_args, ) @@ -111,6 +115,7 @@ fn should_not_transfer_from_without_enough_allowance() { let transfer_from_request_1 = ExecuteRequestBuilder::contract_call_by_hash( sender, addressable_cep18_token, + addressable_cep18_token, METHOD_TRANSFER_FROM, cep18_transfer_from_args, ) @@ -136,6 +141,9 @@ fn should_not_transfer_from_without_enough_allowance() { fn test_decrease_allowance() { let (mut builder, TestContext { cep18_token, .. }) = setup(); + let addressable_cep18_token = AddressableEntityHash::new(cep18_token.value()); + + let addressable_cep18_token = AddressableEntityHash::new(cep18_token.value()); let sender = Key::Account(*DEFAULT_ACCOUNT_ADDR); @@ -152,6 +160,7 @@ fn test_decrease_allowance() { let decrease_allowance_request = ExecuteRequestBuilder::contract_call_by_hash( sender.into_account().unwrap(), addressable_cep18_token, + addressable_cep18_token, DECREASE_ALLOWANCE, runtime_args! { ARG_SPENDER => spender, @@ -162,6 +171,7 @@ fn test_decrease_allowance() { let increase_allowance_request = ExecuteRequestBuilder::contract_call_by_hash( sender.into_account().unwrap(), addressable_cep18_token, + addressable_cep18_token, INCREASE_ALLOWANCE, runtime_args! { ARG_SPENDER => spender, diff --git a/tests/src/utility/installer_request_builders.rs b/tests/src/utility/installer_request_builders.rs index 85eec35..0f6db38 100644 --- a/tests/src/utility/installer_request_builders.rs +++ b/tests/src/utility/installer_request_builders.rs @@ -1,18 +1,23 @@ use casper_engine_test_support::{ utils::create_run_genesis_request, ExecuteRequest, ExecuteRequestBuilder, LmdbWasmTestBuilder, DEFAULT_ACCOUNT_ADDR, DEFAULT_ACCOUNT_PUBLIC_KEY, + utils::create_run_genesis_request, ExecuteRequest, ExecuteRequestBuilder, LmdbWasmTestBuilder, + DEFAULT_ACCOUNT_ADDR, DEFAULT_ACCOUNT_PUBLIC_KEY, }; use casper_types::{ - account::AccountHash, addressable_entity::EntityKindTag, bytesrepr::FromBytes, runtime_args, - AddressableEntityHash, CLTyped, EntityAddr, GenesisAccount, Key, Motes, PackageHash, - RuntimeArgs, U256, U512, + account::AccountHash, addressable_entity::EntityKindTag, bytesrepr::FromBytes, + runtime_args, AddressableEntityHash, CLTyped, EntityAddr, + GenesisAccount, Key, Motes, PackageHash, RuntimeArgs, U256, U512, }; use crate::utility::constants::{ - ALLOWANCE_AMOUNT_1, ALLOWANCE_AMOUNT_2, TOTAL_SUPPLY_KEY, TRANSFER_AMOUNT_1, TRANSFER_AMOUNT_2, + ALLOWANCE_AMOUNT_1, ALLOWANCE_AMOUNT_2, TOTAL_SUPPLY_KEY, + TRANSFER_AMOUNT_1, TRANSFER_AMOUNT_2, }; use super::constants::{ + ACCOUNT_1_PUBLIC_KEY, ACCOUNT_2_PUBLIC_KEY, ARG_ADDRESS, ARG_AMOUNT, ARG_DECIMALS, ARG_NAME, + ARG_OWNER, ARG_RECIPIENT, ARG_SPENDER, ARG_SYMBOL, ARG_TOKEN_CONTRACT, ARG_TOTAL_SUPPLY, ACCOUNT_1_PUBLIC_KEY, ACCOUNT_2_PUBLIC_KEY, ARG_ADDRESS, ARG_AMOUNT, ARG_DECIMALS, ARG_NAME, ARG_OWNER, ARG_RECIPIENT, ARG_SPENDER, ARG_SYMBOL, ARG_TOKEN_CONTRACT, ARG_TOTAL_SUPPLY, CEP18_CONTRACT_WASM, CEP18_TEST_CONTRACT_KEY, CEP18_TEST_CONTRACT_WASM, @@ -38,8 +43,11 @@ pub(crate) fn invert_cep18_address(address: Key) -> Key { pub(crate) struct TestContext { pub(crate) cep18_token: AddressableEntityHash, pub(crate) cep18_test_contract_package: PackageHash, + pub(crate) cep18_token: AddressableEntityHash, + pub(crate) cep18_test_contract_package: PackageHash, } +pub(crate) fn setup() -> (LmdbWasmTestBuilder, TestContext) { pub(crate) fn setup() -> (LmdbWasmTestBuilder, TestContext) { setup_with_args(runtime_args! { ARG_NAME => TOKEN_NAME, @@ -49,6 +57,25 @@ pub(crate) fn setup() -> (LmdbWasmTestBuilder, TestContext) { }) } +pub(crate) fn setup_with_args(install_args: RuntimeArgs) -> (LmdbWasmTestBuilder, TestContext) { + let mut builder = LmdbWasmTestBuilder::default(); + builder.run_genesis(create_run_genesis_request(vec![ + GenesisAccount::Account { + public_key: DEFAULT_ACCOUNT_PUBLIC_KEY.clone(), + balance: Motes::new(U512::from(5_000_000_000_000_u64)), + validator: None, + }, + GenesisAccount::Account { + public_key: ACCOUNT_1_PUBLIC_KEY.clone(), + balance: Motes::new(U512::from(5_000_000_000_000_u64)), + validator: None, + }, + GenesisAccount::Account { + public_key: ACCOUNT_2_PUBLIC_KEY.clone(), + balance: Motes::new(U512::from(5_000_000_000_000_u64)), + validator: None, + }, + ])); pub(crate) fn setup_with_args(install_args: RuntimeArgs) -> (LmdbWasmTestBuilder, TestContext) { let mut builder = LmdbWasmTestBuilder::default(); builder.run_genesis(create_run_genesis_request(vec![ @@ -83,24 +110,28 @@ pub(crate) fn setup_with_args(install_args: RuntimeArgs) -> (LmdbWasmTestBuilder builder.exec(install_request_1).expect_success().commit(); builder.exec(install_request_2).expect_success().commit(); - let account = builder - .get_entity_with_named_keys_by_account_hash(*DEFAULT_ACCOUNT_ADDR) - .unwrap(); + let account = + builder.get_entity_with_named_keys_by_account_hash(*DEFAULT_ACCOUNT_ADDR).unwrap(); let account_named_keys = account.named_keys(); // println!("{account_named_keys:?}"); // let account_named_keys_2 = builder.get_named_keys(EntityAddr::new_account(DEFAULT_ACCOUNT_ADDR.value())); // println!("{account_named_keys_2:?}"); + let cep18_token = account_named_keys let cep18_token = account_named_keys .get(CEP18_TOKEN_CONTRACT_KEY) .and_then(|key| key.into_entity_hash()) + .and_then(|key| key.into_entity_hash()) .expect("should have contract hash"); + let cep18_test_contract_package = account_named_keys let cep18_test_contract_package = account_named_keys .get(CEP18_TEST_CONTRACT_KEY) .and_then(|key| key.into_package_hash()) .expect("should have package hash"); + .and_then(|key| key.into_package_hash()) + .expect("should have package hash"); let test_context = TestContext { cep18_token, @@ -113,8 +144,11 @@ pub(crate) fn setup_with_args(install_args: RuntimeArgs) -> (LmdbWasmTestBuilder pub(crate) fn cep18_check_total_supply( builder: &mut LmdbWasmTestBuilder, cep18_contract_hash: &AddressableEntityHash, + builder: &mut LmdbWasmTestBuilder, + cep18_contract_hash: &AddressableEntityHash, ) -> U256 { let account = builder + .get_entity_with_named_keys_by_account_hash(*DEFAULT_ACCOUNT_ADDR) .get_entity_with_named_keys_by_account_hash(*DEFAULT_ACCOUNT_ADDR) .expect("should have account"); @@ -122,10 +156,12 @@ pub(crate) fn cep18_check_total_supply( .named_keys() .get(CEP18_TEST_CONTRACT_KEY) .and_then(|key| key.into_package_hash()) + .and_then(|key| key.into_package_hash()) .expect("should have test contract hash"); let check_total_supply_args = runtime_args! { ARG_TOKEN_CONTRACT => Key::addressable_entity_key(EntityKindTag::SmartContract, *cep18_contract_hash), + ARG_TOKEN_CONTRACT => Key::addressable_entity_key(EntityKindTag::SmartContract, *cep18_contract_hash), }; let exec_request = ExecuteRequestBuilder::versioned_contract_call_by_hash( @@ -144,25 +180,36 @@ pub(crate) fn cep18_check_total_supply( pub(crate) fn get_test_result( builder: &mut LmdbWasmTestBuilder, cep18_test_contract_package: PackageHash, + builder: &mut LmdbWasmTestBuilder, + cep18_test_contract_package: PackageHash, ) -> T { let contract_package = builder + .get_package(cep18_test_contract_package) .get_package(cep18_test_contract_package) .expect("should have contract package"); let enabled_versions = contract_package.enabled_versions(); + let contract_hash = enabled_versions + .contract_hashes() + .last() let contract_hash = enabled_versions .contract_hashes() .last() .expect("should have latest version"); let entity_addr = EntityAddr::new_smart_contract(contract_hash.value()); builder.get_value(entity_addr, RESULT_KEY) + let entity_addr = EntityAddr::new_smart_contract(contract_hash.value()); + builder.get_value(entity_addr, RESULT_KEY) } pub(crate) fn cep18_check_balance_of( + builder: &mut LmdbWasmTestBuilder, + cep18_contract_hash: &AddressableEntityHash, builder: &mut LmdbWasmTestBuilder, cep18_contract_hash: &AddressableEntityHash, address: Key, ) -> U256 { let account = builder + .get_entity_with_named_keys_by_account_hash(*DEFAULT_ACCOUNT_ADDR) .get_entity_with_named_keys_by_account_hash(*DEFAULT_ACCOUNT_ADDR) .expect("should have account"); @@ -170,9 +217,11 @@ pub(crate) fn cep18_check_balance_of( .named_keys() .get(CEP18_TEST_CONTRACT_KEY) .and_then(|key| key.into_package_hash()) + .and_then(|key| key.into_package_hash()) .expect("should have test contract hash"); let check_balance_args = runtime_args! { + ARG_TOKEN_CONTRACT => Key::addressable_entity_key(EntityKindTag::SmartContract, *cep18_contract_hash), ARG_TOKEN_CONTRACT => Key::addressable_entity_key(EntityKindTag::SmartContract, *cep18_contract_hash), ARG_ADDRESS => address, }; @@ -190,25 +239,30 @@ pub(crate) fn cep18_check_balance_of( } pub(crate) fn cep18_check_allowance_of( + builder: &mut LmdbWasmTestBuilder, builder: &mut LmdbWasmTestBuilder, owner: Key, spender: Key, ) -> U256 { let account = builder + .get_entity_with_named_keys_by_account_hash(*DEFAULT_ACCOUNT_ADDR) .get_entity_with_named_keys_by_account_hash(*DEFAULT_ACCOUNT_ADDR) .expect("should have account"); let cep18_contract_hash = account .named_keys() .get(CEP18_TOKEN_CONTRACT_KEY) .and_then(|key| key.into_entity_hash()) + .and_then(|key| key.into_entity_hash()) .expect("should have test contract hash"); let cep18_test_contract_package = account .named_keys() .get(CEP18_TEST_CONTRACT_KEY) .and_then(|key| key.into_package_hash()) + .and_then(|key| key.into_package_hash()) .expect("should have test contract hash"); let check_balance_args = runtime_args! { + ARG_TOKEN_CONTRACT => Key::addressable_entity_key(EntityKindTag::SmartContract, cep18_contract_hash), ARG_TOKEN_CONTRACT => Key::addressable_entity_key(EntityKindTag::SmartContract, cep18_contract_hash), ARG_OWNER => owner, ARG_SPENDER => spender, @@ -227,6 +281,7 @@ pub(crate) fn cep18_check_allowance_of( } pub(crate) fn test_cep18_transfer( + builder: &mut LmdbWasmTestBuilder, builder: &mut LmdbWasmTestBuilder, test_context: &TestContext, sender1: Key, @@ -292,6 +347,7 @@ pub(crate) fn test_cep18_transfer( pub(crate) fn make_cep18_transfer_request( sender: Key, cep18_token: &AddressableEntityHash, + cep18_token: &AddressableEntityHash, recipient: Key, amount: U256, ) -> ExecuteRequest { @@ -299,6 +355,7 @@ pub(crate) fn make_cep18_transfer_request( Key::Account(sender) => ExecuteRequestBuilder::contract_call_by_hash( sender, AddressableEntityHash::new(cep18_token.value()), + AddressableEntityHash::new(cep18_token.value()), METHOD_TRANSFER, runtime_args! { ARG_AMOUNT => amount, @@ -309,9 +366,11 @@ pub(crate) fn make_cep18_transfer_request( Key::Hash(contract_package_hash) => ExecuteRequestBuilder::versioned_contract_call_by_hash( *DEFAULT_ACCOUNT_ADDR, PackageHash::new(contract_package_hash), + PackageHash::new(contract_package_hash), None, METHOD_TRANSFER_AS_STORED_CONTRACT, runtime_args! { + ARG_TOKEN_CONTRACT => Key::addressable_entity_key(EntityKindTag::SmartContract, *cep18_token), ARG_TOKEN_CONTRACT => Key::addressable_entity_key(EntityKindTag::SmartContract, *cep18_token), ARG_AMOUNT => amount, ARG_RECIPIENT => recipient, @@ -325,6 +384,7 @@ pub(crate) fn make_cep18_transfer_request( pub(crate) fn make_cep18_approve_request( sender: Key, cep18_token: &AddressableEntityHash, + cep18_token: &AddressableEntityHash, spender: Key, amount: U256, ) -> ExecuteRequest { @@ -332,6 +392,7 @@ pub(crate) fn make_cep18_approve_request( Key::Account(sender) => ExecuteRequestBuilder::contract_call_by_hash( sender, AddressableEntityHash::new(cep18_token.value()), + AddressableEntityHash::new(cep18_token.value()), METHOD_APPROVE, runtime_args! { ARG_SPENDER => spender, @@ -342,9 +403,11 @@ pub(crate) fn make_cep18_approve_request( Key::Hash(contract_package_hash) => ExecuteRequestBuilder::versioned_contract_call_by_hash( *DEFAULT_ACCOUNT_ADDR, PackageHash::new(contract_package_hash), + PackageHash::new(contract_package_hash), None, METHOD_APPROVE_AS_STORED_CONTRACT, runtime_args! { + ARG_TOKEN_CONTRACT => Key::addressable_entity_key(EntityKindTag::SmartContract, *cep18_token), ARG_TOKEN_CONTRACT => Key::addressable_entity_key(EntityKindTag::SmartContract, *cep18_token), ARG_SPENDER => spender, ARG_AMOUNT => amount, @@ -356,6 +419,7 @@ pub(crate) fn make_cep18_approve_request( } pub(crate) fn test_approve_for( + builder: &mut LmdbWasmTestBuilder, builder: &mut LmdbWasmTestBuilder, test_context: &TestContext, sender: Key, @@ -381,6 +445,10 @@ pub(crate) fn test_approve_for( let account_1_allowance_after = cep18_check_allowance_of(builder, owner, spender); assert_eq!(account_1_allowance_after, allowance_amount_1); + let total_supply: U256 = builder.get_value( + EntityAddr::new_smart_contract(cep18_token.value()), + TOTAL_SUPPLY_KEY, + ); let total_supply: U256 = builder.get_value( EntityAddr::new_smart_contract(cep18_token.value()), TOTAL_SUPPLY_KEY, @@ -401,6 +469,10 @@ pub(crate) fn test_approve_for( let inverted_spender_allowance = cep18_check_allowance_of(builder, owner, inverted_spender_key); assert_eq!(inverted_spender_allowance, U256::zero()); + let total_supply: U256 = builder.get_value( + EntityAddr::new_smart_contract(cep18_token.value()), + TOTAL_SUPPLY_KEY, + ); let total_supply: U256 = builder.get_value( EntityAddr::new_smart_contract(cep18_token.value()), TOTAL_SUPPLY_KEY, From 927de5085e03f5989efe1d7027e17bf347350d1f Mon Sep 17 00:00:00 2001 From: Deuszex Date: Mon, 17 Jun 2024 15:01:17 +0200 Subject: [PATCH 07/27] 2.0.0-rc2 --- Cargo.toml | 16 ++++++++ Makefile | 4 +- cep18-test-contract/Cargo.toml | 8 +--- cep18-test-contract/src/main.rs | 6 +++ cep18/Cargo.toml | 6 +-- cep18/src/entry_points.rs | 15 +++++++ cep18/src/events.rs | 72 +++++++++++++++++---------------- cep18/src/main.rs | 10 +++-- cep18/src/modalities.rs | 6 ++- tests/Cargo.toml | 8 ++-- 10 files changed, 97 insertions(+), 54 deletions(-) create mode 100644 Cargo.toml diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..b00622b --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,16 @@ +[workspace] +members = [ + "cep18", + "cep18-test-contract", + "tests", +] +default-members = [ + "cep18", + "cep18-test-contract", + "tests", +] +resolver = "2" + +[profile.release] +codegen-units = 1 +lto = true diff --git a/Makefile b/Makefile index 9c957f2..0201af0 100644 --- a/Makefile +++ b/Makefile @@ -13,8 +13,8 @@ build-contract: setup-test: build-contract mkdir -p tests/wasm - cp ./cep18/target/wasm32-unknown-unknown/release/cep18.wasm tests/wasm - cp ./cep18-test-contract/target/wasm32-unknown-unknown/release/cep18_test_contract.wasm tests/wasm + cp ./target/wasm32-unknown-unknown/release/cep18.wasm tests/wasm + cp ./target/wasm32-unknown-unknown/release/cep18_test_contract.wasm tests/wasm test: setup-test cd tests && cargo test diff --git a/cep18-test-contract/Cargo.toml b/cep18-test-contract/Cargo.toml index dc052de..444744d 100644 --- a/cep18-test-contract/Cargo.toml +++ b/cep18-test-contract/Cargo.toml @@ -11,9 +11,5 @@ doctest = false test = false [dependencies] -casper-types = { git = "https://github.com/casper-network/casper-node.git", branch = "release-2.0.0-rc1" } -casper-contract = { git = "https://github.com/casper-network/casper-node.git", branch = "release-2.0.0-rc1" } - -[profile.release] -codegen-units = 1 -lto = true +casper-types = { git = "https://github.com/casper-network/casper-node.git", branch = "release-2.0.0-rc2"} +casper-contract = { git = "https://github.com/casper-network/casper-node.git", branch = "release-2.0.0-rc2"} diff --git a/cep18-test-contract/src/main.rs b/cep18-test-contract/src/main.rs index 32efbdd..ce1cb23 100644 --- a/cep18-test-contract/src/main.rs +++ b/cep18-test-contract/src/main.rs @@ -171,6 +171,7 @@ pub extern "C" fn call() { <()>::cl_type(), EntryPointAccess::Public, EntryPointType::Called, + casper_types::EntryPointPayment::Caller ); let check_balance_of_entrypoint = EntryPoint::new( String::from(CHECK_BALANCE_OF_ENTRY_POINT_NAME), @@ -184,6 +185,7 @@ pub extern "C" fn call() { <()>::cl_type(), EntryPointAccess::Public, EntryPointType::Called, + casper_types::EntryPointPayment::Caller ); let check_allowance_of_entrypoint = EntryPoint::new( String::from(CHECK_ALLOWANCE_OF_ENTRY_POINT_NAME), @@ -198,6 +200,7 @@ pub extern "C" fn call() { <()>::cl_type(), EntryPointAccess::Public, EntryPointType::Called, + casper_types::EntryPointPayment::Caller ); let transfer_as_stored_contract_entrypoint = EntryPoint::new( @@ -213,6 +216,7 @@ pub extern "C" fn call() { <()>::cl_type(), EntryPointAccess::Public, EntryPointType::Called, + casper_types::EntryPointPayment::Caller ); let approve_as_stored_contract_entrypoint = EntryPoint::new( @@ -228,6 +232,7 @@ pub extern "C" fn call() { <()>::cl_type(), EntryPointAccess::Public, EntryPointType::Called, + casper_types::EntryPointPayment::Caller ); let transfer_from_as_stored_contract_entrypoint = EntryPoint::new( @@ -244,6 +249,7 @@ pub extern "C" fn call() { <()>::cl_type(), EntryPointAccess::Public, EntryPointType::Called, + casper_types::EntryPointPayment::Caller ); entry_points.add_entry_point(check_total_supply_entrypoint); diff --git a/cep18/Cargo.toml b/cep18/Cargo.toml index 1db64b3..940641d 100644 --- a/cep18/Cargo.toml +++ b/cep18/Cargo.toml @@ -18,11 +18,11 @@ test = false [dependencies] base64 = { version = "0.20.0", default-features = false, features = ["alloc"] } -casper-types = { git = "https://github.com/casper-network/casper-node.git", branch = "release-2.0.0-rc1" } -casper-contract = { git = "https://github.com/casper-network/casper-node.git", branch = "release-2.0.0-rc1" } +casper-types = { git = "https://github.com/casper-network/casper-node.git", branch = "release-2.0.0-rc2"} +casper-contract = { git = "https://github.com/casper-network/casper-node.git", branch = "release-2.0.0-rc2"} hex = { version = "0.4.3", default-features = false } once_cell = { version = "1.16.0", default-features = false } -# casper-event-standard = { version = "0.4.1", default-features = false } +casper-event-standard = { git = "https://github.com/deuszex/casper-event-standard", branch = "condor", default-features = false } [profile.release] codegen-units = 1 diff --git a/cep18/src/entry_points.rs b/cep18/src/entry_points.rs index 532b2f5..3478c72 100644 --- a/cep18/src/entry_points.rs +++ b/cep18/src/entry_points.rs @@ -23,6 +23,7 @@ pub fn name() -> EntryPoint { String::cl_type(), EntryPointAccess::Public, EntryPointType::Called, + casper_types::EntryPointPayment::Caller ) } @@ -34,6 +35,7 @@ pub fn symbol() -> EntryPoint { String::cl_type(), EntryPointAccess::Public, EntryPointType::Called, + casper_types::EntryPointPayment::Caller ) } @@ -49,6 +51,7 @@ pub fn transfer_from() -> EntryPoint { CLType::Unit, EntryPointAccess::Public, EntryPointType::Called, + casper_types::EntryPointPayment::Caller ) } @@ -63,6 +66,7 @@ pub fn allowance() -> EntryPoint { U256::cl_type(), EntryPointAccess::Public, EntryPointType::Called, + casper_types::EntryPointPayment::Caller ) } @@ -77,6 +81,7 @@ pub fn approve() -> EntryPoint { CLType::Unit, EntryPointAccess::Public, EntryPointType::Called, + casper_types::EntryPointPayment::Caller ) } @@ -91,6 +96,7 @@ pub fn increase_allowance() -> EntryPoint { CLType::Unit, EntryPointAccess::Public, EntryPointType::Called, + casper_types::EntryPointPayment::Caller ) } @@ -105,6 +111,7 @@ pub fn decrease_allowance() -> EntryPoint { CLType::Unit, EntryPointAccess::Public, EntryPointType::Called, + casper_types::EntryPointPayment::Caller ) } @@ -119,6 +126,7 @@ pub fn transfer() -> EntryPoint { CLType::Unit, EntryPointAccess::Public, EntryPointType::Called, + casper_types::EntryPointPayment::Caller ) } @@ -130,6 +138,7 @@ pub fn balance_of() -> EntryPoint { U256::cl_type(), EntryPointAccess::Public, EntryPointType::Called, + casper_types::EntryPointPayment::Caller ) } @@ -141,6 +150,7 @@ pub fn total_supply() -> EntryPoint { U256::cl_type(), EntryPointAccess::Public, EntryPointType::Called, + casper_types::EntryPointPayment::Caller ) } @@ -152,6 +162,7 @@ pub fn decimals() -> EntryPoint { u8::cl_type(), EntryPointAccess::Public, EntryPointType::Called, + casper_types::EntryPointPayment::Caller ) } @@ -166,6 +177,7 @@ pub fn burn() -> EntryPoint { CLType::Unit, EntryPointAccess::Public, EntryPointType::Called, + casper_types::EntryPointPayment::Caller ) } @@ -180,6 +192,7 @@ pub fn mint() -> EntryPoint { CLType::Unit, EntryPointAccess::Public, EntryPointType::Called, + casper_types::EntryPointPayment::Caller ) } @@ -200,6 +213,7 @@ pub fn change_security() -> EntryPoint { CLType::Unit, EntryPointAccess::Public, EntryPointType::Called, + casper_types::EntryPointPayment::Caller ) } @@ -211,6 +225,7 @@ pub fn init() -> EntryPoint { CLType::Unit, EntryPointAccess::Public, EntryPointType::Called, + casper_types::EntryPointPayment::Caller ) } diff --git a/cep18/src/events.rs b/cep18/src/events.rs index 9b00b16..e44e9e2 100644 --- a/cep18/src/events.rs +++ b/cep18/src/events.rs @@ -10,7 +10,7 @@ use crate::{ utils::{read_from, SecurityBadge}, }; -// use casper_event_standard::{emit, Event, Schemas}; +use casper_event_standard::{emit, Event, Schemas}; pub fn record_event_dictionary(event: Event) { let events_mode: EventsMode = @@ -18,10 +18,14 @@ pub fn record_event_dictionary(event: Event) { match events_mode { EventsMode::NoEvents => {} - // EventsMode::CES => ces(event), + EventsMode::CES => ces(event), EventsMode::Native => { runtime::emit_message(EVENTS, &format!("{event:?}").into()).unwrap_or_revert() } + EventsMode::NativeNCES =>{ + runtime::emit_message(EVENTS, &format!("{event:?}").into()).unwrap_or_revert(); + ces(event); + } } } @@ -37,26 +41,26 @@ pub enum Event { ChangeSecurity(ChangeSecurity), } -#[derive(Debug, PartialEq, Eq)] +#[derive(Event, Debug, PartialEq, Eq)] pub struct Mint { pub recipient: Key, pub amount: U256, } -#[derive(Debug, PartialEq, Eq)] +#[derive(Event, Debug, PartialEq, Eq)] pub struct Burn { pub owner: Key, pub amount: U256, } -#[derive(Debug, PartialEq, Eq)] +#[derive(Event, Debug, PartialEq, Eq)] pub struct SetAllowance { pub owner: Key, pub spender: Key, pub allowance: U256, } -#[derive(Debug, PartialEq, Eq)] +#[derive(Event, Debug, PartialEq, Eq)] pub struct IncreaseAllowance { pub owner: Key, pub spender: Key, @@ -64,7 +68,7 @@ pub struct IncreaseAllowance { pub inc_by: U256, } -#[derive(Debug, PartialEq, Eq)] +#[derive(Event, Debug, PartialEq, Eq)] pub struct DecreaseAllowance { pub owner: Key, pub spender: Key, @@ -72,14 +76,14 @@ pub struct DecreaseAllowance { pub decr_by: U256, } -#[derive(Debug, PartialEq, Eq)] +#[derive(Event, Debug, PartialEq, Eq)] pub struct Transfer { pub sender: Key, pub recipient: Key, pub amount: U256, } -#[derive(Debug, PartialEq, Eq)] +#[derive(Event, Debug, PartialEq, Eq)] pub struct TransferFrom { pub spender: Key, pub owner: Key, @@ -87,39 +91,39 @@ pub struct TransferFrom { pub amount: U256, } -#[derive(Debug, PartialEq, Eq)] +#[derive(Event, Debug, PartialEq, Eq)] pub struct ChangeSecurity { pub admin: Key, pub sec_change_map: BTreeMap, } -// fn ces(event: Event) { -// match event { -// Event::Mint(ev) => emit(ev), -// Event::Burn(ev) => emit(ev), -// Event::SetAllowance(ev) => emit(ev), -// Event::IncreaseAllowance(ev) => emit(ev), -// Event::DecreaseAllowance(ev) => emit(ev), -// Event::Transfer(ev) => emit(ev), -// Event::TransferFrom(ev) => emit(ev), -// Event::ChangeSecurity(ev) => emit(ev), -// } -// } +fn ces(event: Event) { + match event { + Event::Mint(ev) => emit(ev), + Event::Burn(ev) => emit(ev), + Event::SetAllowance(ev) => emit(ev), + Event::IncreaseAllowance(ev) => emit(ev), + Event::DecreaseAllowance(ev) => emit(ev), + Event::Transfer(ev) => emit(ev), + Event::TransferFrom(ev) => emit(ev), + Event::ChangeSecurity(ev) => emit(ev), + } +} pub fn init_events() { let events_mode: EventsMode = EventsMode::try_from(read_from::(EVENTS_MODE)).unwrap_or_revert(); - // if events_mode == EventsMode::CES { - // let schemas = Schemas::new() - // .with::() - // .with::() - // .with::() - // .with::() - // .with::() - // .with::() - // .with::() - // .with::(); - // casper_event_standard::init(schemas); - // } + if events_mode == EventsMode::CES { + let schemas = Schemas::new() + .with::() + .with::() + .with::() + .with::() + .with::() + .with::() + .with::() + .with::(); + casper_event_standard::init(schemas); + } } diff --git a/cep18/src/main.rs b/cep18/src/main.rs index 2c1a6a6..1d18fa3 100644 --- a/cep18/src/main.rs +++ b/cep18/src/main.rs @@ -47,8 +47,7 @@ use constants::{ }; pub use error::Cep18Error; use events::{ - Burn, ChangeSecurity, DecreaseAllowance, Event, IncreaseAllowance, Mint, SetAllowance, - Transfer, TransferFrom, + init_events, Burn, ChangeSecurity, DecreaseAllowance, Event, IncreaseAllowance, Mint, SetAllowance, Transfer, TransferFrom }; use modalities::EventsMode; use utils::{ @@ -295,7 +294,12 @@ pub extern "C" fn init() { let minter_list: Option> = utils::get_optional_named_arg_with_user_errors(MINTER_LIST, Cep18Error::InvalidMinterList); - // init_events(); + let events_mode: EventsMode = + EventsMode::try_from(read_from::(EVENTS_MODE)).unwrap_or_revert(); + + if [EventsMode::CES, EventsMode::NativeNCES].contains(&events_mode){ + init_events(); + } if let Some(minter_list) = minter_list { for minter in minter_list { diff --git a/cep18/src/modalities.rs b/cep18/src/modalities.rs index 7b2d46f..161e126 100644 --- a/cep18/src/modalities.rs +++ b/cep18/src/modalities.rs @@ -7,8 +7,9 @@ use crate::Cep18Error; #[allow(clippy::upper_case_acronyms)] pub enum EventsMode { NoEvents = 0, - // CES = 1, + CES = 1, Native = 2, + NativeNCES = 3, } impl TryFrom for EventsMode { @@ -17,8 +18,9 @@ impl TryFrom for EventsMode { fn try_from(value: u8) -> Result { match value { 0 => Ok(EventsMode::NoEvents), - // 1 => Ok(EventsMode::CES), + 1 => Ok(EventsMode::CES), 2 => Ok(EventsMode::Native), + 3 => Ok(EventsMode::NativeNCES), _ => Err(Cep18Error::InvalidEventsMode), } } diff --git a/tests/Cargo.toml b/tests/Cargo.toml index a36af90..0babae2 100644 --- a/tests/Cargo.toml +++ b/tests/Cargo.toml @@ -4,10 +4,10 @@ version = "2.0.0" edition = "2021" [dependencies] -casper-types = { git = "https://github.com/casper-network/casper-node.git", branch = "release-2.0.0-rc1", default-features = false} -casper-engine-test-support = { git = "https://github.com/casper-network/casper-node.git", branch = "release-2.0.0-rc1", default-features = false} -casper-execution-engine = { git = "https://github.com/casper-network/casper-node.git", branch = "release-2.0.0-rc1", default-features = false} -casper-storage = { git = "https://github.com/casper-network/casper-node.git", branch = "release-2.0.0-rc1", default-features = false} +casper-types = { git = "https://github.com/casper-network/casper-node.git", branch = "release-2.0.0-rc2", default-features = false} +casper-engine-test-support = { git = "https://github.com/casper-network/casper-node.git", branch = "release-2.0.0-rc2", default-features = false} +casper-execution-engine = { git = "https://github.com/casper-network/casper-node.git", branch = "release-2.0.0-rc2", default-features = false} +casper-storage = { git = "https://github.com/casper-network/casper-node.git", branch = "release-2.0.0-rc2", default-features = false} once_cell = "1.16.0" [lib] From a40ad8f2cda681205019a586c0c8261714b1e353 Mon Sep 17 00:00:00 2001 From: Deuszex Date: Mon, 17 Jun 2024 20:39:39 +0200 Subject: [PATCH 08/27] 2.0.0-rc2 stable --- cep18-test-contract/rust-toolchain | 2 +- cep18-test-contract/src/main.rs | 12 +- cep18/rust-toolchain | 2 +- cep18/src/constants.rs | 4 + cep18/src/entry_points.rs | 30 +-- cep18/src/error.rs | 2 + cep18/src/events.rs | 31 ++- cep18/src/main.rs | 248 ++++++++++++++++-- cep18/src/utils.rs | 15 +- tests/src/allowance.rs | 77 +++--- tests/src/install.rs | 2 +- tests/src/mint_and_burn.rs | 26 +- tests/src/transfer.rs | 132 +++++----- tests/src/utility/constants.rs | 6 +- .../src/utility/installer_request_builders.rs | 150 +++++------ 15 files changed, 481 insertions(+), 258 deletions(-) diff --git a/cep18-test-contract/rust-toolchain b/cep18-test-contract/rust-toolchain index f9e5e5e..579ba54 100755 --- a/cep18-test-contract/rust-toolchain +++ b/cep18-test-contract/rust-toolchain @@ -1 +1 @@ -nightly-2023-03-25 +nightly-2024-05-28 diff --git a/cep18-test-contract/src/main.rs b/cep18-test-contract/src/main.rs index ce1cb23..975431f 100644 --- a/cep18-test-contract/src/main.rs +++ b/cep18-test-contract/src/main.rs @@ -171,7 +171,7 @@ pub extern "C" fn call() { <()>::cl_type(), EntryPointAccess::Public, EntryPointType::Called, - casper_types::EntryPointPayment::Caller + casper_types::EntryPointPayment::Caller, ); let check_balance_of_entrypoint = EntryPoint::new( String::from(CHECK_BALANCE_OF_ENTRY_POINT_NAME), @@ -185,7 +185,7 @@ pub extern "C" fn call() { <()>::cl_type(), EntryPointAccess::Public, EntryPointType::Called, - casper_types::EntryPointPayment::Caller + casper_types::EntryPointPayment::Caller, ); let check_allowance_of_entrypoint = EntryPoint::new( String::from(CHECK_ALLOWANCE_OF_ENTRY_POINT_NAME), @@ -200,7 +200,7 @@ pub extern "C" fn call() { <()>::cl_type(), EntryPointAccess::Public, EntryPointType::Called, - casper_types::EntryPointPayment::Caller + casper_types::EntryPointPayment::Caller, ); let transfer_as_stored_contract_entrypoint = EntryPoint::new( @@ -216,7 +216,7 @@ pub extern "C" fn call() { <()>::cl_type(), EntryPointAccess::Public, EntryPointType::Called, - casper_types::EntryPointPayment::Caller + casper_types::EntryPointPayment::Caller, ); let approve_as_stored_contract_entrypoint = EntryPoint::new( @@ -232,7 +232,7 @@ pub extern "C" fn call() { <()>::cl_type(), EntryPointAccess::Public, EntryPointType::Called, - casper_types::EntryPointPayment::Caller + casper_types::EntryPointPayment::Caller, ); let transfer_from_as_stored_contract_entrypoint = EntryPoint::new( @@ -249,7 +249,7 @@ pub extern "C" fn call() { <()>::cl_type(), EntryPointAccess::Public, EntryPointType::Called, - casper_types::EntryPointPayment::Caller + casper_types::EntryPointPayment::Caller, ); entry_points.add_entry_point(check_total_supply_entrypoint); diff --git a/cep18/rust-toolchain b/cep18/rust-toolchain index f9e5e5e..11157ba 100755 --- a/cep18/rust-toolchain +++ b/cep18/rust-toolchain @@ -1 +1 @@ -nightly-2023-03-25 +nightly-2024-05-28 \ No newline at end of file diff --git a/cep18/src/constants.rs b/cep18/src/constants.rs index edf1d5a..995b6a6 100644 --- a/cep18/src/constants.rs +++ b/cep18/src/constants.rs @@ -13,6 +13,7 @@ pub const ALLOWANCES: &str = "allowances"; /// Name of named-key for `total_supply` pub const TOTAL_SUPPLY: &str = "total_supply"; pub const EVENTS: &str = "events"; +pub const REVERT: &str = "revert"; pub const HASH_KEY_NAME_PREFIX: &str = "cep18_contract_package_"; pub const ACCESS_KEY_NAME_PREFIX: &str = "cep18_contract_package_access_"; @@ -45,6 +46,8 @@ pub const BURN_ENTRY_POINT_NAME: &str = "burn"; pub const INIT_ENTRY_POINT_NAME: &str = "init"; /// Name of `change_security` entry point. pub const CHANGE_SECURITY_ENTRY_POINT_NAME: &str = "change_security"; +pub const MIGRATE_USER_BALANCE_KEYS_ENTRY_POINT_NAME: &str = "migrate_user_balance_keys"; +pub const MIGRATE_USER_ALLOWANCE_KEYS_ENTRY_POINT_NAME: &str = "migrate_user_allowance_keys"; pub const INCREASE_ALLOWANCE_ENTRY_POINT_NAME: &str = "increase_allowance"; pub const DECREASE_ALLOWANCE_ENTRY_POINT_NAME: &str = "decrease_allowance"; @@ -69,3 +72,4 @@ pub const BURNER_LIST: &str = "burner_list"; pub const NONE_LIST: &str = "none_list"; pub const MINT_AND_BURN_LIST: &str = "mint_and_burn_list"; pub const ENABLE_MINT_BURN: &str = "enable_mint_burn"; +pub const USER_KEY_MAP: &str = "user_key_map"; diff --git a/cep18/src/entry_points.rs b/cep18/src/entry_points.rs index 3478c72..6bdcad7 100644 --- a/cep18/src/entry_points.rs +++ b/cep18/src/entry_points.rs @@ -23,7 +23,7 @@ pub fn name() -> EntryPoint { String::cl_type(), EntryPointAccess::Public, EntryPointType::Called, - casper_types::EntryPointPayment::Caller + casper_types::EntryPointPayment::Caller, ) } @@ -35,7 +35,7 @@ pub fn symbol() -> EntryPoint { String::cl_type(), EntryPointAccess::Public, EntryPointType::Called, - casper_types::EntryPointPayment::Caller + casper_types::EntryPointPayment::Caller, ) } @@ -51,7 +51,7 @@ pub fn transfer_from() -> EntryPoint { CLType::Unit, EntryPointAccess::Public, EntryPointType::Called, - casper_types::EntryPointPayment::Caller + casper_types::EntryPointPayment::Caller, ) } @@ -66,7 +66,7 @@ pub fn allowance() -> EntryPoint { U256::cl_type(), EntryPointAccess::Public, EntryPointType::Called, - casper_types::EntryPointPayment::Caller + casper_types::EntryPointPayment::Caller, ) } @@ -81,7 +81,7 @@ pub fn approve() -> EntryPoint { CLType::Unit, EntryPointAccess::Public, EntryPointType::Called, - casper_types::EntryPointPayment::Caller + casper_types::EntryPointPayment::Caller, ) } @@ -96,7 +96,7 @@ pub fn increase_allowance() -> EntryPoint { CLType::Unit, EntryPointAccess::Public, EntryPointType::Called, - casper_types::EntryPointPayment::Caller + casper_types::EntryPointPayment::Caller, ) } @@ -111,7 +111,7 @@ pub fn decrease_allowance() -> EntryPoint { CLType::Unit, EntryPointAccess::Public, EntryPointType::Called, - casper_types::EntryPointPayment::Caller + casper_types::EntryPointPayment::Caller, ) } @@ -126,7 +126,7 @@ pub fn transfer() -> EntryPoint { CLType::Unit, EntryPointAccess::Public, EntryPointType::Called, - casper_types::EntryPointPayment::Caller + casper_types::EntryPointPayment::Caller, ) } @@ -138,7 +138,7 @@ pub fn balance_of() -> EntryPoint { U256::cl_type(), EntryPointAccess::Public, EntryPointType::Called, - casper_types::EntryPointPayment::Caller + casper_types::EntryPointPayment::Caller, ) } @@ -150,7 +150,7 @@ pub fn total_supply() -> EntryPoint { U256::cl_type(), EntryPointAccess::Public, EntryPointType::Called, - casper_types::EntryPointPayment::Caller + casper_types::EntryPointPayment::Caller, ) } @@ -162,7 +162,7 @@ pub fn decimals() -> EntryPoint { u8::cl_type(), EntryPointAccess::Public, EntryPointType::Called, - casper_types::EntryPointPayment::Caller + casper_types::EntryPointPayment::Caller, ) } @@ -177,7 +177,7 @@ pub fn burn() -> EntryPoint { CLType::Unit, EntryPointAccess::Public, EntryPointType::Called, - casper_types::EntryPointPayment::Caller + casper_types::EntryPointPayment::Caller, ) } @@ -192,7 +192,7 @@ pub fn mint() -> EntryPoint { CLType::Unit, EntryPointAccess::Public, EntryPointType::Called, - casper_types::EntryPointPayment::Caller + casper_types::EntryPointPayment::Caller, ) } @@ -213,7 +213,7 @@ pub fn change_security() -> EntryPoint { CLType::Unit, EntryPointAccess::Public, EntryPointType::Called, - casper_types::EntryPointPayment::Caller + casper_types::EntryPointPayment::Caller, ) } @@ -225,7 +225,7 @@ pub fn init() -> EntryPoint { CLType::Unit, EntryPointAccess::Public, EntryPointType::Called, - casper_types::EntryPointPayment::Caller + casper_types::EntryPointPayment::Caller, ) } diff --git a/cep18/src/error.rs b/cep18/src/error.rs index 0199dcc..24f08ef 100644 --- a/cep18/src/error.rs +++ b/cep18/src/error.rs @@ -49,6 +49,8 @@ pub enum Cep18Error { InvalidBurnTarget = 60018, MissingPackageHashForUpgrade = 60019, MissingContractHashForUpgrade = 60020, + WrongKeyType = 60021, + KeyTypeMigrationMismatch = 60022, } impl From for ApiError { diff --git a/cep18/src/events.rs b/cep18/src/events.rs index e44e9e2..a987f9d 100644 --- a/cep18/src/events.rs +++ b/cep18/src/events.rs @@ -1,6 +1,6 @@ use core::convert::TryFrom; -use alloc::{collections::BTreeMap, format}; +use alloc::{collections::BTreeMap, format, string::String, vec::Vec}; use casper_contract::{contract_api::runtime, unwrap_or_revert::UnwrapOrRevert}; use casper_types::{Key, U256}; @@ -22,7 +22,7 @@ pub fn record_event_dictionary(event: Event) { EventsMode::Native => { runtime::emit_message(EVENTS, &format!("{event:?}").into()).unwrap_or_revert() } - EventsMode::NativeNCES =>{ + EventsMode::NativeNCES => { runtime::emit_message(EVENTS, &format!("{event:?}").into()).unwrap_or_revert(); ces(event); } @@ -39,6 +39,8 @@ pub enum Event { Transfer(Transfer), TransferFrom(TransferFrom), ChangeSecurity(ChangeSecurity), + BalanceMigration(BalanceMigration), + AllowanceMigration(AllowanceMigration), } #[derive(Event, Debug, PartialEq, Eq)] @@ -97,6 +99,25 @@ pub struct ChangeSecurity { pub sec_change_map: BTreeMap, } +/// `success_list` -> Vec<(Key,Key)> where the tuple is the pair of old_key and new_key. +/// `failure_map` -> BTreeMap where the key is the provided old_key, and the String +/// value is the failure reason, while the String is the failure reason. +#[derive(Event, Debug, PartialEq, Eq)] +pub struct BalanceMigration { + pub success_map: Vec<(Key, Key)>, + pub failure_map: BTreeMap, +} + +/// `success_list` -> Vec<(Key,Key)> where one tuple is the pair of old_key and new_key for the +/// spender and another is the same for owner. +/// `failure_map` -> BTreeMap<(Key,Key), String> where the Key tuples is the pair of old_spender_key +/// and old_owner_key, while the String is the failure reason. +#[derive(Event, Debug, PartialEq, Eq)] +pub struct AllowanceMigration { + pub success_map: Vec<((Key, Key), (Key, Key))>, + pub failure_map: BTreeMap<(Key, Option), String>, +} + fn ces(event: Event) { match event { Event::Mint(ev) => emit(ev), @@ -107,6 +128,8 @@ fn ces(event: Event) { Event::Transfer(ev) => emit(ev), Event::TransferFrom(ev) => emit(ev), Event::ChangeSecurity(ev) => emit(ev), + Event::BalanceMigration(ev) => emit(ev), + Event::AllowanceMigration(ev) => emit(ev), } } @@ -123,7 +146,9 @@ pub fn init_events() { .with::() .with::() .with::() - .with::(); + .with::() + .with::() + .with::(); casper_event_standard::init(schemas); } } diff --git a/cep18/src/main.rs b/cep18/src/main.rs index 1d18fa3..ff0ff96 100644 --- a/cep18/src/main.rs +++ b/cep18/src/main.rs @@ -26,28 +26,25 @@ use entry_points::generate_entry_points; use casper_contract::{ contract_api::{ - runtime::{self, get_call_stack, get_caller, get_key, get_named_arg, put_key, revert}, + runtime::{self, get_caller, get_key, get_named_arg, put_key, revert}, storage::{self, dictionary_put}, }, unwrap_or_revert::UnwrapOrRevert, }; use casper_types::{ - addressable_entity::{EntityKindTag, NamedKeys}, - bytesrepr::ToBytes, - contract_messages::MessageTopicOperation, - contracts::{ContractHash, ContractPackageHash}, - runtime_args, CLValue, Key, PackageHash, U256, + addressable_entity::{EntityKindTag, NamedKeys}, bytesrepr::ToBytes, contract_messages::MessageTopicOperation, runtime_args, AddressableEntityHash, CLValue, EntityAddr, Key, PackageHash, U256 }; use constants::{ - ACCESS_KEY_NAME_PREFIX, ADDRESS, ADMIN_LIST, ALLOWANCES, AMOUNT, BALANCES, + ACCESS_KEY_NAME_PREFIX, ADDRESS, ADMIN_LIST, ALLOWANCES, AMOUNT, BALANCES, CONTRACT_HASH, CONTRACT_NAME_PREFIX, CONTRACT_VERSION_PREFIX, DECIMALS, ENABLE_MINT_BURN, EVENTS, EVENTS_MODE, HASH_KEY_NAME_PREFIX, INIT_ENTRY_POINT_NAME, MINTER_LIST, NAME, NONE_LIST, OWNER, PACKAGE_HASH, - RECIPIENT, SECURITY_BADGES, SPENDER, SYMBOL, TOTAL_SUPPLY, + RECIPIENT, REVERT, SECURITY_BADGES, SPENDER, SYMBOL, TOTAL_SUPPLY, USER_KEY_MAP, }; pub use error::Cep18Error; use events::{ - init_events, Burn, ChangeSecurity, DecreaseAllowance, Event, IncreaseAllowance, Mint, SetAllowance, Transfer, TransferFrom + init_events, AllowanceMigration, BalanceMigration, Burn, ChangeSecurity, DecreaseAllowance, + Event, IncreaseAllowance, Mint, SetAllowance, Transfer, TransferFrom, }; use modalities::EventsMode; use utils::{ @@ -274,18 +271,20 @@ pub extern "C" fn init() { } let package_hash = get_named_arg::(PACKAGE_HASH); put_key(PACKAGE_HASH, package_hash); + let contract_hash = get_named_arg::(CONTRACT_HASH); put_key(CONTRACT_HASH, contract_hash); + storage::new_dictionary(ALLOWANCES).unwrap_or_revert(); let balances_uref = storage::new_dictionary(BALANCES).unwrap_or_revert(); let initial_supply = runtime::get_named_arg(TOTAL_SUPPLY); let caller = get_caller(); - write_balance_to(balances_uref, caller.into(), initial_supply); + write_balance_to(balances_uref, Key::AddressableEntity(EntityAddr::Account(caller.value())), initial_supply); let security_badges_dict = storage::new_dictionary(SECURITY_BADGES).unwrap_or_revert(); dictionary_put( security_badges_dict, - &base64::encode(Key::from(get_caller()).to_bytes().unwrap_or_revert()), + &base64::encode(Key::AddressableEntity(EntityAddr::Account(caller.value())).to_bytes().unwrap_or_revert()), SecurityBadge::Admin, ); @@ -297,7 +296,7 @@ pub extern "C" fn init() { let events_mode: EventsMode = EventsMode::try_from(read_from::(EVENTS_MODE)).unwrap_or_revert(); - if [EventsMode::CES, EventsMode::NativeNCES].contains(&events_mode){ + if [EventsMode::CES, EventsMode::NativeNCES].contains(&events_mode) { init_events(); } @@ -320,7 +319,7 @@ pub extern "C" fn init() { } } events::record_event_dictionary(Event::Mint(Mint { - recipient: caller.into(), + recipient: Key::AddressableEntity(EntityAddr::Account(caller.value())), amount: initial_supply, })) } @@ -371,19 +370,222 @@ pub extern "C" fn change_security() { })); } +/// Entrypoint to migrate user and contract keys regarding balances provided as argument from 1.x to +/// 2.x key/hash storage version. Argument is a single BTreeMap. The key is the already +/// stored key in the system (previously Key::Hash or Key::Account), while the bool value is the +/// verification for the key's flag (true for account, false for contract). +/// Going forward these will be stored are Key::AddressableEntity(EntityAddr::Account) +/// and Key::AddressableEntity(EntityAddr::SmartContract) respectively. +#[no_mangle] +pub fn migrate_users_balance_keys() { + let event_on: bool = get_named_arg(EVENTS); + let revert_on: bool = get_named_arg(REVERT); + let mut success_map: Vec<(Key, Key)> = Vec::new(); + let mut failure_map: BTreeMap = BTreeMap::new(); + + let keys: BTreeMap = get_named_arg(USER_KEY_MAP); + let balances_uref = get_balances_uref(); + for (old_key, flag) in keys { + let migrated_key = match old_key { + Key::Account(account_hash) => { + if !flag { + if event_on { + failure_map.insert(old_key, String::from("FlagMismatch")); + } else if revert_on { + revert(Cep18Error::KeyTypeMigrationMismatch) + } + continue; + } + Key::AddressableEntity(EntityAddr::Account(account_hash.value())) + } + Key::Hash(contract_package) => { + if flag { + if event_on { + failure_map.insert(old_key, String::from("FlagMismatch")); + } else if revert_on { + revert(Cep18Error::KeyTypeMigrationMismatch) + } + continue; + } + Key::AddressableEntity(EntityAddr::SmartContract(contract_package)) + } + _ => { + if event_on { + failure_map.insert(old_key, String::from("WrongKeyType")); + } else if revert_on { + revert(Cep18Error::WrongKeyType) + } + continue; + } + }; + let balance = read_balance_from(balances_uref, old_key); + if balance > U256::zero() { + write_balance_to(balances_uref, old_key, U256::zero()); + write_balance_to(balances_uref, migrated_key, balance) + } else if event_on { + failure_map.insert(old_key, String::from("NoOldKeyBal")); + } else if revert_on { + revert(Cep18Error::InsufficientBalance) + } + success_map.push((old_key, migrated_key)); + } + + if event_on { + events::record_event_dictionary(Event::BalanceMigration(BalanceMigration { + success_map, + failure_map, + })); + } +} + +/// Entrypoint to migrate users' and contracts' keys regarding allowances provided as argument from +/// 1.x to 2.x key/hash storage version. Argument is a single BTreeMap>. The key is +/// the already stored key in the system (previously Key::Hash or Key::Account), while the Vec +/// is a list of allowance keys, whose owners have already been migrated. Going forward these will +/// be stored are Key::AddressableEntity(EntityAddr::Account) +/// and Key::AddressableEntity(EntityAddr::SmartContract) respectively. +#[no_mangle] +pub fn migrate_users_allowance_keys() { + let event_on: bool = get_named_arg(EVENTS); + let revert_on: bool = get_named_arg(REVERT); + let mut success_map: Vec<((Key, Key), (Key, Key))> = Vec::new(); + let mut failure_map: BTreeMap<(Key, Option), String> = BTreeMap::new(); + + let keys: BTreeMap<(Key, bool), Vec<(Key, bool)>> = get_named_arg(USER_KEY_MAP); + let allowances_uref = get_allowances_uref(); + for ((spender_key, spender_flag), allowance_owner_keys) in keys { + let migrated_spender_key = match spender_key { + Key::Account(account_hash) => { + if !spender_flag { + if event_on { + failure_map + .insert((spender_key, None), String::from("SpenderFlagMismatch")); + continue; + } else if revert_on { + revert(Cep18Error::KeyTypeMigrationMismatch) + } + } + Key::AddressableEntity(EntityAddr::Account(account_hash.value())) + } + Key::Hash(contract_package) => { + if spender_flag { + if event_on { + failure_map + .insert((spender_key, None), String::from("SpenderFlagMismatch")); + continue; + } else if revert_on { + revert(Cep18Error::KeyTypeMigrationMismatch) + } + } + Key::AddressableEntity(EntityAddr::SmartContract(contract_package)) + } + _ => { + if event_on { + failure_map.insert((spender_key, None), String::from("SpenderWrongKeyType")); + } else if revert_on { + revert(Cep18Error::WrongKeyType) + } + continue; + } + }; + for (owner_key, owner_flag) in allowance_owner_keys { + let migrated_owner_key = match owner_key { + Key::Account(account_hash) => { + if !owner_flag { + if event_on { + failure_map.insert( + (spender_key, Some(owner_key)), + String::from("OwnerFlagMismatch"), + ); + } else if revert_on { + revert(Cep18Error::KeyTypeMigrationMismatch) + } + continue; + } + Key::AddressableEntity(EntityAddr::Account(account_hash.value())) + } + Key::Hash(contract_package) => { + if owner_flag { + if event_on { + failure_map.insert( + (spender_key, Some(owner_key)), + String::from("OwnerFlagMismatch"), + ); + continue; + } else if revert_on { + revert(Cep18Error::KeyTypeMigrationMismatch) + } + } + Key::AddressableEntity(EntityAddr::SmartContract(contract_package)) + } + _ => { + if event_on { + failure_map.insert( + (spender_key, Some(owner_key)), + String::from("OwnerWrongKeyType"), + ); + } else if revert_on { + revert(Cep18Error::WrongKeyType) + } + continue; + } + }; + let allowance = + read_allowance_from(allowances_uref, migrated_owner_key, migrated_spender_key); + if allowance > U256::zero() { + write_allowance_to( + allowances_uref, + migrated_owner_key, + migrated_spender_key, + U256::zero(), + ); + write_allowance_to( + allowances_uref, + migrated_owner_key, + migrated_spender_key, + allowance, + ) + } else if event_on { + failure_map.insert( + (spender_key, Some(owner_key)), + String::from("NoOldKeyAllowance"), + ); + } else if revert_on { + revert(Cep18Error::InsufficientAllowance) + } + success_map.push(( + (spender_key, migrated_spender_key), + (owner_key, migrated_owner_key), + )); + } + } + if event_on { + events::record_event_dictionary(Event::AllowanceMigration(AllowanceMigration { + success_map, + failure_map, + })); + } +} + pub fn upgrade(name: &str) { let entry_points = generate_entry_points(); - let old_contract_package_hash = runtime::get_key(&format!("{HASH_KEY_NAME_PREFIX}{name}")) - .unwrap_or_revert() - .into_entity_hash() - .unwrap_or_revert_with(Cep18Error::MissingPackageHashForUpgrade); - let contract_package_hash = PackageHash::new(old_contract_package_hash.value()); + let old_contract_package_hash = match runtime::get_key(&format!("{HASH_KEY_NAME_PREFIX}{name}")) + .unwrap_or_revert(){ + Key::Hash(contract_hash) => contract_hash, + Key::AddressableEntity(EntityAddr::SmartContract(contract_hash)) => contract_hash, + Key::Package(package_hash) => package_hash, + _=>revert(Cep18Error::MissingPackageHashForUpgrade) + }; + let contract_package_hash = PackageHash::new(old_contract_package_hash); - let previous_contract_hash = runtime::get_key(&format!("{CONTRACT_NAME_PREFIX}{name}")) - .unwrap_or_revert() - .into_entity_hash() - .unwrap_or_revert_with(Cep18Error::MissingContractHashForUpgrade); + let previous_contract_hash = match runtime::get_key(&format!("{CONTRACT_NAME_PREFIX}{name}")) + .unwrap_or_revert(){ + Key::Hash(contract_hash) => contract_hash, + Key::AddressableEntity(EntityAddr::SmartContract(contract_hash)) => contract_hash, + _=>revert(Cep18Error::MissingContractHashForUpgrade) + }; + let converted_previous_contract_hash = AddressableEntityHash::new(previous_contract_hash); let events_mode: EventsMode = EventsMode::try_from( utils::get_optional_named_arg_with_user_errors(EVENTS_MODE, Cep18Error::InvalidEventsMode) @@ -410,7 +612,7 @@ pub fn upgrade(name: &str) { message_topics, ); - storage::disable_contract_version(contract_package_hash, previous_contract_hash) + storage::disable_contract_version(contract_package_hash, converted_previous_contract_hash) .unwrap_or_revert(); // migrate old ContractPackageHash as PackageHash so it's stored in a uniform format with the diff --git a/cep18/src/utils.rs b/cep18/src/utils.rs index 4d94699..6f5232d 100644 --- a/cep18/src/utils.rs +++ b/cep18/src/utils.rs @@ -15,8 +15,7 @@ use casper_types::{ api_error, bytesrepr::{self, FromBytes, ToBytes}, system::Caller, - system::Caller, - ApiError, CLTyped, Key, URef, U256, + ApiError, CLTyped, EntityAddr, Key, URef, U256, }; use crate::{ @@ -46,17 +45,21 @@ where /// /// For `Session` and `StoredSession` variants it will return account hash, and for `StoredContract` /// case it will use contract package hash as the address. -fn call_stack_element_to_address(call_stack_element: Caller) -> Key { fn call_stack_element_to_address(call_stack_element: Caller) -> Key { match call_stack_element { - Caller::Initiator { account_hash } => Key::from(account_hash), - Caller::Entity { package_hash, .. } => Key::from(package_hash), + Caller::Initiator { account_hash } => { + Key::AddressableEntity(EntityAddr::Account(account_hash.value())) + } + Caller::Entity { package_hash, .. } => { + Key::AddressableEntity(EntityAddr::SmartContract(package_hash.value())) + } } } /// Gets the immediate session caller of the current execution. /// -/// This function ensures that Contracts can participate and no middleman (contract) acts for users. +/// This function ensures that Contracts can participate and no middleman (contract) acts for +/// users. pub(crate) fn get_immediate_caller_address() -> Result { let call_stack = runtime::get_call_stack(); call_stack diff --git a/tests/src/allowance.rs b/tests/src/allowance.rs index db5364b..803e045 100644 --- a/tests/src/allowance.rs +++ b/tests/src/allowance.rs @@ -1,5 +1,7 @@ use casper_engine_test_support::{ExecuteRequestBuilder, DEFAULT_ACCOUNT_ADDR}; -use casper_types::{addressable_entity::EntityKindTag, runtime_args, AddressableEntityHash, ApiError, Key, U256}; +use casper_types::{ + addressable_entity::EntityKindTag, runtime_args, AddressableEntityHash, ApiError, EntityAddr, Key, U256 +}; use crate::utility::{ constants::{ @@ -12,7 +14,6 @@ use crate::utility::{ }, }; use casper_execution_engine::{engine_state::Error as CoreError, execution::ExecError}; -use casper_execution_engine::{engine_state::Error as CoreError, execution::ExecError}; #[test] fn should_approve_funds_contract_to_account() { @@ -25,9 +26,15 @@ fn should_approve_funds_contract_to_account() { test_approve_for( &mut builder, &test_context, - Key::addressable_entity_key(EntityKindTag::SmartContract, AddressableEntityHash::new(cep18_test_contract_package.value())), - Key::addressable_entity_key(EntityKindTag::SmartContract, AddressableEntityHash::new(cep18_test_contract_package.value())), - Key::Account(*DEFAULT_ACCOUNT_ADDR), + Key::addressable_entity_key( + EntityKindTag::SmartContract, + AddressableEntityHash::new(cep18_test_contract_package.value()), + ), + Key::addressable_entity_key( + EntityKindTag::SmartContract, + AddressableEntityHash::new(cep18_test_contract_package.value()), + ), + Key::AddressableEntity(EntityAddr::Account(DEFAULT_ACCOUNT_ADDR.value())), ); } @@ -42,9 +49,9 @@ fn should_approve_funds_contract_to_contract() { test_approve_for( &mut builder, &test_context, - Key::Hash(cep18_test_contract_package.value()), - Key::Hash(cep18_test_contract_package.value()), - Key::Hash([42; 32]), + Key::AddressableEntity(EntityAddr::SmartContract(cep18_test_contract_package.value())), + Key::AddressableEntity(EntityAddr::SmartContract(cep18_test_contract_package.value())), + Key::AddressableEntity(EntityAddr::SmartContract([42; 32])), ); } @@ -55,9 +62,9 @@ fn should_approve_funds_account_to_account() { test_approve_for( &mut builder, &test_context, - Key::Account(*DEFAULT_ACCOUNT_ADDR), - Key::Account(*DEFAULT_ACCOUNT_ADDR), - Key::Account(*ACCOUNT_1_ADDR), + Key::AddressableEntity(EntityAddr::Account(DEFAULT_ACCOUNT_ADDR.value())), + Key::AddressableEntity(EntityAddr::Account(DEFAULT_ACCOUNT_ADDR.value())), + Key::AddressableEntity(EntityAddr::Account(ACCOUNT_1_ADDR.value())), ); } @@ -67,9 +74,9 @@ fn should_approve_funds_account_to_contract() { test_approve_for( &mut builder, &test_context, - Key::Account(*DEFAULT_ACCOUNT_ADDR), - Key::Account(*DEFAULT_ACCOUNT_ADDR), - Key::Hash([42; 32]), + Key::AddressableEntity(EntityAddr::Account(DEFAULT_ACCOUNT_ADDR.value())), + Key::AddressableEntity(EntityAddr::Account(DEFAULT_ACCOUNT_ADDR.value())), + Key::AddressableEntity(EntityAddr::SmartContract([42; 32])), ); } @@ -79,8 +86,6 @@ fn should_not_transfer_from_without_enough_allowance() { let addressable_cep18_token = AddressableEntityHash::new(cep18_token.value()); - let addressable_cep18_token = AddressableEntityHash::new(cep18_token.value()); - let allowance_amount_1 = U256::from(ALLOWANCE_AMOUNT_1); let transfer_from_amount_1 = allowance_amount_1 + U256::one(); @@ -89,24 +94,23 @@ fn should_not_transfer_from_without_enough_allowance() { let recipient = *ACCOUNT_1_ADDR; let cep18_approve_args = runtime_args! { - ARG_OWNER => Key::Account(owner), - ARG_SPENDER => Key::Account(recipient), + ARG_OWNER => Key::AddressableEntity(EntityAddr::Account(owner.value())), + ARG_SPENDER => Key::AddressableEntity(EntityAddr::Account(recipient.value())), ARG_AMOUNT => allowance_amount_1, }; let cep18_transfer_from_args = runtime_args! { - ARG_OWNER => Key::Account(owner), - ARG_RECIPIENT => Key::Account(recipient), + ARG_OWNER => Key::AddressableEntity(EntityAddr::Account(owner.value())), + ARG_RECIPIENT => Key::AddressableEntity(EntityAddr::Account(recipient.value())), ARG_AMOUNT => transfer_from_amount_1, }; let spender_allowance_before = - cep18_check_allowance_of(&mut builder, Key::Account(owner), Key::Account(recipient)); + cep18_check_allowance_of(&mut builder, Key::AddressableEntity(EntityAddr::Account(owner.value())), Key::AddressableEntity(EntityAddr::Account(recipient.value()))); assert_eq!(spender_allowance_before, U256::zero()); let approve_request_1 = ExecuteRequestBuilder::contract_call_by_hash( sender, addressable_cep18_token, - addressable_cep18_token, METHOD_APPROVE, cep18_approve_args, ) @@ -115,7 +119,6 @@ fn should_not_transfer_from_without_enough_allowance() { let transfer_from_request_1 = ExecuteRequestBuilder::contract_call_by_hash( sender, addressable_cep18_token, - addressable_cep18_token, METHOD_TRANSFER_FROM, cep18_transfer_from_args, ) @@ -124,7 +127,7 @@ fn should_not_transfer_from_without_enough_allowance() { builder.exec(approve_request_1).expect_success().commit(); let account_1_allowance_after = - cep18_check_allowance_of(&mut builder, Key::Account(owner), Key::Account(recipient)); + cep18_check_allowance_of(&mut builder, Key::AddressableEntity(EntityAddr::Account(owner.value())), Key::AddressableEntity(EntityAddr::Account(recipient.value()))); assert_eq!(account_1_allowance_after, allowance_amount_1); builder.exec(transfer_from_request_1).commit(); @@ -143,23 +146,22 @@ fn test_decrease_allowance() { let addressable_cep18_token = AddressableEntityHash::new(cep18_token.value()); - - let addressable_cep18_token = AddressableEntityHash::new(cep18_token.value()); - - let sender = Key::Account(*DEFAULT_ACCOUNT_ADDR); - let owner = Key::Account(*DEFAULT_ACCOUNT_ADDR); + let owner = *DEFAULT_ACCOUNT_ADDR; let spender = Key::Hash([42; 32]); + + let owner_key = Key::AddressableEntity(EntityAddr::Account(owner.value())); + let spender_key = Key::AddressableEntity(EntityAddr::SmartContract([42; 32])); + let allowance_amount_1 = U256::from(ALLOWANCE_AMOUNT_1); let allowance_amount_2 = U256::from(ALLOWANCE_AMOUNT_2); - let spender_allowance_before = cep18_check_allowance_of(&mut builder, owner, spender); + let spender_allowance_before = cep18_check_allowance_of(&mut builder, owner_key, spender_key); assert_eq!(spender_allowance_before, U256::zero()); let approve_request = - make_cep18_approve_request(sender, &cep18_token, spender, allowance_amount_1); + make_cep18_approve_request(owner_key, &cep18_token, spender, allowance_amount_1); let decrease_allowance_request = ExecuteRequestBuilder::contract_call_by_hash( - sender.into_account().unwrap(), - addressable_cep18_token, + owner, addressable_cep18_token, DECREASE_ALLOWANCE, runtime_args! { @@ -169,8 +171,7 @@ fn test_decrease_allowance() { ) .build(); let increase_allowance_request = ExecuteRequestBuilder::contract_call_by_hash( - sender.into_account().unwrap(), - addressable_cep18_token, + owner, addressable_cep18_token, INCREASE_ALLOWANCE, runtime_args! { @@ -182,7 +183,7 @@ fn test_decrease_allowance() { builder.exec(approve_request).expect_success().commit(); - let account_1_allowance_after = cep18_check_allowance_of(&mut builder, owner, spender); + let account_1_allowance_after = cep18_check_allowance_of(&mut builder, owner_key, spender); assert_eq!(account_1_allowance_after, allowance_amount_1); @@ -191,7 +192,7 @@ fn test_decrease_allowance() { .expect_success() .commit(); - let account_1_allowance_after_decrease = cep18_check_allowance_of(&mut builder, owner, spender); + let account_1_allowance_after_decrease = cep18_check_allowance_of(&mut builder, owner_key, spender); assert_eq!( account_1_allowance_after_decrease, @@ -203,7 +204,7 @@ fn test_decrease_allowance() { .expect_success() .commit(); - let account_1_allowance_after_increase = cep18_check_allowance_of(&mut builder, owner, spender); + let account_1_allowance_after_increase = cep18_check_allowance_of(&mut builder, owner_key, spender); assert_eq!( account_1_allowance_after_increase, diff --git a/tests/src/install.rs b/tests/src/install.rs index 520bc33..71f066a 100644 --- a/tests/src/install.rs +++ b/tests/src/install.rs @@ -29,7 +29,7 @@ fn should_have_queryable_properties() { let total_supply: U256 = builder.get_value(cep18_entity_addr, TOTAL_SUPPLY_KEY); assert_eq!(total_supply, U256::from(TOKEN_TOTAL_SUPPLY)); - let owner_key = Key::Account(*DEFAULT_ACCOUNT_ADDR); + let owner_key = Key::AddressableEntity(EntityAddr::Account(DEFAULT_ACCOUNT_ADDR.value())); let owner_balance = cep18_check_balance_of(&mut builder, &cep18_token, owner_key); assert_eq!(owner_balance, total_supply); diff --git a/tests/src/mint_and_burn.rs b/tests/src/mint_and_burn.rs index 0bdc54a..f41c1d9 100644 --- a/tests/src/mint_and_burn.rs +++ b/tests/src/mint_and_burn.rs @@ -1,5 +1,5 @@ use casper_engine_test_support::{ExecuteRequestBuilder, DEFAULT_ACCOUNT_ADDR}; -use casper_types::{runtime_args, AddressableEntityHash, ApiError, Key, U256}; +use casper_types::{runtime_args, AddressableEntityHash, ApiError, EntityAddr, Key, U256}; use crate::utility::{ constants::{ @@ -48,7 +48,7 @@ fn test_mint_and_burn_tokens() { cep18_check_balance_of( &mut builder, &cep18_token, - Key::Account(*DEFAULT_ACCOUNT_ADDR) + Key::AddressableEntity(casper_types::EntityAddr::Account(DEFAULT_ACCOUNT_ADDR.value())) ), U256::from(TOKEN_TOTAL_SUPPLY), ); @@ -96,7 +96,7 @@ fn test_mint_and_burn_tokens() { addressable_cep18_token, METHOD_BURN, runtime_args! { - ARG_OWNER => Key::Account(*DEFAULT_ACCOUNT_ADDR), + ARG_OWNER => Key::AddressableEntity(casper_types::EntityAddr::Account(DEFAULT_ACCOUNT_ADDR.value())), ARG_AMOUNT => mint_amount, }, ) @@ -108,7 +108,7 @@ fn test_mint_and_burn_tokens() { cep18_check_balance_of( &mut builder, &cep18_token, - Key::Account(*DEFAULT_ACCOUNT_ADDR) + Key::AddressableEntity(casper_types::EntityAddr::Account(DEFAULT_ACCOUNT_ADDR.value())) ), U256::from(999999999), ); @@ -196,7 +196,7 @@ fn test_should_not_burn_above_balance() { addressable_cep18_token, METHOD_BURN, runtime_args! { - ARG_OWNER => Key::Account(*DEFAULT_ACCOUNT_ADDR), + ARG_OWNER => Key::AddressableEntity(casper_types::EntityAddr::Account(DEFAULT_ACCOUNT_ADDR.value())), ARG_AMOUNT => U256::from(TOKEN_TOTAL_SUPPLY)+1, }, ) @@ -284,7 +284,7 @@ fn test_security_no_rights() { addressable_cep18_token, METHOD_MINT, runtime_args! { - ARG_OWNER => Key::Account(*ACCOUNT_1_ADDR), + ARG_OWNER => Key::AddressableEntity(casper_types::EntityAddr::Account(ACCOUNT_1_ADDR.value())), ARG_AMOUNT => mint_amount, }, ) @@ -304,7 +304,7 @@ fn test_security_no_rights() { addressable_cep18_token, METHOD_MINT, runtime_args! { - ARG_OWNER => Key::Account(*ACCOUNT_1_ADDR), + ARG_OWNER => Key::AddressableEntity(EntityAddr::Account(ACCOUNT_1_ADDR.value())), ARG_AMOUNT => mint_amount, }, ) @@ -320,7 +320,7 @@ fn test_security_no_rights() { addressable_cep18_token, METHOD_BURN, runtime_args! { - ARG_OWNER => Key::Account(*ACCOUNT_1_ADDR), + ARG_OWNER => Key::AddressableEntity(EntityAddr::Account(ACCOUNT_1_ADDR.value())), ARG_AMOUNT => mint_amount, }, ) @@ -339,7 +339,7 @@ fn test_security_minter_rights() { ARG_DECIMALS => TOKEN_DECIMALS, ARG_TOTAL_SUPPLY => U256::from(TOKEN_TOTAL_SUPPLY), ENABLE_MINT_BURN => true, - MINTER_LIST => vec![Key::Account(*ACCOUNT_1_ADDR)] + MINTER_LIST => vec![Key::AddressableEntity(casper_types::EntityAddr::Account(ACCOUNT_1_ADDR.value()))] }); let addressable_cep18_token = AddressableEntityHash::new(cep18_token.value()); @@ -396,7 +396,7 @@ fn test_security_burner_rights() { addressable_cep18_token, METHOD_MINT, runtime_args! { - ARG_OWNER => Key::Account(*DEFAULT_ACCOUNT_ADDR), + ARG_OWNER => Key::AddressableEntity(casper_types::EntityAddr::Account(DEFAULT_ACCOUNT_ADDR.value())), ARG_AMOUNT => mint_amount, }, ) @@ -410,7 +410,7 @@ fn test_security_burner_rights() { addressable_cep18_token, METHOD_BURN, runtime_args! { - ARG_OWNER => Key::Account(*DEFAULT_ACCOUNT_ADDR), + ARG_OWNER => Key::AddressableEntity(casper_types::EntityAddr::Account(DEFAULT_ACCOUNT_ADDR.value())), ARG_AMOUNT => mint_amount, }, ) @@ -429,7 +429,7 @@ fn test_change_security() { ARG_DECIMALS => TOKEN_DECIMALS, ARG_TOTAL_SUPPLY => U256::from(TOKEN_TOTAL_SUPPLY), ENABLE_MINT_BURN => true, - ADMIN_LIST => vec![Key::Account(*ACCOUNT_1_ADDR)] + ADMIN_LIST => vec![Key::AddressableEntity(casper_types::EntityAddr::Account(ACCOUNT_1_ADDR.value()))] }); let addressable_cep18_token = AddressableEntityHash::new(cep18_token.value()); @@ -438,7 +438,7 @@ fn test_change_security() { addressable_cep18_token, CHANGE_SECURITY, runtime_args! { - NONE_LIST => vec![Key::Account(*DEFAULT_ACCOUNT_ADDR)], + NONE_LIST => vec![Key::AddressableEntity(casper_types::EntityAddr::Account(DEFAULT_ACCOUNT_ADDR.value()))], }, ) .build(); diff --git a/tests/src/transfer.rs b/tests/src/transfer.rs index ff1a594..142d74c 100644 --- a/tests/src/transfer.rs +++ b/tests/src/transfer.rs @@ -29,19 +29,19 @@ fn should_transfer_full_owned_amount() { let transfer_1_sender = *DEFAULT_ACCOUNT_ADDR; let cep18_transfer_1_args = runtime_args! { - ARG_RECIPIENT => Key::Account(*ACCOUNT_1_ADDR), + ARG_RECIPIENT => Key::AddressableEntity(EntityAddr::Account(ACCOUNT_1_ADDR.value())), ARG_AMOUNT => transfer_amount_1, }; let owner_balance_before = cep18_check_balance_of( &mut builder, &cep18_token, - Key::Account(*DEFAULT_ACCOUNT_ADDR), + Key::AddressableEntity(EntityAddr::Account(DEFAULT_ACCOUNT_ADDR.value())), ); assert_eq!(owner_balance_before, initial_supply); let account_1_balance_before = - cep18_check_balance_of(&mut builder, &cep18_token, Key::Account(*ACCOUNT_1_ADDR)); + cep18_check_balance_of(&mut builder, &cep18_token, Key::AddressableEntity(EntityAddr::Account(ACCOUNT_1_ADDR.value()))); assert_eq!(account_1_balance_before, U256::zero()); let token_transfer_request_1 = ExecuteRequestBuilder::contract_call_by_hash( @@ -58,13 +58,13 @@ fn should_transfer_full_owned_amount() { .commit(); let account_1_balance_after = - cep18_check_balance_of(&mut builder, &cep18_token, Key::Account(*ACCOUNT_1_ADDR)); + cep18_check_balance_of(&mut builder, &cep18_token, Key::AddressableEntity(EntityAddr::Account(ACCOUNT_1_ADDR.value()))); assert_eq!(account_1_balance_after, transfer_amount_1); let owner_balance_after = cep18_check_balance_of( &mut builder, &cep18_token, - Key::Account(*DEFAULT_ACCOUNT_ADDR), + Key::AddressableEntity(EntityAddr::Account(DEFAULT_ACCOUNT_ADDR.value())), ); assert_eq!(owner_balance_after, U256::zero()); @@ -88,20 +88,20 @@ fn should_not_transfer_more_than_owned_balance() { let transfer_1_recipient = *ACCOUNT_1_ADDR; let cep18_transfer_1_args = runtime_args! { - ARG_RECIPIENT => Key::Account(transfer_1_recipient), + ARG_RECIPIENT => Key::AddressableEntity(EntityAddr::Account(transfer_1_recipient.value())), ARG_AMOUNT => transfer_amount, }; let owner_balance_before = cep18_check_balance_of( &mut builder, &cep18_token, - Key::Account(*DEFAULT_ACCOUNT_ADDR), + Key::AddressableEntity(EntityAddr::Account(DEFAULT_ACCOUNT_ADDR.value())), ); assert_eq!(owner_balance_before, initial_supply); assert!(transfer_amount > owner_balance_before); let account_1_balance_before = - cep18_check_balance_of(&mut builder, &cep18_token, Key::Account(*ACCOUNT_1_ADDR)); + cep18_check_balance_of(&mut builder, &cep18_token, Key::AddressableEntity(EntityAddr::Account(ACCOUNT_1_ADDR.value()))); assert_eq!(account_1_balance_before, U256::zero()); let token_transfer_request_1 = ExecuteRequestBuilder::contract_call_by_hash( @@ -124,12 +124,12 @@ fn should_not_transfer_more_than_owned_balance() { let account_1_balance_after = cep18_check_balance_of( &mut builder, &cep18_token, - Key::Account(transfer_1_recipient), + Key::AddressableEntity(EntityAddr::Account(transfer_1_recipient.value())), ); assert_eq!(account_1_balance_after, account_1_balance_before); let owner_balance_after = - cep18_check_balance_of(&mut builder, &cep18_token, Key::Account(transfer_1_sender)); + cep18_check_balance_of(&mut builder, &cep18_token, Key::AddressableEntity(EntityAddr::Account(transfer_1_sender.value()))); assert_eq!(owner_balance_after, initial_supply); let total_supply: U256 = builder.get_value( @@ -153,18 +153,18 @@ fn should_transfer_from_from_account_to_account() { let spender = *ACCOUNT_1_ADDR; let cep18_approve_args = runtime_args! { - ARG_OWNER => Key::Account(owner), - ARG_SPENDER => Key::Account(spender), + ARG_OWNER => Key::AddressableEntity(EntityAddr::Account(owner.value())), + ARG_SPENDER => Key::AddressableEntity(EntityAddr::Account(spender.value())), ARG_AMOUNT => allowance_amount_1, }; let cep18_transfer_from_args = runtime_args! { - ARG_OWNER => Key::Account(owner), - ARG_RECIPIENT => Key::Account(spender), + ARG_OWNER => Key::AddressableEntity(EntityAddr::Account(owner.value())), + ARG_RECIPIENT => Key::AddressableEntity(EntityAddr::Account(spender.value())), ARG_AMOUNT => transfer_from_amount_1, }; let spender_allowance_before = - cep18_check_allowance_of(&mut builder, Key::Account(owner), Key::Account(spender)); + cep18_check_allowance_of(&mut builder, Key::AddressableEntity(EntityAddr::Account(owner.value())), Key::AddressableEntity(EntityAddr::Account(spender.value()))); assert_eq!(spender_allowance_before, U256::zero()); let approve_request_1 = ExecuteRequestBuilder::contract_call_by_hash( @@ -186,11 +186,11 @@ fn should_transfer_from_from_account_to_account() { builder.exec(approve_request_1).expect_success().commit(); let account_1_balance_before = - cep18_check_balance_of(&mut builder, &cep18_token, Key::Account(owner)); + cep18_check_balance_of(&mut builder, &cep18_token, Key::AddressableEntity(EntityAddr::Account(owner.value()))); assert_eq!(account_1_balance_before, initial_supply); let account_1_allowance_before = - cep18_check_allowance_of(&mut builder, Key::Account(owner), Key::Account(spender)); + cep18_check_allowance_of(&mut builder, Key::AddressableEntity(EntityAddr::Account(owner.value())), Key::AddressableEntity(EntityAddr::Account(spender.value()))); assert_eq!(account_1_allowance_before, allowance_amount_1); builder @@ -199,14 +199,14 @@ fn should_transfer_from_from_account_to_account() { .commit(); let account_1_allowance_after = - cep18_check_allowance_of(&mut builder, Key::Account(owner), Key::Account(spender)); + cep18_check_allowance_of(&mut builder, Key::AddressableEntity(EntityAddr::Account(owner.value())), Key::AddressableEntity(EntityAddr::Account(spender.value()))); assert_eq!( account_1_allowance_after, account_1_allowance_before - transfer_from_amount_1 ); let account_1_balance_after = - cep18_check_balance_of(&mut builder, &cep18_token, Key::Account(owner)); + cep18_check_balance_of(&mut builder, &cep18_token, Key::AddressableEntity(EntityAddr::Account(owner.value()))); assert_eq!( account_1_balance_after, account_1_balance_before - transfer_from_amount_1 @@ -232,23 +232,23 @@ fn should_transfer_from_account_by_contract() { let owner = *DEFAULT_ACCOUNT_ADDR; - let spender = Key::Hash(cep18_test_contract_package.value()); - let recipient = Key::Account(*ACCOUNT_1_ADDR); + let spender = Key::AddressableEntity(EntityAddr::SmartContract(cep18_test_contract_package.value())); + let recipient = Key::AddressableEntity(EntityAddr::Account(ACCOUNT_1_ADDR.value())); let cep18_approve_args = runtime_args! { - ARG_OWNER => Key::Account(owner), + ARG_OWNER => Key::AddressableEntity(EntityAddr::Account(owner.value())), ARG_SPENDER => spender, ARG_AMOUNT => allowance_amount_1, }; let cep18_transfer_from_args = runtime_args! { ARG_TOKEN_CONTRACT => Key::addressable_entity_key(EntityKindTag::SmartContract, cep18_token), - ARG_OWNER => Key::Account(owner), + ARG_OWNER => Key::AddressableEntity(EntityAddr::Account(owner.value())), ARG_RECIPIENT => recipient, ARG_AMOUNT => transfer_from_amount_1, }; let spender_allowance_before = - cep18_check_allowance_of(&mut builder, Key::Account(owner), spender); + cep18_check_allowance_of(&mut builder, Key::AddressableEntity(EntityAddr::Account(owner.value())), spender); assert_eq!(spender_allowance_before, U256::zero()); let approve_request_1 = ExecuteRequestBuilder::contract_call_by_hash( @@ -271,11 +271,11 @@ fn should_transfer_from_account_by_contract() { builder.exec(approve_request_1).expect_success().commit(); let owner_balance_before = - cep18_check_balance_of(&mut builder, &cep18_token, Key::Account(owner)); + cep18_check_balance_of(&mut builder, &cep18_token, Key::AddressableEntity(EntityAddr::Account(owner.value()))); assert_eq!(owner_balance_before, initial_supply); let spender_allowance_before = - cep18_check_allowance_of(&mut builder, Key::Account(owner), spender); + cep18_check_allowance_of(&mut builder, Key::AddressableEntity(EntityAddr::Account(owner.value())), spender); assert_eq!(spender_allowance_before, allowance_amount_1); builder @@ -284,14 +284,14 @@ fn should_transfer_from_account_by_contract() { .commit(); let spender_allowance_after = - cep18_check_allowance_of(&mut builder, Key::Account(owner), spender); + cep18_check_allowance_of(&mut builder, Key::AddressableEntity(EntityAddr::Account(owner.value())), spender); assert_eq!( spender_allowance_after, spender_allowance_before - transfer_from_amount_1 ); let owner_balance_after = - cep18_check_balance_of(&mut builder, &cep18_token, Key::Account(owner)); + cep18_check_balance_of(&mut builder, &cep18_token, Key::AddressableEntity(EntityAddr::Account(owner.value()))); assert_eq!( owner_balance_after, owner_balance_before - transfer_from_amount_1 @@ -302,8 +302,8 @@ fn should_transfer_from_account_by_contract() { fn should_not_be_able_to_own_transfer() { let (mut builder, TestContext { cep18_token, .. }) = setup(); - let sender = Key::Account(*DEFAULT_ACCOUNT_ADDR); - let recipient = Key::Account(*DEFAULT_ACCOUNT_ADDR); + let sender = Key::AddressableEntity(EntityAddr::Account(DEFAULT_ACCOUNT_ADDR.value())); + let recipient = Key::AddressableEntity(EntityAddr::Account(DEFAULT_ACCOUNT_ADDR.value())); let transfer_amount = U256::from(TRANSFER_AMOUNT_1); @@ -331,16 +331,17 @@ fn should_not_be_able_to_own_transfer_from() { let addressable_cep18_token = AddressableEntityHash::new(cep18_token.value()); - let owner = Key::Account(*DEFAULT_ACCOUNT_ADDR); - let spender = Key::Account(*DEFAULT_ACCOUNT_ADDR); - let sender = Key::Account(*DEFAULT_ACCOUNT_ADDR); - let recipient = Key::Account(*DEFAULT_ACCOUNT_ADDR); + let sender = *DEFAULT_ACCOUNT_ADDR; + let owner = Key::AddressableEntity(EntityAddr::Account(sender.value())); + let spender = Key::AddressableEntity(EntityAddr::Account(sender.value())); + let sender_key = Key::AddressableEntity(EntityAddr::Account(sender.value())); + let recipient = Key::AddressableEntity(EntityAddr::Account(sender.value())); let allowance_amount = U256::from(ALLOWANCE_AMOUNT_1); let transfer_amount = U256::from(TRANSFER_AMOUNT_1); let approve_request = - make_cep18_approve_request(sender, &cep18_token, spender, allowance_amount); + make_cep18_approve_request(sender_key, &cep18_token, spender, allowance_amount); builder.exec(approve_request).commit(); @@ -351,7 +352,7 @@ fn should_not_be_able_to_own_transfer_from() { error ); - let sender_balance_before = cep18_check_balance_of(&mut builder, &cep18_token, sender); + let sender_balance_before = cep18_check_balance_of(&mut builder, &cep18_token, sender_key); let recipient_balance_before = cep18_check_balance_of(&mut builder, &cep18_token, recipient); assert_eq!(sender_balance_before, recipient_balance_before); @@ -363,7 +364,7 @@ fn should_not_be_able_to_own_transfer_from() { ARG_AMOUNT => transfer_amount, }; ExecuteRequestBuilder::contract_call_by_hash( - sender.into_account().unwrap(), + sender, addressable_cep18_token, METHOD_TRANSFER_FROM, cep18_transfer_from_args, @@ -385,8 +386,8 @@ fn should_not_be_able_to_own_transfer_from() { fn should_verify_zero_amount_transfer_is_noop() { let (mut builder, TestContext { cep18_token, .. }) = setup(); - let sender = Key::Account(*DEFAULT_ACCOUNT_ADDR); - let recipient = Key::Account(*ACCOUNT_1_ADDR); + let sender = Key::AddressableEntity(EntityAddr::Account(DEFAULT_ACCOUNT_ADDR.value())); + let recipient = Key::AddressableEntity(EntityAddr::Account(ACCOUNT_1_ADDR.value())); let transfer_amount = U256::zero(); @@ -414,31 +415,32 @@ fn should_verify_zero_amount_transfer_from_is_noop() { let addressable_cep18_token = AddressableEntityHash::new(cep18_token.value()); - let owner = Key::Account(*DEFAULT_ACCOUNT_ADDR); - let spender = Key::Account(*ACCOUNT_1_ADDR); - let recipient = Key::Account(*ACCOUNT_2_ADDR); + let owner = *DEFAULT_ACCOUNT_ADDR; + let owner_key = Key::AddressableEntity(EntityAddr::Account(owner.value())); + let spender = Key::AddressableEntity(EntityAddr::Account(ACCOUNT_1_ADDR.value())); + let recipient = Key::AddressableEntity(EntityAddr::Account(ACCOUNT_2_ADDR.value())); let allowance_amount = U256::from(1); let transfer_amount = U256::zero(); let approve_request = - make_cep18_approve_request(owner, &cep18_token, spender, allowance_amount); + make_cep18_approve_request(owner_key, &cep18_token, spender, allowance_amount); builder.exec(approve_request).expect_success().commit(); - let spender_allowance_before = cep18_check_allowance_of(&mut builder, owner, spender); + let spender_allowance_before = cep18_check_allowance_of(&mut builder, owner_key, spender); - let owner_balance_before = cep18_check_balance_of(&mut builder, &cep18_token, owner); + let owner_balance_before = cep18_check_balance_of(&mut builder, &cep18_token, owner_key); let recipient_balance_before = cep18_check_balance_of(&mut builder, &cep18_token, recipient); let transfer_from_request = { let cep18_transfer_from_args = runtime_args! { - ARG_OWNER => owner, + ARG_OWNER => owner_key, ARG_RECIPIENT => recipient, ARG_AMOUNT => transfer_amount, }; ExecuteRequestBuilder::contract_call_by_hash( - owner.into_account().unwrap(), + owner, addressable_cep18_token, METHOD_TRANSFER_FROM, cep18_transfer_from_args, @@ -451,13 +453,13 @@ fn should_verify_zero_amount_transfer_from_is_noop() { .expect_success() .commit(); - let owner_balance_after = cep18_check_balance_of(&mut builder, &cep18_token, owner); + let owner_balance_after = cep18_check_balance_of(&mut builder, &cep18_token, owner_key); assert_eq!(owner_balance_before, owner_balance_after); let recipient_balance_after = cep18_check_balance_of(&mut builder, &cep18_token, recipient); assert_eq!(recipient_balance_before, recipient_balance_after); - let spender_allowance_after = cep18_check_allowance_of(&mut builder, owner, spender); + let spender_allowance_after = cep18_check_allowance_of(&mut builder, owner_key, spender); assert_eq!(spender_allowance_after, spender_allowance_before); } @@ -469,10 +471,10 @@ fn should_transfer_contract_to_contract() { .. } = test_context; - let sender1 = Key::Account(*DEFAULT_ACCOUNT_ADDR); - let recipient1 = Key::Hash(cep18_test_contract_package.value()); - let sender2 = Key::Hash(cep18_test_contract_package.value()); - let recipient2 = Key::Hash([42; 32]); + let sender1 = Key::AddressableEntity(EntityAddr::Account(DEFAULT_ACCOUNT_ADDR.value())); + let recipient1 = Key::AddressableEntity(EntityAddr::SmartContract(cep18_test_contract_package.value())); + let sender2 = Key::AddressableEntity(EntityAddr::SmartContract(cep18_test_contract_package.value())); + let recipient2 = Key::AddressableEntity(EntityAddr::SmartContract([42; 32])); test_cep18_transfer( &mut builder, @@ -492,11 +494,11 @@ fn should_transfer_contract_to_account() { .. } = test_context; - let sender1 = Key::Account(*DEFAULT_ACCOUNT_ADDR); - let recipient1 = Key::Hash(cep18_test_contract_package.value()); + let sender1 = Key::AddressableEntity(EntityAddr::Account(DEFAULT_ACCOUNT_ADDR.value())); + let recipient1 = Key::AddressableEntity(EntityAddr::SmartContract(cep18_test_contract_package.value())); - let sender2 = Key::Hash(cep18_test_contract_package.value()); - let recipient2 = Key::Account(*ACCOUNT_1_ADDR); + let sender2 = Key::AddressableEntity(EntityAddr::SmartContract(cep18_test_contract_package.value())); + let recipient2 = Key::AddressableEntity(EntityAddr::Account(ACCOUNT_1_ADDR.value())); test_cep18_transfer( &mut builder, @@ -512,10 +514,10 @@ fn should_transfer_contract_to_account() { fn should_transfer_account_to_contract() { let (mut builder, test_context) = setup(); - let sender1 = Key::Account(*DEFAULT_ACCOUNT_ADDR); - let recipient1 = Key::Account(*ACCOUNT_1_ADDR); - let sender2 = Key::Account(*ACCOUNT_1_ADDR); - let recipient2 = Key::Hash(test_context.cep18_test_contract_package.value()); + let sender1 = Key::AddressableEntity(EntityAddr::Account(DEFAULT_ACCOUNT_ADDR.value())); + let recipient1 = Key::AddressableEntity(EntityAddr::Account(ACCOUNT_1_ADDR.value())); + let sender2 = Key::AddressableEntity(EntityAddr::Account(ACCOUNT_1_ADDR.value())); + let recipient2 = Key::AddressableEntity(EntityAddr::SmartContract(test_context.cep18_test_contract_package.value())); test_cep18_transfer( &mut builder, @@ -530,10 +532,10 @@ fn should_transfer_account_to_contract() { #[test] fn should_transfer_account_to_account() { let (mut builder, test_context) = setup(); - let sender1 = Key::Account(*DEFAULT_ACCOUNT_ADDR); - let recipient1 = Key::Account(*ACCOUNT_1_ADDR); - let sender2 = Key::Account(*ACCOUNT_1_ADDR); - let recipient2 = Key::Account(*ACCOUNT_2_ADDR); + let sender1 = Key::AddressableEntity(EntityAddr::Account(DEFAULT_ACCOUNT_ADDR.value())); + let recipient1 = Key::AddressableEntity(EntityAddr::Account(ACCOUNT_1_ADDR.value())); + let sender2 = Key::AddressableEntity(EntityAddr::Account(ACCOUNT_1_ADDR.value())); + let recipient2 = Key::AddressableEntity(EntityAddr::Account(ACCOUNT_2_ADDR.value())); test_cep18_transfer( &mut builder, diff --git a/tests/src/utility/constants.rs b/tests/src/utility/constants.rs index 738e52e..f88d31a 100644 --- a/tests/src/utility/constants.rs +++ b/tests/src/utility/constants.rs @@ -1,4 +1,4 @@ -use casper_types::{account::AccountHash, Key, PublicKey, SecretKey}; +use casper_types::{account::AccountHash, EntityAddr, Key, PublicKey, SecretKey}; use once_cell::sync::Lazy; pub const CEP18_CONTRACT_WASM: &str = "cep18.wasm"; @@ -67,9 +67,9 @@ pub const METHOD_TRANSFER_AS_STORED_CONTRACT: &str = "transfer_as_stored_contrac pub const METHOD_APPROVE_AS_STORED_CONTRACT: &str = "approve_as_stored_contract"; pub const METHOD_FROM_AS_STORED_CONTRACT: &str = "transfer_from_as_stored_contract"; -pub const TOKEN_OWNER_ADDRESS_1: Key = Key::Account(AccountHash::new([42; 32])); +pub const TOKEN_OWNER_ADDRESS_1: Key = Key::AddressableEntity(EntityAddr::Account([42; 32])); pub const TOKEN_OWNER_AMOUNT_1: u64 = 1_000_000; -pub const TOKEN_OWNER_ADDRESS_2: Key = Key::Hash([42; 32]); +pub const TOKEN_OWNER_ADDRESS_2: Key = Key::AddressableEntity(EntityAddr::SmartContract([42; 32])); pub const TOKEN_OWNER_AMOUNT_2: u64 = 2_000_000; pub const METHOD_MINT: &str = "mint"; diff --git a/tests/src/utility/installer_request_builders.rs b/tests/src/utility/installer_request_builders.rs index 0f6db38..5912f70 100644 --- a/tests/src/utility/installer_request_builders.rs +++ b/tests/src/utility/installer_request_builders.rs @@ -1,23 +1,18 @@ use casper_engine_test_support::{ utils::create_run_genesis_request, ExecuteRequest, ExecuteRequestBuilder, LmdbWasmTestBuilder, DEFAULT_ACCOUNT_ADDR, DEFAULT_ACCOUNT_PUBLIC_KEY, - utils::create_run_genesis_request, ExecuteRequest, ExecuteRequestBuilder, LmdbWasmTestBuilder, - DEFAULT_ACCOUNT_ADDR, DEFAULT_ACCOUNT_PUBLIC_KEY, }; use casper_types::{ - account::AccountHash, addressable_entity::EntityKindTag, bytesrepr::FromBytes, - runtime_args, AddressableEntityHash, CLTyped, EntityAddr, - GenesisAccount, Key, Motes, PackageHash, RuntimeArgs, U256, U512, + account::AccountHash, addressable_entity::EntityKindTag, bytesrepr::FromBytes, runtime_args, + AddressableEntityHash, CLTyped, EntityAddr, GenesisAccount, Key, Motes, PackageHash, + RuntimeArgs, U256, U512, }; use crate::utility::constants::{ - ALLOWANCE_AMOUNT_1, ALLOWANCE_AMOUNT_2, TOTAL_SUPPLY_KEY, - TRANSFER_AMOUNT_1, TRANSFER_AMOUNT_2, + ALLOWANCE_AMOUNT_1, ALLOWANCE_AMOUNT_2, TOTAL_SUPPLY_KEY, TRANSFER_AMOUNT_1, TRANSFER_AMOUNT_2, }; use super::constants::{ - ACCOUNT_1_PUBLIC_KEY, ACCOUNT_2_PUBLIC_KEY, ARG_ADDRESS, ARG_AMOUNT, ARG_DECIMALS, ARG_NAME, - ARG_OWNER, ARG_RECIPIENT, ARG_SPENDER, ARG_SYMBOL, ARG_TOKEN_CONTRACT, ARG_TOTAL_SUPPLY, ACCOUNT_1_PUBLIC_KEY, ACCOUNT_2_PUBLIC_KEY, ARG_ADDRESS, ARG_AMOUNT, ARG_DECIMALS, ARG_NAME, ARG_OWNER, ARG_RECIPIENT, ARG_SPENDER, ARG_SYMBOL, ARG_TOKEN_CONTRACT, ARG_TOTAL_SUPPLY, CEP18_CONTRACT_WASM, CEP18_TEST_CONTRACT_KEY, CEP18_TEST_CONTRACT_WASM, @@ -35,6 +30,11 @@ pub(crate) fn invert_cep18_address(address: Key) -> Key { match address { Key::Account(account_hash) => Key::Hash(account_hash.value()), Key::Hash(contract_hash) => Key::Account(AccountHash::new(contract_hash)), + Key::AddressableEntity(entity_addr)=>match entity_addr { + EntityAddr::System(_) => panic!("Unsupported Key variant"), + EntityAddr::Account(account) => Key::AddressableEntity(EntityAddr::SmartContract(account)), + EntityAddr::SmartContract(contract) => Key::AddressableEntity(EntityAddr::Account(contract)), + } _ => panic!("Unsupported Key variant"), } } @@ -43,11 +43,8 @@ pub(crate) fn invert_cep18_address(address: Key) -> Key { pub(crate) struct TestContext { pub(crate) cep18_token: AddressableEntityHash, pub(crate) cep18_test_contract_package: PackageHash, - pub(crate) cep18_token: AddressableEntityHash, - pub(crate) cep18_test_contract_package: PackageHash, } -pub(crate) fn setup() -> (LmdbWasmTestBuilder, TestContext) { pub(crate) fn setup() -> (LmdbWasmTestBuilder, TestContext) { setup_with_args(runtime_args! { ARG_NAME => TOKEN_NAME, @@ -57,25 +54,6 @@ pub(crate) fn setup() -> (LmdbWasmTestBuilder, TestContext) { }) } -pub(crate) fn setup_with_args(install_args: RuntimeArgs) -> (LmdbWasmTestBuilder, TestContext) { - let mut builder = LmdbWasmTestBuilder::default(); - builder.run_genesis(create_run_genesis_request(vec![ - GenesisAccount::Account { - public_key: DEFAULT_ACCOUNT_PUBLIC_KEY.clone(), - balance: Motes::new(U512::from(5_000_000_000_000_u64)), - validator: None, - }, - GenesisAccount::Account { - public_key: ACCOUNT_1_PUBLIC_KEY.clone(), - balance: Motes::new(U512::from(5_000_000_000_000_u64)), - validator: None, - }, - GenesisAccount::Account { - public_key: ACCOUNT_2_PUBLIC_KEY.clone(), - balance: Motes::new(U512::from(5_000_000_000_000_u64)), - validator: None, - }, - ])); pub(crate) fn setup_with_args(install_args: RuntimeArgs) -> (LmdbWasmTestBuilder, TestContext) { let mut builder = LmdbWasmTestBuilder::default(); builder.run_genesis(create_run_genesis_request(vec![ @@ -110,28 +88,20 @@ pub(crate) fn setup_with_args(install_args: RuntimeArgs) -> (LmdbWasmTestBuilder builder.exec(install_request_1).expect_success().commit(); builder.exec(install_request_2).expect_success().commit(); - let account = - builder.get_entity_with_named_keys_by_account_hash(*DEFAULT_ACCOUNT_ADDR).unwrap(); + let account = builder + .get_entity_with_named_keys_by_account_hash(*DEFAULT_ACCOUNT_ADDR) + .unwrap(); let account_named_keys = account.named_keys(); - // println!("{account_named_keys:?}"); - - // let account_named_keys_2 = builder.get_named_keys(EntityAddr::new_account(DEFAULT_ACCOUNT_ADDR.value())); - // println!("{account_named_keys_2:?}"); - let cep18_token = account_named_keys let cep18_token = account_named_keys .get(CEP18_TOKEN_CONTRACT_KEY) .and_then(|key| key.into_entity_hash()) - .and_then(|key| key.into_entity_hash()) .expect("should have contract hash"); - let cep18_test_contract_package = account_named_keys let cep18_test_contract_package = account_named_keys .get(CEP18_TEST_CONTRACT_KEY) .and_then(|key| key.into_package_hash()) .expect("should have package hash"); - .and_then(|key| key.into_package_hash()) - .expect("should have package hash"); let test_context = TestContext { cep18_token, @@ -144,11 +114,8 @@ pub(crate) fn setup_with_args(install_args: RuntimeArgs) -> (LmdbWasmTestBuilder pub(crate) fn cep18_check_total_supply( builder: &mut LmdbWasmTestBuilder, cep18_contract_hash: &AddressableEntityHash, - builder: &mut LmdbWasmTestBuilder, - cep18_contract_hash: &AddressableEntityHash, ) -> U256 { let account = builder - .get_entity_with_named_keys_by_account_hash(*DEFAULT_ACCOUNT_ADDR) .get_entity_with_named_keys_by_account_hash(*DEFAULT_ACCOUNT_ADDR) .expect("should have account"); @@ -156,12 +123,10 @@ pub(crate) fn cep18_check_total_supply( .named_keys() .get(CEP18_TEST_CONTRACT_KEY) .and_then(|key| key.into_package_hash()) - .and_then(|key| key.into_package_hash()) .expect("should have test contract hash"); let check_total_supply_args = runtime_args! { ARG_TOKEN_CONTRACT => Key::addressable_entity_key(EntityKindTag::SmartContract, *cep18_contract_hash), - ARG_TOKEN_CONTRACT => Key::addressable_entity_key(EntityKindTag::SmartContract, *cep18_contract_hash), }; let exec_request = ExecuteRequestBuilder::versioned_contract_call_by_hash( @@ -180,36 +145,26 @@ pub(crate) fn cep18_check_total_supply( pub(crate) fn get_test_result( builder: &mut LmdbWasmTestBuilder, cep18_test_contract_package: PackageHash, - builder: &mut LmdbWasmTestBuilder, - cep18_test_contract_package: PackageHash, ) -> T { let contract_package = builder - .get_package(cep18_test_contract_package) .get_package(cep18_test_contract_package) .expect("should have contract package"); let enabled_versions = contract_package.enabled_versions(); - let contract_hash = enabled_versions - .contract_hashes() - .last() + let contract_hash = enabled_versions .contract_hashes() .last() .expect("should have latest version"); let entity_addr = EntityAddr::new_smart_contract(contract_hash.value()); builder.get_value(entity_addr, RESULT_KEY) - let entity_addr = EntityAddr::new_smart_contract(contract_hash.value()); - builder.get_value(entity_addr, RESULT_KEY) } pub(crate) fn cep18_check_balance_of( - builder: &mut LmdbWasmTestBuilder, - cep18_contract_hash: &AddressableEntityHash, builder: &mut LmdbWasmTestBuilder, cep18_contract_hash: &AddressableEntityHash, address: Key, ) -> U256 { let account = builder - .get_entity_with_named_keys_by_account_hash(*DEFAULT_ACCOUNT_ADDR) .get_entity_with_named_keys_by_account_hash(*DEFAULT_ACCOUNT_ADDR) .expect("should have account"); @@ -217,11 +172,9 @@ pub(crate) fn cep18_check_balance_of( .named_keys() .get(CEP18_TEST_CONTRACT_KEY) .and_then(|key| key.into_package_hash()) - .and_then(|key| key.into_package_hash()) .expect("should have test contract hash"); let check_balance_args = runtime_args! { - ARG_TOKEN_CONTRACT => Key::addressable_entity_key(EntityKindTag::SmartContract, *cep18_contract_hash), ARG_TOKEN_CONTRACT => Key::addressable_entity_key(EntityKindTag::SmartContract, *cep18_contract_hash), ARG_ADDRESS => address, }; @@ -239,30 +192,25 @@ pub(crate) fn cep18_check_balance_of( } pub(crate) fn cep18_check_allowance_of( - builder: &mut LmdbWasmTestBuilder, builder: &mut LmdbWasmTestBuilder, owner: Key, spender: Key, ) -> U256 { let account = builder - .get_entity_with_named_keys_by_account_hash(*DEFAULT_ACCOUNT_ADDR) .get_entity_with_named_keys_by_account_hash(*DEFAULT_ACCOUNT_ADDR) .expect("should have account"); let cep18_contract_hash = account .named_keys() .get(CEP18_TOKEN_CONTRACT_KEY) .and_then(|key| key.into_entity_hash()) - .and_then(|key| key.into_entity_hash()) .expect("should have test contract hash"); let cep18_test_contract_package = account .named_keys() .get(CEP18_TEST_CONTRACT_KEY) .and_then(|key| key.into_package_hash()) - .and_then(|key| key.into_package_hash()) .expect("should have test contract hash"); let check_balance_args = runtime_args! { - ARG_TOKEN_CONTRACT => Key::addressable_entity_key(EntityKindTag::SmartContract, cep18_contract_hash), ARG_TOKEN_CONTRACT => Key::addressable_entity_key(EntityKindTag::SmartContract, cep18_contract_hash), ARG_OWNER => owner, ARG_SPENDER => spender, @@ -281,7 +229,6 @@ pub(crate) fn cep18_check_allowance_of( } pub(crate) fn test_cep18_transfer( - builder: &mut LmdbWasmTestBuilder, builder: &mut LmdbWasmTestBuilder, test_context: &TestContext, sender1: Key, @@ -347,7 +294,6 @@ pub(crate) fn test_cep18_transfer( pub(crate) fn make_cep18_transfer_request( sender: Key, cep18_token: &AddressableEntityHash, - cep18_token: &AddressableEntityHash, recipient: Key, amount: U256, ) -> ExecuteRequest { @@ -355,7 +301,6 @@ pub(crate) fn make_cep18_transfer_request( Key::Account(sender) => ExecuteRequestBuilder::contract_call_by_hash( sender, AddressableEntityHash::new(cep18_token.value()), - AddressableEntityHash::new(cep18_token.value()), METHOD_TRANSFER, runtime_args! { ARG_AMOUNT => amount, @@ -366,17 +311,42 @@ pub(crate) fn make_cep18_transfer_request( Key::Hash(contract_package_hash) => ExecuteRequestBuilder::versioned_contract_call_by_hash( *DEFAULT_ACCOUNT_ADDR, PackageHash::new(contract_package_hash), - PackageHash::new(contract_package_hash), None, METHOD_TRANSFER_AS_STORED_CONTRACT, runtime_args! { - ARG_TOKEN_CONTRACT => Key::addressable_entity_key(EntityKindTag::SmartContract, *cep18_token), ARG_TOKEN_CONTRACT => Key::addressable_entity_key(EntityKindTag::SmartContract, *cep18_token), ARG_AMOUNT => amount, ARG_RECIPIENT => recipient, }, ) .build(), + Key::AddressableEntity(entity_addr)=>{ + match entity_addr { + EntityAddr::System(_) => panic!("Not a use case"), + EntityAddr::Account(account_addr) => ExecuteRequestBuilder::contract_call_by_hash( + AccountHash::new(account_addr), + AddressableEntityHash::new(cep18_token.value()), + METHOD_TRANSFER, + runtime_args! { + ARG_AMOUNT => amount, + ARG_RECIPIENT => recipient, + }, + ) + .build(), + EntityAddr::SmartContract(contract_hash) => ExecuteRequestBuilder::versioned_contract_call_by_hash( + *DEFAULT_ACCOUNT_ADDR, + PackageHash::new(contract_hash), + None, + METHOD_TRANSFER_AS_STORED_CONTRACT, + runtime_args! { + ARG_TOKEN_CONTRACT => Key::addressable_entity_key(EntityKindTag::SmartContract, *cep18_token), + ARG_AMOUNT => amount, + ARG_RECIPIENT => recipient, + } + ) + .build(), + } + } _ => panic!("Unknown variant"), } } @@ -384,7 +354,6 @@ pub(crate) fn make_cep18_transfer_request( pub(crate) fn make_cep18_approve_request( sender: Key, cep18_token: &AddressableEntityHash, - cep18_token: &AddressableEntityHash, spender: Key, amount: U256, ) -> ExecuteRequest { @@ -392,7 +361,6 @@ pub(crate) fn make_cep18_approve_request( Key::Account(sender) => ExecuteRequestBuilder::contract_call_by_hash( sender, AddressableEntityHash::new(cep18_token.value()), - AddressableEntityHash::new(cep18_token.value()), METHOD_APPROVE, runtime_args! { ARG_SPENDER => spender, @@ -403,23 +371,47 @@ pub(crate) fn make_cep18_approve_request( Key::Hash(contract_package_hash) => ExecuteRequestBuilder::versioned_contract_call_by_hash( *DEFAULT_ACCOUNT_ADDR, PackageHash::new(contract_package_hash), - PackageHash::new(contract_package_hash), None, METHOD_APPROVE_AS_STORED_CONTRACT, runtime_args! { - ARG_TOKEN_CONTRACT => Key::addressable_entity_key(EntityKindTag::SmartContract, *cep18_token), ARG_TOKEN_CONTRACT => Key::addressable_entity_key(EntityKindTag::SmartContract, *cep18_token), ARG_SPENDER => spender, ARG_AMOUNT => amount, }, ) .build(), + Key::AddressableEntity(entity_addr)=>{ + match entity_addr { + EntityAddr::System(_) => panic!("Not a use case"), + EntityAddr::Account(account_addr) => ExecuteRequestBuilder::contract_call_by_hash( + AccountHash::new(account_addr), + AddressableEntityHash::new(cep18_token.value()), + METHOD_APPROVE, + runtime_args! { + ARG_SPENDER => spender, + ARG_AMOUNT => amount, + }, + ) + .build(), + EntityAddr::SmartContract(contract_hash) => ExecuteRequestBuilder::versioned_contract_call_by_hash( + *DEFAULT_ACCOUNT_ADDR, + PackageHash::new(contract_hash), + None, + METHOD_APPROVE_AS_STORED_CONTRACT, + runtime_args! { + ARG_TOKEN_CONTRACT => Key::addressable_entity_key(EntityKindTag::SmartContract, *cep18_token), + ARG_SPENDER => spender, + ARG_AMOUNT => amount, + }, + ) + .build(), + } + } _ => panic!("Unknown variant"), } } pub(crate) fn test_approve_for( - builder: &mut LmdbWasmTestBuilder, builder: &mut LmdbWasmTestBuilder, test_context: &TestContext, sender: Key, @@ -445,10 +437,6 @@ pub(crate) fn test_approve_for( let account_1_allowance_after = cep18_check_allowance_of(builder, owner, spender); assert_eq!(account_1_allowance_after, allowance_amount_1); - let total_supply: U256 = builder.get_value( - EntityAddr::new_smart_contract(cep18_token.value()), - TOTAL_SUPPLY_KEY, - ); let total_supply: U256 = builder.get_value( EntityAddr::new_smart_contract(cep18_token.value()), TOTAL_SUPPLY_KEY, @@ -469,10 +457,6 @@ pub(crate) fn test_approve_for( let inverted_spender_allowance = cep18_check_allowance_of(builder, owner, inverted_spender_key); assert_eq!(inverted_spender_allowance, U256::zero()); - let total_supply: U256 = builder.get_value( - EntityAddr::new_smart_contract(cep18_token.value()), - TOTAL_SUPPLY_KEY, - ); let total_supply: U256 = builder.get_value( EntityAddr::new_smart_contract(cep18_token.value()), TOTAL_SUPPLY_KEY, From a3557b862eaf254d6713a5eb9493282a9dd44fd4 Mon Sep 17 00:00:00 2001 From: Deuszex Date: Thu, 20 Jun 2024 16:02:10 +0200 Subject: [PATCH 09/27] addressing comments, first round of fixture fights --- cep18/src/error.rs | 2 +- cep18/src/main.rs | 73 ++--- tests/Cargo.toml | 9 +- .../cep18-1.5.6-minted/global_state/data.lmdb | Bin 0 -> 581632 bytes .../global_state/data.lmdb-lock | Bin 0 -> 32896 bytes tests/fixtures/cep18-1.5.6-minted/state.json | 6 + tests/src/allowance.rs | 33 ++- tests/src/fixture_gen.rs | 254 ++++++++++++++++++ tests/src/migration.rs | 76 +++++- tests/src/mint_and_burn.rs | 8 +- tests/src/transfer.rs | 122 ++++++--- .../src/utility/installer_request_builders.rs | 12 +- 12 files changed, 511 insertions(+), 84 deletions(-) create mode 100644 tests/fixtures/cep18-1.5.6-minted/global_state/data.lmdb create mode 100644 tests/fixtures/cep18-1.5.6-minted/global_state/data.lmdb-lock create mode 100644 tests/fixtures/cep18-1.5.6-minted/state.json create mode 100644 tests/src/fixture_gen.rs diff --git a/cep18/src/error.rs b/cep18/src/error.rs index 24f08ef..d2a4040 100644 --- a/cep18/src/error.rs +++ b/cep18/src/error.rs @@ -49,7 +49,7 @@ pub enum Cep18Error { InvalidBurnTarget = 60018, MissingPackageHashForUpgrade = 60019, MissingContractHashForUpgrade = 60020, - WrongKeyType = 60021, + InvalidKeyType = 60021, KeyTypeMigrationMismatch = 60022, } diff --git a/cep18/src/main.rs b/cep18/src/main.rs index ff0ff96..546ff7e 100644 --- a/cep18/src/main.rs +++ b/cep18/src/main.rs @@ -32,7 +32,10 @@ use casper_contract::{ unwrap_or_revert::UnwrapOrRevert, }; use casper_types::{ - addressable_entity::{EntityKindTag, NamedKeys}, bytesrepr::ToBytes, contract_messages::MessageTopicOperation, runtime_args, AddressableEntityHash, CLValue, EntityAddr, Key, PackageHash, U256 + addressable_entity::{EntityKindTag, NamedKeys}, + bytesrepr::ToBytes, + contract_messages::MessageTopicOperation, + runtime_args, AddressableEntityHash, CLValue, EntityAddr, Key, PackageHash, U256, }; use constants::{ @@ -279,12 +282,20 @@ pub extern "C" fn init() { let balances_uref = storage::new_dictionary(BALANCES).unwrap_or_revert(); let initial_supply = runtime::get_named_arg(TOTAL_SUPPLY); let caller = get_caller(); - write_balance_to(balances_uref, Key::AddressableEntity(EntityAddr::Account(caller.value())), initial_supply); + write_balance_to( + balances_uref, + Key::AddressableEntity(EntityAddr::Account(caller.value())), + initial_supply, + ); let security_badges_dict = storage::new_dictionary(SECURITY_BADGES).unwrap_or_revert(); dictionary_put( security_badges_dict, - &base64::encode(Key::AddressableEntity(EntityAddr::Account(caller.value())).to_bytes().unwrap_or_revert()), + &base64::encode( + Key::AddressableEntity(EntityAddr::Account(caller.value())) + .to_bytes() + .unwrap_or_revert(), + ), SecurityBadge::Admin, ); @@ -373,8 +384,8 @@ pub extern "C" fn change_security() { /// Entrypoint to migrate user and contract keys regarding balances provided as argument from 1.x to /// 2.x key/hash storage version. Argument is a single BTreeMap. The key is the already /// stored key in the system (previously Key::Hash or Key::Account), while the bool value is the -/// verification for the key's flag (true for account, false for contract). -/// Going forward these will be stored are Key::AddressableEntity(EntityAddr::Account) +/// verification for the key's state of being an account or a contract (true for account, false for +/// contract). Going forward these will be stored are Key::AddressableEntity(EntityAddr::Account) /// and Key::AddressableEntity(EntityAddr::SmartContract) respectively. #[no_mangle] pub fn migrate_users_balance_keys() { @@ -385,10 +396,10 @@ pub fn migrate_users_balance_keys() { let keys: BTreeMap = get_named_arg(USER_KEY_MAP); let balances_uref = get_balances_uref(); - for (old_key, flag) in keys { + for (old_key, is_account_flag) in keys { let migrated_key = match old_key { Key::Account(account_hash) => { - if !flag { + if !is_account_flag { if event_on { failure_map.insert(old_key, String::from("FlagMismatch")); } else if revert_on { @@ -399,7 +410,7 @@ pub fn migrate_users_balance_keys() { Key::AddressableEntity(EntityAddr::Account(account_hash.value())) } Key::Hash(contract_package) => { - if flag { + if is_account_flag { if event_on { failure_map.insert(old_key, String::from("FlagMismatch")); } else if revert_on { @@ -413,7 +424,7 @@ pub fn migrate_users_balance_keys() { if event_on { failure_map.insert(old_key, String::from("WrongKeyType")); } else if revert_on { - revert(Cep18Error::WrongKeyType) + revert(Cep18Error::InvalidKeyType) } continue; } @@ -453,10 +464,10 @@ pub fn migrate_users_allowance_keys() { let keys: BTreeMap<(Key, bool), Vec<(Key, bool)>> = get_named_arg(USER_KEY_MAP); let allowances_uref = get_allowances_uref(); - for ((spender_key, spender_flag), allowance_owner_keys) in keys { + for ((spender_key, spender_is_account_flag), allowance_owner_keys) in keys { let migrated_spender_key = match spender_key { Key::Account(account_hash) => { - if !spender_flag { + if !spender_is_account_flag { if event_on { failure_map .insert((spender_key, None), String::from("SpenderFlagMismatch")); @@ -468,7 +479,7 @@ pub fn migrate_users_allowance_keys() { Key::AddressableEntity(EntityAddr::Account(account_hash.value())) } Key::Hash(contract_package) => { - if spender_flag { + if spender_is_account_flag { if event_on { failure_map .insert((spender_key, None), String::from("SpenderFlagMismatch")); @@ -483,15 +494,15 @@ pub fn migrate_users_allowance_keys() { if event_on { failure_map.insert((spender_key, None), String::from("SpenderWrongKeyType")); } else if revert_on { - revert(Cep18Error::WrongKeyType) + revert(Cep18Error::InvalidKeyType) } continue; } }; - for (owner_key, owner_flag) in allowance_owner_keys { + for (owner_key, owner_is_account_flag) in allowance_owner_keys { let migrated_owner_key = match owner_key { Key::Account(account_hash) => { - if !owner_flag { + if !owner_is_account_flag { if event_on { failure_map.insert( (spender_key, Some(owner_key)), @@ -505,7 +516,7 @@ pub fn migrate_users_allowance_keys() { Key::AddressableEntity(EntityAddr::Account(account_hash.value())) } Key::Hash(contract_package) => { - if owner_flag { + if owner_is_account_flag { if event_on { failure_map.insert( (spender_key, Some(owner_key)), @@ -525,7 +536,7 @@ pub fn migrate_users_allowance_keys() { String::from("OwnerWrongKeyType"), ); } else if revert_on { - revert(Cep18Error::WrongKeyType) + revert(Cep18Error::InvalidKeyType) } continue; } @@ -570,20 +581,20 @@ pub fn migrate_users_allowance_keys() { pub fn upgrade(name: &str) { let entry_points = generate_entry_points(); - let old_contract_package_hash = match runtime::get_key(&format!("{HASH_KEY_NAME_PREFIX}{name}")) - .unwrap_or_revert(){ - Key::Hash(contract_hash) => contract_hash, - Key::AddressableEntity(EntityAddr::SmartContract(contract_hash)) => contract_hash, - Key::Package(package_hash) => package_hash, - _=>revert(Cep18Error::MissingPackageHashForUpgrade) - }; + let old_contract_package_hash = + match runtime::get_key(&format!("{HASH_KEY_NAME_PREFIX}{name}")).unwrap_or_revert() { + Key::Hash(contract_hash) => contract_hash, + Key::AddressableEntity(EntityAddr::SmartContract(contract_hash)) => contract_hash, + Key::Package(package_hash) => package_hash, + _ => revert(Cep18Error::MissingPackageHashForUpgrade), + }; let contract_package_hash = PackageHash::new(old_contract_package_hash); - let previous_contract_hash = match runtime::get_key(&format!("{CONTRACT_NAME_PREFIX}{name}")) - .unwrap_or_revert(){ + let previous_contract_hash = + match runtime::get_key(&format!("{CONTRACT_NAME_PREFIX}{name}")).unwrap_or_revert() { Key::Hash(contract_hash) => contract_hash, Key::AddressableEntity(EntityAddr::SmartContract(contract_hash)) => contract_hash, - _=>revert(Cep18Error::MissingContractHashForUpgrade) + _ => revert(Cep18Error::MissingContractHashForUpgrade), }; let converted_previous_contract_hash = AddressableEntityHash::new(previous_contract_hash); @@ -637,10 +648,12 @@ pub fn install_contract(name: &str) { let symbol: String = runtime::get_named_arg(SYMBOL); let decimals: u8 = runtime::get_named_arg(DECIMALS); let total_supply: U256 = runtime::get_named_arg(TOTAL_SUPPLY); - let events_mode: u8 = + let events_mode_arg: u8 = utils::get_optional_named_arg_with_user_errors(EVENTS_MODE, Cep18Error::InvalidEventsMode) .unwrap_or(0u8); + let events_mode: EventsMode = EventsMode::try_from(events_mode_arg).unwrap_or_revert(); + let admin_list: Option> = utils::get_optional_named_arg_with_user_errors(ADMIN_LIST, Cep18Error::InvalidAdminList); let minter_list: Option> = @@ -662,7 +675,7 @@ pub fn install_contract(name: &str) { ); named_keys.insert( EVENTS_MODE.to_string(), - storage::new_uref(events_mode).into(), + storage::new_uref(events_mode_arg).into(), ); named_keys.insert( ENABLE_MINT_BURN.to_string(), @@ -670,7 +683,7 @@ pub fn install_contract(name: &str) { ); let entry_points = generate_entry_points(); - let message_topics = if events_mode == 2 { + let message_topics = if events_mode == EventsMode::Native { let mut message_topics = BTreeMap::new(); message_topics.insert(EVENTS.to_string(), MessageTopicOperation::Add); Some(message_topics) diff --git a/tests/Cargo.toml b/tests/Cargo.toml index 0babae2..cb46472 100644 --- a/tests/Cargo.toml +++ b/tests/Cargo.toml @@ -9,8 +9,15 @@ casper-engine-test-support = { git = "https://github.com/casper-network/casper-n casper-execution-engine = { git = "https://github.com/casper-network/casper-node.git", branch = "release-2.0.0-rc2", default-features = false} casper-storage = { git = "https://github.com/casper-network/casper-node.git", branch = "release-2.0.0-rc2", default-features = false} once_cell = "1.16.0" +casper-fixtures = { git = "https://github.com/deuszex/casper-fixtures", branch = "2.0.0-rc2"} [lib] name = "tests" bench = false -doctest = false \ No newline at end of file +doctest = false + +[[bin]] +name = "fixture_gen" +path = "src/fixture_gen.rs" +bench = false +doctest = false diff --git a/tests/fixtures/cep18-1.5.6-minted/global_state/data.lmdb b/tests/fixtures/cep18-1.5.6-minted/global_state/data.lmdb new file mode 100644 index 0000000000000000000000000000000000000000..22b725a7cb65df855f3da8c7bc3de21ef5175658 GIT binary patch literal 581632 zcmeDk2|N|sd&a)+L?PKCd$zJfNC}0AMAx;1>^qUYB1L6wv4qGH(PB+Vwv-~-%UUWb z(PGPg23Pddy|29Lz5kux@7_5xbIv#CeDlqj^X&kD+K6TfPfFjEfT&Zmd(KgKPXz!( z@?WleDa}^Go!tTAbNkudms(#+i?6uP)ju(}C!5=2cUTNyF@VJY76VueU@?Hj02TvS z3}7*U#Q+uqSPWn>uzVQ!!j~@(|L?GfW-h716T}TF@VLua$o>w9taM=4BQ6`N$!I|M8ZTB1dasrm#~F@ z2?o6I;Eu8ZTNN!k#cteJuYT@E+3Q(P$67a9&}OO7!~6Q!yMC^B0VwGU0Dyo+HF1u; z=EILVoaND_1|K6T{qo*7lU|}sB6liH$UPYcRiTc3)>B1Vd!W;C=g`(PyV|1-w$$yh z(QA8({c)4DXiH!)!ES=hIfl~vIjQs?4OOmWRK&A+K0?N7{9)pSoZf2ok%brpfsBsb zPls-+B9f%3Yu_>C3ThPGzklJv`V#&d0c6g{!nV!T27%nQHT20j@p!fgfp)-4{*8cz zy>P~8^2md|bgS6;GEdD{3^gR96lxwj(KyTy*Kma$cX%pfyH;UyjNu@szQWqhH4E+! z0yT7l*Dr^dR(D-Z+Onm-W9(8$*4XuW=hj#OA?Yc(=Ll3~5MX&A%9Z!vv!KC?*jJ~LWuH9pQ1wu2<7+aPK_(8kfF9;Nd=moC`zaCJM z8@tMfT!CUsp#46Ax50P#no6ynr99G~8QLr`PAMCLY&jA-H>pvt@ivYAss`hI3dy3X zo5dU3iCXLUKwuq-?(-z)kqZr1b>wlggzL2;-Z63M7D$V-sH!{TMdr@J2_~{SVAW?n zXvIR&$~<b~Nv)?v0ITQdAwVID^nW6GIr zYW<-G1|&YOJiV%Se+2~um`4v&@C;thHP?32lN}R?KfSj2@=@Nit)t?bu5cjU9)KEv z;7vRoFQMVRJBam;x$rzXcc<`(lk-Mqs)756d;{h7y(SBwf&k6dVK>Fg z{}tXxab%;?luai~^v!CE7|=!pD9-F5h=gqEd=pvxQd(Vnothb;%;TD=NP#i}(W%P^LaLAYgU~}|SOL23 zY~LBnuXBwVKR9^M-+cR9U~8e@N2y~thy6s1L2zJFNLc(z)Fq`5>TQQKlBzhhjRp^H zOg$?%&UIWJC$4Iy`~SCp!ZRah1GX2}eO~*TdO%(wC-mBV_M*?s<~%HAQ-VB1E258{ zyRHuqaJ1V7Z$cRR`y49nx})P!9Gn{8j%+WEMlj$09ObM&4LvvjAOuPuG>TtbNklxe z5McwhT|Ss`^OjdR^644T*w>Y7gP1?IAO^L?I4=x-*7U-o06{Rt<>~bp{XU(74|V&r z>Q5h!b096$>!@s%4GK!vZ3j|8@W~rcM{I6*dUOh{%f1-kmYG2`i#9J^$9u6#7X9#yde!T<=gR!WvU#cLmKAZ_cZ z6riFFrxqj(7SA>8su1I=xvif7*Ab9Qz7ufxN#em_dxcT)wk?-Oj2v7&bi0)EnMP=)MZ(a(eiit`3D&KqgNY2lR5T<@(r1flVn>Av4 zYyc*r=IW0+PwhV*@)VAXjnjnf7MA!MrynAswYt<4ZNmbT6aV;4tT9O9GpmsHsj6{{Tr zA~!v|`tW$saZc6hV^g9>+gSb9SY0QCRc~glX12~w2pc<7GaMa3i_ICQ@tWPj@vK}w zKUy`Irupo43f1`vH_5Ulg(yctc(G}Vv@yex_rJqbBPHf$Z5q|cix)7)_jvm12ix(m z7s8va>5nJy`lf4^^~a$6+s-aEh4Ufr!nRPWjKiq7=N+0Gehjc{_Yx3~7d(dD!{Req{o?*C z1`UtI!~$sxJq#3%mpBDg0-%1tbc&+9g=P3ME(p@hUUoD?I+-EnQM2^f?++MmV`*!$ z1f;*7{XQfZ2bQ*$&Zs?f2#*9lgkJ~L{(C5b>%gRq+QT47K=2ENeJRadf=PTKL4AD; zSA(7tj6!Eeq^;9_GslHxxHB+8IYKYFy(RP#k}iH` zS$QEtPlVXy2C>u~s@3n(J?HC#fOcLJ>m#At{K{>62A!ERtX8;@^e%PuF*WSv!CY z*q5qFu${9+yJeA&XarlE@mkCk)Wf5fU<(LcG7Fxviz0BP^70 zL^_+{kOs@YmdkWq0qwLT6IZ;>zV)FbkfX`#MQ1W`Rpn}_zY0x0ZACny(GD!xnBXBU z{Prww1KV_4=(Y`>vTGtbC)tB{5W-Rl_%|oXSWtvX4`x^GT^yav$jFIinQNH+cQ%7r z-`NsW0LcEzy2FjX@W(_{WRN6+5jr;`?Sfc*JA|3LrIWLztvO-_{`aoINLc{uOP{ES zA*eIxGDDg|LMTYUF^f1yGdHB8>9?;y00C$`w9wAc%+$ri46#r!@Qu@74+*~cLIJ_T zGTcYF+s=06lx)x*?!J?qi=&Ad8C-NmI+~j~&lG3nNknjEOH*?EnIXYA6K{9a>~;%{GV#y|0HYSDD}mp*_%@f*#&#wXX1+}ph-0xd2Le30KJ zA%Bz1F!RIXBae4Z#h{vifNzo+>7?(5L+7T3@jNeb3~aiWr7u}Q|3J5;sSO9{uD5ROy+IenTuv+i=MlY!%62JHmL^x~$BE%i zJ`~m_P!{*e`5~~gjX*#i57~5yl1F8i!)3$$S58-}>JFD2f0anou=w2`=`pv1&n#GqP!Z!nCJWHH`EA`y zi4DEY7FuoJ?Cn9^T{8**Ho$z$f|I^-E2*9Qj?G5QMpp@qJ)8{b;`Xou_HI2TrOor* z00Fxee;KNibxpxUvN1K6bmj33EQGf|J6Vb6mo3Ej(#mhXx8E#a!b*U^07>E14rL4b z`06U@&DS1Vu^FjY6Z-PfHSY1pdtqD409jkT)jRFwy7BGs>-|j?1MG!^wkuA#ke-j*?>8ZJsWW=BJm=6twETT`1_UUM7W?YZTI`iCW-BeyJ zv9|LtZPnL4-Ayn4(pXmP(hz0K{R0S5H~Nxk%ayG~ABs+D`Om}ZIW6L{0^!YJ$Z=$P z>j811k%>&r#HA6RxS;Z1iXgB!z~tjoez{fbeLju%Cq0p0H38A{cVD#tfOYV)9Dv@vTHcuAJWEBIv?Bp;WRIUr zZ=&T$qn#L2O;ct{-kQ1@lDXWE4Hz4q>X9o}^sqD&+b*rq`2rL>)=9Ol)pBj&Varn> z=p`0j_H69rfOg(%VPb?e-x1Q76Wh7fuN^?BtdD4gy_SE5*xY3RKF+ti8-hQ2%0yuckUKV35gMk{yP~RoG=l_d6!RBi!RQ2PO=KSIiG1vc``!Wf94>i>IL+>JB|GYh z$Yb6P_lK?=7h3_n`12bk=DqWk;R43iyNg3;%x*CMZ%_P~;t0il5;~G{VtZm`kb#($fbNTl0IDi>{}l#M((@W$MT;#b z7=k!Y7^(Z_sS~Fe;(W{3*)`++ zNS5`qTG#%B!NYq!KA(E+V|IO0>;w?}kji`#eiSfAuoC*sS&$)+P6#JwJ4mvKFtM|R zY>7;qzkd~ee<`4!vz?Wh?R=XV!67VWfhqWQMl%+3(x{ zo?iZH5(mMDO%!M0e=%}f>KyzizSDW+oCVyX9xs%(e(CYTv+#TP4X6;LRnS$Gjn)#_ zMsxW)*!Z4N7Tdy`!UkST+!>QPut4`4L;LbDzSoh3b5uR*O*nSDf?@{xuPxi z8jPoM3A!3`kBDlU-cZf4a<66r2qYDrflTHuJE9vx#7j<`(%jSTi7!d%E9g=4NW+J4 zAKr{nClOFGnMO7t04a2MQII)Z!l+vJ`f>DEAKvIxxB1{b)<--UMx(}=AKG>#v62F4 z&7Eh-mG}!pbUtl3X!|+?k{Ccqnb7*6p*0bQnFVOW{WPTmt;Ev7xGLQU6{1Rs$((FIxGtuvIJA@w>g|(pf3W}C-DE)CvfcyRk5MBFah~Uv!C}x z+`NR};;TzE9Q||;n{PVt>f@V|PiS%y*=%??vtWTFOfWzQo(nhG@Ft02kBg*4{x?l0 z@%9fk?CtcdKDxiWPEd{f^OyN%rUUuxrfs@ zO3=om9$G(vEUTWH2n#=s_O+>Nic!5vzNcApq$!!<6u?SL4X!l^8T&zj9nSK+8eHn-?+4mM4&xh zug&Q}E6tOx(_0@e2^ItzQx7t#Jr1J+Hm=U~)X-mFLn%_IY3$3?Wl&vs%8OZh$!6a? ziPDYHUu+k{@j)WpckAEr0^>h3>GvjjLR6b+tkaRQpQeTZ)hw+LIkE6tf z(R;UuB;)KFI*)<95D41Ek2X&bb!K;(_xn35kTvdYbCLosTV${tYtg@3%zg(#2X+Ga zCQW?*0?LY>R%DHDGf%Tct(k5O^P9b$tFG)@r(@Ki^iY^SD$GxgMGq&_^C& z3z0o=Q-f0=B&S6);6+$+1NTI&ANLLi*NLs-TEixEAb6;&SnlX;#pG#f!W~*k?ulk? z1x1f({Jp%6xU(DEn3Q9(^1vYSNXF}=JEB&)!z~6v&uBNwvfl|h{4!W9Mky`wT=fM` zcRhGocz$>X*C&1+(YqEBhs}s@ZO|e*-Bo2=*)t+fxv4-Ta0EMXEKS?!&Z20Q#=H0M zmCMr9H2Q^g#gc^y=My@MPV-Umy+kB;HMa+AVk|udjv3Co5LMz+k$MA9h`4cU37H|^gIVkUS=;}wE&mh9^(d*xFtPYY zVFqQgAvkku$N~yoC^NL-IcA+n@JdfsOB15rXtN+;=kg5EZ6BU7lx z6EnQmaQdEQOW_~HOO$KqUcC9TEJl^od{t`u`j-8ccHs8RwfZ|ftc`87tEM16A;)@x z;oxL~&Dcd=rh^$3YPW?9-PW4+HsO<6BM(?mK(?`1yDD^V=8s-SQ54ov4{NFSqcpU* z9g3oick5N^DTxZ(B{Q71;I2CxXbz@b6^|!R%wk-9L!RXVG)LX!UY~{Mu2d%I%Co;D zpCtHj)4($!iLg+M{LxqE85kgg1vae33f5u;Yq0{qVOWcm??st@D?s_M@dg$JQAR zD=@pk{J#uAC7BF?6loVpC{YU;BgFtI=KlX!?|&2~mwz8N1Ck9WD`{f~a|b8L6wU<{ zB$KRkmD)Zf3&9|_fOjP_Y`7T9^av(9Yp&@4W{CvsniBi~*uLQC69=|w@b z)4f=hHl*I~!yx8njf)42HL(8iECP!#`P&SvQ8J7(?gJ*=ovHnn*KpIHs+a{9yN^h* zd{XJo4vMYBf1{DROmbGVNlh?_*=|7Zr)olMFd28#E!QF}Eh~M?%eS!~Hc}gprgg8N z?fC@BP|L5aSI!rq*LS2Iy*~czhW;)jH`VThP~~ek#VU7a__$zgHWpFevL2u)DrxVw zUgE1=s<@BOkK}HM%E~Oi&HaFKXuV(glwB)359F$hfi4ccj^2};@_lQtv~ZfLuM#Uu z%q9f-E*DzHMAt-E!3Xdwei+JEP2$o!(p%Y{^H9EXLP7*f0jIZ zbl7b$TEkjNLhSr`BXNnXvBjF7Zba4Yu6A3~phQ3U^6%pE_sFwlFJ6Ctsxt51IB|*c z(Nm8LGq)6N+R`(KbLYCw;fcgP@g+y`Psy{y<&l)@@utF+o=?3R};M1=8nO z(ci=IZvTjkGY!LtQ$laGXwzs+_gFmF=8VrU6Q`(IzwxTgyX}+ouiPv$NX^U$9$O|( z>1V7KQ7iprrw~IduCW%POHOh>d=mS0TLN|e$6q&;PF3tm_sjU>C*!3rE7GMCP0DM= z`Xm9u1fO~CRlnm5BQ5!AwX+C`f0i0#QP+Or00a)|m93JP{8ae7N%oG(8qZt#TV&eU z-`qGFw7%A=nPkFZ0dUOI@GfYyRPpD6s-eC_Vp=Pzh)$gli3w}w4(8(GdtS0f{u~ef z8HGMS=O}OIb^yb*3Q+E1x!6}h)YI4LHg6cY<0r?D7xi#B{E*(Sw%3)pH}XB0UO|<{ zIZr3QDq1VF@Wvn*E4)7cH^o{-kp>2#R8du-5&q3UsPiqCEc{#;(xdr^l@jz3Xy6_r_zavUFvckXCcl^o%-3K30!pHKdh0Ek_;;5d$4dkjmyu1H zwNI_A-Jns7Y-8pwj*}q^g`zvG`mFmlF1%26PCM$SkLzU;=DSIu(pxUkE=40{Yv;&T zZF8FxiPayD^1pHa*86~3*hc|%{{qaC=AR`eytk9|DrdCy z)m73t`i~Lx1WHaBLn#Jj%{c3y#W0Rk+*Dxfs2XvX(qHiW$<)H z2qL-yjd&0A9nk8x=c5`3=htjF)B%qH1OlcyE~q4ph89v8#D`Lq@EB$Kb+DJ{JWRq< zc4jOxnDzgj8SZlV>|g^g_MK1rzYV+ykj}k3a6&;wU^`(11r>=t@peKYlE;Lf2(^fk z!KM9G6lWGxY`|3a6=$^$vmM!z;nxcDII0*^&TLcb4>d3#@pbHWXV&!_TdK7ww_7>D%x;rLBe41T*IykF}|AH`U&uyDIk}8 zC*bgt#Dl~33Zvp}TP}|nIk=s(G7#W_<}L3{kRSahIA)a z@?K8@fn|wxg)WjCgf1+_{2&}y{9*K(Rle~S>AI2I$Gk$p_lnm}-=$%`R_;TnSg3!Hl z(Axsu9nKP4WwQCk8TQoD?1*82vD$0q2zQgDQz1;#`r{-g}0X0@h1|}%wOgKEKjc(dgs_udZ@* zzdN+!#g$#3Pgtf#nh4>2nSB_x z$B|219qr7f8MpTzf4Wgurfu)D(AKzh^0cDQPL!+(gw{vsvz?+%R}6C13sJEKd#L$R zi!vTLwY7XAB|v?x`Ca2%;X9pfwTWP4v5GFWI5Y>i=}c8pMs>DS=nWkA<+B`+JR&uI z;E=foNEYM7lzJl^o+q#ozJH8j(6vIXxOdml<@Px>j8+Y_aO4?<^2ab*i0ipp$lv!! zBNBD=)%RZTT)*c zF8L^zTleQNa~C%BCT1YI^zC>mOgdRF2<<2vIHn>#0tm zB4EID1^>`xi=Q9-uER5=2=#Ddj?KF5dc_t!72Egf3(3a{Wq6H9+6S#L(q1d_7@UDV z-|DNvKVj$j)h(9)@ZV_03CzE#Ce%qzd|`Vh}|VZR~1AK0K|o5FX^PfaQ}H4fkG%9!QebvLQZq3?yr177X&i z#0vxioisLs`1}+85^GnXiYjC>L>78ZwN&_dfEieMbk%nb;c#w_jy) z7Y-8+Ny!1BFP~M)>O%8??w~=}_R?b4KIgudhD{stWSu@)wZ*g@1N<)+TR#oZe=lluY&wt}()nVfaMkNfX-C8chIiqAw%RVv_R`BJtoZ zj@5A{A$j`bK?0mxsg6W-AAi3$g8*Fxc-_I#ZX3J_VeId7sJQEnjz@8DYJ5Agy*L`d zeEV~hv-&(*9Q)e4G<_p5dgOn z_kp9hQiNB&1PGX~i7ouQFo5EdcN!VrEXLKRVGTbZ>o6)1ZXnn8jwkWhy^5pu(Fb3T z@Xs(OUxxN)@yU&;d(FfJHa&mwaqNha`uTO`#_va+?v`3V&)O)vYW;`5O{UAM{QH8n z@0CE{kFb$NaSc1Iq^4d<@RQ=To$nkwlPnieOvELhde87w!?Ov+jFBRYBys3-?V(jA zVPH>!&9(PEy87+cP@M7(Nx}T-RN2_#$`*GAn-Jr`0h5nU`Q=uz_xUv5pY%im;1f;m z5J}ZEe(6gV%xCLITVF?p@rQR_wtBErCr&fO`4%2uAYb2^gN7f}*D=N3irsQ#!v+DD zVw_h`?n_)4WM2E>_99MMP#vHp9)IQDd-kaO$B6sEQSCU06|saCCFW#W*X~ZZ&m+ca z-As`UQ1@|q@?C>JoC=nyyueAm0{vo;1Zp z&8iyGF-#X?jgwO({6@8jZzes3pfdl&NCUQ(?pp$^!qUF%{|9n0EJ=iKl4hzgtWd0&>fd`v}Hi|?Jc^8`*d*$r~hu8@yWwXqa=k2WpY;pG-K11LUc zN6V1AWTe3vcj3oEEU`6R?0Mm0Ypb8JIVLcP-f!G;ZJ8t`IP!05gnSxaW=vL2yB>5+ z5WS}>q1IGUaZ~0Y+4WPTX3(Iql9DB#{LIO>bDqNJO%=buz(xqn7X^L8*}Wvc;AC-W zxttIdLq&J=LssMekh#X9zW%mI%+!mz0UbaQ3#cL`_S#)_?RGtEc~(w^O<|hT84(|y zJN=ITiZ3S-Y}p5IU&8v~0CYeL={kxQQXKM2WXz;5iP{JZ$oz4#NLm)?|53aHHd`jv zV}6!`e)eB*1^^qtc;yg(c9FMQ;ACQ6Tlp&YM|HK-1qEtv?)6{9*~dZ^4PcrB5~w{Z zO^EWtXOeovrzU?YGeh&!lR+C z8vKXm`~S3g-V7R1d@VX}i-m6v)bP)e7D8!{{ww1M7P+wQ;V4(M~lotVREBG5ejF zHxuhcw-kY)J%@@%MJuw#x0$EeqSj2ehWX88v6Vw-;`+3~^$xKuS~U|wtG=2E7r_Mq zwb)v3K#NR!>g@I~(|rkj+dl-9J)%!ezG8gNX{Fyb??u&r^5DzsjasMc#=a<@+7{8Q zN-ZnR7PRCucCpmh!rmR8Io^*3CTJ6`}@yh!i7?W@U^tuW&z6D`Bkuf5B7x@~LdMOQ}mO zze*#UpCP*{nrbSp_vvK2#_q%gm6xfA)&DQ)N{ZG0FB5(JHZWNIKUV+0Fz58!ue`*1=JTFT|(vW$wn8z1OeY9Gtx=l z4TsK64dZ!U z50&jwqAX3W)Q=OxoqQ;)O`t69lk*d|0MREULqY$C6RZNwa~W3m@}%^+@;T{Kcn3T1705@j8Z)@F~h0puHt55%3QGH-FD_T1V6iLkPm&-&88z=U=X5$Nx z{xACqU>1`Hm7xm37OLXB^1?2+y63y`$8g%>Za=5E(l4wQYr%M>RwZR6B)MD;zu;xG zcEtt=T|k?2MU-Z9-@B^EbPvYXv1f>An1?7-NVlxIBmL1_Nj_akPRzMAZ{ckIS160m zfWVz>I{}@{tE@`qYMbMIrJk(3kfA3+Y;uEG>JHWFcj=z<^_Mpb)I3R-5PI) zM(&Pv*AjPB1!Ud4a*)2UdRqc54sHr-`-c7Tf(B+vZbUKhV&SK@#DpPK4MH0qZ#++8 zs_|OLaA362alpRXDI#%?cs6Gx@nth*uE*o?g)CkfN+N6Eo-jZ^M>`i=Q-qV5rMa!8 zog=I-b3{6u;gAN)zn05%T>&c5}bB#@)Y>qTcWaaHANDMC1yl(U^P(i-99 zVsCHlf%9oA;t`E@V9CY=4{_nQXL%dgrrScdZSa&`6VW-z9=wAPURT0DW280G*2HW< z5hgu+=x6WZ=wwDlP6UF`Y7{VWG=qBbr39S<$UqQQ7}{9cI-~Y*W4IF11{#So5fvFO z2r|yqK|uBF5N7U{PR^FL=7<^i-@67QWdW=&eWD_Upw6Jn3~7omMmj-gnn9eSnH$p4 z^xIb;fB-ZeT4?8JX6j;MhFFNSZ=C*mNbt?!tXz<4)YfUgnd8DT+()?E&UWOKY|tL= zzLTAcqlpPuUg9df)UIZi-d z=|96K4Fk3f1kuGLh8{LHNbL_&{1?vw*#Ex`FM>jfv<+{7B#LB!$cvDlP#EVvA;Vv| z{~x>=w%ISn04hEV+H@;8=-h5+J5CHR&E>2KeYbYZ$|x=^a_#lc!N`!ak0Ye*6LMTV z_LCfYso34I=F3OqKN25!;gkFGQ_^56DEJR&oI%;z1fALxmi^@9*&DX&^0mU0ifU9V zo^Dvj)BE7=+ETCU@z6pHgLtDyZ;W}F3>Z1b%6&So?an`rs|EPI>1A#@rX|%7A70%B zf~gzVymU1!WZ$^meAYS+ zjw@+8-GqIU8y|}iR&ZA3_42^$ZNM89=lFz^2qHP?5xwM#`>cBhFCX<`DiU~4=dfnC z)5?iE^4gJK=F-28_g@(ea^Wz3O^~p56d!ZU<(f_%)o3zO*o9}+sSLc~)NbyB0fLk{ zQmL#RpFJiq;$yBKW7R>3@L4k+7C!T!yT8xp+(~Pb3T5`~S^@GyBcvCwQK=R_;lE!l zMy*?d-2|I+45jyTQt3Y$s$9vah-dSBgpAer!^8_Yz18d^01n0R_90rk{MIK!_7f>D zQW?`+<*1|Mf+EjJSgJ>Cl{qzgbK?4Wh(uNgtoqCctym~pna9qQZtFaD8Zkr=Qb2X` zD(xqqI{=Q&8nHb#025Jj^+%nj_8$*<3ddQqB^AUyD^oL095vcF2N765rx8K>X+Vu1 z@{nrrm!UdY*Az@78&h*hS02y6LU{YLla+XW*+Psjt^80mzrDvq$OZznx014{5e)Td zTZCi>eFCp^3r;lUo_xz#X;OVYQ@bq_qh#%n_9k_W|K*A6o@bJC9agh~oRumu-Zo>O z4QR-RA1Mh25<^_qe>9srG~?FSKHW_({?b@h?9vcr%l!igQ8)UMY0H(ZMIVYzYWdGS zA`s{~E#k5Q;mu*lab$Yy0db;{iA>>~JKNJE;~I9`?Odt;xAVb+z}D3o6$FIAC92GY z=E%czbm_Le78+sKo&vm-r72GeZ^Mf_KVHdg`^Mg{y1HTXM8u`o2C#p7j?5!GDM@+7gY2f>6%l&QyK_1(#(Jh*;?jsu{B~RT&S+!&EN%`0 zXa$>#8+?iXAE=d7nEH`CcrYt{kmoA!M$A))nn0}vAlu6=Fxnuq9iteOAiA&X*LVN_ zI;NQ)E)Kv9yd}`0I8UxX{+3`pslwk5sWuPlUr~sho4*fr70VsHt(ZJbO}IlV$vx4m zt)S>JjlY-I5qEZD8t@0_;StR(y6vh zl_|I5*9Gm){vunP!#+k)Nifq#qquvoY>ti+Z;M;qK^mFI8VHW-QXQrz4Ajw7%d(jK zKojo(arY@MPp`-5_vsXTsN1JifBJZw18JdNM`f#QP*A#VJCF*3Z0IAOKd{o?muuVZ zvhQH*^Whq`V)x7!T90SWtv?`t2{46oSxyqLFX2aok zdAl_u<$2_JAnqQUjT_y2`m@(jbhB0@`6Q22fB6~OJDrc$1SV*3)9E*k>AX{PD~iFp zDmH+TR0tyvtna5$!@&0iorxVpH=jrl(jQ!}9$2|6sfS%orljW+#T%=T!pJ?G#!-Sc z9`(?=4V#7LXG!N9?NOGHyhV+DWERSt?CtA3bCjO5?lL;K>d?8nZTIiL<3Dp^pckSj zmJ5$@Wu!_YUrPvYb9#T9PyGbjIo(*>EZG&QmA)ofuR#nV=A+B|2=x*K1Em|8HW#(K z#S81%CU%}yp*_gmQMoPseN=%hOV_RJWVjMGx9&F{D4NqKZ_2fZ$|*9_HG{92kjz{P zboF^-#?vb~cFFEs`DlVYs$$2&b>j@a?GOr9mrupsWs4G3XIY^`LZ-mj@)7^;Z~&!) zSExXx^}_pN95&zwvkpP=QT_vQMZ~mLR1uvzArceT%pJ_d#rM2qkNi0v{4)xDe$G+e z&g}~^I$vJyxFRAmkl6rEDI0=pITAWIsZp=-HjVzO2IGAS$)c*8#T(m+TI=`#9P@K= zMK8)^Ni3R!yYN>gy^ND1H=??7;Q0&9Hyv?ITF@*F8*!jEd;qp~001_Cy+we!*KepV z?n*psQQmG=x3#=$whVM1DO+{nE+V=bQRzmp>r2U>vN7j+_XP`JxbEb&MQ@i+@eZVbnUFaYrnxk0}cRJYHe*H zll?7mv8P;^Nm}cgz4m)3>r!3rI1+0ATz#E}su&0Fwu>+BraVgvZH(>%!3yhgInv|J z%G3w<$g!u{WIgkc#UtAMz~XczOHYntFO+}{nh&uSBk%)WYJ;tfW;fXWpMe0<|5FT* zz9oqxvL;|4ctXUDTT95ikStNve{w(juApMSp-n`JY3#Sslhx9Ms5PG1kfA=iPvUzj z7LRrL?l?hj8@G`uRO5*m-fK90&$6X(nbvqOaMq|2r{}C0@o0*ObXMEd8=N3Z8OKpt z*yR)SK*eeuCcFU#n!gB-e(eB4Wqm{= zwc7INlCgQVe(X{IqE;x1GTyCMsi!0=Y?sV%+Jd|8Y@j)qc2zu{JTZ%L^$mHJ3lK&9 zhRkjM%)E@UPTZg37g_F&AA_s2*W#st)9H5F-8;o2{0F>c4yPSD$5;uC8NJNr$s15d zY=ojdL;k}1h|wcwyY_eW?MxQKeC%JVK!KL8JJ^l>mTxlm5)_4Q1#1^T*@$ z%ZRGFT+v6$*rC0yC*K#nWwQg(h1g*5k9at}6? zT^!;i^_e&dQMI1RRTA%Zi>h~K?>9i2Flv(qn-i_%7MgTQQKlBzhhjRp^HOg$?%&UIWJC$4I_@bT}2LpR@T9Dos!A$&_7N2*3FL+D6Agthvg z#|^fC#Q+uqSPWn>@befzS>mFNi%qa9Lozq=^;lUz%P#GyaV4>vH~JhXPua4cDVLd! z4e$?z_-be)n$KCSM#z@VH<86JrPamPshJVVJg%9F6euGQow|G=r242oyk1~f0lMyN z-x);qAQ zdTJsp{5aazrmiVQ^(y(EX3deNWQJ4x?r&K(Fyi@P1zP zE6H}?{e}2rZFQQ@ygM6}Ms%NRy4n>;^W4wAi;+I6Yf9ZIBEh68)*!|}vfiJvmuHt< z){_$%x_)lQB765kX@xw=Dc`pSOADu|`YN%q#H>C)#7$mjTJuIAs6(fgu?Grc<2|i> zrf$>_V+nd+;vCCqc}J6DvQSZQ%=N~2SZqNPlyVejzlm769d35eh;Sne2jm%U7_YwIsA$b>4 z^};cO09viX!s>J7Gc$WOz$#^Rp?N@e(4cF3X|ZdcbKgtDrVV+rPM@q=W0Cwy7Xi>t zsopOj#D+tuB0VusQR2h63(Ykx+??QAm9nQ{-a!hu;^X)Nawl{zuCja)77`K@bka}V zUR~DvxkSd!5y9r-4`o7o0##@=>LpHutM!x*Px9y2ZY6Q2DI3r;TG95Ts1xo9JUG|~ z%^vA7w}a2XUt3hzGGaW~#c{!gii>SgO`Kz|`S7C-XL)p~!N-V7zr6R&q?ag@$el_P za!rTY(^^9guc9VjeGp@Uf9+${C|2eg8;?RVqYDa3!S!3 zpxtNQ@E}tPBjcOJdHDQUb;spR3iIZk6y$7W_r^o=<#o&c4;DS0S!Kh84h58%bmqC$ zx~aTcVr}PP+QPayqX>J^{#Up;&+Fu|R)tvWmPOR}!v`n}JM&;q7(al(rGkca--X2BUaO6lx|18_pzeikJ_8A1MpY$aJWu`A(z z{Bhg_TsZ&V1Im*W!juW*dX(}Ms212g<`^Is?F#uAg_1b@y8M5?!uu4RED@_Az?@FL zu8Fd;LYwD_pr|#?cDKe~#du8CldP5O}-+k5uM(VSIG?he*B%@<62G8oIE?)zrC%AF1};4%Asx{oi(g zUN-gvBSNC2?g9fg3W8IgU2ZVh@&ls^ICf??MoYqFIYpxwR^RF8xF<-b)@AISv<$B+nQ` zui(SeN({Tn*+4a5JqBFU!~$t)3#D*3fpSW? zQI|eH=WsjP`W_k1LlFuthSJcE*bqD2y2rkcrPMzvm+4^fj)vO!JtPC$PEz=l3Y7hY^| zkY+Lc?lqMT)7Fij$8Wu9l9JN)bTt-w3Z}iVl?*39R{_2eI*$*u4jp*zy>H3qrQ_(7 zeG9pG`1U`zVGx7=2OKQeiBdAzH@L_j!`@$c2WhI`X(#!u47a@0hrB3#3I^RMnmFB69&83z6u`pc|%)2EujZ zo7U#+9hjb^t)jWGwFuXXtWTzBF2*A;}jGyMpUXfkP2RCPRD$_a(tNFhBtL zZ~R9$Z2Z5U$_R>$|My$s|54BB>(B-fK)toUN?!*llbYV@o%ZtHuN9ujBDd}W^+vCc zUBJ^1Ooie)G5HqNiWyLsAYn5$s4zCDFp4Jk4J4^a`|(HizG7(6E0HYn99y14g?AZB#zemZnp6_F%OUHgt9S5Twi{{0IV z)|c?#2q1Gl7Pf7!G6cN?7|1@ZUK7x8wi9aQ-2U9|)-XI6e7)PB)9vO&rWjilnW~W7U_{+`(7Y z&}BfILs7cbcABjz(CAW;#J*+XfWJW#3r_zI3-JZFUr=1`@P+&;Gr-w^gL-AFBql!< zK5vq}W3tBcR{j>5Hug6+js~r-wQ44ruz>vd{l;{MR__~Ck73K#k1}E!v3_vis@Dy> zgbPWZE-UMq0}ux}E9hC!UuenqWuwNYW}pX9*=IfO-MJS?FoY-7&@9D}c41X^x!Ifb z%J#yFGzKm{_N6fhgiVeo)gYTSI6k$D3RrqKX7p-5eX~>i;Ha$0RCMH3 zNmN}BSoK-=ZCrSvB|_sCo5O8ipVJi$GU5GJ#3i)_O6|6B&rmg&+=D>UJ#qT5dwH=W z2&=R#b*ifl=D=Z<7~eg9JVZmj!{YmvZ1xLJzC=AOkHm2jUR|;~herrT2OoN640Sh^ zCh<;09WRKuuBJme4XwM;XAVan4-}p06Et)X4C}4#*4sx`;L>Q(SrRK7705!4)E3d> zxu~C0>lL8N_6zazXWi;F)`Dz_(7hmlR^YlUkMSQ`PonADPg;npj0Wo!!D0Ag39Gz z4OOSW)zTRYpS11M6IM5cMhSvTuY|EO?4RO5u`=x6BEz0}ZLv)55XxplR410kg*3w3 zmnV-Uf7i;oBAl8FR6;|Kt1?Ngj&BN1fmTM#5ieq^qGhMpjr;1=&)q0{J?rUM>qZOO zEERfqUmttd&-E?<6DuGLa*>o@Td$liLa*;gJ$ilo*$w?&NN%d#38Bi@Zi-dz&hT-8 zXESWP2sGd1AH0lOy{$jA)yIyYH)_wSMk+dF_xfvmTW(6n#0Pxd$2xrC{(uogQD~Vb z404g6jj)S0%q9l52T;r&(8-n{GkEQ5!BgXek8&(8g5H;1~7>{{7*3?YhIjtmiK75CVPJind>miLaR z*)P$LA=#=9SxuuGpL>UMiBz>X&(+S$BzJAzepzJjdA2HTXt%dUl*xycMjN&$@3gcy zdSf<9IVvbN{=9S&ony>G{pU}N4FgW;C!qh+O+oZY?3V5VW@g+dtH@|_?}FwY#RG@x z#9n7_rJVY>C+p%-j#nEvn54ovRaqC=3t%mAuyK}TtOcFk7|6OFWhhKgp{(tGY$aXG zB)*p#MT`GwA?3^&=VAZ8_?o3S`X)xz$V!a&N* zB+mxa_$pd#Il&ObdBRBDHz)sn3{L`Ac5Lf;w~Mdbfz1aUF#HyPfNzo+>7?(5L+7T3 z@jNeb3~aiWr7u}Q|3J5fEI9XX{8;>ty(@voVr%0wy!M?`A_O-XguM(y7q@{Y%E+pw|L=Q^n3V?TA`Zgp&?32$?rpDljTv%A<7p|+IuK(r0!c$`5Dd(61Ld2sC=R071 z)v?Rb*Yupo9wzY_RtMeq$efvBjw_z5ghMd*z`I7oFzNV6F1>T8E2Wd^q#rkfg!3Mp zcUcbyQMMfLV(F{$-CEj@_b+$^PmoUoYot~@Jy``pMWU`$ttNC($ zHn5eIy_b%&U`mT0nuukDpqiXQQRa@*4fF%)%Fk}4P{vd^@s~G>N7$hi zbI^f1KD&lw0xEO)2B;-G#ZBM7Qrnk)9JTEU!4W3~e>V+L+-C{vV{<;wBOri?arp}; znxE`S0RpQZaId@mV473wW$a#p9G)&42V39Qk19!~jBW;Pr9KRWieGSG0G{^STe?6# zFNEf1=Do%P=BvqD7(SdWlkV^j(S417s(|v$Rhsdmw-yr;mERp&D_wh|fU5eeL;Tr+&}2mrE^Tz=jgR+5I-?5R#T6xq3-$@3!7nt+{2 zrgZABHc%v25J7s;h+DoGpwV;#%@{^)8!8vZRGC2 z{2mUosp zwd``(5J1umlC-F}k8VI&w=wyhL@#GCF^qz>ZioFrXF6jI`DnY;Clrp%M~iu)y&v>y zOy|ct(Br2yLH5oVct#@v6M(5ZHoc_V(pTJf>VxvC!>VQ8Ec;LL=Z}(ejs$zJcSS+U zJ>)nTNJ`B+CVgl-L}cv4mP-E8 zPE^~_Ba+tc%FUECp;Vj%e!Mx_Pb+xX@7~f(KvNhIAqkoN{;(V{N7?AEm+yGAU#J>& z&x>I+K*cTVw1lP!)!ld3gZHy2$X8*c$3@1s`-k+n7(|=KMppSroDBP9V7E7oyW-E2{|CU>W*y7O)?tACfyY>2Q;^c z#%{@Q8{xM(xk68M!`8m{R z`YoeZYwDy=eb3GnMBZ1!WCH=!>y3;JTW*m(Fp|CU`2+q`Uim5Dq8-%GuVdV7nW}gL`;vIrscz+1 zIlfb?@nO%^03#27<;J$v2LaS>4K-JK|MStiIwL}CGy|h*cIYZ@ioAMh&l^ysEQI$4 zK2ioWyCS(A|NnTgK-W7wLwL1CRiw~6oOiG|YYi!p=6Q|>mL!SE8QqH7%enlBi1NlN zG5UVzFdMa&@8DmNb1w9hs^OiM@@N-l+0FH$7n6eyaBo8v*M*k|2$NUF^`>=a*1rCV2@ zcw|M@78|*tyVx5yQH7=yz#;JyeIt_TxB@z<@LyQ*=JJjAr9Lc;ZZA8Mi0)Lb5dZ-6 zS_vdT>FHP#(-6-4trI*<(xb@7Ej=^haKnG+KF};6D)kLcq=KqE&z^qC8p!((5p)Cu zmxvD*_gnvr;Pi)%GIF% z%0IpiBAK(^LJaZ<{@s27=F93Ko?V<1cV2fE?UESFXG+T&Xl=`U$iC_0L+vYqj9$Ia zJcW!>fb8Y96xxBCO6tia{pC@zB*yfqZQGg;nOcJ~nHw|}T`UYNRqsqel7IJ@jXjnn zg^I_$$v~#?K#4Nh%Hu}fR z^x*4+T@EXzZ(qLJjzP9e`8*ClfYCZ7_^ljE&Tid^G%6Kowc61%9YOaUrMr)BN_rXE zKyhN`9TxxZZ^vx){I6*Le@X%wG9i*~Vp75!0!o4^B1WPR0#UTtDE7coU;zdCW`!|K z5W4&AJ(mReMVHIGG49dMXgmcSOe z_ODgg=o^~r8yhXGC1ic?#fl2&9UE1RYftg@NxOD!6xx&Ncet@@@Ve~`Sv}jgYk|Vk zK!)fGD#Mw~OAQ;0K9F!eavkk)?cMvEADv1N)2c7AVQ`JNP-tFBut=L6jqL1At*sVT zqZLdTZOc=^?Q>A9F#Xld(DZVf$~`=OCwjBFwS&r-Yzb)Q8|45|VxB}GlJ5bMIq0lJ z5ovg!uRZ`vIlDu)9$umA!J^G_d`8NQmvH1`CRg@tnTz3ZkM>zBu962pB>JKdX@+Jz z_z!8!p{#QUED`GRV5zh6L5ZYX)fsp=yVs?4|mhW5OHB1-2Q zCeZf0@apJgnX#1*m6s?TCqQ^Z&!Kqq`!{?1Ua>{c6T?z!ub|pEI&Y_sZ}UDVamRMWVUn1*LtY;R(mEo=L|I?MbU>efs(K5ecwKepK3ZO+&>>JWzEl4N0#a*~D)0&X zN@W=ejrDz(ExTydW1C`)-#cnjS(uZ{fDIv?KAU)?@>IxH>r5l{OC-Pp-! zly_)3hs|=f`wJ=YWg0S-FTG3Bo_U-tCGmRk0P{hQiZBg_ec2t!u}9L=)+xnb_%i?B zUx9#5sxrO|Yq#?1XU)%T^zEyphx>&4UWT-%?~}aa-qY*$m-6u3|FpS@Z>oI{u@;w4 z2({s%KspM`*JBRhee>=yIMJ)sh?DKu%w~+F55JxAm2SKU#`t*98Hb!(*${8_r1}#A z0L=Y(`Pfwu?0~2XuciLGt01e>bF=4@K~RlnguE_8^!&T9(c|Zy9(vlVn8brOEckk$ zT+Nyq<{#*-LR1U?g_*y;fstw-qsUCy*%PAech8=)W&XvJ!qRqyeE02q?UTdDC63hK=$)y8!hUEqBpxy5U!=Y zWg6CWkLCKlCEAl5U`y$BWVhEO_wW&DxpiQsX4EG=z?xF105|H8XG_iCzi3YaQ_g54 zIF9~_&`|1ux78kLYwp!^U1~{cd3dV8Yui;Fw@EjQWZlLeOjv0vrAVwr6~y!y^sW*Z zxY%nii`&l(*f@2UmNkLk)q6DY?hd(9V;ycT4rBV-I80Y~sQFZ=$|hxvZDY=&Frl2_ zx(OG1CNhf^HC1;a$-M(MMz!KqkENfFtCzxgSaa+aB*(7i6S_{Ro0>ix{f1nowxL@~ zgW~R>;-L{Ir~F0XH@)U&4U&S>LyimwL5J+N9Ae;aIfJbWk5d( zNnC>{^FbvfzFA4mIoE1q`&MPPZOmPRJZnSC+fUla7r*kT)vHpkQ9n@s(zkXmfTPZ8 z9(?n5Q8UGLf-?+CPPK#AZsWf?GV(x!`av)0gO(3D8(!MrUzaW zuG#^Uel7cuHky5kykXuiJcNAH-J@4Rb}AUiN)VS>AR;<327V?;A()}J3yV%X%Pc?T z-~4?1T3eI(=!JKbt6etbJL8;uTQ5y2UWMY(zW*rXzG-tIM)NVU?(rqgT3Mot*=t8! z8pFaJ6p`kMKxh79^^?B$`LXLilET@)WN2#QrV$&bmZ2IKoqx&zp%@PGwEQzB1LDCim z&XY$md6O{k@xwq)jeV1T(whX!)s{T&4-*{yQMaG!Y#ceaTz4fs-|#+e`HstnwDb*E zs*?B_6RujnWeSAmNo04E7n57Kc&|jHNuHvTc2`Qg!>Xz`;JPFAoXEtw0C}9aJ3om$ z|B`Cg%(oK9Pdd8Mwn`K934drT4B@~c;<5tE9qY#zhAH*}1`)-7;zrNu(} zfPh?VwFjU=sycN}Cg`9}La)sGW91FBNl8}>qU~3CNqfxY1_7Dkn&}O1s9uPQX9woi zG8auV7_+S|pWP|5hAnidGyhTn#dR5`WxjB64i(zj^v# z6aOv>z&gz23$@fARgVav)JE{bp8Du*H0w1}>9b*{d=bi_{K&l>V;!a_=|w>TSn8SF zTH`oFIc(X$>gYoHG(?c8H4^{KZ8e-V%TdiSo!2QawImnW$?~Rn){^`H8o3>M2N50M z=7-@A!AJ&yKy6ZrElnRz*AFd^dvt{vcWCOAj7nisl=c9truc@AwevkjTWBHTT+ikH zRUpbBfQXw70xPe$b6hU+kn@?maJ}^=y>mlNHB~`@+~EDbWE`Eg+RyJv z91Sd_)QS(JEM?Qn^lE1=ReP9-+U>BiNb3{w_iT5U3Q+_L!`vH_&aZW0A4v-bWxMaW zHsSs2B|?-tZTNc(hj+|z)*Qw6o({|3Ax67N_Imj%>rGE~Hr-sf32 z@`btQij<`fPvTfcX766NvoCtYx%f$db|_~6SHgw1I4%hteRn7u2>d!2X5bEpZYUFQ zkvWZiQ(gA}w>K!4&18rl@3$@^P|$ueLy}YK?&M)DNK3f-{9{}pfwJ&7d|cA@?{2Zn zpJs|ykG0AYUXfbqX_)l}#2|ij$3{g)p>g@rY8Z3G461C@OiTH?Vq|o<3Zz zf+h#(BUn!-R@CMc&!u5?p(A7ujqCdMO6iO5A_}ZlKfQ4|X_jVKm`?P-efI%-;-=|` z9&>f3*wy=NxxCQWL#mb&4?6_63-8|hh~8y|B0uC-gaKj*Ru%W%^j}y2-uWTAi7hPj zf2aRY%tR0fm#`2R%pP{CXYtdKihlRVJLmY7IQm#gZ@Ds`Hs+g!|2O6Sz5;kAO;Mqq zEEiW()jmEQ#;SG8*ECngtk$Y%+Kk~^*~LCmDrhDZ5gep$I&M)KaYJ`N%y`WmJLh|^ z6<%K1JAK+THQaCuWCexR)x8q`zOrJ+MwaJb&2qcm1H!e=#^zUo#Dwq(uhYPVyu zOi+)o;_1I1)^GkAH2Vt^2Eslhs~<$OAoP=vEPrNwcM+ciP3xc6)@c8KX*?BjfV`AQ z8jq4tgJ6`P5T5}n0r=*aV}JOsPyp6v8dq#fsp9YEJ(>-7@dwV} zZxcflUJnFKP9)ydZ&G3zZ{yx<>QSH3quM#B$m=>PY&aDeew82DWdji*1x+VJQ(-jU zAsQrupg*j+MOrKV9Uo(C`nA{ygTP;5TRVzE*eTijb(5YpwfQM8R_gEW{R+#j#oM#g zhFbvLoXj|Ic*6&Cy}0!74cDgq^iQ38942U!knQMlh}gehqN{!F=h6B@WX22W`}>Q8 z(fcZ_#FzN!t0Qhz`xXuzl*R_$`=t_JIC?0e3I>PWhrRc#tM+^OO?0t0Vs{A^`q^1PTcYbf`ZpLLE)aiL*Jlu_3U&rIfstFJ^EWGYHR|4Ke;hb5QZ@2&I;47RYv9LBymBZuYk0L`^ zjJuOIVtr>TC~O=gZdozj`ZmIr8g>xaM6CWi(P8BL zqpND7xLI6vDq+KP>(mPbxmU}|JD|dImWKF$WE`ZW!T(3c3&TYUPlicYc4}y-Abh~X zmWM6$9nc`hHizx(jd18Q_;bSFd?-KGyjOKuh*6$*oyh=O`)0CW8Q%TWKcO9 zeHTk3D~Guv4_VLen%T=AFf$fOdfqT6^No`_sfCHkq^)}&zS}nKJOVW43_8TnK~sAN zJ5z(h4o153Pc}NJd&tPh9^q%GFhB%?l+fvrsg=HksjCqLayZ+F#qOc8|IkQj`j*y* ztsDr^ae1HzqSF*iP#UU<3OX4ZK|NU5lSOYr83EEyk46`;GdgtG>fooIqaXeCJ#==m zx%~Evk~6IcphDS)K_mIA*w1zi_le6J2ZBlkT$|}Lf8tCWP0hriuWsD|`JUFn7o|!!}7{Fm^v+~=iYv6srS7KGt<|z_G2Y z*s<54w_m$)`*mUaar2f~eGbXw&w0O0{d=8Yk2B9sHcUYF=JEmGEBFbQlV5z~Vw=?G zds%dWRP24dud4tDVK5nl=LOM+T58BiqUw~CkOimxpq8-XiRFa}dnl^A9-9kR)A1fa z@0k9(2fRDW3{|AS zX~r4rjZYN%Gj33}on?&i3Xrvl`goT$;*{7lG!P(&WGRHs1YrGheuQbQ;jcLo&f#zU zHNWel;8SVQzU*D3|O%$Z69$ucqsZ>x~d;a{k zQjWZ1qz?W;(g31V?Nf7!0M_|-qxb*Ls4Oy7vg0I_^la<(BR;vFt%+! z6wj$;B{(9mR)O+ z>d9Lc!ElpkGm!sPZ+`6Z-)vA1BF9C5hynu2Tc<*&ZJ)J@S_IHqek>yu9>g`!wO&4S zI{b_wP3m1`H08F=l8*@xPQLaoxbph_38K>7CxRarX6`E5xvP5s=k_%<-;Wo1c>(;Q zmIwz0Cx)(^%aoMz=GP+BEfiL{xKw3iL zVzF*4^|~HjH}hQS>W>dN6EAp4V%bH)Nm1q4qlo|){xsYDs$te3lG3mE;ftn$0OGFT zi~N{eK#}8)huLPV2wkiFUhhbzyU2J~(E}3uc4z;5ZqPH7*$whv`nd-PxN4Ns^G%Ky zK5rDhZMgQxjr?6gt;~aYCr)mwHg6*SXtLn>OC~)60Su5@7CiXNB>W}fQCV?^F!=P= zsGz3xe(Tn;KQG-c8qJ1xR=n5CA;QC<4bv|XaPzRKGx|$3Z*|6f#MA!P#vn4gal5f( z4ICCPeUDc%HRaCr>4A=yg0$2+CAO&EgrIk`^i)&?0ZRF>8pQd}yU~*>-*Y@>V>3pno zrv*mTC3elv+@GEU0*|+&NQ43k<0Egr4`&Y}^U;@ByDX@F4ONbBgKY0DAGIHB&+kzT zd57_3J0Ww>hBr5EnXP(xKR5pJ&T$>57WeGN4eC?X9HTFE?%$1O`ZE9GSMYw0zRsED z=2mOJzRjc0T2SZkOO9JL>>el3d1It;=gdAmO=v%djOKtGRSXf(5ra5%*r;NQ59M!i zmul8SjtQU>XkeEU4bIF^9)B|6#`iI9d^!Ksj!TaIZimjW4`%*l^BA}L1TK= zd5)2JMJZ=A0%wPkX6stdCblM%Y9sZ#41<%!=C&OdwxthjvyE+5x%+Vo+H(9qdcc^< zh+qbo#)So{&j}L1(y$c_TncK-b7{udN1O@x!-iIm;}!FI#U`%e=}cX)Gre z?1g^0&n?`|-ym44Bpb6LPJeuM-Dg22KJC@&8W(=m-G0 z1Qlp^k)M3PYx2~9EdpisIi3eHU329x7I#Y=KYKs+rJik;YbLl~_T&zX*5t^M*QF#( zJ9zSH<*S}MQS=B;bWM@C$$KELmT1T>$=+`>hf71Dxvi& zsZVjZeW>Jp`$`P&^zan4C_sv^hh8{fBd`A))`GuPSp5Ire3OC2|HtD0!&3kj4eFPo zLBaOLSp0vOA^+?%ES1LM|Nr`NhHU^4O^BAXsaX8~B_-#4W$;Xj#s8lt$se77@qUqm zeIlarBV|C-amn!iH_VR;I{u&Qod3To9*h7`P4MrF007Gpj9zPNG9SJ0j&iljrhI3d zlW*&#NyV#BJlgjkh1@r7F2qQdfUMtpiL+Lg=wkNT5tqiWa0j`)J$?zo6mcwNg-?&3 ze6YuS6DG0*tbT8v4}Zr5%u!hUe=OS9((vadoXxWpF#(rfPP&}EDAiLDax-??{Lm@g zi`(*EG&gX>ckCNpzAio!z|n4sR{~xnxjsv^Fgok6=V7M2^P*|YM*3nU6@I4QhButS69f&}rpchkr7ojWpdxs2#DPG(;VbLA-^&{F z&%hj_!R4>LaWi9Y#@@C^S0?h(q=`4T#cQL!b+QXS}_b)S$(K=h!-Dj` zZwg6!mi1z#{+N5P)q#ggkzhK>jgMU%_QY{_ug9eCk|5h;PmplH*z?x2TyJlT-VEe8 z^-F~)2+<#l*!5Q?gy(7xI}$K}eYshp75mD5$|pCP4PS3KLeitL-_Dm@=JDDIXfzSc zOhDw(ls_^cF;()cf1Nf#D(b}cfgU{0s5M^eTscp5B~@;`mWYAo6F_Y~9+lr}ciET3 z!%lT8$I9`YT8$5Tt_B!+_$xQItv(3Nlt?R)S!Tc@O#YP}Fu3zcqJYkb126y~C>8Qb zQc7YCq7YOszCZpn;E0ZDfjzJkz)}DM3c&I?ME6=u(YArCISjt*`?Ai8wJR5!v2U&2 z!jf;p6Q-hSF8Q(`(*Azz>Q;S?g=-;9Jd3~R491SbUzD1=SZcpu*w!N4(qB%+d}L3` ziQASNstb=*jXQ8ZxbJad*8`%;T@romzmfIMT~7&D^EMw&zvF4Q*22db-@FXGN4A80$%maVrc{O6O^KsgEW-$|zbk_pz2;H|b*cY~3DZth{2m7vwCt<@BqFE0* zFnU*hl4H|s17JUv5UCr~@xzxM$Mg9Vmy=E!wN0(6-mX-v-^#GQI8KN(5F-?c!+6h{ ziTf zv|8N$W@MSxSXiak^>57B*% zf2x4;%vGB4qqpHFVs!p)xG=1X`%hX4fp!RsOE?upp|0}0SR(0?NbjK5 znXO?NL#9#hOGA1{Xj<_zCO!t4c&MlZJmC{$z(7L+YSDVh8WL$u#+5kbm;|L~UEJ0G zHun7vDudDVt`#)h?hJ$*M8)%TK`>}F+d+jJyb@;T#PJy&}7=zFcTwZj}|PrvAaIBNCz$GAcQ zW#Mo5xTNjh-C~zN%@nO3Yn3IuBDK=fFzXE_D+we)Fw_AM^bxG56Dw+Sis#a>y3i4_ zhsJe%d!_WncM%0vtDoMuoP^GVjmh@?V~s8WuN*h%R!)%~Ts!cFPM=|&__Dg|9}H$z zvFsIAT-ETAIif=0^D1ZdIavFsA0zj-OMw6)!2V)|m)brGM*C+Ku*APCLcGjLVAO4* zRjKdJBLkgMVCK8H=v(V|3cEaqX@bUB}DUUX3lpJNX_!^~A1Y=B_HTPP#QP zy=!BqW&Cn7f}QCr!|L*j@YU%6VSJiONkRpf9<_0fM=ekLZWHC!*YlTJhL=W&{2mAa@wEmwGH;YV*M4C94zyOc( zyCEy*tHLI*6!@1?0Nx*Tq*Yl58X8s6`JUcgK#0!~bJ5S-jNVZA#^#cpSI$=%F46vA zDX=4!Nc)T&nE-2I^&?C@&r!0uFH~_*+-sK!lF0H~whPgcS?J9?pL%d--C!SY+*ydD zkZtmqP6?ZpD6wunmEEj->}62WqxB!Fz1Az(I)0SmRT(l|27+QvhuGaaK8VDY9f?h% z)tC}XnKX=T8yJ*ykXzG#aQS^qcAVJgaX&K9|5zD|IUQ}sMny}HO+eP`-OoGmh*GNw z>+i-WDtV?iuzH%FK3uM{a0h~k81;(|{8lzWSbK&q#s=EF|3mf+5b6MBINt&5tBzfc zzNY6y_ArUhusZ0*N9N27b6oLcB}D5S(8sIU|4hh21!RxX5~wP)CV>F@(?4wJU}|l3 za#^=%ft~ad>XBzW?mkCdi$7F4u-?E4sU3-|=%<-DPYz`@(YHEiVZ>yk?_z0W<=}r= znaCsXz0|v?x3}myUhmOrow5q_#(B3RIaJbQlUFGE0hXp#4oHz}DWQD!4p7zlb_e0Q zttIQcj-9e(k*v6Q!AFAc{lsDFTov4Gzv?~Zok1Qa#i@uXn4n_wvc{&mV7VJdVseMi zwz~72rU=$guoZU0kG<7WH#iXWGDaVxwZw_M4{nAp^c-SE#$!~SIVNyb0I$IwhDLuC zfk*M7RZl_iABr=B6tfa7f5KX_p~rve4|px{_%f}+o~p#SGg(ucR%kgZm}E0K>c1d5 znv#2D_{?5Ew0(cc`fXV#XUTF-N47(y`NV#{jW3>U;R^~h$sc_cvwS%b2#$Hv26V|5 zI;)ikCnekZ3vMY4b;32#p_tCHH#C(%{!mJ9Q$xwQa)zW zO@nu%_V>yxo@eb4rr-7+GSm6KxOp5>3C&zBvgth44!5zVK<9h9 zLSD6~e3L4P4Cn#SHZFCo_oa{5j+{-(v0cLmvR3Yi^053ctwl{X)F8>}Lj+|}z23;! zu;mun0~7iD6$SUT%{uSwI`ee|7ul>9DleM?CEp zbYmx@QQo2D95&0@?k^(T5NmPygisqE3Z$d3d_Cq6-bcL3DD7fw z@i$2Tc@eQ54x(&1;KkBc<-5baM$Gj-V*^`;-l#$5`&NZSMv|`{^)J4Jy>ut0sRB;z zh?E|pU6%1`+lh_`B|Zlk8Og322NIuDvbs~lvBAhMTkB$-D+Q@4bfdkxQ<%Tg3m zWBap`Pq4h&&O#>;$|}pKgUKo$1BoD#J{%e_tj)m>axH9Q@;P)peH#PY&~JrvblkIlae z=@cX9n*|U4GP!yF5{>_7g>oaYLTw^?Kv;&4j{BDfWD%;OR0)}9SIzTTe|Z94tss4t z7IN|b(P$7ErQ-jn{Z|zQ0p+bzq0_d{T171aXe~dMkqQsu8t7UtA37a=#*ilUuJUZl z;MK}Dcd4V+_&pK(>FX(%>340*W#3gI7!`kPT8DAybnOd0=-aato)UpJc zQ{-nd9sG^i{K8V4b87l<9-S2jFe;8?Z3N4&U#1n9MbzF94|-1hWUawIkK{_ls$1D? z&5jPS)X#@g&@12C*kd1trn*InC0tC6cw_{XI$nZ2{vDK?T1+<-`kDrVAaayXL`M`5 z5n>7yTsPrj&qQXiqNeIjB)NCM#;8`j>aq0marII-4{MIyf>uJ+d_vbLbyL%aqu-Fr z)HZZ$X;9qVbGG45#k7=rDF1LDdbNa=@BJK(gL&=Mv3=9^>gtLmbrqXR?gG2qY7>ZZ z3M|&AcjD?KtF!xm^(FYd@jbS<=LY{bgbM)eFpSm`)m?5 zuap#NrDkUMeZXYbj)Cqi<4xjiZgcgaX00vHXm$uQ-#+Qv@5d7*nI0Zpb)MB(1HEZ~ z?yInFoP&+~o(Z3?5z&q9Duf|V?-*2ekBCz2EZFKZg5B1C?+f}V|A9GwNkk*S`RcL$ zlDq2KKctLb#JL@7@2Hm%WA${pABqfW$&fqELc;NLjroTphW_J7>0VbA|!vw&&|iWLptnW zpnOrQo3FLI?SSED!a%gfbY~3$@<;8T^v0y~YhBn!(!xR6?t89Hc>j8d5amuA{vN~O z9Sf?3>is!JSHIF768ID)9oM?nKdZ zq;45w&1gZZsd)FsH~zzY>xPeE@^!TcenT7K-0QmABkJ;5A1a~sE2&R$xP7SPefvrb z@AU8#wCZqZu)}%xWI&&yRi#M8f8zHng;;^V|NA%U$Lc3f8uN= za2-l71puHI06>Y7UKAvNC9}z` zHI6fs!N7d9Mv4td7T1N$*jmumN&(-wlDB%0MU$Zo=Di$ zz`u4iu=0vK$K@gqIiJZ3*IRGWJ2%u+Qxz1*4c_ld#?e_#8HpZaWb}l2PJr?vxgUbS zCSvvHi4G&@A6-=w#m(ZXQwbZUTc=(i$h}%t-T@V!^XZvbaD@NxY5>+VUSv+A-&EH< z!0ipnWiuJ#$NR0z2o$v6%#h@ix;uGT3;GKGV|qrcFZ<`$e5q3~Zd#gt=rLDkie0_W zmdgu`J)~+m@vuX1yYTM4kLX=iDDp$X27rEA{cT)mp{cIY4JO-Lo<~C}9tnl^nG=;( z7f7~Q#XSQ-o#8u=mp%Bfi8*7dlJP0=3c+Uj+k#`plA;$SMR*)qu7lu<$t_&GS0d6R zPfdA6(HC657(_ySyw|q@= zWz1@=il)sNu9aQvBc+07Qsjsu$TmJ$i;bT#h~H5sjQ5JnxJT=vbDae04cWzKq(zvd zE@?_+%c|j%y49K`=sw%20cNC$Dj5`?~&1Whf zCM6V1fYx^)RRr-Kuw}lFX#M{L=tDXIs^YvL%){dT&r{~lOu#!?M70funvzaV0lZqY zrUVd}lWQZo56u(y5827i_XkQ~4vYU=i>dS>FI5`$mhkZjvQD%u zKeEtH1QWYg2u;+9vng9!@DMAdX2a{sRVyV>md{5>84ccld?})_hIwRRCol$8fc!x= z+@(#*jz?J~_*et|96JCL8?KDeq>%>)meDh_X9h2S+hBCX&%Ul)^XEup_^$;!VvHE|2 zYo2Y;{{JetSrn=i0O}ywI0+%K8-WV$V>Aj2_P|mAO92ci;5(a-33wOwalGb<-YHet z*7KH+R*W#QDOa3e5WZlABN|N3Q;iQ0ZJ_PBFlOEm=5equjzqE-h`i%qDA0S_!^$}D zi8o8UA)pOag_fn@qD^F&poHaGp8b{p9br?|m|CaJnEMf~I18r4g1Bepat0Sh^>(1q zJjC`|+sc|^_D2iC-(Ezp45;SW(KRN-ljfmY{OkNP=keRTo>1%A%_nN;D};>u=c zTgy`hJ}(T%Iypq>nR}1c*G?YM|4V`IcDbMT@*Ev<8i-V~kmTcuiP7WblZq`?9?y%Y z-dE+c_K_s*WIv|1MB41+wKr#NiD3E2doszC4P%i*E|l^kd*#lR^AL{e>~G(d87N=W zu`}?Ni|EMEaSDz+)ca;W7p;Q9FtcpJ;S1*8hS#svIve7&|p0kBTnZ`DmY*+9$xvX=3{ zz@-yM>54YLTV}g_0jj6 zVo2AFgva*7jw1Q3x1zAAxq`Dm)ZsGG^|;D-Qag4qA^1M zL$CEg4R$s9<}PQF@EEqdEBmumn^RwD0~l&n?L?XFuAnum#c+J_0qb}6Rj$vC+pJS+ zQg2?R7S7KQrjMkYitFi|Y*X5I0l+ue8d-TV@8C);t{SqP8?q0)n3<%xLw#PV2-l6Y zSE%R$dhLgfH}R>h=rlV1pB}1~%#8#f%pw@T*FzPeCec4&4=e?+6u?s8hbREgg%%b? zuV1IB^7S1xckw^y{NgP6Q?)p!be))x4x*lNS8hz_!fFGny3ScE6L^n<sPqRl}5R|S6T5(GH7Q#w;hgu0%G^|+Vsm82+3s??0*!JT}_rHa3s_h|Ny z)jL}X1VmB#jcF8Yd-mF1(mr%0q)JwOs5IbJLbc2VrL0jc+|P?c5ING(;tjzN$>i*8 zX(qrtWlf>+v96N?j%{Vdj=c`O{o0M&uM69co43U3b4VrwAQHkiF7E{1W|7nC$@Hc# zgHD}_I(f!R-bP;7<2hf3;s|F`@%!>E`+fG%DAn#1rtk(FWQf>5z`l?{{8|36Qplc@N3K*7HiB;vi4l;Q48*2WZknM z|5wRv^VjJ8zc-Ewc_k?&u{VweQ3$FRe|r8`YywMxpF;r{!7I!oufK_6=CL#0onXs) zi_LrPDzA!Ey)jcR9kxZ4Wj*;bcN zak3Gvh&&N}O>+?{fIx2fgu?jITtC6EJ%gv)%>}a#h}&ffiNC6zBx7MCdXm0Kp})QL zYs9CBoAkxnywu`TcvTc`=`W{ZKC&m}#BEC`Z0xbBaR=@P_dQPRdO%dUOQNqGqg4^2 zSp$d&zJ!5o_Yy^5#70e8jKBW>d$MyeA0oP3c{HaPXRJ3qQRvUOLD_beF~%!E)+XxX zUDk+GV$%y}n16Ov^b;+&zd^qL*0SkmK{tJaxByTK`-398Atx-~o>1J8RowCRYM!pk zj&u6`m8YHTXU>r2DYhh!y5NCG8PJVjX|wX%scYbU)V=uWZ8eu-zts3P{kGyrU4~oJ z5f1V*v;Lq+6+ttq?CH681xZ1VeODI7cE=1{<(u4s@elvR7UO5VD?iD@V3u|;jzx&n z4eI#eOONCEe2U9SCym;sR#k6TD%NjhSYI3`L>h<@j>TcTXU*}%*A#tKWa^pLY)TPK zJ1L~DRkFH%%jngbI_Xm&$aAHuKR)10yx=8?Wfuu2MU`WZCIVde(`@&vhFOD1N--YK zKQW{4?9{*n1izJI$=R(Nkw&E=tyVjlrX%RSqjdN2O-V098z@f9yjwVfAkhJ%07{=K zS=#VNUo`B;qZ=)#=DR}=EhzgFlHBukqW%B1aI?suKRh>*QUXch2*OdU_5VD5un8;$ zzK;U%PS!ohq{?69cn9wu%H3UYwj{>S`K-;=m{ wksB`ThaOF?PRfN_ut1UnC6CM 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 ZiaeS+_uBY^)7P*;&0b0%K6>px{|Ck)o|^yw literal 0 HcmV?d00001 diff --git a/tests/fixtures/cep18-1.5.6-minted/global_state/data.lmdb-lock b/tests/fixtures/cep18-1.5.6-minted/global_state/data.lmdb-lock new file mode 100644 index 0000000000000000000000000000000000000000..4b23cdb218786f874809c92f7a194d7734b8d9b4 GIT binary patch literal 32896 zcmeIuu?>JQ35a&u?DTP?NZ*>f@WWzt7I z`-^+dcTDvCxYlQjO%fnLfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZ zfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&U zAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+0D*r9 Ee2-}mp#T5? literal 0 HcmV?d00001 diff --git a/tests/fixtures/cep18-1.5.6-minted/state.json b/tests/fixtures/cep18-1.5.6-minted/state.json new file mode 100644 index 0000000..992219d --- /dev/null +++ b/tests/fixtures/cep18-1.5.6-minted/state.json @@ -0,0 +1,6 @@ +{ + "genesis_request": { + "protocol_version": "1.0.0" + }, + "post_state_hash": "5dcbe7efeadf90d16aa93251726fd7e6dbdc1a2eb81a7aff614ad94e4b0c97e3" +} \ No newline at end of file diff --git a/tests/src/allowance.rs b/tests/src/allowance.rs index 803e045..ce5e853 100644 --- a/tests/src/allowance.rs +++ b/tests/src/allowance.rs @@ -1,6 +1,7 @@ use casper_engine_test_support::{ExecuteRequestBuilder, DEFAULT_ACCOUNT_ADDR}; use casper_types::{ - addressable_entity::EntityKindTag, runtime_args, AddressableEntityHash, ApiError, EntityAddr, Key, U256 + addressable_entity::EntityKindTag, runtime_args, AddressableEntityHash, ApiError, EntityAddr, + Key, U256, }; use crate::utility::{ @@ -49,8 +50,12 @@ fn should_approve_funds_contract_to_contract() { test_approve_for( &mut builder, &test_context, - Key::AddressableEntity(EntityAddr::SmartContract(cep18_test_contract_package.value())), - Key::AddressableEntity(EntityAddr::SmartContract(cep18_test_contract_package.value())), + Key::AddressableEntity(EntityAddr::SmartContract( + cep18_test_contract_package.value(), + )), + Key::AddressableEntity(EntityAddr::SmartContract( + cep18_test_contract_package.value(), + )), Key::AddressableEntity(EntityAddr::SmartContract([42; 32])), ); } @@ -104,8 +109,11 @@ fn should_not_transfer_from_without_enough_allowance() { ARG_AMOUNT => transfer_from_amount_1, }; - let spender_allowance_before = - cep18_check_allowance_of(&mut builder, Key::AddressableEntity(EntityAddr::Account(owner.value())), Key::AddressableEntity(EntityAddr::Account(recipient.value()))); + let spender_allowance_before = cep18_check_allowance_of( + &mut builder, + Key::AddressableEntity(EntityAddr::Account(owner.value())), + Key::AddressableEntity(EntityAddr::Account(recipient.value())), + ); assert_eq!(spender_allowance_before, U256::zero()); let approve_request_1 = ExecuteRequestBuilder::contract_call_by_hash( @@ -126,8 +134,11 @@ fn should_not_transfer_from_without_enough_allowance() { builder.exec(approve_request_1).expect_success().commit(); - let account_1_allowance_after = - cep18_check_allowance_of(&mut builder, Key::AddressableEntity(EntityAddr::Account(owner.value())), Key::AddressableEntity(EntityAddr::Account(recipient.value()))); + let account_1_allowance_after = cep18_check_allowance_of( + &mut builder, + Key::AddressableEntity(EntityAddr::Account(owner.value())), + Key::AddressableEntity(EntityAddr::Account(recipient.value())), + ); assert_eq!(account_1_allowance_after, allowance_amount_1); builder.exec(transfer_from_request_1).commit(); @@ -151,7 +162,7 @@ fn test_decrease_allowance() { let owner_key = Key::AddressableEntity(EntityAddr::Account(owner.value())); let spender_key = Key::AddressableEntity(EntityAddr::SmartContract([42; 32])); - + let allowance_amount_1 = U256::from(ALLOWANCE_AMOUNT_1); let allowance_amount_2 = U256::from(ALLOWANCE_AMOUNT_2); @@ -192,7 +203,8 @@ fn test_decrease_allowance() { .expect_success() .commit(); - let account_1_allowance_after_decrease = cep18_check_allowance_of(&mut builder, owner_key, spender); + let account_1_allowance_after_decrease = + cep18_check_allowance_of(&mut builder, owner_key, spender); assert_eq!( account_1_allowance_after_decrease, @@ -204,7 +216,8 @@ fn test_decrease_allowance() { .expect_success() .commit(); - let account_1_allowance_after_increase = cep18_check_allowance_of(&mut builder, owner_key, spender); + let account_1_allowance_after_increase = + cep18_check_allowance_of(&mut builder, owner_key, spender); assert_eq!( account_1_allowance_after_increase, diff --git a/tests/src/fixture_gen.rs b/tests/src/fixture_gen.rs new file mode 100644 index 0000000..3b2c1f7 --- /dev/null +++ b/tests/src/fixture_gen.rs @@ -0,0 +1,254 @@ +use casper_engine_test_support::utils::create_run_genesis_request; +use casper_engine_test_support::{ + ExecuteRequestBuilder, LmdbWasmTestBuilder, DEFAULT_ACCOUNT_ADDR, DEFAULT_ACCOUNT_PUBLIC_KEY, +}; +use casper_fixtures::generate_fixture; +use casper_types::addressable_entity::EntityKindTag; +use casper_types::bytesrepr::FromBytes; +use casper_types::{runtime_args, RuntimeArgs, U256}; +use casper_types::{ + AddressableEntityHash, CLTyped, EntityAddr, GenesisAccount, Motes, PackageHash, U512, +}; + +use casper_types::{account::AccountHash, Key, PublicKey, SecretKey}; +use once_cell::sync::Lazy; + +pub const CEP18_CONTRACT_WASM: &str = "cep18.wasm"; +pub const CEP18_TEST_CONTRACT_WASM: &str = "cep18_test_contract.wasm"; +pub const NAME_KEY: &str = "name"; +pub const SYMBOL_KEY: &str = "symbol"; +pub const CEP18_TOKEN_CONTRACT_KEY: &str = "cep18_contract_hash_CasperTest"; +pub const DECIMALS_KEY: &str = "decimals"; +pub const TOTAL_SUPPLY_KEY: &str = "total_supply"; +pub const BALANCES_KEY: &str = "balances"; +pub const ALLOWANCES_KEY: &str = "allowances"; +pub const OWNER: &str = "owner"; +pub const AMOUNT: &str = "amount"; + +pub const ARG_NAME: &str = "name"; +pub const ARG_SYMBOL: &str = "symbol"; +pub const ARG_DECIMALS: &str = "decimals"; +pub const ARG_TOTAL_SUPPLY: &str = "total_supply"; + +pub const _ERROR_INVALID_CONTEXT: u16 = 60000; +pub const ERROR_INSUFFICIENT_BALANCE: u16 = 60001; +pub const ERROR_INSUFFICIENT_ALLOWANCE: u16 = 60002; +pub const ERROR_OVERFLOW: u16 = 60003; + +pub const TOKEN_NAME: &str = "CasperTest"; +pub const TOKEN_SYMBOL: &str = "CSPRT"; +pub const TOKEN_DECIMALS: u8 = 100; +pub const TOKEN_TOTAL_SUPPLY: u64 = 1_000_000_000; + +pub const METHOD_TRANSFER: &str = "transfer"; +pub const ARG_AMOUNT: &str = "amount"; +pub const ARG_RECIPIENT: &str = "recipient"; + +pub const METHOD_APPROVE: &str = "approve"; +pub const ARG_OWNER: &str = "owner"; +pub const ARG_SPENDER: &str = "spender"; + +pub const METHOD_TRANSFER_FROM: &str = "transfer_from"; + +pub const CHECK_TOTAL_SUPPLY_ENTRYPOINT: &str = "check_total_supply"; +pub const CHECK_BALANCE_OF_ENTRYPOINT: &str = "check_balance_of"; +pub const CHECK_ALLOWANCE_OF_ENTRYPOINT: &str = "check_allowance_of"; +pub const ARG_TOKEN_CONTRACT: &str = "token_contract"; +pub const ARG_ADDRESS: &str = "address"; +pub const RESULT_KEY: &str = "result"; +pub const CEP18_TEST_CONTRACT_KEY: &str = "cep18_test_contract"; + +pub static ACCOUNT_1_SECRET_KEY: Lazy = + Lazy::new(|| SecretKey::secp256k1_from_bytes([221u8; 32]).unwrap()); +pub static ACCOUNT_1_PUBLIC_KEY: Lazy = + Lazy::new(|| PublicKey::from(&*ACCOUNT_1_SECRET_KEY)); +pub static ACCOUNT_1_ADDR: Lazy = Lazy::new(|| ACCOUNT_1_PUBLIC_KEY.to_account_hash()); + +pub static ACCOUNT_2_SECRET_KEY: Lazy = + Lazy::new(|| SecretKey::secp256k1_from_bytes([212u8; 32]).unwrap()); +pub static ACCOUNT_2_PUBLIC_KEY: Lazy = + Lazy::new(|| PublicKey::from(&*ACCOUNT_2_SECRET_KEY)); +pub static ACCOUNT_2_ADDR: Lazy = Lazy::new(|| ACCOUNT_2_PUBLIC_KEY.to_account_hash()); + +pub const TRANSFER_AMOUNT_1: u64 = 200_001; +pub const TRANSFER_AMOUNT_2: u64 = 19_999; +pub const ALLOWANCE_AMOUNT_1: u64 = 456_789; +pub const ALLOWANCE_AMOUNT_2: u64 = 87_654; + +pub const METHOD_TRANSFER_AS_STORED_CONTRACT: &str = "transfer_as_stored_contract"; +pub const METHOD_APPROVE_AS_STORED_CONTRACT: &str = "approve_as_stored_contract"; +pub const METHOD_FROM_AS_STORED_CONTRACT: &str = "transfer_from_as_stored_contract"; + +pub const TOKEN_OWNER_ADDRESS_1: Key = Key::Account(AccountHash::new([42; 32])); +pub const TOKEN_OWNER_AMOUNT_1: u64 = 1_000_000; +pub const TOKEN_OWNER_ADDRESS_2: Key = Key::Hash([42; 32]); +pub const TOKEN_OWNER_AMOUNT_2: u64 = 2_000_000; + +pub const METHOD_MINT: &str = "mint"; +pub const METHOD_BURN: &str = "burn"; +pub const DECREASE_ALLOWANCE: &str = "decrease_allowance"; +pub const INCREASE_ALLOWANCE: &str = "increase_allowance"; +pub const ENABLE_MINT_BURN: &str = "enable_mint_burn"; +pub const ADMIN_LIST: &str = "admin_list"; +pub const MINTER_LIST: &str = "minter_list"; +pub const NONE_LIST: &str = "none_list"; +pub const CHANGE_SECURITY: &str = "change_security"; + +pub(crate) fn get_test_result( + builder: &mut LmdbWasmTestBuilder, + cep18_test_contract_package: PackageHash, +) -> T { + let contract_package = builder + .get_package(cep18_test_contract_package) + .expect("should have contract package"); + let enabled_versions = contract_package.enabled_versions(); + + let contract_hash = enabled_versions + .contract_hashes() + .last() + .expect("should have latest version"); + let entity_addr = EntityAddr::new_smart_contract(contract_hash.value()); + builder.get_value(entity_addr, RESULT_KEY) +} + +pub(crate) fn cep18_check_balance_of( + builder: &mut LmdbWasmTestBuilder, + cep18_contract_hash: &AddressableEntityHash, + address: Key, +) -> U256 { + let account = builder + .get_entity_with_named_keys_by_account_hash(*DEFAULT_ACCOUNT_ADDR) + .expect("should have account"); + + let cep18_test_contract_package = account + .named_keys() + .get(CEP18_TEST_CONTRACT_KEY) + .and_then(|key| key.into_package_hash()) + .expect("should have test contract hash"); + + let check_balance_args = runtime_args! { + ARG_TOKEN_CONTRACT => Key::addressable_entity_key(EntityKindTag::SmartContract, *cep18_contract_hash), + ARG_ADDRESS => address, + }; + let exec_request = ExecuteRequestBuilder::versioned_contract_call_by_hash( + *DEFAULT_ACCOUNT_ADDR, + cep18_test_contract_package, + None, + CHECK_BALANCE_OF_ENTRYPOINT, + check_balance_args, + ) + .build(); + builder.exec(exec_request).expect_success().commit(); + + get_test_result(builder, cep18_test_contract_package) +} + +fn main() { + let genesis_request = create_run_genesis_request(vec![ + GenesisAccount::Account { + public_key: DEFAULT_ACCOUNT_PUBLIC_KEY.clone(), + balance: Motes::new(U512::from(5_000_000_000_000_u64)), + validator: None, + }, + GenesisAccount::Account { + public_key: ACCOUNT_1_PUBLIC_KEY.clone(), + balance: Motes::new(U512::from(5_000_000_000_000_u64)), + validator: None, + }, + GenesisAccount::Account { + public_key: ACCOUNT_2_PUBLIC_KEY.clone(), + balance: Motes::new(U512::from(5_000_000_000_000_u64)), + validator: None, + }, + ]); + generate_fixture("cep18-2.0.0-rc2-minted", genesis_request, |builder|{ + let mint_amount = U256::one(); + + let install_args = runtime_args! { + ARG_NAME => TOKEN_NAME, + ARG_SYMBOL => TOKEN_SYMBOL, + ARG_DECIMALS => TOKEN_DECIMALS, + ARG_TOTAL_SUPPLY => U256::from(TOKEN_TOTAL_SUPPLY), + ENABLE_MINT_BURN => true, + }; + let install_request_1 = + ExecuteRequestBuilder::standard(*DEFAULT_ACCOUNT_ADDR, CEP18_CONTRACT_WASM, install_args) + .build(); + + let install_request_2 = ExecuteRequestBuilder::standard( + *DEFAULT_ACCOUNT_ADDR, + CEP18_TEST_CONTRACT_WASM, + RuntimeArgs::default(), + ) + .build(); + + builder.exec(install_request_1).expect_success().commit(); + builder.exec(install_request_2).expect_success().commit(); + + let account = builder + .get_entity_with_named_keys_by_account_hash(*DEFAULT_ACCOUNT_ADDR) + .unwrap(); + let account_named_keys = account.named_keys(); + + let cep18_token = account_named_keys + .get(CEP18_TOKEN_CONTRACT_KEY) + .and_then(|key| key.into_entity_hash()) + .expect("should have contract hash"); + + let addressable_cep18_token = AddressableEntityHash::new(cep18_token.value()); + let mint_request = ExecuteRequestBuilder::contract_call_by_hash( + *DEFAULT_ACCOUNT_ADDR, + addressable_cep18_token, + METHOD_MINT, + runtime_args! {OWNER => TOKEN_OWNER_ADDRESS_1, AMOUNT => U256::from(TOKEN_OWNER_AMOUNT_1)}, + ) + .build(); + builder.exec(mint_request).expect_success().commit(); + let mint_request_2 = ExecuteRequestBuilder::contract_call_by_hash( + *DEFAULT_ACCOUNT_ADDR, + addressable_cep18_token, + METHOD_MINT, + runtime_args! {OWNER => TOKEN_OWNER_ADDRESS_2, AMOUNT => U256::from(TOKEN_OWNER_AMOUNT_2)}, + ) + .build(); + builder.exec(mint_request_2).expect_success().commit(); + assert_eq!( + cep18_check_balance_of( + builder, + &cep18_token, + Key::AddressableEntity(casper_types::EntityAddr::Account(DEFAULT_ACCOUNT_ADDR.value())) + ), + U256::from(TOKEN_TOTAL_SUPPLY), + ); + assert_eq!( + cep18_check_balance_of(builder, &cep18_token, TOKEN_OWNER_ADDRESS_1), + U256::from(TOKEN_OWNER_AMOUNT_1) + ); + assert_eq!( + cep18_check_balance_of(builder, &cep18_token, TOKEN_OWNER_ADDRESS_2), + U256::from(TOKEN_OWNER_AMOUNT_2) + ); + + let mint_request = ExecuteRequestBuilder::contract_call_by_hash( + *DEFAULT_ACCOUNT_ADDR, + addressable_cep18_token, + METHOD_MINT, + runtime_args! { + ARG_OWNER => TOKEN_OWNER_ADDRESS_1, + ARG_AMOUNT => mint_amount, + }, + ) + .build(); + + builder.exec(mint_request).expect_success().commit(); + + assert_eq!( + cep18_check_balance_of(builder, &cep18_token, TOKEN_OWNER_ADDRESS_1), + U256::from(TOKEN_OWNER_AMOUNT_1) + mint_amount, + ); + assert_eq!( + cep18_check_balance_of(builder, &cep18_token, TOKEN_OWNER_ADDRESS_2), + U256::from(TOKEN_OWNER_AMOUNT_2) + ); + }).unwrap(); +} diff --git a/tests/src/migration.rs b/tests/src/migration.rs index a502218..034d3c4 100644 --- a/tests/src/migration.rs +++ b/tests/src/migration.rs @@ -3,8 +3,8 @@ use casper_types::{runtime_args, Key, U256}; use crate::utility::{ constants::{ - ARG_DECIMALS, ARG_NAME, ARG_SYMBOL, ARG_TOTAL_SUPPLY, CEP18_CONTRACT_WASM, TOKEN_DECIMALS, - TOKEN_NAME, TOKEN_SYMBOL, TOKEN_TOTAL_SUPPLY, + ARG_DECIMALS, ARG_NAME, ARG_SYMBOL, ARG_TOTAL_SUPPLY, CEP18_CONTRACT_WASM, + CEP18_TOKEN_CONTRACT_KEY, TOKEN_DECIMALS, TOKEN_NAME, TOKEN_SYMBOL, TOKEN_TOTAL_SUPPLY, }, installer_request_builders::{setup, TestContext}, }; @@ -55,3 +55,75 @@ fn should_upgrade_contract_version() { assert!(version_0 < version_1); } + +#[test] +fn should_migrate_1_5_6_to_2_0_0_rc2() { + let (mut builder, lmdb_fixture_state, _) = + casper_fixtures::builder_from_global_state_fixture("cep18-1.5.6-minted"); + + let get_entity_by_account_hash = builder.get_entity_by_account_hash(*DEFAULT_ACCOUNT_ADDR); + println!("{get_entity_by_account_hash:?}"); + + let query_named_key_by_account_hash = builder.query_named_key_by_account_hash( + Some(lmdb_fixture_state.post_state_hash), + *DEFAULT_ACCOUNT_ADDR, + CEP18_TOKEN_CONTRACT_KEY, + ); + println!("{query_named_key_by_account_hash:?}"); + + let get_entity_hash_by_account_hash = + builder.get_entity_hash_by_account_hash(*DEFAULT_ACCOUNT_ADDR); + println!("{get_entity_hash_by_account_hash:?}"); + + let account = builder + .get_entity_with_named_keys_by_account_hash(*DEFAULT_ACCOUNT_ADDR) + .unwrap(); + let account_named_keys = account.named_keys(); + + let _cep18_token = account_named_keys + .get(CEP18_TOKEN_CONTRACT_KEY) + .and_then(|key| key.into_entity_hash()) + .expect("should have contract hash"); + + let version_0: u32 = builder + .query( + None, + Key::Account(*DEFAULT_ACCOUNT_ADDR), + &["cep18_contract_version_CasperTest".to_string()], + ) + .unwrap() + .as_cl_value() + .unwrap() + .clone() + .into_t() + .unwrap(); + + let upgrade_request = ExecuteRequestBuilder::standard( + *DEFAULT_ACCOUNT_ADDR, + CEP18_CONTRACT_WASM, + runtime_args! { + ARG_NAME => TOKEN_NAME, + ARG_SYMBOL => TOKEN_SYMBOL, + ARG_DECIMALS => TOKEN_DECIMALS, + ARG_TOTAL_SUPPLY => U256::from(TOKEN_TOTAL_SUPPLY), + }, + ) + .build(); + + builder.exec(upgrade_request).expect_success().commit(); + + let version_1: u32 = builder + .query( + None, + Key::Account(*DEFAULT_ACCOUNT_ADDR), + &["cep18_contract_version_CasperTest".to_string()], + ) + .unwrap() + .as_cl_value() + .unwrap() + .clone() + .into_t() + .unwrap(); + + assert!(version_0 < version_1); +} diff --git a/tests/src/mint_and_burn.rs b/tests/src/mint_and_burn.rs index f41c1d9..94455be 100644 --- a/tests/src/mint_and_burn.rs +++ b/tests/src/mint_and_burn.rs @@ -48,7 +48,9 @@ fn test_mint_and_burn_tokens() { cep18_check_balance_of( &mut builder, &cep18_token, - Key::AddressableEntity(casper_types::EntityAddr::Account(DEFAULT_ACCOUNT_ADDR.value())) + Key::AddressableEntity(casper_types::EntityAddr::Account( + DEFAULT_ACCOUNT_ADDR.value() + )) ), U256::from(TOKEN_TOTAL_SUPPLY), ); @@ -108,7 +110,9 @@ fn test_mint_and_burn_tokens() { cep18_check_balance_of( &mut builder, &cep18_token, - Key::AddressableEntity(casper_types::EntityAddr::Account(DEFAULT_ACCOUNT_ADDR.value())) + Key::AddressableEntity(casper_types::EntityAddr::Account( + DEFAULT_ACCOUNT_ADDR.value() + )) ), U256::from(999999999), ); diff --git a/tests/src/transfer.rs b/tests/src/transfer.rs index 142d74c..2da58f0 100644 --- a/tests/src/transfer.rs +++ b/tests/src/transfer.rs @@ -40,8 +40,11 @@ fn should_transfer_full_owned_amount() { ); assert_eq!(owner_balance_before, initial_supply); - let account_1_balance_before = - cep18_check_balance_of(&mut builder, &cep18_token, Key::AddressableEntity(EntityAddr::Account(ACCOUNT_1_ADDR.value()))); + let account_1_balance_before = cep18_check_balance_of( + &mut builder, + &cep18_token, + Key::AddressableEntity(EntityAddr::Account(ACCOUNT_1_ADDR.value())), + ); assert_eq!(account_1_balance_before, U256::zero()); let token_transfer_request_1 = ExecuteRequestBuilder::contract_call_by_hash( @@ -57,8 +60,11 @@ fn should_transfer_full_owned_amount() { .expect_success() .commit(); - let account_1_balance_after = - cep18_check_balance_of(&mut builder, &cep18_token, Key::AddressableEntity(EntityAddr::Account(ACCOUNT_1_ADDR.value()))); + let account_1_balance_after = cep18_check_balance_of( + &mut builder, + &cep18_token, + Key::AddressableEntity(EntityAddr::Account(ACCOUNT_1_ADDR.value())), + ); assert_eq!(account_1_balance_after, transfer_amount_1); let owner_balance_after = cep18_check_balance_of( @@ -100,8 +106,11 @@ fn should_not_transfer_more_than_owned_balance() { assert_eq!(owner_balance_before, initial_supply); assert!(transfer_amount > owner_balance_before); - let account_1_balance_before = - cep18_check_balance_of(&mut builder, &cep18_token, Key::AddressableEntity(EntityAddr::Account(ACCOUNT_1_ADDR.value()))); + let account_1_balance_before = cep18_check_balance_of( + &mut builder, + &cep18_token, + Key::AddressableEntity(EntityAddr::Account(ACCOUNT_1_ADDR.value())), + ); assert_eq!(account_1_balance_before, U256::zero()); let token_transfer_request_1 = ExecuteRequestBuilder::contract_call_by_hash( @@ -128,8 +137,11 @@ fn should_not_transfer_more_than_owned_balance() { ); assert_eq!(account_1_balance_after, account_1_balance_before); - let owner_balance_after = - cep18_check_balance_of(&mut builder, &cep18_token, Key::AddressableEntity(EntityAddr::Account(transfer_1_sender.value()))); + let owner_balance_after = cep18_check_balance_of( + &mut builder, + &cep18_token, + Key::AddressableEntity(EntityAddr::Account(transfer_1_sender.value())), + ); assert_eq!(owner_balance_after, initial_supply); let total_supply: U256 = builder.get_value( @@ -163,8 +175,11 @@ fn should_transfer_from_from_account_to_account() { ARG_AMOUNT => transfer_from_amount_1, }; - let spender_allowance_before = - cep18_check_allowance_of(&mut builder, Key::AddressableEntity(EntityAddr::Account(owner.value())), Key::AddressableEntity(EntityAddr::Account(spender.value()))); + let spender_allowance_before = cep18_check_allowance_of( + &mut builder, + Key::AddressableEntity(EntityAddr::Account(owner.value())), + Key::AddressableEntity(EntityAddr::Account(spender.value())), + ); assert_eq!(spender_allowance_before, U256::zero()); let approve_request_1 = ExecuteRequestBuilder::contract_call_by_hash( @@ -185,12 +200,18 @@ fn should_transfer_from_from_account_to_account() { builder.exec(approve_request_1).expect_success().commit(); - let account_1_balance_before = - cep18_check_balance_of(&mut builder, &cep18_token, Key::AddressableEntity(EntityAddr::Account(owner.value()))); + let account_1_balance_before = cep18_check_balance_of( + &mut builder, + &cep18_token, + Key::AddressableEntity(EntityAddr::Account(owner.value())), + ); assert_eq!(account_1_balance_before, initial_supply); - let account_1_allowance_before = - cep18_check_allowance_of(&mut builder, Key::AddressableEntity(EntityAddr::Account(owner.value())), Key::AddressableEntity(EntityAddr::Account(spender.value()))); + let account_1_allowance_before = cep18_check_allowance_of( + &mut builder, + Key::AddressableEntity(EntityAddr::Account(owner.value())), + Key::AddressableEntity(EntityAddr::Account(spender.value())), + ); assert_eq!(account_1_allowance_before, allowance_amount_1); builder @@ -198,15 +219,21 @@ fn should_transfer_from_from_account_to_account() { .expect_success() .commit(); - let account_1_allowance_after = - cep18_check_allowance_of(&mut builder, Key::AddressableEntity(EntityAddr::Account(owner.value())), Key::AddressableEntity(EntityAddr::Account(spender.value()))); + let account_1_allowance_after = cep18_check_allowance_of( + &mut builder, + Key::AddressableEntity(EntityAddr::Account(owner.value())), + Key::AddressableEntity(EntityAddr::Account(spender.value())), + ); assert_eq!( account_1_allowance_after, account_1_allowance_before - transfer_from_amount_1 ); - let account_1_balance_after = - cep18_check_balance_of(&mut builder, &cep18_token, Key::AddressableEntity(EntityAddr::Account(owner.value()))); + let account_1_balance_after = cep18_check_balance_of( + &mut builder, + &cep18_token, + Key::AddressableEntity(EntityAddr::Account(owner.value())), + ); assert_eq!( account_1_balance_after, account_1_balance_before - transfer_from_amount_1 @@ -232,7 +259,9 @@ fn should_transfer_from_account_by_contract() { let owner = *DEFAULT_ACCOUNT_ADDR; - let spender = Key::AddressableEntity(EntityAddr::SmartContract(cep18_test_contract_package.value())); + let spender = Key::AddressableEntity(EntityAddr::SmartContract( + cep18_test_contract_package.value(), + )); let recipient = Key::AddressableEntity(EntityAddr::Account(ACCOUNT_1_ADDR.value())); let cep18_approve_args = runtime_args! { @@ -247,8 +276,11 @@ fn should_transfer_from_account_by_contract() { ARG_AMOUNT => transfer_from_amount_1, }; - let spender_allowance_before = - cep18_check_allowance_of(&mut builder, Key::AddressableEntity(EntityAddr::Account(owner.value())), spender); + let spender_allowance_before = cep18_check_allowance_of( + &mut builder, + Key::AddressableEntity(EntityAddr::Account(owner.value())), + spender, + ); assert_eq!(spender_allowance_before, U256::zero()); let approve_request_1 = ExecuteRequestBuilder::contract_call_by_hash( @@ -270,12 +302,18 @@ fn should_transfer_from_account_by_contract() { builder.exec(approve_request_1).expect_success().commit(); - let owner_balance_before = - cep18_check_balance_of(&mut builder, &cep18_token, Key::AddressableEntity(EntityAddr::Account(owner.value()))); + let owner_balance_before = cep18_check_balance_of( + &mut builder, + &cep18_token, + Key::AddressableEntity(EntityAddr::Account(owner.value())), + ); assert_eq!(owner_balance_before, initial_supply); - let spender_allowance_before = - cep18_check_allowance_of(&mut builder, Key::AddressableEntity(EntityAddr::Account(owner.value())), spender); + let spender_allowance_before = cep18_check_allowance_of( + &mut builder, + Key::AddressableEntity(EntityAddr::Account(owner.value())), + spender, + ); assert_eq!(spender_allowance_before, allowance_amount_1); builder @@ -283,15 +321,21 @@ fn should_transfer_from_account_by_contract() { .expect_success() .commit(); - let spender_allowance_after = - cep18_check_allowance_of(&mut builder, Key::AddressableEntity(EntityAddr::Account(owner.value())), spender); + let spender_allowance_after = cep18_check_allowance_of( + &mut builder, + Key::AddressableEntity(EntityAddr::Account(owner.value())), + spender, + ); assert_eq!( spender_allowance_after, spender_allowance_before - transfer_from_amount_1 ); - let owner_balance_after = - cep18_check_balance_of(&mut builder, &cep18_token, Key::AddressableEntity(EntityAddr::Account(owner.value()))); + let owner_balance_after = cep18_check_balance_of( + &mut builder, + &cep18_token, + Key::AddressableEntity(EntityAddr::Account(owner.value())), + ); assert_eq!( owner_balance_after, owner_balance_before - transfer_from_amount_1 @@ -472,8 +516,12 @@ fn should_transfer_contract_to_contract() { } = test_context; let sender1 = Key::AddressableEntity(EntityAddr::Account(DEFAULT_ACCOUNT_ADDR.value())); - let recipient1 = Key::AddressableEntity(EntityAddr::SmartContract(cep18_test_contract_package.value())); - let sender2 = Key::AddressableEntity(EntityAddr::SmartContract(cep18_test_contract_package.value())); + let recipient1 = Key::AddressableEntity(EntityAddr::SmartContract( + cep18_test_contract_package.value(), + )); + let sender2 = Key::AddressableEntity(EntityAddr::SmartContract( + cep18_test_contract_package.value(), + )); let recipient2 = Key::AddressableEntity(EntityAddr::SmartContract([42; 32])); test_cep18_transfer( @@ -495,9 +543,13 @@ fn should_transfer_contract_to_account() { } = test_context; let sender1 = Key::AddressableEntity(EntityAddr::Account(DEFAULT_ACCOUNT_ADDR.value())); - let recipient1 = Key::AddressableEntity(EntityAddr::SmartContract(cep18_test_contract_package.value())); + let recipient1 = Key::AddressableEntity(EntityAddr::SmartContract( + cep18_test_contract_package.value(), + )); - let sender2 = Key::AddressableEntity(EntityAddr::SmartContract(cep18_test_contract_package.value())); + let sender2 = Key::AddressableEntity(EntityAddr::SmartContract( + cep18_test_contract_package.value(), + )); let recipient2 = Key::AddressableEntity(EntityAddr::Account(ACCOUNT_1_ADDR.value())); test_cep18_transfer( @@ -517,7 +569,9 @@ fn should_transfer_account_to_contract() { let sender1 = Key::AddressableEntity(EntityAddr::Account(DEFAULT_ACCOUNT_ADDR.value())); let recipient1 = Key::AddressableEntity(EntityAddr::Account(ACCOUNT_1_ADDR.value())); let sender2 = Key::AddressableEntity(EntityAddr::Account(ACCOUNT_1_ADDR.value())); - let recipient2 = Key::AddressableEntity(EntityAddr::SmartContract(test_context.cep18_test_contract_package.value())); + let recipient2 = Key::AddressableEntity(EntityAddr::SmartContract( + test_context.cep18_test_contract_package.value(), + )); test_cep18_transfer( &mut builder, diff --git a/tests/src/utility/installer_request_builders.rs b/tests/src/utility/installer_request_builders.rs index 5912f70..4313ede 100644 --- a/tests/src/utility/installer_request_builders.rs +++ b/tests/src/utility/installer_request_builders.rs @@ -30,11 +30,15 @@ pub(crate) fn invert_cep18_address(address: Key) -> Key { match address { Key::Account(account_hash) => Key::Hash(account_hash.value()), Key::Hash(contract_hash) => Key::Account(AccountHash::new(contract_hash)), - Key::AddressableEntity(entity_addr)=>match entity_addr { + Key::AddressableEntity(entity_addr) => match entity_addr { EntityAddr::System(_) => panic!("Unsupported Key variant"), - EntityAddr::Account(account) => Key::AddressableEntity(EntityAddr::SmartContract(account)), - EntityAddr::SmartContract(contract) => Key::AddressableEntity(EntityAddr::Account(contract)), - } + EntityAddr::Account(account) => { + Key::AddressableEntity(EntityAddr::SmartContract(account)) + } + EntityAddr::SmartContract(contract) => { + Key::AddressableEntity(EntityAddr::Account(contract)) + } + }, _ => panic!("Unsupported Key variant"), } } From 5e9fe8c89a6dd07a27ed1ed1645e11c97f7e66c2 Mon Sep 17 00:00:00 2001 From: Deuszex Date: Thu, 20 Jun 2024 19:06:09 +0200 Subject: [PATCH 10/27] rc3 --- cep18-test-contract/Cargo.toml | 4 ++-- cep18/Cargo.toml | 4 ++-- tests/Cargo.toml | 10 +++++----- tests/src/fixture_gen.rs | 2 +- tests/src/migration.rs | 8 ++++++-- 5 files changed, 16 insertions(+), 12 deletions(-) diff --git a/cep18-test-contract/Cargo.toml b/cep18-test-contract/Cargo.toml index 444744d..265ebc9 100644 --- a/cep18-test-contract/Cargo.toml +++ b/cep18-test-contract/Cargo.toml @@ -11,5 +11,5 @@ doctest = false test = false [dependencies] -casper-types = { git = "https://github.com/casper-network/casper-node.git", branch = "release-2.0.0-rc2"} -casper-contract = { git = "https://github.com/casper-network/casper-node.git", branch = "release-2.0.0-rc2"} +casper-types = { git = "https://github.com/casper-network/casper-node.git", branch = "release-2.0.0-rc3"} +casper-contract = { git = "https://github.com/casper-network/casper-node.git", branch = "release-2.0.0-rc3"} diff --git a/cep18/Cargo.toml b/cep18/Cargo.toml index 940641d..9c8c912 100644 --- a/cep18/Cargo.toml +++ b/cep18/Cargo.toml @@ -18,8 +18,8 @@ test = false [dependencies] base64 = { version = "0.20.0", default-features = false, features = ["alloc"] } -casper-types = { git = "https://github.com/casper-network/casper-node.git", branch = "release-2.0.0-rc2"} -casper-contract = { git = "https://github.com/casper-network/casper-node.git", branch = "release-2.0.0-rc2"} +casper-types = { git = "https://github.com/casper-network/casper-node.git", branch = "release-2.0.0-rc3"} +casper-contract = { git = "https://github.com/casper-network/casper-node.git", branch = "release-2.0.0-rc3"} hex = { version = "0.4.3", default-features = false } once_cell = { version = "1.16.0", default-features = false } casper-event-standard = { git = "https://github.com/deuszex/casper-event-standard", branch = "condor", default-features = false } diff --git a/tests/Cargo.toml b/tests/Cargo.toml index cb46472..8838d0e 100644 --- a/tests/Cargo.toml +++ b/tests/Cargo.toml @@ -4,12 +4,12 @@ version = "2.0.0" edition = "2021" [dependencies] -casper-types = { git = "https://github.com/casper-network/casper-node.git", branch = "release-2.0.0-rc2", default-features = false} -casper-engine-test-support = { git = "https://github.com/casper-network/casper-node.git", branch = "release-2.0.0-rc2", default-features = false} -casper-execution-engine = { git = "https://github.com/casper-network/casper-node.git", branch = "release-2.0.0-rc2", default-features = false} -casper-storage = { git = "https://github.com/casper-network/casper-node.git", branch = "release-2.0.0-rc2", default-features = false} +casper-types = { git = "https://github.com/casper-network/casper-node.git", branch = "release-2.0.0-rc3", default-features = false} +casper-engine-test-support = { git = "https://github.com/casper-network/casper-node.git", branch = "release-2.0.0-rc3", default-features = false} +casper-execution-engine = { git = "https://github.com/casper-network/casper-node.git", branch = "release-2.0.0-rc3", default-features = false} +casper-storage = { git = "https://github.com/casper-network/casper-node.git", branch = "release-2.0.0-rc3", default-features = false} once_cell = "1.16.0" -casper-fixtures = { git = "https://github.com/deuszex/casper-fixtures", branch = "2.0.0-rc2"} +casper-fixtures = { git = "https://github.com/deuszex/casper-fixtures", branch = "2.0.0-rc3"} [lib] name = "tests" diff --git a/tests/src/fixture_gen.rs b/tests/src/fixture_gen.rs index 3b2c1f7..70f6751 100644 --- a/tests/src/fixture_gen.rs +++ b/tests/src/fixture_gen.rs @@ -161,7 +161,7 @@ fn main() { validator: None, }, ]); - generate_fixture("cep18-2.0.0-rc2-minted", genesis_request, |builder|{ + generate_fixture("cep18-2.0.0-rc3-minted", genesis_request, |builder|{ let mint_amount = U256::one(); let install_args = runtime_args! { diff --git a/tests/src/migration.rs b/tests/src/migration.rs index 034d3c4..59e6179 100644 --- a/tests/src/migration.rs +++ b/tests/src/migration.rs @@ -1,5 +1,5 @@ use casper_engine_test_support::{ExecuteRequestBuilder, DEFAULT_ACCOUNT_ADDR}; -use casper_types::{runtime_args, Key, U256}; +use casper_types::{runtime_args, AddressableEntityHash, Key, U256}; use crate::utility::{ constants::{ @@ -58,9 +58,13 @@ fn should_upgrade_contract_version() { #[test] fn should_migrate_1_5_6_to_2_0_0_rc2() { - let (mut builder, lmdb_fixture_state, _) = + let (mut builder, lmdb_fixture_state, _temp_dir) = casper_fixtures::builder_from_global_state_fixture("cep18-1.5.6-minted"); + // println!("get_entity_with_named_keys_by_entity_hash: {:?}", builder.get_entity_with_named_keys_by_entity_hash(AddressableEntityHash::new(DEFAULT_ACCOUNT_ADDR.value()))); + // println!("get_engine_state: {:?}", builder.get_engine_state()); + // println!("get_genesis_account: {:?}", builder.get_genesis_account()); + let get_entity_by_account_hash = builder.get_entity_by_account_hash(*DEFAULT_ACCOUNT_ADDR); println!("{get_entity_by_account_hash:?}"); From 66e870eeb1d4c43f8436dfb2aa6ab21cddf97404 Mon Sep 17 00:00:00 2001 From: Deuszex Date: Fri, 21 Jun 2024 13:13:31 +0200 Subject: [PATCH 11/27] fixture work --- tests/src/migration.rs | 60 ++++++++++++++++++++++++++++++++---------- 1 file changed, 46 insertions(+), 14 deletions(-) diff --git a/tests/src/migration.rs b/tests/src/migration.rs index 59e6179..54d7cfd 100644 --- a/tests/src/migration.rs +++ b/tests/src/migration.rs @@ -1,5 +1,9 @@ -use casper_engine_test_support::{ExecuteRequestBuilder, DEFAULT_ACCOUNT_ADDR}; -use casper_types::{runtime_args, AddressableEntityHash, Key, U256}; +use std::collections::BTreeMap; + +use casper_engine_test_support::{ + ExecuteRequestBuilder, UpgradeRequestBuilder, DEFAULT_ACCOUNT_ADDR, +}; +use casper_types::{runtime_args, AccessRights, AddressableEntityHash, CLValue, EraId, Key, ProtocolVersion, SystemEntityRegistry, URef, U256}; use crate::utility::{ constants::{ @@ -57,23 +61,51 @@ fn should_upgrade_contract_version() { } #[test] -fn should_migrate_1_5_6_to_2_0_0_rc2() { - let (mut builder, lmdb_fixture_state, _temp_dir) = +fn should_migrate_1_5_6_to_2_0_0_rc3() { + let (mut builder, _lmdb_fixture_state, _temp_dir) = casper_fixtures::builder_from_global_state_fixture("cep18-1.5.6-minted"); - // println!("get_entity_with_named_keys_by_entity_hash: {:?}", builder.get_entity_with_named_keys_by_entity_hash(AddressableEntityHash::new(DEFAULT_ACCOUNT_ADDR.value()))); - // println!("get_engine_state: {:?}", builder.get_engine_state()); - // println!("get_genesis_account: {:?}", builder.get_genesis_account()); + let key = URef::new([0u8;32], AccessRights::all()).into(); - let get_entity_by_account_hash = builder.get_entity_by_account_hash(*DEFAULT_ACCOUNT_ADDR); - println!("{get_entity_by_account_hash:?}"); + let system_contract_hashes = builder.query(Some(_lmdb_fixture_state.post_state_hash), key, &Vec::new()) + .expect("must have stored system").as_cl_value() + .expect("must clvalue") + .clone().into_t::().expect("convert btree"); - let query_named_key_by_account_hash = builder.query_named_key_by_account_hash( - Some(lmdb_fixture_state.post_state_hash), - *DEFAULT_ACCOUNT_ADDR, - CEP18_TOKEN_CONTRACT_KEY, + let mut global_state_update = BTreeMap::new(); + let registry = CLValue::from_t(system_contract_hashes).expect("must StoredValue").into(); + + global_state_update.insert(Key::SystemEntityRegistry, registry); + + let mut upgrade_config = UpgradeRequestBuilder::new() + .with_current_protocol_version(ProtocolVersion::V1_0_0) + .with_new_protocol_version(ProtocolVersion::V2_0_0) + .with_migrate_legacy_accounts(true) + .with_migrate_legacy_contracts(true) + .with_global_state_update(global_state_update) + .with_activation_point(EraId::new(1)) + .build(); + + builder.upgrade(&mut upgrade_config).expect_upgrade_success(); + + println!( + "get_entity_with_named_keys_by_entity_hash: {:?}", + builder.get_entity_with_named_keys_by_entity_hash(AddressableEntityHash::new( + DEFAULT_ACCOUNT_ADDR.value() + )) ); - println!("{query_named_key_by_account_hash:?}"); + println!("get_engine_state: {:?}", builder.get_engine_state()); + println!("get_genesis_account: {:?}", builder.get_genesis_account()); + + // let get_entity_by_account_hash = builder.get_entity_by_account_hash(*DEFAULT_ACCOUNT_ADDR); + // println!("{get_entity_by_account_hash:?}"); + + // let query_named_key_by_account_hash = builder.query_named_key_by_account_hash( + // Some(lmdb_fixture_state.post_state_hash), + // *DEFAULT_ACCOUNT_ADDR, + // CEP18_TOKEN_CONTRACT_KEY, + // ); + // println!("{query_named_key_by_account_hash:?}"); let get_entity_hash_by_account_hash = builder.get_entity_hash_by_account_hash(*DEFAULT_ACCOUNT_ADDR); From f2ca245a1166727f16f12385c4f38e576699ae65 Mon Sep 17 00:00:00 2001 From: Deuszex Date: Fri, 21 Jun 2024 21:36:43 +0200 Subject: [PATCH 12/27] fixedture --- Makefile | 2 +- cep18/Cargo.toml | 6 +- cep18/src/constants.rs | 2 + cep18/src/entry_points.rs | 77 +++++++- cep18/src/main.rs | 164 ++++++++++++++---- cep18/src/utils.rs | 50 +++++- tests/src/migration.rs | 142 +++++++++------ tests/src/utility/constants.rs | 14 ++ .../src/utility/installer_request_builders.rs | 2 +- 9 files changed, 354 insertions(+), 105 deletions(-) diff --git a/Makefile b/Makefile index 0201af0..40fd081 100644 --- a/Makefile +++ b/Makefile @@ -17,7 +17,7 @@ setup-test: build-contract cp ./target/wasm32-unknown-unknown/release/cep18_test_contract.wasm tests/wasm test: setup-test - cd tests && cargo test + cd tests && cargo test --lib clippy: cd cep18 && cargo clippy --all-targets -- -D warnings diff --git a/cep18/Cargo.toml b/cep18/Cargo.toml index 9c8c912..6b04bf2 100644 --- a/cep18/Cargo.toml +++ b/cep18/Cargo.toml @@ -22,8 +22,4 @@ casper-types = { git = "https://github.com/casper-network/casper-node.git", bran casper-contract = { git = "https://github.com/casper-network/casper-node.git", branch = "release-2.0.0-rc3"} hex = { version = "0.4.3", default-features = false } once_cell = { version = "1.16.0", default-features = false } -casper-event-standard = { git = "https://github.com/deuszex/casper-event-standard", branch = "condor", default-features = false } - -[profile.release] -codegen-units = 1 -lto = true +casper-event-standard = { git = "https://github.com/deuszex/casper-event-standard", branch = "condor", default-features = false } \ No newline at end of file diff --git a/cep18/src/constants.rs b/cep18/src/constants.rs index 995b6a6..0969c69 100644 --- a/cep18/src/constants.rs +++ b/cep18/src/constants.rs @@ -48,6 +48,8 @@ pub const INIT_ENTRY_POINT_NAME: &str = "init"; pub const CHANGE_SECURITY_ENTRY_POINT_NAME: &str = "change_security"; pub const MIGRATE_USER_BALANCE_KEYS_ENTRY_POINT_NAME: &str = "migrate_user_balance_keys"; pub const MIGRATE_USER_ALLOWANCE_KEYS_ENTRY_POINT_NAME: &str = "migrate_user_allowance_keys"; +pub const MIGRATE_USER_SEC_KEYS_ENTRY_POINT_NAME: &str = "migrate_sec_keys"; +pub const CHANGE_EVENTS_MODE_ENTRY_POINT_NAME: &str = "change_events_mode"; pub const INCREASE_ALLOWANCE_ENTRY_POINT_NAME: &str = "increase_allowance"; pub const DECREASE_ALLOWANCE_ENTRY_POINT_NAME: &str = "decrease_allowance"; diff --git a/cep18/src/entry_points.rs b/cep18/src/entry_points.rs index 6bdcad7..eb0ecc7 100644 --- a/cep18/src/entry_points.rs +++ b/cep18/src/entry_points.rs @@ -8,11 +8,14 @@ use casper_types::{ use crate::constants::{ ADDRESS, ALLOWANCE_ENTRY_POINT_NAME, AMOUNT, APPROVE_ENTRY_POINT_NAME, - BALANCE_OF_ENTRY_POINT_NAME, BURN_ENTRY_POINT_NAME, CHANGE_SECURITY_ENTRY_POINT_NAME, - DECIMALS_ENTRY_POINT_NAME, DECREASE_ALLOWANCE_ENTRY_POINT_NAME, - INCREASE_ALLOWANCE_ENTRY_POINT_NAME, INIT_ENTRY_POINT_NAME, MINT_ENTRY_POINT_NAME, - NAME_ENTRY_POINT_NAME, OWNER, RECIPIENT, SPENDER, SYMBOL_ENTRY_POINT_NAME, - TOTAL_SUPPLY_ENTRY_POINT_NAME, TRANSFER_ENTRY_POINT_NAME, TRANSFER_FROM_ENTRY_POINT_NAME, + BALANCE_OF_ENTRY_POINT_NAME, BURN_ENTRY_POINT_NAME, CHANGE_EVENTS_MODE_ENTRY_POINT_NAME, + CHANGE_SECURITY_ENTRY_POINT_NAME, DECIMALS_ENTRY_POINT_NAME, + DECREASE_ALLOWANCE_ENTRY_POINT_NAME, EVENTS, EVENTS_MODE, INCREASE_ALLOWANCE_ENTRY_POINT_NAME, + INIT_ENTRY_POINT_NAME, MIGRATE_USER_ALLOWANCE_KEYS_ENTRY_POINT_NAME, + MIGRATE_USER_BALANCE_KEYS_ENTRY_POINT_NAME, MIGRATE_USER_SEC_KEYS_ENTRY_POINT_NAME, + MINT_ENTRY_POINT_NAME, NAME_ENTRY_POINT_NAME, OWNER, RECIPIENT, REVERT, SPENDER, + SYMBOL_ENTRY_POINT_NAME, TOTAL_SUPPLY_ENTRY_POINT_NAME, TRANSFER_ENTRY_POINT_NAME, + TRANSFER_FROM_ENTRY_POINT_NAME, }; /// Returns the `name` entry point. @@ -196,6 +199,66 @@ pub fn mint() -> EntryPoint { ) } +/// Returns the `migrate_user_allowance_keys` entry point. +pub fn migrate_user_allowance_keys() -> EntryPoint { + EntryPoint::new( + String::from(MIGRATE_USER_ALLOWANCE_KEYS_ENTRY_POINT_NAME), + vec![ + Parameter::new(EVENTS, bool::cl_type()), + Parameter::new(REVERT, bool::cl_type()), + // Parameter::new(USER_KEY_MAP, BTreeMap::::cl_type()), + ], + CLType::Unit, + EntryPointAccess::Public, + EntryPointType::Called, + casper_types::EntryPointPayment::Caller, + ) +} + +/// Returns the `migrate_user_balance_keys` entry point. +pub fn migrate_user_balance_keys() -> EntryPoint { + EntryPoint::new( + String::from(MIGRATE_USER_BALANCE_KEYS_ENTRY_POINT_NAME), + vec![ + Parameter::new(EVENTS, bool::cl_type()), + Parameter::new(REVERT, bool::cl_type()), + // Parameter::new(USER_KEY_MAP, BTreeMap::::cl_type()), + ], + CLType::Unit, + EntryPointAccess::Public, + EntryPointType::Called, + casper_types::EntryPointPayment::Caller, + ) +} + +/// Returns the `migrate_user_sec_keys` entry point. +pub fn migrate_user_sec_keys() -> EntryPoint { + EntryPoint::new( + String::from(MIGRATE_USER_SEC_KEYS_ENTRY_POINT_NAME), + vec![ + Parameter::new(EVENTS, bool::cl_type()), + Parameter::new(REVERT, bool::cl_type()), + // Parameter::new(USER_KEY_MAP, BTreeMap::::cl_type()), + ], + CLType::Unit, + EntryPointAccess::Public, + EntryPointType::Called, + casper_types::EntryPointPayment::Caller, + ) +} + +/// Returns the `migrate_user_sec_keys` entry point. +pub fn change_events_mode() -> EntryPoint { + EntryPoint::new( + String::from(CHANGE_EVENTS_MODE_ENTRY_POINT_NAME), + vec![Parameter::new(EVENTS_MODE, u8::cl_type())], + CLType::Unit, + EntryPointAccess::Public, + EntryPointType::Called, + casper_types::EntryPointPayment::Caller, + ) +} + /// Returns the `change_security` entry point. pub fn change_security() -> EntryPoint { EntryPoint::new( @@ -247,5 +310,9 @@ pub fn generate_entry_points() -> EntryPoints { entry_points.add_entry_point(change_security()); entry_points.add_entry_point(burn()); entry_points.add_entry_point(mint()); + entry_points.add_entry_point(migrate_user_allowance_keys()); + entry_points.add_entry_point(migrate_user_balance_keys()); + entry_points.add_entry_point(migrate_user_sec_keys()); + entry_points.add_entry_point(change_events_mode()); entry_points } diff --git a/cep18/src/main.rs b/cep18/src/main.rs index 546ff7e..3d77b3c 100644 --- a/cep18/src/main.rs +++ b/cep18/src/main.rs @@ -26,8 +26,11 @@ use entry_points::generate_entry_points; use casper_contract::{ contract_api::{ - runtime::{self, get_caller, get_key, get_named_arg, put_key, revert}, - storage::{self, dictionary_put}, + runtime::{ + self, call_contract, get_caller, get_key, get_named_arg, manage_message_topic, put_key, + revert, + }, + storage::{self, dictionary_put, named_dictionary_get, named_dictionary_put}, }, unwrap_or_revert::UnwrapOrRevert, }; @@ -39,10 +42,11 @@ use casper_types::{ }; use constants::{ - ACCESS_KEY_NAME_PREFIX, ADDRESS, ADMIN_LIST, ALLOWANCES, AMOUNT, BALANCES, CONTRACT_HASH, - CONTRACT_NAME_PREFIX, CONTRACT_VERSION_PREFIX, DECIMALS, ENABLE_MINT_BURN, EVENTS, EVENTS_MODE, - HASH_KEY_NAME_PREFIX, INIT_ENTRY_POINT_NAME, MINTER_LIST, NAME, NONE_LIST, OWNER, PACKAGE_HASH, - RECIPIENT, REVERT, SECURITY_BADGES, SPENDER, SYMBOL, TOTAL_SUPPLY, USER_KEY_MAP, + ACCESS_KEY_NAME_PREFIX, ADDRESS, ADMIN_LIST, ALLOWANCES, AMOUNT, BALANCES, + CHANGE_EVENTS_MODE_ENTRY_POINT_NAME, CONTRACT_HASH, CONTRACT_NAME_PREFIX, + CONTRACT_VERSION_PREFIX, DECIMALS, ENABLE_MINT_BURN, EVENTS, EVENTS_MODE, HASH_KEY_NAME_PREFIX, + INIT_ENTRY_POINT_NAME, MINTER_LIST, NAME, NONE_LIST, OWNER, PACKAGE_HASH, RECIPIENT, REVERT, + SECURITY_BADGES, SPENDER, SYMBOL, TOTAL_SUPPLY, USER_KEY_MAP, }; pub use error::Cep18Error; use events::{ @@ -388,7 +392,7 @@ pub extern "C" fn change_security() { /// contract). Going forward these will be stored are Key::AddressableEntity(EntityAddr::Account) /// and Key::AddressableEntity(EntityAddr::SmartContract) respectively. #[no_mangle] -pub fn migrate_users_balance_keys() { +pub fn migrate_user_balance_keys() { let event_on: bool = get_named_arg(EVENTS); let revert_on: bool = get_named_arg(REVERT); let mut success_map: Vec<(Key, Key)> = Vec::new(); @@ -429,10 +433,15 @@ pub fn migrate_users_balance_keys() { continue; } }; - let balance = read_balance_from(balances_uref, old_key); - if balance > U256::zero() { + let old_balance = read_balance_from(balances_uref, old_key); + if old_balance > U256::zero() { + let new_key_existing_balance = read_balance_from(balances_uref, migrated_key); write_balance_to(balances_uref, old_key, U256::zero()); - write_balance_to(balances_uref, migrated_key, balance) + write_balance_to( + balances_uref, + migrated_key, + new_key_existing_balance + old_balance, + ) } else if event_on { failure_map.insert(old_key, String::from("NoOldKeyBal")); } else if revert_on { @@ -456,7 +465,7 @@ pub fn migrate_users_balance_keys() { /// be stored are Key::AddressableEntity(EntityAddr::Account) /// and Key::AddressableEntity(EntityAddr::SmartContract) respectively. #[no_mangle] -pub fn migrate_users_allowance_keys() { +pub fn migrate_user_allowance_keys() { let event_on: bool = get_named_arg(EVENTS); let revert_on: bool = get_named_arg(REVERT); let mut success_map: Vec<((Key, Key), (Key, Key))> = Vec::new(); @@ -541,9 +550,11 @@ pub fn migrate_users_allowance_keys() { continue; } }; - let allowance = + let old_allowance = read_allowance_from(allowances_uref, migrated_owner_key, migrated_spender_key); - if allowance > U256::zero() { + if old_allowance > U256::zero() { + let new_key_existing_allowance = + read_allowance_from(allowances_uref, migrated_owner_key, migrated_spender_key); write_allowance_to( allowances_uref, migrated_owner_key, @@ -554,7 +565,7 @@ pub fn migrate_users_allowance_keys() { allowances_uref, migrated_owner_key, migrated_spender_key, - allowance, + new_key_existing_allowance + old_allowance, ) } else if event_on { failure_map.insert( @@ -578,6 +589,95 @@ pub fn migrate_users_allowance_keys() { } } +#[no_mangle] +pub fn migrate_sec_keys() { + let event_on: bool = get_named_arg(EVENTS); + let revert_on: bool = get_named_arg(REVERT); + let mut success_map: Vec<(Key, Key)> = Vec::new(); + let mut failure_map: BTreeMap = BTreeMap::new(); + + let keys: BTreeMap = get_named_arg(USER_KEY_MAP); + for (old_key, is_account_flag) in keys { + let migrated_key = match old_key { + Key::Account(account_hash) => { + if !is_account_flag { + if event_on { + failure_map.insert(old_key, String::from("FlagMismatch")); + } else if revert_on { + revert(Cep18Error::KeyTypeMigrationMismatch) + } + continue; + } + Key::AddressableEntity(EntityAddr::Account(account_hash.value())) + } + Key::Hash(contract_package) => { + if is_account_flag { + if event_on { + failure_map.insert(old_key, String::from("FlagMismatch")); + } else if revert_on { + revert(Cep18Error::KeyTypeMigrationMismatch) + } + continue; + } + Key::AddressableEntity(EntityAddr::SmartContract(contract_package)) + } + _ => { + if event_on { + failure_map.insert(old_key, String::from("WrongKeyType")); + } else if revert_on { + revert(Cep18Error::InvalidKeyType) + } + continue; + } + }; + let old_user_sec_key = old_key.to_bytes().unwrap_or_revert(); + let old_encoded_user_sec_key = base64::encode(old_user_sec_key); + + let user_sec_key = migrated_key.to_bytes().unwrap_or_revert(); + let migrated_encoded_user_sec_key = base64::encode(user_sec_key); + + let sec: SecurityBadge = named_dictionary_get(SECURITY_BADGES, &old_encoded_user_sec_key) + .unwrap_or_revert() + .unwrap_or(SecurityBadge::None); + if ![SecurityBadge::Admin, SecurityBadge::Minter].contains(&sec) { + named_dictionary_put(SECURITY_BADGES, &migrated_encoded_user_sec_key, sec); + } else if event_on { + failure_map.insert(old_key, String::from("NoValidBadge")); + } else if revert_on { + revert(Cep18Error::InsufficientRights) + } + success_map.push((old_key, migrated_key)); + } + + if event_on { + events::record_event_dictionary(Event::BalanceMigration(BalanceMigration { + success_map, + failure_map, + })); + } +} + +#[no_mangle] +fn change_events_mode() { + sec_check(vec![SecurityBadge::Admin]); + let events_mode: EventsMode = + EventsMode::try_from(read_from::(EVENTS_MODE)).unwrap_or_revert(); + + match events_mode { + EventsMode::NoEvents => {} + EventsMode::CES => init_events(), + EventsMode::Native => { + manage_message_topic(EVENTS, MessageTopicOperation::Add).unwrap_or_revert() + } + EventsMode::NativeNCES => { + init_events(); + manage_message_topic(EVENTS, MessageTopicOperation::Add).unwrap_or_revert() + } + } + + put_key(EVENTS_MODE, storage::new_uref(events_mode as u8).into()); +} + pub fn upgrade(name: &str) { let entry_points = generate_entry_points(); @@ -598,34 +698,30 @@ pub fn upgrade(name: &str) { }; let converted_previous_contract_hash = AddressableEntityHash::new(previous_contract_hash); - let events_mode: EventsMode = EventsMode::try_from( - utils::get_optional_named_arg_with_user_errors(EVENTS_MODE, Cep18Error::InvalidEventsMode) - .unwrap_or(0u8), - ) - .unwrap_or(EventsMode::NoEvents); - - // If event mode is native append the Add operation to the contract - let mut message_topics = BTreeMap::new(); - if EventsMode::Native != events_mode { - message_topics.insert(EVENTS.to_string(), MessageTopicOperation::Add); - }; - - let mut named_keys = NamedKeys::new(); - named_keys.insert( - EVENTS_MODE.to_string(), - storage::new_uref(events_mode as u8).into(), - ); - let (contract_hash, contract_version) = storage::add_contract_version( contract_package_hash, entry_points, - named_keys, - message_topics, + NamedKeys::new(), + BTreeMap::new(), ); storage::disable_contract_version(contract_package_hash, converted_previous_contract_hash) .unwrap_or_revert(); + let events_mode = utils::get_optional_named_arg_with_user_errors::( + EVENTS_MODE, + Cep18Error::InvalidEventsMode, + ); + if let Some(events_mode) = events_mode { + call_contract::<()>( + contract_hash, + CHANGE_EVENTS_MODE_ENTRY_POINT_NAME, + runtime_args! { + EVENTS_MODE => events_mode + }, + ); + } + // migrate old ContractPackageHash as PackageHash so it's stored in a uniform format with the // new `new_contract` implementation runtime::put_key( diff --git a/cep18/src/utils.rs b/cep18/src/utils.rs index 6f5232d..9c51ca0 100644 --- a/cep18/src/utils.rs +++ b/cep18/src/utils.rs @@ -12,6 +12,7 @@ use casper_contract::{ unwrap_or_revert::UnwrapOrRevert, }; use casper_types::{ + account::AccountHash, api_error, bytesrepr::{self, FromBytes, ToBytes}, system::Caller, @@ -180,17 +181,50 @@ impl FromBytes for SecurityBadge { } pub fn sec_check(allowed_badge_list: Vec) { - let caller = get_immediate_caller_address() - .unwrap_or_revert() + let caller = get_immediate_caller_address().unwrap_or_revert(); + let hash: [u8; 32] = match caller { + Key::Account(account) => account.0, + Key::Hash(hash) => hash, + Key::AddressableEntity(addressable) => match addressable { + EntityAddr::System(_) => revert(Cep18Error::InvalidKeyType), + EntityAddr::Account(account) => account, + EntityAddr::SmartContract(hash) => hash, + }, + _ => revert(Cep18Error::InvalidKeyType), + }; + let new_style_key_bytes = Key::AddressableEntity(EntityAddr::Account(hash)) .to_bytes() .unwrap_or_revert(); - if !allowed_badge_list.contains( - &dictionary_get::(get_uref(SECURITY_BADGES), &base64::encode(caller)) + match dictionary_get::( + get_uref(SECURITY_BADGES), + &base64::encode(new_style_key_bytes), + ) + .unwrap_or_revert() + { + Some(badge) => { + if !allowed_badge_list.contains(&badge) { + revert(Cep18Error::InsufficientRights) + } + } + None => { + let old_style_key_bytes = Key::Account(AccountHash::new(hash)) + .to_bytes() + .unwrap_or_revert(); + match dictionary_get::( + get_uref(SECURITY_BADGES), + &base64::encode(old_style_key_bytes), + ) .unwrap_or_revert() - .unwrap_or_revert_with(Cep18Error::InsufficientRights), - ) { - revert(Cep18Error::InsufficientRights) - } + { + Some(badge) => { + if !allowed_badge_list.contains(&badge) { + revert(Cep18Error::InsufficientRights) + } + } + None => revert(Cep18Error::InsufficientRights), + }; + } + }; } pub fn change_sec_badge(badge_map: &BTreeMap) { diff --git a/tests/src/migration.rs b/tests/src/migration.rs index 54d7cfd..d1cc6fc 100644 --- a/tests/src/migration.rs +++ b/tests/src/migration.rs @@ -3,14 +3,17 @@ use std::collections::BTreeMap; use casper_engine_test_support::{ ExecuteRequestBuilder, UpgradeRequestBuilder, DEFAULT_ACCOUNT_ADDR, }; -use casper_types::{runtime_args, AccessRights, AddressableEntityHash, CLValue, EraId, Key, ProtocolVersion, SystemEntityRegistry, URef, U256}; +use casper_types::{runtime_args, EraId, Key, ProtocolVersion, RuntimeArgs, U256}; use crate::utility::{ constants::{ - ARG_DECIMALS, ARG_NAME, ARG_SYMBOL, ARG_TOTAL_SUPPLY, CEP18_CONTRACT_WASM, - CEP18_TOKEN_CONTRACT_KEY, TOKEN_DECIMALS, TOKEN_NAME, TOKEN_SYMBOL, TOKEN_TOTAL_SUPPLY, + AMOUNT, ARG_DECIMALS, ARG_NAME, ARG_SYMBOL, ARG_TOTAL_SUPPLY, CEP18_CONTRACT_WASM, + CEP18_TEST_CONTRACT_WASM, CEP18_TOKEN_CONTRACT_KEY, EVENTS, EVENTS_MODE, METHOD_MINT, + MIGRATE_USER_BALANCE_KEYS_ENTRY_POINT_NAME, MIGRATE_USER_SEC_KEYS_ENTRY_POINT_NAME, OWNER, + REVERT, TOKEN_DECIMALS, TOKEN_NAME, TOKEN_OWNER_ADDRESS_1, TOKEN_OWNER_ADDRESS_1_OLD, + TOKEN_OWNER_AMOUNT_1, TOKEN_SYMBOL, TOKEN_TOTAL_SUPPLY, USER_KEY_MAP, }, - installer_request_builders::{setup, TestContext}, + installer_request_builders::{cep18_check_balance_of, setup, TestContext}, }; #[test] @@ -62,64 +65,30 @@ fn should_upgrade_contract_version() { #[test] fn should_migrate_1_5_6_to_2_0_0_rc3() { - let (mut builder, _lmdb_fixture_state, _temp_dir) = + let (mut builder, lmdb_fixture_state, _temp_dir) = casper_fixtures::builder_from_global_state_fixture("cep18-1.5.6-minted"); - let key = URef::new([0u8;32], AccessRights::all()).into(); - - let system_contract_hashes = builder.query(Some(_lmdb_fixture_state.post_state_hash), key, &Vec::new()) - .expect("must have stored system").as_cl_value() - .expect("must clvalue") - .clone().into_t::().expect("convert btree"); - - let mut global_state_update = BTreeMap::new(); - let registry = CLValue::from_t(system_contract_hashes).expect("must StoredValue").into(); - - global_state_update.insert(Key::SystemEntityRegistry, registry); + assert_eq!( + builder.get_post_state_hash(), + lmdb_fixture_state.post_state_hash + ); let mut upgrade_config = UpgradeRequestBuilder::new() - .with_current_protocol_version(ProtocolVersion::V1_0_0) + .with_current_protocol_version(lmdb_fixture_state.genesis_protocol_version()) .with_new_protocol_version(ProtocolVersion::V2_0_0) .with_migrate_legacy_accounts(true) .with_migrate_legacy_contracts(true) - .with_global_state_update(global_state_update) .with_activation_point(EraId::new(1)) .build(); - builder.upgrade(&mut upgrade_config).expect_upgrade_success(); - - println!( - "get_entity_with_named_keys_by_entity_hash: {:?}", - builder.get_entity_with_named_keys_by_entity_hash(AddressableEntityHash::new( - DEFAULT_ACCOUNT_ADDR.value() - )) + builder + .upgrade(&mut upgrade_config) + .expect_upgrade_success() + .commit(); + assert_ne!( + builder.get_post_state_hash(), + lmdb_fixture_state.post_state_hash ); - println!("get_engine_state: {:?}", builder.get_engine_state()); - println!("get_genesis_account: {:?}", builder.get_genesis_account()); - - // let get_entity_by_account_hash = builder.get_entity_by_account_hash(*DEFAULT_ACCOUNT_ADDR); - // println!("{get_entity_by_account_hash:?}"); - - // let query_named_key_by_account_hash = builder.query_named_key_by_account_hash( - // Some(lmdb_fixture_state.post_state_hash), - // *DEFAULT_ACCOUNT_ADDR, - // CEP18_TOKEN_CONTRACT_KEY, - // ); - // println!("{query_named_key_by_account_hash:?}"); - - let get_entity_hash_by_account_hash = - builder.get_entity_hash_by_account_hash(*DEFAULT_ACCOUNT_ADDR); - println!("{get_entity_hash_by_account_hash:?}"); - - let account = builder - .get_entity_with_named_keys_by_account_hash(*DEFAULT_ACCOUNT_ADDR) - .unwrap(); - let account_named_keys = account.named_keys(); - - let _cep18_token = account_named_keys - .get(CEP18_TOKEN_CONTRACT_KEY) - .and_then(|key| key.into_entity_hash()) - .expect("should have contract hash"); let version_0: u32 = builder .query( @@ -142,6 +111,7 @@ fn should_migrate_1_5_6_to_2_0_0_rc3() { ARG_SYMBOL => TOKEN_SYMBOL, ARG_DECIMALS => TOKEN_DECIMALS, ARG_TOTAL_SUPPLY => U256::from(TOKEN_TOTAL_SUPPLY), + EVENTS_MODE => 3_u8 }, ) .build(); @@ -162,4 +132,74 @@ fn should_migrate_1_5_6_to_2_0_0_rc3() { .unwrap(); assert!(version_0 < version_1); + + let account = builder + .get_entity_with_named_keys_by_account_hash(*DEFAULT_ACCOUNT_ADDR) + .unwrap(); + let account_named_keys = account.named_keys(); + + let cep18_token = account_named_keys + .get(CEP18_TOKEN_CONTRACT_KEY) + .and_then(|key| key.into_entity_hash()) + .expect("should have contract hash"); + + let mut user_map: BTreeMap = BTreeMap::new(); + user_map.insert(Key::Account(*DEFAULT_ACCOUNT_ADDR), true); + user_map.insert(TOKEN_OWNER_ADDRESS_1_OLD, true); + + let sec_key_migrate_request = ExecuteRequestBuilder::contract_call_by_hash( + *DEFAULT_ACCOUNT_ADDR, + cep18_token, + MIGRATE_USER_SEC_KEYS_ENTRY_POINT_NAME, + runtime_args! {EVENTS => true, REVERT => true, USER_KEY_MAP => &user_map}, + ) + .build(); + + builder + .exec(sec_key_migrate_request) + .expect_success() + .commit(); + + let mint_request = ExecuteRequestBuilder::contract_call_by_hash( + *DEFAULT_ACCOUNT_ADDR, + cep18_token, + METHOD_MINT, + runtime_args! {OWNER => TOKEN_OWNER_ADDRESS_1, AMOUNT => U256::from(TOKEN_OWNER_AMOUNT_1)}, + ) + .build(); + + builder.exec(mint_request).expect_success().commit(); + + let test_contract = ExecuteRequestBuilder::standard( + *DEFAULT_ACCOUNT_ADDR, + CEP18_TEST_CONTRACT_WASM, + RuntimeArgs::default(), + ) + .build(); + + builder.exec(test_contract).expect_success().commit(); + + assert_eq!( + cep18_check_balance_of(&mut builder, &cep18_token, TOKEN_OWNER_ADDRESS_1), + U256::from(TOKEN_OWNER_AMOUNT_1), + ); + + let balance_migrate_request = ExecuteRequestBuilder::contract_call_by_hash( + *DEFAULT_ACCOUNT_ADDR, + cep18_token, + MIGRATE_USER_BALANCE_KEYS_ENTRY_POINT_NAME, + runtime_args! {EVENTS => true, REVERT => true, USER_KEY_MAP => user_map}, + ) + .build(); + + builder + .exec(balance_migrate_request) + .expect_success() + .commit(); + + // even if we minted before migrating, the balance should persist + assert_eq!( + cep18_check_balance_of(&mut builder, &cep18_token, TOKEN_OWNER_ADDRESS_1), + U256::from(TOKEN_OWNER_AMOUNT_1 * 2), + ); } diff --git a/tests/src/utility/constants.rs b/tests/src/utility/constants.rs index f88d31a..ff83908 100644 --- a/tests/src/utility/constants.rs +++ b/tests/src/utility/constants.rs @@ -51,12 +51,16 @@ pub static ACCOUNT_1_SECRET_KEY: Lazy = pub static ACCOUNT_1_PUBLIC_KEY: Lazy = Lazy::new(|| PublicKey::from(&*ACCOUNT_1_SECRET_KEY)); pub static ACCOUNT_1_ADDR: Lazy = Lazy::new(|| ACCOUNT_1_PUBLIC_KEY.to_account_hash()); +pub static _ACCOUNT_1_ENTITY_ADDR_KEY: Lazy = + Lazy::new(|| Key::AddressableEntity(EntityAddr::Account(ACCOUNT_1_ADDR.value()))); pub static ACCOUNT_2_SECRET_KEY: Lazy = Lazy::new(|| SecretKey::secp256k1_from_bytes([212u8; 32]).unwrap()); pub static ACCOUNT_2_PUBLIC_KEY: Lazy = Lazy::new(|| PublicKey::from(&*ACCOUNT_2_SECRET_KEY)); pub static ACCOUNT_2_ADDR: Lazy = Lazy::new(|| ACCOUNT_2_PUBLIC_KEY.to_account_hash()); +pub static _ACCOUNT_2_ENTITY_ADDR_KEY: Lazy = + Lazy::new(|| Key::AddressableEntity(EntityAddr::Account(ACCOUNT_2_ADDR.value()))); pub const TRANSFER_AMOUNT_1: u64 = 200_001; pub const TRANSFER_AMOUNT_2: u64 = 19_999; @@ -71,6 +75,8 @@ pub const TOKEN_OWNER_ADDRESS_1: Key = Key::AddressableEntity(EntityAddr::Accoun pub const TOKEN_OWNER_AMOUNT_1: u64 = 1_000_000; pub const TOKEN_OWNER_ADDRESS_2: Key = Key::AddressableEntity(EntityAddr::SmartContract([42; 32])); pub const TOKEN_OWNER_AMOUNT_2: u64 = 2_000_000; +pub const TOKEN_OWNER_ADDRESS_1_OLD: Key = Key::Account(AccountHash::new([42; 32])); +pub const _TOKEN_OWNER_ADDRESS_2_OLD: Key = Key::Hash([42; 32]); pub const METHOD_MINT: &str = "mint"; pub const METHOD_BURN: &str = "burn"; @@ -81,3 +87,11 @@ pub const ADMIN_LIST: &str = "admin_list"; pub const MINTER_LIST: &str = "minter_list"; pub const NONE_LIST: &str = "none_list"; pub const CHANGE_SECURITY: &str = "change_security"; + +pub const USER_KEY_MAP: &str = "user_key_map"; +pub const EVENTS: &str = "events"; +pub const REVERT: &str = "revert"; +pub const EVENTS_MODE: &str = "events_mode"; +pub const MIGRATE_USER_BALANCE_KEYS_ENTRY_POINT_NAME: &str = "migrate_user_balance_keys"; +pub const _MIGRATE_USER_ALLOWANCE_KEYS_ENTRY_POINT_NAME: &str = "migrate_user_allowance_keys"; +pub const MIGRATE_USER_SEC_KEYS_ENTRY_POINT_NAME: &str = "migrate_sec_keys"; diff --git a/tests/src/utility/installer_request_builders.rs b/tests/src/utility/installer_request_builders.rs index 4313ede..f81713d 100644 --- a/tests/src/utility/installer_request_builders.rs +++ b/tests/src/utility/installer_request_builders.rs @@ -176,7 +176,7 @@ pub(crate) fn cep18_check_balance_of( .named_keys() .get(CEP18_TEST_CONTRACT_KEY) .and_then(|key| key.into_package_hash()) - .expect("should have test contract hash"); + .expect("should have test contract package hash"); let check_balance_args = runtime_args! { ARG_TOKEN_CONTRACT => Key::addressable_entity_key(EntityKindTag::SmartContract, *cep18_contract_hash), From 2367dca37e58cd4e147016edb2287e2711583620 Mon Sep 17 00:00:00 2001 From: gRoussac Date: Mon, 24 Jun 2024 16:25:26 +0200 Subject: [PATCH 13/27] Small review changes --- Makefile | 2 ++ cep18/src/constants.rs | 2 -- cep18/src/main.rs | 17 +++++++---------- cep18/src/modalities.rs | 3 ++- 4 files changed, 11 insertions(+), 13 deletions(-) diff --git a/Makefile b/Makefile index 40fd081..a94dca2 100644 --- a/Makefile +++ b/Makefile @@ -1,3 +1,5 @@ +PINNED_TOOLCHAIN := $(shell cat cep18/rust-toolchain) + prepare: rustup target add wasm32-unknown-unknown rustup component add clippy --toolchain ${PINNED_TOOLCHAIN} diff --git a/cep18/src/constants.rs b/cep18/src/constants.rs index 0969c69..c324a29 100644 --- a/cep18/src/constants.rs +++ b/cep18/src/constants.rs @@ -70,8 +70,6 @@ pub const EVENTS_MODE: &str = "events_mode"; pub const SECURITY_BADGES: &str = "security_badges"; pub const ADMIN_LIST: &str = "admin_list"; pub const MINTER_LIST: &str = "minter_list"; -pub const BURNER_LIST: &str = "burner_list"; pub const NONE_LIST: &str = "none_list"; -pub const MINT_AND_BURN_LIST: &str = "mint_and_burn_list"; pub const ENABLE_MINT_BURN: &str = "enable_mint_burn"; pub const USER_KEY_MAP: &str = "user_key_map"; diff --git a/cep18/src/main.rs b/cep18/src/main.rs index 3d77b3c..5f6369d 100644 --- a/cep18/src/main.rs +++ b/cep18/src/main.rs @@ -744,12 +744,10 @@ pub fn install_contract(name: &str) { let symbol: String = runtime::get_named_arg(SYMBOL); let decimals: u8 = runtime::get_named_arg(DECIMALS); let total_supply: U256 = runtime::get_named_arg(TOTAL_SUPPLY); - let events_mode_arg: u8 = + let events_mode: u8 = utils::get_optional_named_arg_with_user_errors(EVENTS_MODE, Cep18Error::InvalidEventsMode) .unwrap_or(0u8); - let events_mode: EventsMode = EventsMode::try_from(events_mode_arg).unwrap_or_revert(); - let admin_list: Option> = utils::get_optional_named_arg_with_user_errors(ADMIN_LIST, Cep18Error::InvalidAdminList); let minter_list: Option> = @@ -771,7 +769,7 @@ pub fn install_contract(name: &str) { ); named_keys.insert( EVENTS_MODE.to_string(), - storage::new_uref(events_mode_arg).into(), + storage::new_uref(events_mode).into(), ); named_keys.insert( ENABLE_MINT_BURN.to_string(), @@ -779,7 +777,7 @@ pub fn install_contract(name: &str) { ); let entry_points = generate_entry_points(); - let message_topics = if events_mode == EventsMode::Native { + let message_topics = if EventsMode::Native == events_mode.try_into().unwrap_or_default() { let mut message_topics = BTreeMap::new(); message_topics.insert(EVENTS.to_string(), MessageTopicOperation::Add); Some(message_topics) @@ -796,18 +794,17 @@ pub fn install_contract(name: &str) { message_topics, ); let package_hash = runtime::get_key(&hash_key_name).unwrap_or_revert(); + let contract_hash_key = + Key::addressable_entity_key(EntityKindTag::SmartContract, contract_hash); // Store contract_hash and contract_version under the keys CONTRACT_NAME and CONTRACT_VERSION - runtime::put_key( - &format!("{CONTRACT_NAME_PREFIX}{name}"), - Key::addressable_entity_key(EntityKindTag::SmartContract, contract_hash), - ); + runtime::put_key(&format!("{CONTRACT_NAME_PREFIX}{name}"), contract_hash_key); runtime::put_key( &format!("{CONTRACT_VERSION_PREFIX}{name}"), storage::new_uref(contract_version).into(), ); // Call contract to initialize it - let mut init_args = runtime_args! {TOTAL_SUPPLY => total_supply, PACKAGE_HASH => package_hash, CONTRACT_HASH => Key::addressable_entity_key(EntityKindTag::SmartContract, contract_hash)}; + let mut init_args = runtime_args! {TOTAL_SUPPLY => total_supply, PACKAGE_HASH => package_hash, CONTRACT_HASH => contract_hash_key}; if let Some(admin_list) = admin_list { init_args.insert(ADMIN_LIST, admin_list).unwrap_or_revert(); diff --git a/cep18/src/modalities.rs b/cep18/src/modalities.rs index 161e126..5dd80d9 100644 --- a/cep18/src/modalities.rs +++ b/cep18/src/modalities.rs @@ -3,9 +3,10 @@ use core::convert::TryFrom; use crate::Cep18Error; #[repr(u8)] -#[derive(PartialEq, Eq)] +#[derive(PartialEq, Eq, Default)] #[allow(clippy::upper_case_acronyms)] pub enum EventsMode { + #[default] NoEvents = 0, CES = 1, Native = 2, From 8c0711554e30f5cca97b5c3bd4c3482e5e33f3e4 Mon Sep 17 00:00:00 2001 From: gRoussac Date: Tue, 25 Jun 2024 16:06:56 +0200 Subject: [PATCH 14/27] Small rename --- tests/src/allowance.rs | 30 +++- tests/src/fixture_gen.rs | 20 +-- tests/src/install.rs | 10 +- tests/src/migration.rs | 14 +- tests/src/mint_and_burn.rs | 92 +++++------ tests/src/transfer.rs | 154 ++++++++++++------ .../src/utility/installer_request_builders.rs | 61 +++---- 7 files changed, 223 insertions(+), 158 deletions(-) diff --git a/tests/src/allowance.rs b/tests/src/allowance.rs index ce5e853..ecbd403 100644 --- a/tests/src/allowance.rs +++ b/tests/src/allowance.rs @@ -87,9 +87,15 @@ fn should_approve_funds_account_to_contract() { #[test] fn should_not_transfer_from_without_enough_allowance() { - let (mut builder, TestContext { cep18_token, .. }) = setup(); + let ( + mut builder, + TestContext { + cep18_contract_hash, + .. + }, + ) = setup(); - let addressable_cep18_token = AddressableEntityHash::new(cep18_token.value()); + let addressable_cep18_contract_hash = AddressableEntityHash::new(cep18_contract_hash.value()); let allowance_amount_1 = U256::from(ALLOWANCE_AMOUNT_1); let transfer_from_amount_1 = allowance_amount_1 + U256::one(); @@ -118,7 +124,7 @@ fn should_not_transfer_from_without_enough_allowance() { let approve_request_1 = ExecuteRequestBuilder::contract_call_by_hash( sender, - addressable_cep18_token, + addressable_cep18_contract_hash, METHOD_APPROVE, cep18_approve_args, ) @@ -126,7 +132,7 @@ fn should_not_transfer_from_without_enough_allowance() { let transfer_from_request_1 = ExecuteRequestBuilder::contract_call_by_hash( sender, - addressable_cep18_token, + addressable_cep18_contract_hash, METHOD_TRANSFER_FROM, cep18_transfer_from_args, ) @@ -153,9 +159,15 @@ fn should_not_transfer_from_without_enough_allowance() { #[test] fn test_decrease_allowance() { - let (mut builder, TestContext { cep18_token, .. }) = setup(); + let ( + mut builder, + TestContext { + cep18_contract_hash, + .. + }, + ) = setup(); - let addressable_cep18_token = AddressableEntityHash::new(cep18_token.value()); + let addressable_cep18_contract_hash = AddressableEntityHash::new(cep18_contract_hash.value()); let owner = *DEFAULT_ACCOUNT_ADDR; let spender = Key::Hash([42; 32]); @@ -170,10 +182,10 @@ fn test_decrease_allowance() { assert_eq!(spender_allowance_before, U256::zero()); let approve_request = - make_cep18_approve_request(owner_key, &cep18_token, spender, allowance_amount_1); + make_cep18_approve_request(owner_key, &cep18_contract_hash, spender, allowance_amount_1); let decrease_allowance_request = ExecuteRequestBuilder::contract_call_by_hash( owner, - addressable_cep18_token, + addressable_cep18_contract_hash, DECREASE_ALLOWANCE, runtime_args! { ARG_SPENDER => spender, @@ -183,7 +195,7 @@ fn test_decrease_allowance() { .build(); let increase_allowance_request = ExecuteRequestBuilder::contract_call_by_hash( owner, - addressable_cep18_token, + addressable_cep18_contract_hash, INCREASE_ALLOWANCE, runtime_args! { ARG_SPENDER => spender, diff --git a/tests/src/fixture_gen.rs b/tests/src/fixture_gen.rs index 70f6751..cbdde98 100644 --- a/tests/src/fixture_gen.rs +++ b/tests/src/fixture_gen.rs @@ -190,15 +190,15 @@ fn main() { .unwrap(); let account_named_keys = account.named_keys(); - let cep18_token = account_named_keys + let cep18_contract_hash = account_named_keys .get(CEP18_TOKEN_CONTRACT_KEY) .and_then(|key| key.into_entity_hash()) .expect("should have contract hash"); - let addressable_cep18_token = AddressableEntityHash::new(cep18_token.value()); + let addressable_cep18_contract_hash = AddressableEntityHash::new(cep18_contract_hash.value()); let mint_request = ExecuteRequestBuilder::contract_call_by_hash( *DEFAULT_ACCOUNT_ADDR, - addressable_cep18_token, + addressable_cep18_contract_hash, METHOD_MINT, runtime_args! {OWNER => TOKEN_OWNER_ADDRESS_1, AMOUNT => U256::from(TOKEN_OWNER_AMOUNT_1)}, ) @@ -206,7 +206,7 @@ fn main() { builder.exec(mint_request).expect_success().commit(); let mint_request_2 = ExecuteRequestBuilder::contract_call_by_hash( *DEFAULT_ACCOUNT_ADDR, - addressable_cep18_token, + addressable_cep18_contract_hash, METHOD_MINT, runtime_args! {OWNER => TOKEN_OWNER_ADDRESS_2, AMOUNT => U256::from(TOKEN_OWNER_AMOUNT_2)}, ) @@ -215,23 +215,23 @@ fn main() { assert_eq!( cep18_check_balance_of( builder, - &cep18_token, + &cep18_contract_hash, Key::AddressableEntity(casper_types::EntityAddr::Account(DEFAULT_ACCOUNT_ADDR.value())) ), U256::from(TOKEN_TOTAL_SUPPLY), ); assert_eq!( - cep18_check_balance_of(builder, &cep18_token, TOKEN_OWNER_ADDRESS_1), + cep18_check_balance_of(builder, &cep18_contract_hash, TOKEN_OWNER_ADDRESS_1), U256::from(TOKEN_OWNER_AMOUNT_1) ); assert_eq!( - cep18_check_balance_of(builder, &cep18_token, TOKEN_OWNER_ADDRESS_2), + cep18_check_balance_of(builder, &cep18_contract_hash, TOKEN_OWNER_ADDRESS_2), U256::from(TOKEN_OWNER_AMOUNT_2) ); let mint_request = ExecuteRequestBuilder::contract_call_by_hash( *DEFAULT_ACCOUNT_ADDR, - addressable_cep18_token, + addressable_cep18_contract_hash, METHOD_MINT, runtime_args! { ARG_OWNER => TOKEN_OWNER_ADDRESS_1, @@ -243,11 +243,11 @@ fn main() { builder.exec(mint_request).expect_success().commit(); assert_eq!( - cep18_check_balance_of(builder, &cep18_token, TOKEN_OWNER_ADDRESS_1), + cep18_check_balance_of(builder, &cep18_contract_hash, TOKEN_OWNER_ADDRESS_1), U256::from(TOKEN_OWNER_AMOUNT_1) + mint_amount, ); assert_eq!( - cep18_check_balance_of(builder, &cep18_token, TOKEN_OWNER_ADDRESS_2), + cep18_check_balance_of(builder, &cep18_contract_hash, TOKEN_OWNER_ADDRESS_2), U256::from(TOKEN_OWNER_AMOUNT_2) ); }).unwrap(); diff --git a/tests/src/install.rs b/tests/src/install.rs index 71f066a..9ec5719 100644 --- a/tests/src/install.rs +++ b/tests/src/install.rs @@ -13,9 +13,9 @@ use crate::utility::{ #[test] fn should_have_queryable_properties() { - let (mut builder, TestContext { cep18_token, .. }) = setup(); + let (mut builder, TestContext { cep18_contract_hash, .. }) = setup(); - let cep18_entity_addr = EntityAddr::new_smart_contract(cep18_token.value()); + let cep18_entity_addr = EntityAddr::new_smart_contract(cep18_contract_hash.value()); let name: String = builder.get_value(cep18_entity_addr, NAME_KEY); assert_eq!(name, TOKEN_NAME); @@ -31,18 +31,18 @@ fn should_have_queryable_properties() { let owner_key = Key::AddressableEntity(EntityAddr::Account(DEFAULT_ACCOUNT_ADDR.value())); - let owner_balance = cep18_check_balance_of(&mut builder, &cep18_token, owner_key); + let owner_balance = cep18_check_balance_of(&mut builder, &cep18_contract_hash, owner_key); assert_eq!(owner_balance, total_supply); let contract_balance = - cep18_check_balance_of(&mut builder, &cep18_token, Key::Hash(cep18_token.value())); + cep18_check_balance_of(&mut builder, &cep18_contract_hash, Key::Hash(cep18_contract_hash.value())); assert_eq!(contract_balance, U256::zero()); // Ensures that Account and Contract ownership is respected and we're not keying ownership under // the raw bytes regardless of variant. let inverted_owner_key = invert_cep18_address(owner_key); let inverted_owner_balance = - cep18_check_balance_of(&mut builder, &cep18_token, inverted_owner_key); + cep18_check_balance_of(&mut builder, &cep18_contract_hash, inverted_owner_key); assert_eq!(inverted_owner_balance, U256::zero()); } diff --git a/tests/src/migration.rs b/tests/src/migration.rs index d1cc6fc..ae60eee 100644 --- a/tests/src/migration.rs +++ b/tests/src/migration.rs @@ -18,7 +18,7 @@ use crate::utility::{ #[test] fn should_upgrade_contract_version() { - let (mut builder, TestContext { cep18_token: _, .. }) = setup(); + let (mut builder, TestContext { cep18_contract_hash: _, .. }) = setup(); let version_0: u32 = builder .query( @@ -138,7 +138,7 @@ fn should_migrate_1_5_6_to_2_0_0_rc3() { .unwrap(); let account_named_keys = account.named_keys(); - let cep18_token = account_named_keys + let cep18_contract_hash = account_named_keys .get(CEP18_TOKEN_CONTRACT_KEY) .and_then(|key| key.into_entity_hash()) .expect("should have contract hash"); @@ -149,7 +149,7 @@ fn should_migrate_1_5_6_to_2_0_0_rc3() { let sec_key_migrate_request = ExecuteRequestBuilder::contract_call_by_hash( *DEFAULT_ACCOUNT_ADDR, - cep18_token, + cep18_contract_hash, MIGRATE_USER_SEC_KEYS_ENTRY_POINT_NAME, runtime_args! {EVENTS => true, REVERT => true, USER_KEY_MAP => &user_map}, ) @@ -162,7 +162,7 @@ fn should_migrate_1_5_6_to_2_0_0_rc3() { let mint_request = ExecuteRequestBuilder::contract_call_by_hash( *DEFAULT_ACCOUNT_ADDR, - cep18_token, + cep18_contract_hash, METHOD_MINT, runtime_args! {OWNER => TOKEN_OWNER_ADDRESS_1, AMOUNT => U256::from(TOKEN_OWNER_AMOUNT_1)}, ) @@ -180,13 +180,13 @@ fn should_migrate_1_5_6_to_2_0_0_rc3() { builder.exec(test_contract).expect_success().commit(); assert_eq!( - cep18_check_balance_of(&mut builder, &cep18_token, TOKEN_OWNER_ADDRESS_1), + cep18_check_balance_of(&mut builder, &cep18_contract_hash, TOKEN_OWNER_ADDRESS_1), U256::from(TOKEN_OWNER_AMOUNT_1), ); let balance_migrate_request = ExecuteRequestBuilder::contract_call_by_hash( *DEFAULT_ACCOUNT_ADDR, - cep18_token, + cep18_contract_hash, MIGRATE_USER_BALANCE_KEYS_ENTRY_POINT_NAME, runtime_args! {EVENTS => true, REVERT => true, USER_KEY_MAP => user_map}, ) @@ -199,7 +199,7 @@ fn should_migrate_1_5_6_to_2_0_0_rc3() { // even if we minted before migrating, the balance should persist assert_eq!( - cep18_check_balance_of(&mut builder, &cep18_token, TOKEN_OWNER_ADDRESS_1), + cep18_check_balance_of(&mut builder, &cep18_contract_hash, TOKEN_OWNER_ADDRESS_1), U256::from(TOKEN_OWNER_AMOUNT_1 * 2), ); } diff --git a/tests/src/mint_and_burn.rs b/tests/src/mint_and_burn.rs index 94455be..92bf22f 100644 --- a/tests/src/mint_and_burn.rs +++ b/tests/src/mint_and_burn.rs @@ -20,17 +20,17 @@ use casper_execution_engine::{engine_state::Error as CoreError, execution::ExecE fn test_mint_and_burn_tokens() { let mint_amount = U256::one(); - let (mut builder, TestContext { cep18_token, .. }) = setup_with_args(runtime_args! { + let (mut builder, TestContext { cep18_contract_hash, .. }) = setup_with_args(runtime_args! { ARG_NAME => TOKEN_NAME, ARG_SYMBOL => TOKEN_SYMBOL, ARG_DECIMALS => TOKEN_DECIMALS, ARG_TOTAL_SUPPLY => U256::from(TOKEN_TOTAL_SUPPLY), ENABLE_MINT_BURN => true, }); - let addressable_cep18_token = AddressableEntityHash::new(cep18_token.value()); + let addressable_cep18_contract_hash = AddressableEntityHash::new(cep18_contract_hash.value()); let mint_request = ExecuteRequestBuilder::contract_call_by_hash( *DEFAULT_ACCOUNT_ADDR, - addressable_cep18_token, + addressable_cep18_contract_hash, METHOD_MINT, runtime_args! {OWNER => TOKEN_OWNER_ADDRESS_1, AMOUNT => U256::from(TOKEN_OWNER_AMOUNT_1)}, ) @@ -38,7 +38,7 @@ fn test_mint_and_burn_tokens() { builder.exec(mint_request).expect_success().commit(); let mint_request_2 = ExecuteRequestBuilder::contract_call_by_hash( *DEFAULT_ACCOUNT_ADDR, - addressable_cep18_token, + addressable_cep18_contract_hash, METHOD_MINT, runtime_args! {OWNER => TOKEN_OWNER_ADDRESS_2, AMOUNT => U256::from(TOKEN_OWNER_AMOUNT_2)}, ) @@ -47,7 +47,7 @@ fn test_mint_and_burn_tokens() { assert_eq!( cep18_check_balance_of( &mut builder, - &cep18_token, + &cep18_contract_hash, Key::AddressableEntity(casper_types::EntityAddr::Account( DEFAULT_ACCOUNT_ADDR.value() )) @@ -55,18 +55,18 @@ fn test_mint_and_burn_tokens() { U256::from(TOKEN_TOTAL_SUPPLY), ); assert_eq!( - cep18_check_balance_of(&mut builder, &cep18_token, TOKEN_OWNER_ADDRESS_1), + cep18_check_balance_of(&mut builder, &cep18_contract_hash, TOKEN_OWNER_ADDRESS_1), U256::from(TOKEN_OWNER_AMOUNT_1) ); assert_eq!( - cep18_check_balance_of(&mut builder, &cep18_token, TOKEN_OWNER_ADDRESS_2), + cep18_check_balance_of(&mut builder, &cep18_contract_hash, TOKEN_OWNER_ADDRESS_2), U256::from(TOKEN_OWNER_AMOUNT_2) ); - let total_supply_before_mint = cep18_check_total_supply(&mut builder, &cep18_token); + let total_supply_before_mint = cep18_check_total_supply(&mut builder, &cep18_contract_hash); let mint_request = ExecuteRequestBuilder::contract_call_by_hash( *DEFAULT_ACCOUNT_ADDR, - addressable_cep18_token, + addressable_cep18_contract_hash, METHOD_MINT, runtime_args! { ARG_OWNER => TOKEN_OWNER_ADDRESS_1, @@ -78,15 +78,15 @@ fn test_mint_and_burn_tokens() { builder.exec(mint_request).expect_success().commit(); assert_eq!( - cep18_check_balance_of(&mut builder, &cep18_token, TOKEN_OWNER_ADDRESS_1), + cep18_check_balance_of(&mut builder, &cep18_contract_hash, TOKEN_OWNER_ADDRESS_1), U256::from(TOKEN_OWNER_AMOUNT_1) + mint_amount, ); assert_eq!( - cep18_check_balance_of(&mut builder, &cep18_token, TOKEN_OWNER_ADDRESS_2), + cep18_check_balance_of(&mut builder, &cep18_contract_hash, TOKEN_OWNER_ADDRESS_2), U256::from(TOKEN_OWNER_AMOUNT_2) ); - let total_supply_after_mint = cep18_check_total_supply(&mut builder, &cep18_token); + let total_supply_after_mint = cep18_check_total_supply(&mut builder, &cep18_contract_hash); assert_eq!( total_supply_after_mint, total_supply_before_mint + mint_amount, @@ -95,7 +95,7 @@ fn test_mint_and_burn_tokens() { let burn_request = ExecuteRequestBuilder::contract_call_by_hash( *DEFAULT_ACCOUNT_ADDR, - addressable_cep18_token, + addressable_cep18_contract_hash, METHOD_BURN, runtime_args! { ARG_OWNER => Key::AddressableEntity(casper_types::EntityAddr::Account(DEFAULT_ACCOUNT_ADDR.value())), @@ -109,7 +109,7 @@ fn test_mint_and_burn_tokens() { assert_eq!( cep18_check_balance_of( &mut builder, - &cep18_token, + &cep18_contract_hash, Key::AddressableEntity(casper_types::EntityAddr::Account( DEFAULT_ACCOUNT_ADDR.value() )) @@ -117,10 +117,10 @@ fn test_mint_and_burn_tokens() { U256::from(999999999), ); assert_eq!( - cep18_check_balance_of(&mut builder, &cep18_token, TOKEN_OWNER_ADDRESS_2), + cep18_check_balance_of(&mut builder, &cep18_contract_hash, TOKEN_OWNER_ADDRESS_2), U256::from(TOKEN_OWNER_AMOUNT_2) ); - let total_supply_after_burn = cep18_check_total_supply(&mut builder, &cep18_token); + let total_supply_after_burn = cep18_check_total_supply(&mut builder, &cep18_contract_hash); assert_eq!( total_supply_after_burn, total_supply_before_burn - mint_amount, @@ -133,7 +133,7 @@ fn test_mint_and_burn_tokens() { fn test_should_not_mint_above_limits() { let mint_amount = U256::MAX; - let (mut builder, TestContext { cep18_token, .. }) = setup_with_args(runtime_args! { + let (mut builder, TestContext { cep18_contract_hash, .. }) = setup_with_args(runtime_args! { ARG_NAME => TOKEN_NAME, ARG_SYMBOL => TOKEN_SYMBOL, ARG_DECIMALS => TOKEN_DECIMALS, @@ -141,10 +141,10 @@ fn test_should_not_mint_above_limits() { "enable_mint_burn" => true, }); - let addressable_cep18_token = AddressableEntityHash::new(cep18_token.value()); + let addressable_cep18_contract_hash = AddressableEntityHash::new(cep18_contract_hash.value()); let mint_request = ExecuteRequestBuilder::contract_call_by_hash( *DEFAULT_ACCOUNT_ADDR, - addressable_cep18_token, + addressable_cep18_contract_hash, METHOD_MINT, runtime_args! {OWNER => TOKEN_OWNER_ADDRESS_1, AMOUNT => U256::from(TOKEN_OWNER_AMOUNT_1)}, ) @@ -152,20 +152,20 @@ fn test_should_not_mint_above_limits() { builder.exec(mint_request).expect_success().commit(); let mint_request_2 = ExecuteRequestBuilder::contract_call_by_hash( *DEFAULT_ACCOUNT_ADDR, - addressable_cep18_token, + addressable_cep18_contract_hash, METHOD_MINT, runtime_args! {OWNER => TOKEN_OWNER_ADDRESS_2, AMOUNT => U256::from(TOKEN_OWNER_AMOUNT_2)}, ) .build(); builder.exec(mint_request_2).expect_success().commit(); assert_eq!( - cep18_check_balance_of(&mut builder, &cep18_token, TOKEN_OWNER_ADDRESS_1), + cep18_check_balance_of(&mut builder, &cep18_contract_hash, TOKEN_OWNER_ADDRESS_1), U256::from(TOKEN_OWNER_AMOUNT_1) ); let mint_request = ExecuteRequestBuilder::contract_call_by_hash( *DEFAULT_ACCOUNT_ADDR, - addressable_cep18_token, + addressable_cep18_contract_hash, METHOD_MINT, runtime_args! { ARG_OWNER => TOKEN_OWNER_ADDRESS_1, @@ -186,7 +186,7 @@ fn test_should_not_mint_above_limits() { #[test] fn test_should_not_burn_above_balance() { - let (mut builder, TestContext { cep18_token, .. }) = setup_with_args(runtime_args! { + let (mut builder, TestContext { cep18_contract_hash, .. }) = setup_with_args(runtime_args! { ARG_NAME => TOKEN_NAME, ARG_SYMBOL => TOKEN_SYMBOL, ARG_DECIMALS => TOKEN_DECIMALS, @@ -194,10 +194,10 @@ fn test_should_not_burn_above_balance() { "enable_mint_burn" => true, }); - let addressable_cep18_token = AddressableEntityHash::new(cep18_token.value()); + let addressable_cep18_contract_hash = AddressableEntityHash::new(cep18_contract_hash.value()); let burn_request = ExecuteRequestBuilder::contract_call_by_hash( *DEFAULT_ACCOUNT_ADDR, - addressable_cep18_token, + addressable_cep18_contract_hash, METHOD_BURN, runtime_args! { ARG_OWNER => Key::AddressableEntity(casper_types::EntityAddr::Account(DEFAULT_ACCOUNT_ADDR.value())), @@ -220,7 +220,7 @@ fn test_should_not_burn_above_balance() { fn test_should_not_mint_or_burn_with_entrypoint_disabled() { let mint_amount = U256::one(); - let (mut builder, TestContext { cep18_token, .. }) = setup_with_args(runtime_args! { + let (mut builder, TestContext { cep18_contract_hash, .. }) = setup_with_args(runtime_args! { ARG_NAME => TOKEN_NAME, ARG_SYMBOL => TOKEN_SYMBOL, ARG_DECIMALS => TOKEN_DECIMALS, @@ -228,10 +228,10 @@ fn test_should_not_mint_or_burn_with_entrypoint_disabled() { ENABLE_MINT_BURN => false, }); - let addressable_cep18_token = AddressableEntityHash::new(cep18_token.value()); + let addressable_cep18_contract_hash = AddressableEntityHash::new(cep18_contract_hash.value()); let mint_request = ExecuteRequestBuilder::contract_call_by_hash( *DEFAULT_ACCOUNT_ADDR, - addressable_cep18_token, + addressable_cep18_contract_hash, METHOD_MINT, runtime_args! { ARG_OWNER => TOKEN_OWNER_ADDRESS_1, @@ -251,7 +251,7 @@ fn test_should_not_mint_or_burn_with_entrypoint_disabled() { let burn_request = ExecuteRequestBuilder::contract_call_by_hash( *DEFAULT_ACCOUNT_ADDR, - addressable_cep18_token, + addressable_cep18_contract_hash, METHOD_BURN, runtime_args! { ARG_OWNER => TOKEN_OWNER_ADDRESS_1, @@ -274,7 +274,7 @@ fn test_should_not_mint_or_burn_with_entrypoint_disabled() { fn test_security_no_rights() { let mint_amount = U256::one(); - let (mut builder, TestContext { cep18_token, .. }) = setup_with_args(runtime_args! { + let (mut builder, TestContext { cep18_contract_hash, .. }) = setup_with_args(runtime_args! { ARG_NAME => TOKEN_NAME, ARG_SYMBOL => TOKEN_SYMBOL, ARG_DECIMALS => TOKEN_DECIMALS, @@ -282,10 +282,10 @@ fn test_security_no_rights() { ENABLE_MINT_BURN => true, }); - let addressable_cep18_token = AddressableEntityHash::new(cep18_token.value()); + let addressable_cep18_contract_hash = AddressableEntityHash::new(cep18_contract_hash.value()); let mint_request = ExecuteRequestBuilder::contract_call_by_hash( *ACCOUNT_1_ADDR, - addressable_cep18_token, + addressable_cep18_contract_hash, METHOD_MINT, runtime_args! { ARG_OWNER => Key::AddressableEntity(casper_types::EntityAddr::Account(ACCOUNT_1_ADDR.value())), @@ -305,7 +305,7 @@ fn test_security_no_rights() { let passing_admin_mint_request = ExecuteRequestBuilder::contract_call_by_hash( *DEFAULT_ACCOUNT_ADDR, - addressable_cep18_token, + addressable_cep18_contract_hash, METHOD_MINT, runtime_args! { ARG_OWNER => Key::AddressableEntity(EntityAddr::Account(ACCOUNT_1_ADDR.value())), @@ -321,7 +321,7 @@ fn test_security_no_rights() { let burn_request = ExecuteRequestBuilder::contract_call_by_hash( *ACCOUNT_1_ADDR, - addressable_cep18_token, + addressable_cep18_contract_hash, METHOD_BURN, runtime_args! { ARG_OWNER => Key::AddressableEntity(EntityAddr::Account(ACCOUNT_1_ADDR.value())), @@ -337,7 +337,7 @@ fn test_security_no_rights() { fn test_security_minter_rights() { let mint_amount = U256::one(); - let (mut builder, TestContext { cep18_token, .. }) = setup_with_args(runtime_args! { + let (mut builder, TestContext { cep18_contract_hash, .. }) = setup_with_args(runtime_args! { ARG_NAME => TOKEN_NAME, ARG_SYMBOL => TOKEN_SYMBOL, ARG_DECIMALS => TOKEN_DECIMALS, @@ -346,10 +346,10 @@ fn test_security_minter_rights() { MINTER_LIST => vec![Key::AddressableEntity(casper_types::EntityAddr::Account(ACCOUNT_1_ADDR.value()))] }); - let addressable_cep18_token = AddressableEntityHash::new(cep18_token.value()); + let addressable_cep18_contract_hash = AddressableEntityHash::new(cep18_contract_hash.value()); let mint_request = ExecuteRequestBuilder::contract_call_by_hash( *ACCOUNT_1_ADDR, - addressable_cep18_token, + addressable_cep18_contract_hash, METHOD_MINT, runtime_args! { ARG_OWNER => TOKEN_OWNER_ADDRESS_1, @@ -365,7 +365,7 @@ fn test_security_minter_rights() { fn test_security_burner_rights() { let mint_amount = U256::one(); - let (mut builder, TestContext { cep18_token, .. }) = setup_with_args(runtime_args! { + let (mut builder, TestContext { cep18_contract_hash, .. }) = setup_with_args(runtime_args! { ARG_NAME => TOKEN_NAME, ARG_SYMBOL => TOKEN_SYMBOL, ARG_DECIMALS => TOKEN_DECIMALS, @@ -373,10 +373,10 @@ fn test_security_burner_rights() { ENABLE_MINT_BURN => true, }); - let addressable_cep18_token = AddressableEntityHash::new(cep18_token.value()); + let addressable_cep18_contract_hash = AddressableEntityHash::new(cep18_contract_hash.value()); let mint_request = ExecuteRequestBuilder::contract_call_by_hash( *ACCOUNT_1_ADDR, - addressable_cep18_token, + addressable_cep18_contract_hash, METHOD_MINT, runtime_args! { ARG_OWNER => TOKEN_OWNER_ADDRESS_1, @@ -397,7 +397,7 @@ fn test_security_burner_rights() { // mint by admin let working_mint_request = ExecuteRequestBuilder::contract_call_by_hash( *DEFAULT_ACCOUNT_ADDR, - addressable_cep18_token, + addressable_cep18_contract_hash, METHOD_MINT, runtime_args! { ARG_OWNER => Key::AddressableEntity(casper_types::EntityAddr::Account(DEFAULT_ACCOUNT_ADDR.value())), @@ -411,7 +411,7 @@ fn test_security_burner_rights() { // any user can burn let burn_request = ExecuteRequestBuilder::contract_call_by_hash( *DEFAULT_ACCOUNT_ADDR, - addressable_cep18_token, + addressable_cep18_contract_hash, METHOD_BURN, runtime_args! { ARG_OWNER => Key::AddressableEntity(casper_types::EntityAddr::Account(DEFAULT_ACCOUNT_ADDR.value())), @@ -427,7 +427,7 @@ fn test_security_burner_rights() { fn test_change_security() { let mint_amount = U256::one(); - let (mut builder, TestContext { cep18_token, .. }) = setup_with_args(runtime_args! { + let (mut builder, TestContext { cep18_contract_hash, .. }) = setup_with_args(runtime_args! { ARG_NAME => TOKEN_NAME, ARG_SYMBOL => TOKEN_SYMBOL, ARG_DECIMALS => TOKEN_DECIMALS, @@ -436,10 +436,10 @@ fn test_change_security() { ADMIN_LIST => vec![Key::AddressableEntity(casper_types::EntityAddr::Account(ACCOUNT_1_ADDR.value()))] }); - let addressable_cep18_token = AddressableEntityHash::new(cep18_token.value()); + let addressable_cep18_contract_hash = AddressableEntityHash::new(cep18_contract_hash.value()); let change_security_request = ExecuteRequestBuilder::contract_call_by_hash( *ACCOUNT_1_ADDR, - addressable_cep18_token, + addressable_cep18_contract_hash, CHANGE_SECURITY, runtime_args! { NONE_LIST => vec![Key::AddressableEntity(casper_types::EntityAddr::Account(DEFAULT_ACCOUNT_ADDR.value()))], @@ -454,7 +454,7 @@ fn test_change_security() { let mint_request = ExecuteRequestBuilder::contract_call_by_hash( *DEFAULT_ACCOUNT_ADDR, - addressable_cep18_token, + addressable_cep18_contract_hash, METHOD_MINT, runtime_args! { ARG_OWNER => TOKEN_OWNER_ADDRESS_1, diff --git a/tests/src/transfer.rs b/tests/src/transfer.rs index 2da58f0..a262f40 100644 --- a/tests/src/transfer.rs +++ b/tests/src/transfer.rs @@ -21,9 +21,15 @@ use casper_execution_engine::{engine_state::Error as CoreError, execution::ExecE #[test] fn should_transfer_full_owned_amount() { - let (mut builder, TestContext { cep18_token, .. }) = setup(); + let ( + mut builder, + TestContext { + cep18_contract_hash, + .. + }, + ) = setup(); - let addressable_cep18_token = AddressableEntityHash::new(cep18_token.value()); + let addressable_cep18_contract_hash = AddressableEntityHash::new(cep18_contract_hash.value()); let initial_supply = U256::from(TOKEN_TOTAL_SUPPLY); let transfer_amount_1 = initial_supply; @@ -35,21 +41,21 @@ fn should_transfer_full_owned_amount() { let owner_balance_before = cep18_check_balance_of( &mut builder, - &cep18_token, + &cep18_contract_hash, Key::AddressableEntity(EntityAddr::Account(DEFAULT_ACCOUNT_ADDR.value())), ); assert_eq!(owner_balance_before, initial_supply); let account_1_balance_before = cep18_check_balance_of( &mut builder, - &cep18_token, + &cep18_contract_hash, Key::AddressableEntity(EntityAddr::Account(ACCOUNT_1_ADDR.value())), ); assert_eq!(account_1_balance_before, U256::zero()); let token_transfer_request_1 = ExecuteRequestBuilder::contract_call_by_hash( transfer_1_sender, - addressable_cep18_token, + addressable_cep18_contract_hash, METHOD_TRANSFER, cep18_transfer_1_args, ) @@ -62,20 +68,20 @@ fn should_transfer_full_owned_amount() { let account_1_balance_after = cep18_check_balance_of( &mut builder, - &cep18_token, + &cep18_contract_hash, Key::AddressableEntity(EntityAddr::Account(ACCOUNT_1_ADDR.value())), ); assert_eq!(account_1_balance_after, transfer_amount_1); let owner_balance_after = cep18_check_balance_of( &mut builder, - &cep18_token, + &cep18_contract_hash, Key::AddressableEntity(EntityAddr::Account(DEFAULT_ACCOUNT_ADDR.value())), ); assert_eq!(owner_balance_after, U256::zero()); let total_supply: U256 = builder.get_value( - EntityAddr::new_smart_contract(cep18_token.value()), + EntityAddr::new_smart_contract(cep18_contract_hash.value()), TOTAL_SUPPLY_KEY, ); assert_eq!(total_supply, initial_supply); @@ -83,9 +89,15 @@ fn should_transfer_full_owned_amount() { #[test] fn should_not_transfer_more_than_owned_balance() { - let (mut builder, TestContext { cep18_token, .. }) = setup(); + let ( + mut builder, + TestContext { + cep18_contract_hash, + .. + }, + ) = setup(); - let addressable_cep18_token = AddressableEntityHash::new(cep18_token.value()); + let addressable_cep18_contract_hash = AddressableEntityHash::new(cep18_contract_hash.value()); let initial_supply = U256::from(TOKEN_TOTAL_SUPPLY); let transfer_amount = initial_supply + U256::one(); @@ -100,7 +112,7 @@ fn should_not_transfer_more_than_owned_balance() { let owner_balance_before = cep18_check_balance_of( &mut builder, - &cep18_token, + &cep18_contract_hash, Key::AddressableEntity(EntityAddr::Account(DEFAULT_ACCOUNT_ADDR.value())), ); assert_eq!(owner_balance_before, initial_supply); @@ -108,14 +120,14 @@ fn should_not_transfer_more_than_owned_balance() { let account_1_balance_before = cep18_check_balance_of( &mut builder, - &cep18_token, + &cep18_contract_hash, Key::AddressableEntity(EntityAddr::Account(ACCOUNT_1_ADDR.value())), ); assert_eq!(account_1_balance_before, U256::zero()); let token_transfer_request_1 = ExecuteRequestBuilder::contract_call_by_hash( transfer_1_sender, - addressable_cep18_token, + addressable_cep18_contract_hash, METHOD_TRANSFER, cep18_transfer_1_args, ) @@ -132,20 +144,20 @@ fn should_not_transfer_more_than_owned_balance() { let account_1_balance_after = cep18_check_balance_of( &mut builder, - &cep18_token, + &cep18_contract_hash, Key::AddressableEntity(EntityAddr::Account(transfer_1_recipient.value())), ); assert_eq!(account_1_balance_after, account_1_balance_before); let owner_balance_after = cep18_check_balance_of( &mut builder, - &cep18_token, + &cep18_contract_hash, Key::AddressableEntity(EntityAddr::Account(transfer_1_sender.value())), ); assert_eq!(owner_balance_after, initial_supply); let total_supply: U256 = builder.get_value( - EntityAddr::new_smart_contract(cep18_token.value()), + EntityAddr::new_smart_contract(cep18_contract_hash.value()), TOTAL_SUPPLY_KEY, ); assert_eq!(total_supply, initial_supply); @@ -153,9 +165,15 @@ fn should_not_transfer_more_than_owned_balance() { #[test] fn should_transfer_from_from_account_to_account() { - let (mut builder, TestContext { cep18_token, .. }) = setup(); + let ( + mut builder, + TestContext { + cep18_contract_hash, + .. + }, + ) = setup(); - let addressable_cep18_token = AddressableEntityHash::new(cep18_token.value()); + let addressable_cep18_contract_hash = AddressableEntityHash::new(cep18_contract_hash.value()); let initial_supply = U256::from(TOKEN_TOTAL_SUPPLY); let allowance_amount_1 = U256::from(ALLOWANCE_AMOUNT_1); @@ -184,7 +202,7 @@ fn should_transfer_from_from_account_to_account() { let approve_request_1 = ExecuteRequestBuilder::contract_call_by_hash( owner, - addressable_cep18_token, + addressable_cep18_contract_hash, METHOD_APPROVE, cep18_approve_args, ) @@ -192,7 +210,7 @@ fn should_transfer_from_from_account_to_account() { let transfer_from_request_1 = ExecuteRequestBuilder::contract_call_by_hash( spender, - addressable_cep18_token, + addressable_cep18_contract_hash, METHOD_TRANSFER_FROM, cep18_transfer_from_args, ) @@ -202,7 +220,7 @@ fn should_transfer_from_from_account_to_account() { let account_1_balance_before = cep18_check_balance_of( &mut builder, - &cep18_token, + &cep18_contract_hash, Key::AddressableEntity(EntityAddr::Account(owner.value())), ); assert_eq!(account_1_balance_before, initial_supply); @@ -231,7 +249,7 @@ fn should_transfer_from_from_account_to_account() { let account_1_balance_after = cep18_check_balance_of( &mut builder, - &cep18_token, + &cep18_contract_hash, Key::AddressableEntity(EntityAddr::Account(owner.value())), ); assert_eq!( @@ -245,13 +263,13 @@ fn should_transfer_from_account_by_contract() { let ( mut builder, TestContext { - cep18_token, + cep18_contract_hash, cep18_test_contract_package, .. }, ) = setup(); - let addressable_cep18_token = AddressableEntityHash::new(cep18_token.value()); + let addressable_cep18_contract_hash = AddressableEntityHash::new(cep18_contract_hash.value()); let initial_supply = U256::from(TOKEN_TOTAL_SUPPLY); let allowance_amount_1 = U256::from(ALLOWANCE_AMOUNT_1); @@ -270,7 +288,7 @@ fn should_transfer_from_account_by_contract() { ARG_AMOUNT => allowance_amount_1, }; let cep18_transfer_from_args = runtime_args! { - ARG_TOKEN_CONTRACT => Key::addressable_entity_key(EntityKindTag::SmartContract, cep18_token), + ARG_TOKEN_CONTRACT => Key::addressable_entity_key(EntityKindTag::SmartContract, cep18_contract_hash), ARG_OWNER => Key::AddressableEntity(EntityAddr::Account(owner.value())), ARG_RECIPIENT => recipient, ARG_AMOUNT => transfer_from_amount_1, @@ -285,7 +303,7 @@ fn should_transfer_from_account_by_contract() { let approve_request_1 = ExecuteRequestBuilder::contract_call_by_hash( owner, - addressable_cep18_token, + addressable_cep18_contract_hash, METHOD_APPROVE, cep18_approve_args, ) @@ -304,7 +322,7 @@ fn should_transfer_from_account_by_contract() { let owner_balance_before = cep18_check_balance_of( &mut builder, - &cep18_token, + &cep18_contract_hash, Key::AddressableEntity(EntityAddr::Account(owner.value())), ); assert_eq!(owner_balance_before, initial_supply); @@ -333,7 +351,7 @@ fn should_transfer_from_account_by_contract() { let owner_balance_after = cep18_check_balance_of( &mut builder, - &cep18_token, + &cep18_contract_hash, Key::AddressableEntity(EntityAddr::Account(owner.value())), ); assert_eq!( @@ -344,20 +362,27 @@ fn should_transfer_from_account_by_contract() { #[test] fn should_not_be_able_to_own_transfer() { - let (mut builder, TestContext { cep18_token, .. }) = setup(); + let ( + mut builder, + TestContext { + cep18_contract_hash, + .. + }, + ) = setup(); let sender = Key::AddressableEntity(EntityAddr::Account(DEFAULT_ACCOUNT_ADDR.value())); let recipient = Key::AddressableEntity(EntityAddr::Account(DEFAULT_ACCOUNT_ADDR.value())); let transfer_amount = U256::from(TRANSFER_AMOUNT_1); - let sender_balance_before = cep18_check_balance_of(&mut builder, &cep18_token, sender); - let recipient_balance_before = cep18_check_balance_of(&mut builder, &cep18_token, recipient); + let sender_balance_before = cep18_check_balance_of(&mut builder, &cep18_contract_hash, sender); + let recipient_balance_before = + cep18_check_balance_of(&mut builder, &cep18_contract_hash, recipient); assert_eq!(sender_balance_before, recipient_balance_before); let token_transfer_request_1 = - make_cep18_transfer_request(sender, &cep18_token, recipient, transfer_amount); + make_cep18_transfer_request(sender, &cep18_contract_hash, recipient, transfer_amount); builder.exec(token_transfer_request_1).commit(); @@ -371,9 +396,15 @@ fn should_not_be_able_to_own_transfer() { #[test] fn should_not_be_able_to_own_transfer_from() { - let (mut builder, TestContext { cep18_token, .. }) = setup(); + let ( + mut builder, + TestContext { + cep18_contract_hash, + .. + }, + ) = setup(); - let addressable_cep18_token = AddressableEntityHash::new(cep18_token.value()); + let addressable_cep18_contract_hash = AddressableEntityHash::new(cep18_contract_hash.value()); let sender = *DEFAULT_ACCOUNT_ADDR; let owner = Key::AddressableEntity(EntityAddr::Account(sender.value())); @@ -385,7 +416,7 @@ fn should_not_be_able_to_own_transfer_from() { let transfer_amount = U256::from(TRANSFER_AMOUNT_1); let approve_request = - make_cep18_approve_request(sender_key, &cep18_token, spender, allowance_amount); + make_cep18_approve_request(sender_key, &cep18_contract_hash, spender, allowance_amount); builder.exec(approve_request).commit(); @@ -396,8 +427,10 @@ fn should_not_be_able_to_own_transfer_from() { error ); - let sender_balance_before = cep18_check_balance_of(&mut builder, &cep18_token, sender_key); - let recipient_balance_before = cep18_check_balance_of(&mut builder, &cep18_token, recipient); + let sender_balance_before = + cep18_check_balance_of(&mut builder, &cep18_contract_hash, sender_key); + let recipient_balance_before = + cep18_check_balance_of(&mut builder, &cep18_contract_hash, recipient); assert_eq!(sender_balance_before, recipient_balance_before); @@ -409,7 +442,7 @@ fn should_not_be_able_to_own_transfer_from() { }; ExecuteRequestBuilder::contract_call_by_hash( sender, - addressable_cep18_token, + addressable_cep18_contract_hash, METHOD_TRANSFER_FROM, cep18_transfer_from_args, ) @@ -428,36 +461,50 @@ fn should_not_be_able_to_own_transfer_from() { #[test] fn should_verify_zero_amount_transfer_is_noop() { - let (mut builder, TestContext { cep18_token, .. }) = setup(); + let ( + mut builder, + TestContext { + cep18_contract_hash, + .. + }, + ) = setup(); let sender = Key::AddressableEntity(EntityAddr::Account(DEFAULT_ACCOUNT_ADDR.value())); let recipient = Key::AddressableEntity(EntityAddr::Account(ACCOUNT_1_ADDR.value())); let transfer_amount = U256::zero(); - let sender_balance_before = cep18_check_balance_of(&mut builder, &cep18_token, sender); - let recipient_balance_before = cep18_check_balance_of(&mut builder, &cep18_token, recipient); + let sender_balance_before = cep18_check_balance_of(&mut builder, &cep18_contract_hash, sender); + let recipient_balance_before = + cep18_check_balance_of(&mut builder, &cep18_contract_hash, recipient); let token_transfer_request_1 = - make_cep18_transfer_request(sender, &cep18_token, recipient, transfer_amount); + make_cep18_transfer_request(sender, &cep18_contract_hash, recipient, transfer_amount); builder .exec(token_transfer_request_1) .expect_success() .commit(); - let sender_balance_after = cep18_check_balance_of(&mut builder, &cep18_token, sender); + let sender_balance_after = cep18_check_balance_of(&mut builder, &cep18_contract_hash, sender); assert_eq!(sender_balance_before, sender_balance_after); - let recipient_balance_after = cep18_check_balance_of(&mut builder, &cep18_token, recipient); + let recipient_balance_after = + cep18_check_balance_of(&mut builder, &cep18_contract_hash, recipient); assert_eq!(recipient_balance_before, recipient_balance_after); } #[test] fn should_verify_zero_amount_transfer_from_is_noop() { - let (mut builder, TestContext { cep18_token, .. }) = setup(); + let ( + mut builder, + TestContext { + cep18_contract_hash, + .. + }, + ) = setup(); - let addressable_cep18_token = AddressableEntityHash::new(cep18_token.value()); + let addressable_cep18_contract_hash = AddressableEntityHash::new(cep18_contract_hash.value()); let owner = *DEFAULT_ACCOUNT_ADDR; let owner_key = Key::AddressableEntity(EntityAddr::Account(owner.value())); @@ -468,14 +515,16 @@ fn should_verify_zero_amount_transfer_from_is_noop() { let transfer_amount = U256::zero(); let approve_request = - make_cep18_approve_request(owner_key, &cep18_token, spender, allowance_amount); + make_cep18_approve_request(owner_key, &cep18_contract_hash, spender, allowance_amount); builder.exec(approve_request).expect_success().commit(); let spender_allowance_before = cep18_check_allowance_of(&mut builder, owner_key, spender); - let owner_balance_before = cep18_check_balance_of(&mut builder, &cep18_token, owner_key); - let recipient_balance_before = cep18_check_balance_of(&mut builder, &cep18_token, recipient); + let owner_balance_before = + cep18_check_balance_of(&mut builder, &cep18_contract_hash, owner_key); + let recipient_balance_before = + cep18_check_balance_of(&mut builder, &cep18_contract_hash, recipient); let transfer_from_request = { let cep18_transfer_from_args = runtime_args! { @@ -485,7 +534,7 @@ fn should_verify_zero_amount_transfer_from_is_noop() { }; ExecuteRequestBuilder::contract_call_by_hash( owner, - addressable_cep18_token, + addressable_cep18_contract_hash, METHOD_TRANSFER_FROM, cep18_transfer_from_args, ) @@ -497,10 +546,11 @@ fn should_verify_zero_amount_transfer_from_is_noop() { .expect_success() .commit(); - let owner_balance_after = cep18_check_balance_of(&mut builder, &cep18_token, owner_key); + let owner_balance_after = cep18_check_balance_of(&mut builder, &cep18_contract_hash, owner_key); assert_eq!(owner_balance_before, owner_balance_after); - let recipient_balance_after = cep18_check_balance_of(&mut builder, &cep18_token, recipient); + let recipient_balance_after = + cep18_check_balance_of(&mut builder, &cep18_contract_hash, recipient); assert_eq!(recipient_balance_before, recipient_balance_after); let spender_allowance_after = cep18_check_allowance_of(&mut builder, owner_key, spender); diff --git a/tests/src/utility/installer_request_builders.rs b/tests/src/utility/installer_request_builders.rs index f81713d..2d2f0a0 100644 --- a/tests/src/utility/installer_request_builders.rs +++ b/tests/src/utility/installer_request_builders.rs @@ -45,7 +45,7 @@ pub(crate) fn invert_cep18_address(address: Key) -> Key { #[derive(Copy, Clone)] pub(crate) struct TestContext { - pub(crate) cep18_token: AddressableEntityHash, + pub(crate) cep18_contract_hash: AddressableEntityHash, pub(crate) cep18_test_contract_package: PackageHash, } @@ -97,7 +97,7 @@ pub(crate) fn setup_with_args(install_args: RuntimeArgs) -> (LmdbWasmTestBuilder .unwrap(); let account_named_keys = account.named_keys(); - let cep18_token = account_named_keys + let cep18_contract_hash = account_named_keys .get(CEP18_TOKEN_CONTRACT_KEY) .and_then(|key| key.into_entity_hash()) .expect("should have contract hash"); @@ -108,7 +108,7 @@ pub(crate) fn setup_with_args(install_args: RuntimeArgs) -> (LmdbWasmTestBuilder .expect("should have package hash"); let test_context = TestContext { - cep18_token, + cep18_contract_hash, cep18_test_contract_package, }; @@ -240,33 +240,36 @@ pub(crate) fn test_cep18_transfer( sender2: Key, recipient2: Key, ) { - let TestContext { cep18_token, .. } = test_context; + let TestContext { + cep18_contract_hash, + .. + } = test_context; let transfer_amount_1 = U256::from(TRANSFER_AMOUNT_1); let transfer_amount_2 = U256::from(TRANSFER_AMOUNT_2); - let sender_balance_before = cep18_check_balance_of(builder, cep18_token, sender1); + let sender_balance_before = cep18_check_balance_of(builder, cep18_contract_hash, sender1); assert_ne!(sender_balance_before, U256::zero()); - let account_1_balance_before = cep18_check_balance_of(builder, cep18_token, recipient1); + let account_1_balance_before = cep18_check_balance_of(builder, cep18_contract_hash, recipient1); assert_eq!(account_1_balance_before, U256::zero()); - let account_2_balance_before = cep18_check_balance_of(builder, cep18_token, recipient1); + let account_2_balance_before = cep18_check_balance_of(builder, cep18_contract_hash, recipient1); assert_eq!(account_2_balance_before, U256::zero()); let token_transfer_request_1 = - make_cep18_transfer_request(sender1, cep18_token, recipient1, transfer_amount_1); + make_cep18_transfer_request(sender1, cep18_contract_hash, recipient1, transfer_amount_1); builder .exec(token_transfer_request_1) .expect_success() .commit(); - let account_1_balance_after = cep18_check_balance_of(builder, cep18_token, recipient1); + let account_1_balance_after = cep18_check_balance_of(builder, cep18_contract_hash, recipient1); assert_eq!(account_1_balance_after, transfer_amount_1); let account_1_balance_before = account_1_balance_after; - let sender_balance_after = cep18_check_balance_of(builder, cep18_token, sender1); + let sender_balance_after = cep18_check_balance_of(builder, cep18_contract_hash, sender1); assert_eq!( sender_balance_after, sender_balance_before - transfer_amount_1 @@ -274,37 +277,37 @@ pub(crate) fn test_cep18_transfer( let sender_balance_before = sender_balance_after; let token_transfer_request_2 = - make_cep18_transfer_request(sender2, cep18_token, recipient2, transfer_amount_2); + make_cep18_transfer_request(sender2, cep18_contract_hash, recipient2, transfer_amount_2); builder .exec(token_transfer_request_2) .expect_success() .commit(); - let sender_balance_after = cep18_check_balance_of(builder, cep18_token, sender1); + let sender_balance_after = cep18_check_balance_of(builder, cep18_contract_hash, sender1); assert_eq!(sender_balance_after, sender_balance_before); - let account_1_balance_after = cep18_check_balance_of(builder, cep18_token, recipient1); + let account_1_balance_after = cep18_check_balance_of(builder, cep18_contract_hash, recipient1); assert!(account_1_balance_after < account_1_balance_before); assert_eq!( account_1_balance_after, transfer_amount_1 - transfer_amount_2 ); - let account_2_balance_after = cep18_check_balance_of(builder, cep18_token, recipient2); + let account_2_balance_after = cep18_check_balance_of(builder, cep18_contract_hash, recipient2); assert_eq!(account_2_balance_after, transfer_amount_2); } pub(crate) fn make_cep18_transfer_request( sender: Key, - cep18_token: &AddressableEntityHash, + cep18_contract_hash: &AddressableEntityHash, recipient: Key, amount: U256, ) -> ExecuteRequest { match sender { Key::Account(sender) => ExecuteRequestBuilder::contract_call_by_hash( sender, - AddressableEntityHash::new(cep18_token.value()), + AddressableEntityHash::new(cep18_contract_hash.value()), METHOD_TRANSFER, runtime_args! { ARG_AMOUNT => amount, @@ -318,7 +321,7 @@ pub(crate) fn make_cep18_transfer_request( None, METHOD_TRANSFER_AS_STORED_CONTRACT, runtime_args! { - ARG_TOKEN_CONTRACT => Key::addressable_entity_key(EntityKindTag::SmartContract, *cep18_token), + ARG_TOKEN_CONTRACT => Key::addressable_entity_key(EntityKindTag::SmartContract, *cep18_contract_hash), ARG_AMOUNT => amount, ARG_RECIPIENT => recipient, }, @@ -329,7 +332,7 @@ pub(crate) fn make_cep18_transfer_request( EntityAddr::System(_) => panic!("Not a use case"), EntityAddr::Account(account_addr) => ExecuteRequestBuilder::contract_call_by_hash( AccountHash::new(account_addr), - AddressableEntityHash::new(cep18_token.value()), + AddressableEntityHash::new(cep18_contract_hash.value()), METHOD_TRANSFER, runtime_args! { ARG_AMOUNT => amount, @@ -343,7 +346,7 @@ pub(crate) fn make_cep18_transfer_request( None, METHOD_TRANSFER_AS_STORED_CONTRACT, runtime_args! { - ARG_TOKEN_CONTRACT => Key::addressable_entity_key(EntityKindTag::SmartContract, *cep18_token), + ARG_TOKEN_CONTRACT => Key::addressable_entity_key(EntityKindTag::SmartContract, *cep18_contract_hash), ARG_AMOUNT => amount, ARG_RECIPIENT => recipient, } @@ -357,14 +360,14 @@ pub(crate) fn make_cep18_transfer_request( pub(crate) fn make_cep18_approve_request( sender: Key, - cep18_token: &AddressableEntityHash, + cep18_contract_hash: &AddressableEntityHash, spender: Key, amount: U256, ) -> ExecuteRequest { match sender { Key::Account(sender) => ExecuteRequestBuilder::contract_call_by_hash( sender, - AddressableEntityHash::new(cep18_token.value()), + AddressableEntityHash::new(cep18_contract_hash.value()), METHOD_APPROVE, runtime_args! { ARG_SPENDER => spender, @@ -378,7 +381,7 @@ pub(crate) fn make_cep18_approve_request( None, METHOD_APPROVE_AS_STORED_CONTRACT, runtime_args! { - ARG_TOKEN_CONTRACT => Key::addressable_entity_key(EntityKindTag::SmartContract, *cep18_token), + ARG_TOKEN_CONTRACT => Key::addressable_entity_key(EntityKindTag::SmartContract, *cep18_contract_hash), ARG_SPENDER => spender, ARG_AMOUNT => amount, }, @@ -389,7 +392,7 @@ pub(crate) fn make_cep18_approve_request( EntityAddr::System(_) => panic!("Not a use case"), EntityAddr::Account(account_addr) => ExecuteRequestBuilder::contract_call_by_hash( AccountHash::new(account_addr), - AddressableEntityHash::new(cep18_token.value()), + AddressableEntityHash::new(cep18_contract_hash.value()), METHOD_APPROVE, runtime_args! { ARG_SPENDER => spender, @@ -403,7 +406,7 @@ pub(crate) fn make_cep18_approve_request( None, METHOD_APPROVE_AS_STORED_CONTRACT, runtime_args! { - ARG_TOKEN_CONTRACT => Key::addressable_entity_key(EntityKindTag::SmartContract, *cep18_token), + ARG_TOKEN_CONTRACT => Key::addressable_entity_key(EntityKindTag::SmartContract, *cep18_contract_hash), ARG_SPENDER => spender, ARG_AMOUNT => amount, }, @@ -422,7 +425,7 @@ pub(crate) fn test_approve_for( owner: Key, spender: Key, ) { - let TestContext { cep18_token, .. } = test_context; + let TestContext { cep18_contract_hash, .. } = test_context; let initial_supply = U256::from(TOKEN_TOTAL_SUPPLY); let allowance_amount_1 = U256::from(ALLOWANCE_AMOUNT_1); let allowance_amount_2 = U256::from(ALLOWANCE_AMOUNT_2); @@ -431,9 +434,9 @@ pub(crate) fn test_approve_for( assert_eq!(spender_allowance_before, U256::zero()); let approve_request_1 = - make_cep18_approve_request(sender, cep18_token, spender, allowance_amount_1); + make_cep18_approve_request(sender, cep18_contract_hash, spender, allowance_amount_1); let approve_request_2 = - make_cep18_approve_request(sender, cep18_token, spender, allowance_amount_2); + make_cep18_approve_request(sender, cep18_contract_hash, spender, allowance_amount_2); builder.exec(approve_request_1).expect_success().commit(); @@ -442,7 +445,7 @@ pub(crate) fn test_approve_for( assert_eq!(account_1_allowance_after, allowance_amount_1); let total_supply: U256 = builder.get_value( - EntityAddr::new_smart_contract(cep18_token.value()), + EntityAddr::new_smart_contract(cep18_contract_hash.value()), TOTAL_SUPPLY_KEY, ); assert_eq!(total_supply, initial_supply); @@ -462,7 +465,7 @@ pub(crate) fn test_approve_for( assert_eq!(inverted_spender_allowance, U256::zero()); let total_supply: U256 = builder.get_value( - EntityAddr::new_smart_contract(cep18_token.value()), + EntityAddr::new_smart_contract(cep18_contract_hash.value()), TOTAL_SUPPLY_KEY, ); assert_eq!(total_supply, initial_supply); From d90839e0d14b2579d939622afc329363166b0496 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zolt=C3=A1n?= <30974986+deuszex@users.noreply.github.com> Date: Thu, 27 Jun 2024 11:41:55 +0200 Subject: [PATCH 15/27] native eventing (#145) * native eventing * change_event_mode * native eventing done --- Makefile | 3 + cep18-test-contract/src/main.rs | 16 +- cep18/src/allowances.rs | 16 +- cep18/src/balances.rs | 6 +- cep18/src/error.rs | 15 + cep18/src/events.rs | 29 +- cep18/src/main.rs | 193 ++++++++----- cep18/src/modalities.rs | 2 +- cep18/src/utils.rs | 29 +- tests/src/events.rs | 90 ++++++ tests/src/lib.rs | 4 + tests/src/migration.rs | 256 ++++++++++++------ tests/src/mint_and_burn.rs | 2 +- tests/src/upgrade.rs | 57 ++++ tests/src/utility/constants.rs | 1 + .../src/utility/installer_request_builders.rs | 3 +- tests/src/utility/message_handlers.rs | 73 +++++ tests/src/utility/mod.rs | 1 + 18 files changed, 605 insertions(+), 191 deletions(-) create mode 100644 tests/src/upgrade.rs create mode 100644 tests/src/utility/message_handlers.rs diff --git a/Makefile b/Makefile index 40fd081..d04dbd1 100644 --- a/Makefile +++ b/Makefile @@ -16,6 +16,9 @@ setup-test: build-contract cp ./target/wasm32-unknown-unknown/release/cep18.wasm tests/wasm cp ./target/wasm32-unknown-unknown/release/cep18_test_contract.wasm tests/wasm +native-test: setup-test + cd tests && cargo test --lib should_transfer_account_to_account + test: setup-test cd tests && cargo test --lib diff --git a/cep18-test-contract/src/main.rs b/cep18-test-contract/src/main.rs index 975431f..a3c9df0 100644 --- a/cep18-test-contract/src/main.rs +++ b/cep18-test-contract/src/main.rs @@ -15,8 +15,8 @@ use casper_contract::{ }; use casper_types::{ - bytesrepr::ToBytes, runtime_args, AddressableEntityHash, CLTyped, EntryPoint, EntryPointAccess, - EntryPointType, EntryPoints, Key, Parameter, RuntimeArgs, U256, + bytesrepr::ToBytes, runtime_args, AddressableEntityHash, ApiError, CLTyped, EntryPoint, + EntryPointAccess, EntryPointType, EntryPoints, Key, Parameter, RuntimeArgs, U256, }; const CHECK_TOTAL_SUPPLY_ENTRY_POINT_NAME: &str = "check_total_supply"; @@ -57,7 +57,7 @@ extern "C" fn check_total_supply() { let token_contract: AddressableEntityHash = runtime::get_named_arg::(TOKEN_CONTRACT_RUNTIME_ARG_NAME) .into_entity_hash() - .unwrap_or_revert(); + .unwrap_or_revert_with(ApiError::User(61000)); let total_supply: U256 = runtime::call_contract( token_contract, TOTAL_SUPPLY_ENTRY_POINT_NAME, @@ -71,7 +71,7 @@ extern "C" fn check_balance_of() { let token_contract: AddressableEntityHash = runtime::get_named_arg::(TOKEN_CONTRACT_RUNTIME_ARG_NAME) .into_entity_hash() - .unwrap_or_revert(); + .unwrap_or_revert_with(ApiError::User(61001)); let address: Key = runtime::get_named_arg(ADDRESS_RUNTIME_ARG_NAME); let balance_args = runtime_args! { @@ -88,7 +88,7 @@ extern "C" fn check_allowance_of() { let token_contract: AddressableEntityHash = runtime::get_named_arg::(TOKEN_CONTRACT_RUNTIME_ARG_NAME) .into_entity_hash() - .unwrap_or_revert(); + .unwrap_or_revert_with(ApiError::User(61002)); let owner: Key = runtime::get_named_arg(OWNER_RUNTIME_ARG_NAME); let spender: Key = runtime::get_named_arg(SPENDER_RUNTIME_ARG_NAME); @@ -107,7 +107,7 @@ extern "C" fn transfer_as_stored_contract() { let token_contract: AddressableEntityHash = runtime::get_named_arg::(TOKEN_CONTRACT_RUNTIME_ARG_NAME) .into_entity_hash() - .unwrap_or_revert(); + .unwrap_or_revert_with(ApiError::User(61003)); let recipient: Key = runtime::get_named_arg(RECIPIENT_RUNTIME_ARG_NAME); let amount: U256 = runtime::get_named_arg(AMOUNT_RUNTIME_ARG_NAME); @@ -124,7 +124,7 @@ extern "C" fn transfer_from_as_stored_contract() { let token_contract: AddressableEntityHash = runtime::get_named_arg::(TOKEN_CONTRACT_RUNTIME_ARG_NAME) .into_entity_hash() - .unwrap_or_revert(); + .unwrap_or_revert_with(ApiError::User(61004)); let owner: Key = runtime::get_named_arg(OWNER_RUNTIME_ARG_NAME); let recipient: Key = runtime::get_named_arg(RECIPIENT_RUNTIME_ARG_NAME); let amount: U256 = runtime::get_named_arg(AMOUNT_RUNTIME_ARG_NAME); @@ -147,7 +147,7 @@ extern "C" fn approve_as_stored_contract() { let token_contract: AddressableEntityHash = runtime::get_named_arg::(TOKEN_CONTRACT_RUNTIME_ARG_NAME) .into_entity_hash() - .unwrap_or_revert(); + .unwrap_or_revert_with(ApiError::User(61005)); let spender: Key = runtime::get_named_arg(SPENDER_RUNTIME_ARG_NAME); let amount: U256 = runtime::get_named_arg(AMOUNT_RUNTIME_ARG_NAME); diff --git a/cep18/src/allowances.rs b/cep18/src/allowances.rs index aef789a..a909f2b 100644 --- a/cep18/src/allowances.rs +++ b/cep18/src/allowances.rs @@ -7,7 +7,7 @@ use casper_contract::{ }; use casper_types::{bytesrepr::ToBytes, Key, URef, U256}; -use crate::{constants::ALLOWANCES, utils}; +use crate::{constants::ALLOWANCES, utils, Cep18Error}; #[inline] pub(crate) fn get_allowances_uref() -> URef { @@ -17,8 +17,16 @@ pub(crate) fn get_allowances_uref() -> URef { /// Creates a dictionary item key for an (owner, spender) pair. pub(crate) fn make_dictionary_item_key(owner: Key, spender: Key) -> String { let mut preimage = Vec::new(); - preimage.append(&mut owner.to_bytes().unwrap_or_revert()); - preimage.append(&mut spender.to_bytes().unwrap_or_revert()); + preimage.append( + &mut owner + .to_bytes() + .unwrap_or_revert_with(Cep18Error::FailedToConvertBytes), + ); + preimage.append( + &mut spender + .to_bytes() + .unwrap_or_revert_with(Cep18Error::FailedToConvertBytes), + ); let key_bytes = runtime::blake2b(&preimage); hex::encode(key_bytes) @@ -34,6 +42,6 @@ pub(crate) fn write_allowance_to(allowance_uref: URef, owner: Key, spender: Key, pub(crate) fn read_allowance_from(allowances_uref: URef, owner: Key, spender: Key) -> U256 { let dictionary_item_key = make_dictionary_item_key(owner, spender); storage::dictionary_get(allowances_uref, &dictionary_item_key) - .unwrap_or_revert() + .unwrap_or_revert_with(Cep18Error::FailedToGetDictionaryValue) .unwrap_or_default() } diff --git a/cep18/src/balances.rs b/cep18/src/balances.rs index d9d497e..04e9f2e 100644 --- a/cep18/src/balances.rs +++ b/cep18/src/balances.rs @@ -10,7 +10,9 @@ use crate::{constants::BALANCES, error::Cep18Error, utils}; /// since stringified Keys are too long to be used as dictionary keys. #[inline] fn make_dictionary_item_key(owner: Key) -> String { - let preimage = owner.to_bytes().unwrap_or_revert(); + let preimage = owner + .to_bytes() + .unwrap_or_revert_with(Cep18Error::FailedToConvertBytes); // NOTE: As for now dictionary item keys are limited to 64 characters only. Instead of using // hashing (which will effectively hash a hash) we'll use base64. Preimage is 33 bytes for // both used Key variants, and approximated base64-encoded length will be 4 * (33 / 3) ~ 44 @@ -38,7 +40,7 @@ pub(crate) fn read_balance_from(balances_uref: URef, address: Key) -> U256 { let dictionary_item_key = make_dictionary_item_key(address); storage::dictionary_get(balances_uref, &dictionary_item_key) - .unwrap_or_revert() + .unwrap_or_revert_with(Cep18Error::FailedToGetDictionaryValue) .unwrap_or_default() } diff --git a/cep18/src/error.rs b/cep18/src/error.rs index d2a4040..f1a11d2 100644 --- a/cep18/src/error.rs +++ b/cep18/src/error.rs @@ -51,6 +51,21 @@ pub enum Cep18Error { MissingContractHashForUpgrade = 60020, InvalidKeyType = 60021, KeyTypeMigrationMismatch = 60022, + FailedToWriteMessage = 60023, + FailedToReturnEntryPointResult = 60024, + FailedToRetrieveImmediateCaller = 60025, + FailedToCreateDictionary = 60026, + FailedToWriteToDictionary = 60027, + FailedToConvertBytes = 60028, + FailedToChangeBalance = 60029, + FailedToChangeAllowance = 60030, + FailedToChangeTotalSupply = 60031, + FailedToGetDictionaryValue = 60032, + FailedToReadFromStorage = 60033, + FailedToGetKey = 60034, + FailedToDisableContractVersion = 60035, + FailedToInsertToSecurityList = 60036, + UrefNotFound = 60037, } impl From for ApiError { diff --git a/cep18/src/events.rs b/cep18/src/events.rs index a987f9d..3b30f5e 100644 --- a/cep18/src/events.rs +++ b/cep18/src/events.rs @@ -8,22 +8,23 @@ use crate::{ constants::{EVENTS, EVENTS_MODE}, modalities::EventsMode, utils::{read_from, SecurityBadge}, + Cep18Error, }; use casper_event_standard::{emit, Event, Schemas}; pub fn record_event_dictionary(event: Event) { - let events_mode: EventsMode = - EventsMode::try_from(read_from::(EVENTS_MODE)).unwrap_or_revert(); + let events_mode: EventsMode = EventsMode::try_from(read_from::(EVENTS_MODE)) + .unwrap_or_revert_with(Cep18Error::InvalidEventsMode); match events_mode { EventsMode::NoEvents => {} EventsMode::CES => ces(event), - EventsMode::Native => { - runtime::emit_message(EVENTS, &format!("{event:?}").into()).unwrap_or_revert() - } + EventsMode::Native => runtime::emit_message(EVENTS, &format!("{event:?}").into()) + .unwrap_or_revert_with(Cep18Error::FailedToWriteMessage), EventsMode::NativeNCES => { - runtime::emit_message(EVENTS, &format!("{event:?}").into()).unwrap_or_revert(); + runtime::emit_message(EVENTS, &format!("{event:?}").into()) + .unwrap_or_revert_with(Cep18Error::FailedToWriteMessage); ces(event); } } @@ -41,6 +42,7 @@ pub enum Event { ChangeSecurity(ChangeSecurity), BalanceMigration(BalanceMigration), AllowanceMigration(AllowanceMigration), + ChangeEventsMode(ChangeEventsMode) } #[derive(Event, Debug, PartialEq, Eq)] @@ -118,6 +120,11 @@ pub struct AllowanceMigration { pub failure_map: BTreeMap<(Key, Option), String>, } +#[derive(Event, Debug, PartialEq, Eq)] +pub struct ChangeEventsMode { + pub events_mode: u8, +} + fn ces(event: Event) { match event { Event::Mint(ev) => emit(ev), @@ -130,14 +137,15 @@ fn ces(event: Event) { Event::ChangeSecurity(ev) => emit(ev), Event::BalanceMigration(ev) => emit(ev), Event::AllowanceMigration(ev) => emit(ev), + Event::ChangeEventsMode(ev) => emit(ev), } } pub fn init_events() { - let events_mode: EventsMode = - EventsMode::try_from(read_from::(EVENTS_MODE)).unwrap_or_revert(); + let events_mode: EventsMode = EventsMode::try_from(read_from::(EVENTS_MODE)) + .unwrap_or_revert_with(Cep18Error::InvalidEventsMode); - if events_mode == EventsMode::CES { + if [EventsMode::CES, EventsMode::NativeNCES].contains(&events_mode) { let schemas = Schemas::new() .with::() .with::() @@ -148,7 +156,8 @@ pub fn init_events() { .with::() .with::() .with::() - .with::(); + .with::() + .with::(); casper_event_standard::init(schemas); } } diff --git a/cep18/src/main.rs b/cep18/src/main.rs index 3d77b3c..093e040 100644 --- a/cep18/src/main.rs +++ b/cep18/src/main.rs @@ -50,8 +50,7 @@ use constants::{ }; pub use error::Cep18Error; use events::{ - init_events, AllowanceMigration, BalanceMigration, Burn, ChangeSecurity, DecreaseAllowance, - Event, IncreaseAllowance, Mint, SetAllowance, Transfer, TransferFrom, + init_events, AllowanceMigration, BalanceMigration, Burn, ChangeEventsMode, ChangeSecurity, DecreaseAllowance, Event, IncreaseAllowance, Mint, SetAllowance, Transfer, TransferFrom }; use modalities::EventsMode; use utils::{ @@ -61,22 +60,34 @@ use utils::{ #[no_mangle] pub extern "C" fn name() { - runtime::ret(CLValue::from_t(utils::read_from::(NAME)).unwrap_or_revert()); + runtime::ret( + CLValue::from_t(utils::read_from::(NAME)) + .unwrap_or_revert_with(Cep18Error::FailedToReturnEntryPointResult), + ); } #[no_mangle] pub extern "C" fn symbol() { - runtime::ret(CLValue::from_t(utils::read_from::(SYMBOL)).unwrap_or_revert()); + runtime::ret( + CLValue::from_t(utils::read_from::(SYMBOL)) + .unwrap_or_revert_with(Cep18Error::FailedToReturnEntryPointResult), + ); } #[no_mangle] pub extern "C" fn decimals() { - runtime::ret(CLValue::from_t(utils::read_from::(DECIMALS)).unwrap_or_revert()); + runtime::ret( + CLValue::from_t(utils::read_from::(DECIMALS)) + .unwrap_or_revert_with(Cep18Error::FailedToReturnEntryPointResult), + ); } #[no_mangle] pub extern "C" fn total_supply() { - runtime::ret(CLValue::from_t(utils::read_from::(TOTAL_SUPPLY)).unwrap_or_revert()); + runtime::ret( + CLValue::from_t(utils::read_from::(TOTAL_SUPPLY)) + .unwrap_or_revert_with(Cep18Error::FailedToReturnEntryPointResult), + ); } #[no_mangle] @@ -84,7 +95,9 @@ pub extern "C" fn balance_of() { let address: Key = runtime::get_named_arg(ADDRESS); let balances_uref = get_balances_uref(); let balance = balances::read_balance_from(balances_uref, address); - runtime::ret(CLValue::from_t(balance).unwrap_or_revert()); + runtime::ret( + CLValue::from_t(balance).unwrap_or_revert_with(Cep18Error::FailedToReturnEntryPointResult), + ); } #[no_mangle] @@ -93,12 +106,15 @@ pub extern "C" fn allowance() { let owner: Key = runtime::get_named_arg(OWNER); let allowances_uref = get_allowances_uref(); let val: U256 = read_allowance_from(allowances_uref, owner, spender); - runtime::ret(CLValue::from_t(val).unwrap_or_revert()); + runtime::ret( + CLValue::from_t(val).unwrap_or_revert_with(Cep18Error::FailedToReturnEntryPointResult), + ); } #[no_mangle] pub extern "C" fn approve() { - let owner = utils::get_immediate_caller_address().unwrap_or_revert(); + let owner = utils::get_immediate_caller_address() + .unwrap_or_revert_with(Cep18Error::FailedToRetrieveImmediateCaller); let spender: Key = runtime::get_named_arg(SPENDER); if spender == owner { revert(Cep18Error::CannotTargetSelfUser); @@ -115,7 +131,8 @@ pub extern "C" fn approve() { #[no_mangle] pub extern "C" fn decrease_allowance() { - let owner = utils::get_immediate_caller_address().unwrap_or_revert(); + let owner = utils::get_immediate_caller_address() + .unwrap_or_revert_with(Cep18Error::FailedToRetrieveImmediateCaller); let spender: Key = runtime::get_named_arg(SPENDER); if spender == owner { revert(Cep18Error::CannotTargetSelfUser); @@ -135,7 +152,8 @@ pub extern "C" fn decrease_allowance() { #[no_mangle] pub extern "C" fn increase_allowance() { - let owner = utils::get_immediate_caller_address().unwrap_or_revert(); + let owner = utils::get_immediate_caller_address() + .unwrap_or_revert_with(Cep18Error::FailedToRetrieveImmediateCaller); let spender: Key = runtime::get_named_arg(SPENDER); if spender == owner { revert(Cep18Error::CannotTargetSelfUser); @@ -155,7 +173,8 @@ pub extern "C" fn increase_allowance() { #[no_mangle] pub extern "C" fn transfer() { - let sender = utils::get_immediate_caller_address().unwrap_or_revert(); + let sender = utils::get_immediate_caller_address() + .unwrap_or_revert_with(Cep18Error::FailedToRetrieveImmediateCaller); let recipient: Key = runtime::get_named_arg(RECIPIENT); if sender == recipient { revert(Cep18Error::CannotTargetSelfUser); @@ -172,7 +191,8 @@ pub extern "C" fn transfer() { #[no_mangle] pub extern "C" fn transfer_from() { - let spender = utils::get_immediate_caller_address().unwrap_or_revert(); + let spender = utils::get_immediate_caller_address() + .unwrap_or_revert_with(Cep18Error::FailedToRetrieveImmediateCaller); let recipient: Key = runtime::get_named_arg(RECIPIENT); let owner: Key = runtime::get_named_arg(OWNER); if owner == recipient { @@ -187,8 +207,7 @@ pub extern "C" fn transfer_from() { let spender_allowance: U256 = read_allowance_from(allowances_uref, owner, spender); let new_spender_allowance = spender_allowance .checked_sub(amount) - .ok_or(Cep18Error::InsufficientAllowance) - .unwrap_or_revert(); + .unwrap_or_revert_with(Cep18Error::InsufficientAllowance); transfer_balance(owner, recipient, amount).unwrap_or_revert(); write_allowance_to(allowances_uref, owner, spender, new_spender_allowance); @@ -217,15 +236,14 @@ pub extern "C" fn mint() { let balance = read_balance_from(balances_uref, owner); balance .checked_add(amount) - .ok_or(Cep18Error::Overflow) - .unwrap_or_revert() + .unwrap_or_revert_with(Cep18Error::Overflow) }; let new_total_supply = { let total_supply: U256 = read_total_supply_from(total_supply_uref); total_supply .checked_add(amount) .ok_or(Cep18Error::Overflow) - .unwrap_or_revert() + .unwrap_or_revert_with(Cep18Error::FailedToChangeTotalSupply) }; write_balance_to(balances_uref, owner, new_balance); write_total_supply_to(total_supply_uref, new_total_supply); @@ -243,7 +261,10 @@ pub extern "C" fn burn() { let owner: Key = runtime::get_named_arg(OWNER); - if owner != get_immediate_caller_address().unwrap_or_revert() { + if owner + != get_immediate_caller_address() + .unwrap_or_revert_with(Cep18Error::FailedToRetrieveImmediateCaller) + { revert(Cep18Error::InvalidBurnTarget); } @@ -254,15 +275,14 @@ pub extern "C" fn burn() { let balance = read_balance_from(balances_uref, owner); balance .checked_sub(amount) - .ok_or(Cep18Error::InsufficientBalance) - .unwrap_or_revert() + .unwrap_or_revert_with(Cep18Error::InsufficientBalance) }; let new_total_supply = { let total_supply = read_total_supply_from(total_supply_uref); total_supply .checked_sub(amount) .ok_or(Cep18Error::Overflow) - .unwrap_or_revert() + .unwrap_or_revert_with(Cep18Error::FailedToChangeTotalSupply) }; write_balance_to(balances_uref, owner, new_balance); write_total_supply_to(total_supply_uref, new_total_supply); @@ -282,8 +302,9 @@ pub extern "C" fn init() { let contract_hash = get_named_arg::(CONTRACT_HASH); put_key(CONTRACT_HASH, contract_hash); - storage::new_dictionary(ALLOWANCES).unwrap_or_revert(); - let balances_uref = storage::new_dictionary(BALANCES).unwrap_or_revert(); + storage::new_dictionary(ALLOWANCES).unwrap_or_revert_with(Cep18Error::FailedToCreateDictionary); + let balances_uref = storage::new_dictionary(BALANCES) + .unwrap_or_revert_with(Cep18Error::FailedToCreateDictionary); let initial_supply = runtime::get_named_arg(TOTAL_SUPPLY); let caller = get_caller(); write_balance_to( @@ -292,13 +313,14 @@ pub extern "C" fn init() { initial_supply, ); - let security_badges_dict = storage::new_dictionary(SECURITY_BADGES).unwrap_or_revert(); + let security_badges_dict = storage::new_dictionary(SECURITY_BADGES) + .unwrap_or_revert_with(Cep18Error::FailedToCreateDictionary); dictionary_put( security_badges_dict, &base64::encode( Key::AddressableEntity(EntityAddr::Account(caller.value())) .to_bytes() - .unwrap_or_revert(), + .unwrap_or_revert_with(Cep18Error::FailedToConvertBytes), ), SecurityBadge::Admin, ); @@ -308,8 +330,8 @@ pub extern "C" fn init() { let minter_list: Option> = utils::get_optional_named_arg_with_user_errors(MINTER_LIST, Cep18Error::InvalidMinterList); - let events_mode: EventsMode = - EventsMode::try_from(read_from::(EVENTS_MODE)).unwrap_or_revert(); + let events_mode: EventsMode = EventsMode::try_from(read_from::(EVENTS_MODE)) + .unwrap_or_revert_with(Cep18Error::InvalidEventsMode); if [EventsMode::CES, EventsMode::NativeNCES].contains(&events_mode) { init_events(); @@ -319,7 +341,11 @@ pub extern "C" fn init() { for minter in minter_list { dictionary_put( security_badges_dict, - &base64::encode(minter.to_bytes().unwrap_or_revert()), + &base64::encode( + minter + .to_bytes() + .unwrap_or_revert_with(Cep18Error::FailedToConvertBytes), + ), SecurityBadge::Minter, ); } @@ -328,15 +354,20 @@ pub extern "C" fn init() { for admin in admin_list { dictionary_put( security_badges_dict, - &base64::encode(admin.to_bytes().unwrap_or_revert()), + &base64::encode( + admin + .to_bytes() + .unwrap_or_revert_with(Cep18Error::FailedToConvertBytes), + ), SecurityBadge::Admin, ); } } + events::record_event_dictionary(Event::Mint(Mint { recipient: Key::AddressableEntity(EntityAddr::Account(caller.value())), amount: initial_supply, - })) + })); } /// Admin EntryPoint to manipulate the security access granted to users. @@ -375,12 +406,13 @@ pub extern "C" fn change_security() { } } - let caller = get_immediate_caller_address().unwrap_or_revert(); + let caller = get_immediate_caller_address() + .unwrap_or_revert_with(Cep18Error::FailedToRetrieveImmediateCaller); badge_map.remove(&caller); utils::change_sec_badge(&badge_map); events::record_event_dictionary(Event::ChangeSecurity(ChangeSecurity { - admin: get_immediate_caller_address().unwrap_or_revert(), + admin: caller, sec_change_map: badge_map, })); } @@ -630,14 +662,18 @@ pub fn migrate_sec_keys() { continue; } }; - let old_user_sec_key = old_key.to_bytes().unwrap_or_revert(); + let old_user_sec_key = old_key + .to_bytes() + .unwrap_or_revert_with(Cep18Error::FailedToConvertBytes); let old_encoded_user_sec_key = base64::encode(old_user_sec_key); - let user_sec_key = migrated_key.to_bytes().unwrap_or_revert(); + let user_sec_key = migrated_key + .to_bytes() + .unwrap_or_revert_with(Cep18Error::FailedToConvertBytes); let migrated_encoded_user_sec_key = base64::encode(user_sec_key); let sec: SecurityBadge = named_dictionary_get(SECURITY_BADGES, &old_encoded_user_sec_key) - .unwrap_or_revert() + .unwrap_or_revert_with(Cep18Error::FailedToGetDictionaryValue) .unwrap_or(SecurityBadge::None); if ![SecurityBadge::Admin, SecurityBadge::Minter].contains(&sec) { named_dictionary_put(SECURITY_BADGES, &migrated_encoded_user_sec_key, sec); @@ -660,58 +696,74 @@ pub fn migrate_sec_keys() { #[no_mangle] fn change_events_mode() { sec_check(vec![SecurityBadge::Admin]); - let events_mode: EventsMode = - EventsMode::try_from(read_from::(EVENTS_MODE)).unwrap_or_revert(); + let events_mode: EventsMode = EventsMode::try_from(get_named_arg::(EVENTS_MODE)) + .unwrap_or_revert_with(Cep18Error::InvalidEventsMode); + let events_mode_u8 = events_mode as u8; + put_key(EVENTS_MODE, storage::new_uref(events_mode_u8).into()); + match events_mode { EventsMode::NoEvents => {} EventsMode::CES => init_events(), EventsMode::Native => { - manage_message_topic(EVENTS, MessageTopicOperation::Add).unwrap_or_revert() - } + let _ = manage_message_topic(EVENTS, MessageTopicOperation::Add); + }, EventsMode::NativeNCES => { init_events(); - manage_message_topic(EVENTS, MessageTopicOperation::Add).unwrap_or_revert() + let _ = manage_message_topic(EVENTS, MessageTopicOperation::Add); } - } - - put_key(EVENTS_MODE, storage::new_uref(events_mode as u8).into()); + }; + events::record_event_dictionary(Event::ChangeEventsMode(ChangeEventsMode {events_mode: events_mode_u8})); } pub fn upgrade(name: &str) { let entry_points = generate_entry_points(); - let old_contract_package_hash = - match runtime::get_key(&format!("{HASH_KEY_NAME_PREFIX}{name}")).unwrap_or_revert() { - Key::Hash(contract_hash) => contract_hash, - Key::AddressableEntity(EntityAddr::SmartContract(contract_hash)) => contract_hash, - Key::Package(package_hash) => package_hash, - _ => revert(Cep18Error::MissingPackageHashForUpgrade), - }; + let old_contract_package_hash = match runtime::get_key(&format!("{HASH_KEY_NAME_PREFIX}{name}")) + .unwrap_or_revert_with(Cep18Error::FailedToGetKey) + { + Key::Hash(contract_hash) => contract_hash, + Key::AddressableEntity(EntityAddr::SmartContract(contract_hash)) => contract_hash, + Key::Package(package_hash) => package_hash, + _ => revert(Cep18Error::MissingPackageHashForUpgrade), + }; let contract_package_hash = PackageHash::new(old_contract_package_hash); - let previous_contract_hash = - match runtime::get_key(&format!("{CONTRACT_NAME_PREFIX}{name}")).unwrap_or_revert() { - Key::Hash(contract_hash) => contract_hash, - Key::AddressableEntity(EntityAddr::SmartContract(contract_hash)) => contract_hash, - _ => revert(Cep18Error::MissingContractHashForUpgrade), - }; + let previous_contract_hash = match runtime::get_key(&format!("{CONTRACT_NAME_PREFIX}{name}")) + .unwrap_or_revert_with(Cep18Error::FailedToGetKey) + { + Key::Hash(contract_hash) => contract_hash, + Key::AddressableEntity(EntityAddr::SmartContract(contract_hash)) => contract_hash, + _ => revert(Cep18Error::MissingContractHashForUpgrade), + }; let converted_previous_contract_hash = AddressableEntityHash::new(previous_contract_hash); + let events_mode = utils::get_optional_named_arg_with_user_errors::( + EVENTS_MODE, + Cep18Error::InvalidEventsMode, + ); + + let mut message_topics = BTreeMap::new(); + + if let Some(mode) = events_mode { + let events_mode: EventsMode = mode + .try_into() + .unwrap_or_revert_with(Cep18Error::InvalidEventsMode); + if [EventsMode::NativeNCES, EventsMode::Native].contains(&events_mode) { + message_topics.insert(EVENTS.to_string(), MessageTopicOperation::Add); + } + } + let (contract_hash, contract_version) = storage::add_contract_version( contract_package_hash, entry_points, NamedKeys::new(), - BTreeMap::new(), + message_topics, ); storage::disable_contract_version(contract_package_hash, converted_previous_contract_hash) - .unwrap_or_revert(); + .unwrap_or_revert_with(Cep18Error::FailedToDisableContractVersion); - let events_mode = utils::get_optional_named_arg_with_user_errors::( - EVENTS_MODE, - Cep18Error::InvalidEventsMode, - ); if let Some(events_mode) = events_mode { call_contract::<()>( contract_hash, @@ -748,7 +800,8 @@ pub fn install_contract(name: &str) { utils::get_optional_named_arg_with_user_errors(EVENTS_MODE, Cep18Error::InvalidEventsMode) .unwrap_or(0u8); - let events_mode: EventsMode = EventsMode::try_from(events_mode_arg).unwrap_or_revert(); + let events_mode: EventsMode = + EventsMode::try_from(events_mode_arg).unwrap_or_revert_with(Cep18Error::InvalidEventsMode); let admin_list: Option> = utils::get_optional_named_arg_with_user_errors(ADMIN_LIST, Cep18Error::InvalidAdminList); @@ -779,7 +832,7 @@ pub fn install_contract(name: &str) { ); let entry_points = generate_entry_points(); - let message_topics = if events_mode == EventsMode::Native { + let message_topics = if [EventsMode::Native, EventsMode::NativeNCES].contains(&events_mode) { let mut message_topics = BTreeMap::new(); message_topics.insert(EVENTS.to_string(), MessageTopicOperation::Add); Some(message_topics) @@ -795,7 +848,9 @@ pub fn install_contract(name: &str) { Some(format!("{ACCESS_KEY_NAME_PREFIX}{name}")), message_topics, ); - let package_hash = runtime::get_key(&hash_key_name).unwrap_or_revert(); + + let package_hash = + runtime::get_key(&hash_key_name).unwrap_or_revert_with(Cep18Error::FailedToGetKey); // Store contract_hash and contract_version under the keys CONTRACT_NAME and CONTRACT_VERSION runtime::put_key( @@ -810,12 +865,14 @@ pub fn install_contract(name: &str) { let mut init_args = runtime_args! {TOTAL_SUPPLY => total_supply, PACKAGE_HASH => package_hash, CONTRACT_HASH => Key::addressable_entity_key(EntityKindTag::SmartContract, contract_hash)}; if let Some(admin_list) = admin_list { - init_args.insert(ADMIN_LIST, admin_list).unwrap_or_revert(); + init_args + .insert(ADMIN_LIST, admin_list) + .unwrap_or_revert_with(Cep18Error::FailedToInsertToSecurityList); } if let Some(minter_list) = minter_list { init_args .insert(MINTER_LIST, minter_list) - .unwrap_or_revert(); + .unwrap_or_revert_with(Cep18Error::FailedToInsertToSecurityList); } runtime::call_contract::<()>(contract_hash, INIT_ENTRY_POINT_NAME, init_args); diff --git a/cep18/src/modalities.rs b/cep18/src/modalities.rs index 161e126..51bee06 100644 --- a/cep18/src/modalities.rs +++ b/cep18/src/modalities.rs @@ -3,7 +3,7 @@ use core::convert::TryFrom; use crate::Cep18Error; #[repr(u8)] -#[derive(PartialEq, Eq)] +#[derive(PartialEq, Eq, Clone, Copy)] #[allow(clippy::upper_case_acronyms)] pub enum EventsMode { NoEvents = 0, diff --git a/cep18/src/utils.rs b/cep18/src/utils.rs index 9c51ca0..1a00c8d 100644 --- a/cep18/src/utils.rs +++ b/cep18/src/utils.rs @@ -28,8 +28,9 @@ use crate::{ pub(crate) fn get_uref(name: &str) -> URef { let key = runtime::get_key(name) .ok_or(ApiError::MissingKey) - .unwrap_or_revert(); - key.try_into().unwrap_or_revert() + .unwrap_or_revert_with(Cep18Error::FailedToGetKey); + key.try_into() + .unwrap_or_revert_with(Cep18Error::InvalidKeyType) } /// Reads value from a named key. @@ -38,7 +39,9 @@ where T: FromBytes + CLTyped, { let uref = get_uref(name); - let value: T = storage::read(uref).unwrap_or_revert().unwrap_or_revert(); + let value: T = storage::read(uref) + .unwrap_or_revert_with(Cep18Error::UrefNotFound) + .unwrap_or_revert_with(Cep18Error::FailedToReadFromStorage); value } @@ -76,7 +79,9 @@ pub fn get_total_supply_uref() -> URef { } pub(crate) fn read_total_supply_from(uref: URef) -> U256 { - storage::read(uref).unwrap_or_revert().unwrap_or_revert() + storage::read(uref) + .unwrap_or_revert_with(Cep18Error::UrefNotFound) + .unwrap_or_revert_with(Cep18Error::FailedToReadFromStorage) } /// Writes a total supply to a specific [`URef`]. @@ -181,7 +186,8 @@ impl FromBytes for SecurityBadge { } pub fn sec_check(allowed_badge_list: Vec) { - let caller = get_immediate_caller_address().unwrap_or_revert(); + let caller = get_immediate_caller_address() + .unwrap_or_revert_with(Cep18Error::FailedToRetrieveImmediateCaller); let hash: [u8; 32] = match caller { Key::Account(account) => account.0, Key::Hash(hash) => hash, @@ -194,12 +200,12 @@ pub fn sec_check(allowed_badge_list: Vec) { }; let new_style_key_bytes = Key::AddressableEntity(EntityAddr::Account(hash)) .to_bytes() - .unwrap_or_revert(); + .unwrap_or_revert_with(Cep18Error::FailedToConvertBytes); match dictionary_get::( get_uref(SECURITY_BADGES), &base64::encode(new_style_key_bytes), ) - .unwrap_or_revert() + .unwrap_or_revert_with(Cep18Error::FailedToGetDictionaryValue) { Some(badge) => { if !allowed_badge_list.contains(&badge) { @@ -209,12 +215,12 @@ pub fn sec_check(allowed_badge_list: Vec) { None => { let old_style_key_bytes = Key::Account(AccountHash::new(hash)) .to_bytes() - .unwrap_or_revert(); + .unwrap_or_revert_with(Cep18Error::FailedToConvertBytes); match dictionary_get::( get_uref(SECURITY_BADGES), &base64::encode(old_style_key_bytes), ) - .unwrap_or_revert() + .unwrap_or_revert_with(Cep18Error::FailedToGetDictionaryValue) { Some(badge) => { if !allowed_badge_list.contains(&badge) { @@ -232,7 +238,10 @@ pub fn change_sec_badge(badge_map: &BTreeMap) { for (&user, &badge) in badge_map { dictionary_put( sec_uref, - &base64::encode(user.to_bytes().unwrap_or_revert()), + &base64::encode( + user.to_bytes() + .unwrap_or_revert_with(Cep18Error::FailedToConvertBytes), + ), badge, ) } diff --git a/tests/src/events.rs b/tests/src/events.rs index e69de29..11b7f0e 100644 --- a/tests/src/events.rs +++ b/tests/src/events.rs @@ -0,0 +1,90 @@ +use std::collections::BTreeMap; + +use casper_engine_test_support::{ExecuteRequestBuilder, DEFAULT_ACCOUNT_ADDR}; +use casper_types::{contract_messages::Message, runtime_args, AddressableEntityHash, Key, U256}; + +use crate::utility::{ + constants::{ + AMOUNT, ARG_DECIMALS, ARG_NAME, ARG_SYMBOL, ARG_TOTAL_SUPPLY, CEP18_TOKEN_CONTRACT_KEY, + ENABLE_MINT_BURN, EVENTS, EVENTS_MODE, METHOD_MINT, OWNER, TOKEN_DECIMALS, TOKEN_NAME, + TOKEN_OWNER_ADDRESS_1, TOKEN_OWNER_ADDRESS_1_OLD, TOKEN_OWNER_AMOUNT_1, TOKEN_SYMBOL, + TOKEN_TOTAL_SUPPLY, + }, + installer_request_builders::{setup_with_args, TestContext}, + message_handlers::{entity, message_topic}, +}; + +#[test] +fn should_have_native_events() { + let (mut builder, TestContext { cep18_token, .. }) = setup_with_args(runtime_args! { + ARG_NAME => TOKEN_NAME, + ARG_SYMBOL => TOKEN_SYMBOL, + ARG_DECIMALS => TOKEN_DECIMALS, + ARG_TOTAL_SUPPLY => U256::from(TOKEN_TOTAL_SUPPLY), + EVENTS_MODE => 2_u8, + ENABLE_MINT_BURN => true, + }); + + let addressable_cep18_token = AddressableEntityHash::new(cep18_token.value()); + let mint_request = ExecuteRequestBuilder::contract_call_by_hash( + *DEFAULT_ACCOUNT_ADDR, + addressable_cep18_token, + METHOD_MINT, + runtime_args! {OWNER => TOKEN_OWNER_ADDRESS_1, AMOUNT => U256::from(TOKEN_OWNER_AMOUNT_1)}, + ) + .build(); + builder.exec(mint_request).expect_success().commit(); + + let account = builder + .get_entity_with_named_keys_by_account_hash(*DEFAULT_ACCOUNT_ADDR) + .unwrap(); + let account_named_keys = account.named_keys(); + + let cep18_token = account_named_keys + .get(CEP18_TOKEN_CONTRACT_KEY) + .and_then(|key| key.into_entity_hash()) + .expect("should have contract hash"); + + // events check + let entity = entity(&builder, &cep18_token); + + let (topic_name, message_topic_hash) = entity + .message_topics() + .iter() + .next() + .expect("should have at least one topic"); + + let mut user_map: BTreeMap = BTreeMap::new(); + user_map.insert(Key::Account(*DEFAULT_ACCOUNT_ADDR), true); + user_map.insert(TOKEN_OWNER_ADDRESS_1_OLD, true); + + assert_eq!(topic_name, &EVENTS.to_string()); + + let mint_request = ExecuteRequestBuilder::contract_call_by_hash( + *DEFAULT_ACCOUNT_ADDR, + cep18_token, + METHOD_MINT, + runtime_args! {OWNER => TOKEN_OWNER_ADDRESS_1, AMOUNT => U256::from(TOKEN_OWNER_AMOUNT_1)}, + ) + .build(); + + builder.exec(mint_request).expect_success().commit(); + + assert_eq!( + message_topic(&builder, &cep18_token, *message_topic_hash).message_count(), + 3 + ); + + let exec_result = builder.get_exec_result_owned(3).unwrap(); + let messages = exec_result.messages(); + let mint_message = "Mint(Mint { recipient: Key::AddressableEntity(account-2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a), amount: 1000000 })"; + let message = Message::new( + casper_types::EntityAddr::SmartContract(cep18_token.value()), + mint_message.into(), + EVENTS.to_string(), + *message_topic_hash, + 2, + 2, + ); + assert_eq!(messages, &vec![message]); +} diff --git a/tests/src/lib.rs b/tests/src/lib.rs index 17dbe6c..cb567ac 100644 --- a/tests/src/lib.rs +++ b/tests/src/lib.rs @@ -1,6 +1,8 @@ #[cfg(test)] mod allowance; #[cfg(test)] +mod events; +#[cfg(test)] mod install; #[cfg(test)] mod migration; @@ -9,4 +11,6 @@ mod mint_and_burn; #[cfg(test)] mod transfer; #[cfg(test)] +mod upgrade; +#[cfg(test)] mod utility; diff --git a/tests/src/migration.rs b/tests/src/migration.rs index d1cc6fc..bba8fb0 100644 --- a/tests/src/migration.rs +++ b/tests/src/migration.rs @@ -1,78 +1,38 @@ use std::collections::BTreeMap; use casper_engine_test_support::{ - ExecuteRequestBuilder, UpgradeRequestBuilder, DEFAULT_ACCOUNT_ADDR, + ExecuteRequestBuilder, LmdbWasmTestBuilder, UpgradeRequestBuilder, DEFAULT_ACCOUNT_ADDR, +}; +use casper_fixtures::LmdbFixtureState; +use casper_types::{ + bytesrepr::FromBytes, runtime_args, AddressableEntityHash, CLTyped, EraId, Key, + ProtocolVersion, RuntimeArgs, U256, }; -use casper_types::{runtime_args, EraId, Key, ProtocolVersion, RuntimeArgs, U256}; use crate::utility::{ constants::{ AMOUNT, ARG_DECIMALS, ARG_NAME, ARG_SYMBOL, ARG_TOTAL_SUPPLY, CEP18_CONTRACT_WASM, - CEP18_TEST_CONTRACT_WASM, CEP18_TOKEN_CONTRACT_KEY, EVENTS, EVENTS_MODE, METHOD_MINT, - MIGRATE_USER_BALANCE_KEYS_ENTRY_POINT_NAME, MIGRATE_USER_SEC_KEYS_ENTRY_POINT_NAME, OWNER, - REVERT, TOKEN_DECIMALS, TOKEN_NAME, TOKEN_OWNER_ADDRESS_1, TOKEN_OWNER_ADDRESS_1_OLD, - TOKEN_OWNER_AMOUNT_1, TOKEN_SYMBOL, TOKEN_TOTAL_SUPPLY, USER_KEY_MAP, + CEP18_TEST_CONTRACT_WASM, CEP18_TOKEN_CONTRACT_KEY, CEP18_TOKEN_CONTRACT_VERSION_KEY, + EVENTS, EVENTS_MODE, METHOD_MINT, MIGRATE_USER_BALANCE_KEYS_ENTRY_POINT_NAME, + MIGRATE_USER_SEC_KEYS_ENTRY_POINT_NAME, OWNER, REVERT, TOKEN_DECIMALS, TOKEN_NAME, + TOKEN_OWNER_ADDRESS_1, TOKEN_OWNER_ADDRESS_1_OLD, TOKEN_OWNER_AMOUNT_1, TOKEN_SYMBOL, + TOKEN_TOTAL_SUPPLY, USER_KEY_MAP, }, - installer_request_builders::{cep18_check_balance_of, setup, TestContext}, + installer_request_builders::cep18_check_balance_of, + message_handlers::{entity, message_summary, message_topic}, }; -#[test] -fn should_upgrade_contract_version() { - let (mut builder, TestContext { cep18_token: _, .. }) = setup(); - - let version_0: u32 = builder - .query( - None, - Key::Account(*DEFAULT_ACCOUNT_ADDR), - &["cep18_contract_version_CasperTest".to_string()], - ) - .unwrap() - .as_cl_value() - .unwrap() - .clone() - .into_t() - .unwrap(); - - let upgrade_request = ExecuteRequestBuilder::standard( - *DEFAULT_ACCOUNT_ADDR, - CEP18_CONTRACT_WASM, - runtime_args! { - ARG_NAME => TOKEN_NAME, - ARG_SYMBOL => TOKEN_SYMBOL, - ARG_DECIMALS => TOKEN_DECIMALS, - ARG_TOTAL_SUPPLY => U256::from(TOKEN_TOTAL_SUPPLY), - }, - ) - .build(); - - builder.exec(upgrade_request).expect_success().commit(); - - let version_1: u32 = builder - .query( - None, - Key::Account(*DEFAULT_ACCOUNT_ADDR), - &["cep18_contract_version_CasperTest".to_string()], - ) - .unwrap() - .as_cl_value() - .unwrap() - .clone() - .into_t() - .unwrap(); - - assert!(version_0 < version_1); -} - -#[test] -fn should_migrate_1_5_6_to_2_0_0_rc3() { - let (mut builder, lmdb_fixture_state, _temp_dir) = - casper_fixtures::builder_from_global_state_fixture("cep18-1.5.6-minted"); - +pub fn upgrade_v1_5_6_fixture_to_v2_0_0_ee( + builder: &mut LmdbWasmTestBuilder, + lmdb_fixture_state: &LmdbFixtureState, +) { + // state hash in builder and lmdb storage should be the same assert_eq!( builder.get_post_state_hash(), lmdb_fixture_state.post_state_hash ); + // we upgrade the execution engines protocol from 1.x to 2.x let mut upgrade_config = UpgradeRequestBuilder::new() .with_current_protocol_version(lmdb_fixture_state.genesis_protocol_version()) .with_new_protocol_version(ProtocolVersion::V2_0_0) @@ -85,24 +45,93 @@ fn should_migrate_1_5_6_to_2_0_0_rc3() { .upgrade(&mut upgrade_config) .expect_upgrade_success() .commit(); + + // the state hash should now be different assert_ne!( builder.get_post_state_hash(), lmdb_fixture_state.post_state_hash ); +} - let version_0: u32 = builder - .query( - None, - Key::Account(*DEFAULT_ACCOUNT_ADDR), - &["cep18_contract_version_CasperTest".to_string()], - ) +pub fn query_contract_value( + builder: &LmdbWasmTestBuilder, + path: &[String], +) -> T { + builder + .query(None, Key::Account(*DEFAULT_ACCOUNT_ADDR), path) .unwrap() .as_cl_value() .unwrap() .clone() .into_t() + .unwrap() +} + +// the difference between the two is that in v1_binary the contract hash is fetched at [u8;32], while in v2_binary it is an AddressaleEntityHash +pub fn get_contract_hash_v1_binary(builder: &LmdbWasmTestBuilder) -> AddressableEntityHash { + let account = builder + .get_entity_with_named_keys_by_account_hash(*DEFAULT_ACCOUNT_ADDR) + .unwrap(); + let account_named_keys = account.named_keys(); + + let cep18_token = account_named_keys + .get(CEP18_TOKEN_CONTRACT_KEY) + .and_then(|key| key.into_hash_addr()) + .map(AddressableEntityHash::new) + .expect("should have contract hash"); + + cep18_token +} + +pub fn get_contract_hash_v2_binary(builder: &LmdbWasmTestBuilder) -> AddressableEntityHash { + let account = builder + .get_entity_with_named_keys_by_account_hash(*DEFAULT_ACCOUNT_ADDR) .unwrap(); + let account_named_keys = account.named_keys(); + + let cep18_token = account_named_keys + .get(CEP18_TOKEN_CONTRACT_KEY) + .and_then(|key| key.into_entity_hash()) + .expect("should have contract hash"); + + cep18_token +} + +#[test] +fn should_be_able_to_call_1x_contract_in_2x_execution_engine() { + // load fixture that was created in a previous EE version + let (mut builder, lmdb_fixture_state, _temp_dir) = + casper_fixtures::builder_from_global_state_fixture("cep18-1.5.6-minted"); + + // upgrade the execution engine to the new protocol version + upgrade_v1_5_6_fixture_to_v2_0_0_ee(&mut builder, &lmdb_fixture_state); + let cep18_token = get_contract_hash_v1_binary(&builder); + + let mint_request = ExecuteRequestBuilder::contract_call_by_hash( + *DEFAULT_ACCOUNT_ADDR, + cep18_token, + METHOD_MINT, + runtime_args! {OWNER => TOKEN_OWNER_ADDRESS_1_OLD, AMOUNT => U256::from(TOKEN_OWNER_AMOUNT_1)}, + ) + .build(); + + builder.exec(mint_request).expect_success().commit(); +} + +#[test] +fn should_migrate_1_5_6_to_2_0_0_rc3() { + // load fixture + let (mut builder, lmdb_fixture_state, _temp_dir) = + casper_fixtures::builder_from_global_state_fixture("cep18-1.5.6-minted"); + + // upgrade engine + upgrade_v1_5_6_fixture_to_v2_0_0_ee(&mut builder, &lmdb_fixture_state); + + let version_0: u32 = + query_contract_value(&builder, &[CEP18_TOKEN_CONTRACT_VERSION_KEY.to_string()]); + + // upgrade the contract itself using a binary built for the new engine let upgrade_request = ExecuteRequestBuilder::standard( *DEFAULT_ACCOUNT_ADDR, CEP18_CONTRACT_WASM, @@ -118,31 +147,14 @@ fn should_migrate_1_5_6_to_2_0_0_rc3() { builder.exec(upgrade_request).expect_success().commit(); - let version_1: u32 = builder - .query( - None, - Key::Account(*DEFAULT_ACCOUNT_ADDR), - &["cep18_contract_version_CasperTest".to_string()], - ) - .unwrap() - .as_cl_value() - .unwrap() - .clone() - .into_t() - .unwrap(); + let version_1: u32 = + query_contract_value(&builder, &[CEP18_TOKEN_CONTRACT_VERSION_KEY.to_string()]); assert!(version_0 < version_1); - let account = builder - .get_entity_with_named_keys_by_account_hash(*DEFAULT_ACCOUNT_ADDR) - .unwrap(); - let account_named_keys = account.named_keys(); - - let cep18_token = account_named_keys - .get(CEP18_TOKEN_CONTRACT_KEY) - .and_then(|key| key.into_entity_hash()) - .expect("should have contract hash"); + let cep18_token = get_contract_hash_v2_binary(&builder); + // use utility entrypoint to migrate the security keys in cep-18 let mut user_map: BTreeMap = BTreeMap::new(); user_map.insert(Key::Account(*DEFAULT_ACCOUNT_ADDR), true); user_map.insert(TOKEN_OWNER_ADDRESS_1_OLD, true); @@ -160,6 +172,7 @@ fn should_migrate_1_5_6_to_2_0_0_rc3() { .expect_success() .commit(); + // mint some new tokens in cep-18 let mint_request = ExecuteRequestBuilder::contract_call_by_hash( *DEFAULT_ACCOUNT_ADDR, cep18_token, @@ -184,6 +197,7 @@ fn should_migrate_1_5_6_to_2_0_0_rc3() { U256::from(TOKEN_OWNER_AMOUNT_1), ); + // migrate the balances from the old owner keys to the new ones in cep-18 let balance_migrate_request = ExecuteRequestBuilder::contract_call_by_hash( *DEFAULT_ACCOUNT_ADDR, cep18_token, @@ -203,3 +217,73 @@ fn should_migrate_1_5_6_to_2_0_0_rc3() { U256::from(TOKEN_OWNER_AMOUNT_1 * 2), ); } + +#[test] +fn should_have_native_events() { + let (mut builder, lmdb_fixture_state, _temp_dir) = + casper_fixtures::builder_from_global_state_fixture("cep18-1.5.6-minted"); + + upgrade_v1_5_6_fixture_to_v2_0_0_ee(&mut builder, &lmdb_fixture_state); + + let upgrade_request = ExecuteRequestBuilder::standard( + *DEFAULT_ACCOUNT_ADDR, + CEP18_CONTRACT_WASM, + runtime_args! { + ARG_NAME => TOKEN_NAME, + ARG_SYMBOL => TOKEN_SYMBOL, + ARG_DECIMALS => TOKEN_DECIMALS, + ARG_TOTAL_SUPPLY => U256::from(TOKEN_TOTAL_SUPPLY), + EVENTS_MODE => 3_u8 + }, + ) + .build(); + + builder.exec(upgrade_request).expect_success().commit(); + + let cep18_token = get_contract_hash_v2_binary(&builder); + + // events check + let entity = entity(&builder, &cep18_token); + + let (topic_name, message_topic_hash) = entity + .message_topics() + .iter() + .next() + .expect("should have at least one topic"); + + let mut user_map: BTreeMap = BTreeMap::new(); + user_map.insert(Key::Account(*DEFAULT_ACCOUNT_ADDR), true); + user_map.insert(TOKEN_OWNER_ADDRESS_1_OLD, true); + + let sec_key_migrate_request = ExecuteRequestBuilder::contract_call_by_hash( + *DEFAULT_ACCOUNT_ADDR, + cep18_token, + MIGRATE_USER_SEC_KEYS_ENTRY_POINT_NAME, + runtime_args! {EVENTS => true, REVERT => true, USER_KEY_MAP => &user_map}, + ) + .build(); + + builder + .exec(sec_key_migrate_request) + .expect_success() + .commit(); + + assert_eq!(topic_name, &EVENTS.to_string()); + + let mint_request = ExecuteRequestBuilder::contract_call_by_hash( + *DEFAULT_ACCOUNT_ADDR, + cep18_token, + METHOD_MINT, + runtime_args! {OWNER => TOKEN_OWNER_ADDRESS_1, AMOUNT => U256::from(TOKEN_OWNER_AMOUNT_1)}, + ) + .build(); + + builder.exec(mint_request).expect_success().commit(); + + assert_eq!( + message_topic(&builder, &cep18_token, *message_topic_hash).message_count(), + 3 + ); + + message_summary(&builder, &cep18_token, message_topic_hash, 0, None).unwrap(); +} diff --git a/tests/src/mint_and_burn.rs b/tests/src/mint_and_burn.rs index 94455be..06813d2 100644 --- a/tests/src/mint_and_burn.rs +++ b/tests/src/mint_and_burn.rs @@ -179,7 +179,7 @@ fn test_should_not_mint_above_limits() { let error = builder.get_error().expect("should have error"); assert!( matches!(error, CoreError::Exec(ExecError::Revert(ApiError::User(user_error))) if user_error == ERROR_OVERFLOW), - "{:?}", + "Should not mint above limits, but instead: {:?}", error ); } diff --git a/tests/src/upgrade.rs b/tests/src/upgrade.rs new file mode 100644 index 0000000..a502218 --- /dev/null +++ b/tests/src/upgrade.rs @@ -0,0 +1,57 @@ +use casper_engine_test_support::{ExecuteRequestBuilder, DEFAULT_ACCOUNT_ADDR}; +use casper_types::{runtime_args, Key, U256}; + +use crate::utility::{ + constants::{ + ARG_DECIMALS, ARG_NAME, ARG_SYMBOL, ARG_TOTAL_SUPPLY, CEP18_CONTRACT_WASM, TOKEN_DECIMALS, + TOKEN_NAME, TOKEN_SYMBOL, TOKEN_TOTAL_SUPPLY, + }, + installer_request_builders::{setup, TestContext}, +}; + +#[test] +fn should_upgrade_contract_version() { + let (mut builder, TestContext { cep18_token: _, .. }) = setup(); + + let version_0: u32 = builder + .query( + None, + Key::Account(*DEFAULT_ACCOUNT_ADDR), + &["cep18_contract_version_CasperTest".to_string()], + ) + .unwrap() + .as_cl_value() + .unwrap() + .clone() + .into_t() + .unwrap(); + + let upgrade_request = ExecuteRequestBuilder::standard( + *DEFAULT_ACCOUNT_ADDR, + CEP18_CONTRACT_WASM, + runtime_args! { + ARG_NAME => TOKEN_NAME, + ARG_SYMBOL => TOKEN_SYMBOL, + ARG_DECIMALS => TOKEN_DECIMALS, + ARG_TOTAL_SUPPLY => U256::from(TOKEN_TOTAL_SUPPLY), + }, + ) + .build(); + + builder.exec(upgrade_request).expect_success().commit(); + + let version_1: u32 = builder + .query( + None, + Key::Account(*DEFAULT_ACCOUNT_ADDR), + &["cep18_contract_version_CasperTest".to_string()], + ) + .unwrap() + .as_cl_value() + .unwrap() + .clone() + .into_t() + .unwrap(); + + assert!(version_0 < version_1); +} diff --git a/tests/src/utility/constants.rs b/tests/src/utility/constants.rs index ff83908..9ea30b4 100644 --- a/tests/src/utility/constants.rs +++ b/tests/src/utility/constants.rs @@ -6,6 +6,7 @@ pub const CEP18_TEST_CONTRACT_WASM: &str = "cep18_test_contract.wasm"; pub const NAME_KEY: &str = "name"; pub const SYMBOL_KEY: &str = "symbol"; pub const CEP18_TOKEN_CONTRACT_KEY: &str = "cep18_contract_hash_CasperTest"; +pub const CEP18_TOKEN_CONTRACT_VERSION_KEY: &str = "cep18_contract_version_CasperTest"; pub const DECIMALS_KEY: &str = "decimals"; pub const TOTAL_SUPPLY_KEY: &str = "total_supply"; pub const BALANCES_KEY: &str = "balances"; diff --git a/tests/src/utility/installer_request_builders.rs b/tests/src/utility/installer_request_builders.rs index f81713d..af9f07e 100644 --- a/tests/src/utility/installer_request_builders.rs +++ b/tests/src/utility/installer_request_builders.rs @@ -17,7 +17,7 @@ use super::constants::{ ARG_OWNER, ARG_RECIPIENT, ARG_SPENDER, ARG_SYMBOL, ARG_TOKEN_CONTRACT, ARG_TOTAL_SUPPLY, CEP18_CONTRACT_WASM, CEP18_TEST_CONTRACT_KEY, CEP18_TEST_CONTRACT_WASM, CEP18_TOKEN_CONTRACT_KEY, CHECK_ALLOWANCE_OF_ENTRYPOINT, CHECK_BALANCE_OF_ENTRYPOINT, - CHECK_TOTAL_SUPPLY_ENTRYPOINT, METHOD_APPROVE, METHOD_APPROVE_AS_STORED_CONTRACT, + CHECK_TOTAL_SUPPLY_ENTRYPOINT, EVENTS_MODE, METHOD_APPROVE, METHOD_APPROVE_AS_STORED_CONTRACT, METHOD_TRANSFER, METHOD_TRANSFER_AS_STORED_CONTRACT, RESULT_KEY, TOKEN_DECIMALS, TOKEN_NAME, TOKEN_SYMBOL, TOKEN_TOTAL_SUPPLY, }; @@ -55,6 +55,7 @@ pub(crate) fn setup() -> (LmdbWasmTestBuilder, TestContext) { ARG_SYMBOL => TOKEN_SYMBOL, ARG_DECIMALS => TOKEN_DECIMALS, ARG_TOTAL_SUPPLY => U256::from(TOKEN_TOTAL_SUPPLY), + EVENTS_MODE => 2_u8 }) } diff --git a/tests/src/utility/message_handlers.rs b/tests/src/utility/message_handlers.rs new file mode 100644 index 0000000..2e14eed --- /dev/null +++ b/tests/src/utility/message_handlers.rs @@ -0,0 +1,73 @@ +use casper_engine_test_support::LmdbWasmTestBuilder; +use casper_types::{ + contract_messages::{MessageChecksum, MessageTopicSummary, TopicNameHash}, + AddressableEntity, AddressableEntityHash, Digest, EntityAddr, Key, StoredValue, +}; + +pub fn entity( + builder: &LmdbWasmTestBuilder, + contract_hash: &AddressableEntityHash, +) -> AddressableEntity { + let query_result = builder + .query(None, Key::contract_entity_key(*contract_hash), &[]) + .expect("should query"); + + if let StoredValue::AddressableEntity(entity) = query_result { + entity + } else { + panic!( + "Stored value is not an addressable entity: {:?}", + query_result + ); + } +} + +pub fn message_topic( + builder: &LmdbWasmTestBuilder, + contract_hash: &AddressableEntityHash, + topic_name_hash: TopicNameHash, +) -> MessageTopicSummary { + let query_result = builder + .query( + None, + Key::message_topic( + EntityAddr::new_smart_contract(contract_hash.value()), + topic_name_hash, + ), + &[], + ) + .expect("should query"); + + match query_result { + StoredValue::MessageTopic(summary) => summary, + _ => { + panic!( + "Stored value is not a message topic summary: {:?}", + query_result + ); + } + } +} + +pub fn message_summary( + builder: &LmdbWasmTestBuilder, + contract_hash: &AddressableEntityHash, + topic_name_hash: &TopicNameHash, + message_index: u32, + state_hash: Option, +) -> Result { + let query_result = builder.query( + state_hash, + Key::message( + EntityAddr::new_smart_contract(contract_hash.value()), + *topic_name_hash, + message_index, + ), + &[], + )?; + + match query_result { + StoredValue::Message(summary) => Ok(summary), + _ => panic!("Stored value is not a message summary: {:?}", query_result), + } +} diff --git a/tests/src/utility/mod.rs b/tests/src/utility/mod.rs index d50bf29..84726d2 100644 --- a/tests/src/utility/mod.rs +++ b/tests/src/utility/mod.rs @@ -1,2 +1,3 @@ pub mod constants; pub mod installer_request_builders; +pub mod message_handlers; From ae334e0f5d676530cb66f70adf064f960413e8dc Mon Sep 17 00:00:00 2001 From: Gregory Roussac Date: Tue, 2 Jul 2024 14:58:28 +0200 Subject: [PATCH 16/27] Use DEFAULT_ACCOUNTS in tests (#146) --- tests/src/allowance.rs | 57 ++-- tests/src/mint_and_burn.rs | 109 ++++++-- tests/src/transfer.rs | 248 +++++++++--------- tests/src/utility/constants.rs | 19 +- .../src/utility/installer_request_builders.rs | 63 +++-- 5 files changed, 267 insertions(+), 229 deletions(-) diff --git a/tests/src/allowance.rs b/tests/src/allowance.rs index ecbd403..107254b 100644 --- a/tests/src/allowance.rs +++ b/tests/src/allowance.rs @@ -6,12 +6,13 @@ use casper_types::{ use crate::utility::{ constants::{ - ACCOUNT_1_ADDR, ALLOWANCE_AMOUNT_1, ALLOWANCE_AMOUNT_2, ARG_AMOUNT, ARG_OWNER, - ARG_RECIPIENT, ARG_SPENDER, DECREASE_ALLOWANCE, ERROR_INSUFFICIENT_ALLOWANCE, - INCREASE_ALLOWANCE, METHOD_APPROVE, METHOD_TRANSFER_FROM, + ALLOWANCE_AMOUNT_1, ALLOWANCE_AMOUNT_2, ARG_AMOUNT, ARG_OWNER, ARG_RECIPIENT, ARG_SPENDER, + DECREASE_ALLOWANCE, ERROR_INSUFFICIENT_ALLOWANCE, INCREASE_ALLOWANCE, METHOD_APPROVE, + METHOD_TRANSFER_FROM, }, installer_request_builders::{ - cep18_check_allowance_of, make_cep18_approve_request, setup, test_approve_for, TestContext, + cep18_check_allowance_of, get_test_account, make_cep18_approve_request, setup, + test_approve_for, TestContext, }, }; use casper_execution_engine::{engine_state::Error as CoreError, execution::ExecError}; @@ -64,23 +65,29 @@ fn should_approve_funds_contract_to_contract() { fn should_approve_funds_account_to_account() { let (mut builder, test_context) = setup(); + let (default_account_user_key, _, _) = get_test_account("ACCOUNT_USER_0"); + let (account_user_1_key, _, _) = get_test_account("ACCOUNT_USER_1"); + test_approve_for( &mut builder, &test_context, - Key::AddressableEntity(EntityAddr::Account(DEFAULT_ACCOUNT_ADDR.value())), - Key::AddressableEntity(EntityAddr::Account(DEFAULT_ACCOUNT_ADDR.value())), - Key::AddressableEntity(EntityAddr::Account(ACCOUNT_1_ADDR.value())), + default_account_user_key, + default_account_user_key, + account_user_1_key, ); } #[test] fn should_approve_funds_account_to_contract() { let (mut builder, test_context) = setup(); + + let (default_account_user_key, _, _) = get_test_account("ACCOUNT_USER_0"); + test_approve_for( &mut builder, &test_context, - Key::AddressableEntity(EntityAddr::Account(DEFAULT_ACCOUNT_ADDR.value())), - Key::AddressableEntity(EntityAddr::Account(DEFAULT_ACCOUNT_ADDR.value())), + default_account_user_key, + default_account_user_key, Key::AddressableEntity(EntityAddr::SmartContract([42; 32])), ); } @@ -95,31 +102,32 @@ fn should_not_transfer_from_without_enough_allowance() { }, ) = setup(); + let (default_account_user_key, default_account_user_account_hash, _) = + get_test_account("ACCOUNT_USER_0"); + let (account_user_1_key, _, _) = get_test_account("ACCOUNT_USER_1"); + let addressable_cep18_contract_hash = AddressableEntityHash::new(cep18_contract_hash.value()); let allowance_amount_1 = U256::from(ALLOWANCE_AMOUNT_1); let transfer_from_amount_1 = allowance_amount_1 + U256::one(); - let sender = *DEFAULT_ACCOUNT_ADDR; - let owner = sender; - let recipient = *ACCOUNT_1_ADDR; + let sender = default_account_user_account_hash; + let sender_key = default_account_user_key; + let owner_key = sender_key; + let recipient_key = account_user_1_key; let cep18_approve_args = runtime_args! { - ARG_OWNER => Key::AddressableEntity(EntityAddr::Account(owner.value())), - ARG_SPENDER => Key::AddressableEntity(EntityAddr::Account(recipient.value())), + ARG_OWNER => owner_key, + ARG_SPENDER => recipient_key, ARG_AMOUNT => allowance_amount_1, }; let cep18_transfer_from_args = runtime_args! { - ARG_OWNER => Key::AddressableEntity(EntityAddr::Account(owner.value())), - ARG_RECIPIENT => Key::AddressableEntity(EntityAddr::Account(recipient.value())), + ARG_OWNER => owner_key, + ARG_RECIPIENT => recipient_key, ARG_AMOUNT => transfer_from_amount_1, }; - let spender_allowance_before = cep18_check_allowance_of( - &mut builder, - Key::AddressableEntity(EntityAddr::Account(owner.value())), - Key::AddressableEntity(EntityAddr::Account(recipient.value())), - ); + let spender_allowance_before = cep18_check_allowance_of(&mut builder, owner_key, recipient_key); assert_eq!(spender_allowance_before, U256::zero()); let approve_request_1 = ExecuteRequestBuilder::contract_call_by_hash( @@ -140,11 +148,8 @@ fn should_not_transfer_from_without_enough_allowance() { builder.exec(approve_request_1).expect_success().commit(); - let account_1_allowance_after = cep18_check_allowance_of( - &mut builder, - Key::AddressableEntity(EntityAddr::Account(owner.value())), - Key::AddressableEntity(EntityAddr::Account(recipient.value())), - ); + let account_1_allowance_after = + cep18_check_allowance_of(&mut builder, owner_key, recipient_key); assert_eq!(account_1_allowance_after, allowance_amount_1); builder.exec(transfer_from_request_1).commit(); diff --git a/tests/src/mint_and_burn.rs b/tests/src/mint_and_burn.rs index d89a152..5c8818f 100644 --- a/tests/src/mint_and_burn.rs +++ b/tests/src/mint_and_burn.rs @@ -1,16 +1,17 @@ use casper_engine_test_support::{ExecuteRequestBuilder, DEFAULT_ACCOUNT_ADDR}; -use casper_types::{runtime_args, AddressableEntityHash, ApiError, EntityAddr, Key, U256}; +use casper_types::{runtime_args, AddressableEntityHash, ApiError, Key, U256}; use crate::utility::{ constants::{ - ACCOUNT_1_ADDR, ADMIN_LIST, AMOUNT, ARG_AMOUNT, ARG_DECIMALS, ARG_NAME, ARG_OWNER, - ARG_SYMBOL, ARG_TOTAL_SUPPLY, CHANGE_SECURITY, ENABLE_MINT_BURN, - ERROR_INSUFFICIENT_BALANCE, ERROR_OVERFLOW, METHOD_BURN, METHOD_MINT, MINTER_LIST, - NONE_LIST, OWNER, TOKEN_DECIMALS, TOKEN_NAME, TOKEN_OWNER_ADDRESS_1, TOKEN_OWNER_ADDRESS_2, - TOKEN_OWNER_AMOUNT_1, TOKEN_OWNER_AMOUNT_2, TOKEN_SYMBOL, TOKEN_TOTAL_SUPPLY, + ADMIN_LIST, AMOUNT, ARG_AMOUNT, ARG_DECIMALS, ARG_NAME, ARG_OWNER, ARG_SYMBOL, + ARG_TOTAL_SUPPLY, CHANGE_SECURITY, ENABLE_MINT_BURN, ERROR_INSUFFICIENT_BALANCE, + ERROR_OVERFLOW, METHOD_BURN, METHOD_MINT, MINTER_LIST, NONE_LIST, OWNER, TOKEN_DECIMALS, + TOKEN_NAME, TOKEN_OWNER_ADDRESS_1, TOKEN_OWNER_ADDRESS_2, TOKEN_OWNER_AMOUNT_1, + TOKEN_OWNER_AMOUNT_2, TOKEN_SYMBOL, TOKEN_TOTAL_SUPPLY, }, installer_request_builders::{ - cep18_check_balance_of, cep18_check_total_supply, setup_with_args, TestContext, + cep18_check_balance_of, cep18_check_total_supply, get_test_account, setup_with_args, + TestContext, }, }; @@ -20,7 +21,13 @@ use casper_execution_engine::{engine_state::Error as CoreError, execution::ExecE fn test_mint_and_burn_tokens() { let mint_amount = U256::one(); - let (mut builder, TestContext { cep18_contract_hash, .. }) = setup_with_args(runtime_args! { + let ( + mut builder, + TestContext { + cep18_contract_hash, + .. + }, + ) = setup_with_args(runtime_args! { ARG_NAME => TOKEN_NAME, ARG_SYMBOL => TOKEN_SYMBOL, ARG_DECIMALS => TOKEN_DECIMALS, @@ -133,7 +140,13 @@ fn test_mint_and_burn_tokens() { fn test_should_not_mint_above_limits() { let mint_amount = U256::MAX; - let (mut builder, TestContext { cep18_contract_hash, .. }) = setup_with_args(runtime_args! { + let ( + mut builder, + TestContext { + cep18_contract_hash, + .. + }, + ) = setup_with_args(runtime_args! { ARG_NAME => TOKEN_NAME, ARG_SYMBOL => TOKEN_SYMBOL, ARG_DECIMALS => TOKEN_DECIMALS, @@ -186,7 +199,13 @@ fn test_should_not_mint_above_limits() { #[test] fn test_should_not_burn_above_balance() { - let (mut builder, TestContext { cep18_contract_hash, .. }) = setup_with_args(runtime_args! { + let ( + mut builder, + TestContext { + cep18_contract_hash, + .. + }, + ) = setup_with_args(runtime_args! { ARG_NAME => TOKEN_NAME, ARG_SYMBOL => TOKEN_SYMBOL, ARG_DECIMALS => TOKEN_DECIMALS, @@ -220,7 +239,13 @@ fn test_should_not_burn_above_balance() { fn test_should_not_mint_or_burn_with_entrypoint_disabled() { let mint_amount = U256::one(); - let (mut builder, TestContext { cep18_contract_hash, .. }) = setup_with_args(runtime_args! { + let ( + mut builder, + TestContext { + cep18_contract_hash, + .. + }, + ) = setup_with_args(runtime_args! { ARG_NAME => TOKEN_NAME, ARG_SYMBOL => TOKEN_SYMBOL, ARG_DECIMALS => TOKEN_DECIMALS, @@ -272,9 +297,17 @@ fn test_should_not_mint_or_burn_with_entrypoint_disabled() { #[test] fn test_security_no_rights() { + let (account_user_1_key, account_user_1_account_hash, _) = get_test_account("ACCOUNT_USER_1"); + let mint_amount = U256::one(); - let (mut builder, TestContext { cep18_contract_hash, .. }) = setup_with_args(runtime_args! { + let ( + mut builder, + TestContext { + cep18_contract_hash, + .. + }, + ) = setup_with_args(runtime_args! { ARG_NAME => TOKEN_NAME, ARG_SYMBOL => TOKEN_SYMBOL, ARG_DECIMALS => TOKEN_DECIMALS, @@ -284,11 +317,11 @@ fn test_security_no_rights() { let addressable_cep18_contract_hash = AddressableEntityHash::new(cep18_contract_hash.value()); let mint_request = ExecuteRequestBuilder::contract_call_by_hash( - *ACCOUNT_1_ADDR, + account_user_1_account_hash, addressable_cep18_contract_hash, METHOD_MINT, runtime_args! { - ARG_OWNER => Key::AddressableEntity(casper_types::EntityAddr::Account(ACCOUNT_1_ADDR.value())), + ARG_OWNER => account_user_1_key, ARG_AMOUNT => mint_amount, }, ) @@ -308,7 +341,7 @@ fn test_security_no_rights() { addressable_cep18_contract_hash, METHOD_MINT, runtime_args! { - ARG_OWNER => Key::AddressableEntity(EntityAddr::Account(ACCOUNT_1_ADDR.value())), + ARG_OWNER => account_user_1_key, ARG_AMOUNT => mint_amount, }, ) @@ -320,11 +353,11 @@ fn test_security_no_rights() { .commit(); let burn_request = ExecuteRequestBuilder::contract_call_by_hash( - *ACCOUNT_1_ADDR, + account_user_1_account_hash, addressable_cep18_contract_hash, METHOD_BURN, runtime_args! { - ARG_OWNER => Key::AddressableEntity(EntityAddr::Account(ACCOUNT_1_ADDR.value())), + ARG_OWNER => account_user_1_key, ARG_AMOUNT => mint_amount, }, ) @@ -335,20 +368,27 @@ fn test_security_no_rights() { #[test] fn test_security_minter_rights() { + let (account_user_1_key, account_user_1_account_hash, _) = get_test_account("ACCOUNT_USER_1"); let mint_amount = U256::one(); - let (mut builder, TestContext { cep18_contract_hash, .. }) = setup_with_args(runtime_args! { + let ( + mut builder, + TestContext { + cep18_contract_hash, + .. + }, + ) = setup_with_args(runtime_args! { ARG_NAME => TOKEN_NAME, ARG_SYMBOL => TOKEN_SYMBOL, ARG_DECIMALS => TOKEN_DECIMALS, ARG_TOTAL_SUPPLY => U256::from(TOKEN_TOTAL_SUPPLY), ENABLE_MINT_BURN => true, - MINTER_LIST => vec![Key::AddressableEntity(casper_types::EntityAddr::Account(ACCOUNT_1_ADDR.value()))] + MINTER_LIST => vec![account_user_1_key] }); let addressable_cep18_contract_hash = AddressableEntityHash::new(cep18_contract_hash.value()); let mint_request = ExecuteRequestBuilder::contract_call_by_hash( - *ACCOUNT_1_ADDR, + account_user_1_account_hash, addressable_cep18_contract_hash, METHOD_MINT, runtime_args! { @@ -363,9 +403,17 @@ fn test_security_minter_rights() { #[test] fn test_security_burner_rights() { + let (default_account_user_key, _, _) = get_test_account("ACCOUNT_USER_0"); + let (_, account_user_1_account_hash, _) = get_test_account("ACCOUNT_USER_1"); let mint_amount = U256::one(); - let (mut builder, TestContext { cep18_contract_hash, .. }) = setup_with_args(runtime_args! { + let ( + mut builder, + TestContext { + cep18_contract_hash, + .. + }, + ) = setup_with_args(runtime_args! { ARG_NAME => TOKEN_NAME, ARG_SYMBOL => TOKEN_SYMBOL, ARG_DECIMALS => TOKEN_DECIMALS, @@ -375,7 +423,7 @@ fn test_security_burner_rights() { let addressable_cep18_contract_hash = AddressableEntityHash::new(cep18_contract_hash.value()); let mint_request = ExecuteRequestBuilder::contract_call_by_hash( - *ACCOUNT_1_ADDR, + account_user_1_account_hash, addressable_cep18_contract_hash, METHOD_MINT, runtime_args! { @@ -400,7 +448,7 @@ fn test_security_burner_rights() { addressable_cep18_contract_hash, METHOD_MINT, runtime_args! { - ARG_OWNER => Key::AddressableEntity(casper_types::EntityAddr::Account(DEFAULT_ACCOUNT_ADDR.value())), + ARG_OWNER => default_account_user_key, ARG_AMOUNT => mint_amount, }, ) @@ -414,7 +462,7 @@ fn test_security_burner_rights() { addressable_cep18_contract_hash, METHOD_BURN, runtime_args! { - ARG_OWNER => Key::AddressableEntity(casper_types::EntityAddr::Account(DEFAULT_ACCOUNT_ADDR.value())), + ARG_OWNER => default_account_user_key, ARG_AMOUNT => mint_amount, }, ) @@ -425,20 +473,27 @@ fn test_security_burner_rights() { #[test] fn test_change_security() { + let (account_user_1_key, account_user_1_account_hash, _) = get_test_account("ACCOUNT_USER_1"); let mint_amount = U256::one(); - let (mut builder, TestContext { cep18_contract_hash, .. }) = setup_with_args(runtime_args! { + let ( + mut builder, + TestContext { + cep18_contract_hash, + .. + }, + ) = setup_with_args(runtime_args! { ARG_NAME => TOKEN_NAME, ARG_SYMBOL => TOKEN_SYMBOL, ARG_DECIMALS => TOKEN_DECIMALS, ARG_TOTAL_SUPPLY => U256::from(TOKEN_TOTAL_SUPPLY), ENABLE_MINT_BURN => true, - ADMIN_LIST => vec![Key::AddressableEntity(casper_types::EntityAddr::Account(ACCOUNT_1_ADDR.value()))] + ADMIN_LIST => vec![account_user_1_key] }); let addressable_cep18_contract_hash = AddressableEntityHash::new(cep18_contract_hash.value()); let change_security_request = ExecuteRequestBuilder::contract_call_by_hash( - *ACCOUNT_1_ADDR, + account_user_1_account_hash, addressable_cep18_contract_hash, CHANGE_SECURITY, runtime_args! { diff --git a/tests/src/transfer.rs b/tests/src/transfer.rs index a262f40..9abcdc7 100644 --- a/tests/src/transfer.rs +++ b/tests/src/transfer.rs @@ -6,14 +6,15 @@ use casper_types::{ use crate::utility::{ constants::{ - ACCOUNT_1_ADDR, ACCOUNT_2_ADDR, ALLOWANCE_AMOUNT_1, ARG_AMOUNT, ARG_OWNER, ARG_RECIPIENT, - ARG_SPENDER, ARG_TOKEN_CONTRACT, ERROR_INSUFFICIENT_BALANCE, METHOD_APPROVE, - METHOD_FROM_AS_STORED_CONTRACT, METHOD_TRANSFER, METHOD_TRANSFER_FROM, TOKEN_TOTAL_SUPPLY, - TOTAL_SUPPLY_KEY, TRANSFER_AMOUNT_1, + ALLOWANCE_AMOUNT_1, ARG_AMOUNT, ARG_OWNER, ARG_RECIPIENT, ARG_SPENDER, ARG_TOKEN_CONTRACT, + ERROR_INSUFFICIENT_BALANCE, METHOD_APPROVE, METHOD_FROM_AS_STORED_CONTRACT, + METHOD_TRANSFER, METHOD_TRANSFER_FROM, TOKEN_TOTAL_SUPPLY, TOTAL_SUPPLY_KEY, + TRANSFER_AMOUNT_1, }, installer_request_builders::{ - cep18_check_allowance_of, cep18_check_balance_of, make_cep18_approve_request, - make_cep18_transfer_request, setup, test_cep18_transfer, TestContext, + cep18_check_allowance_of, cep18_check_balance_of, get_test_account, + make_cep18_approve_request, make_cep18_transfer_request, setup, test_cep18_transfer, + TestContext, }, }; @@ -29,28 +30,25 @@ fn should_transfer_full_owned_amount() { }, ) = setup(); + let (default_account_user_key, _, _) = get_test_account("ACCOUNT_USER_0"); + let (account_user_1_key, _, _) = get_test_account("ACCOUNT_USER_1"); + let addressable_cep18_contract_hash = AddressableEntityHash::new(cep18_contract_hash.value()); let initial_supply = U256::from(TOKEN_TOTAL_SUPPLY); let transfer_amount_1 = initial_supply; let transfer_1_sender = *DEFAULT_ACCOUNT_ADDR; let cep18_transfer_1_args = runtime_args! { - ARG_RECIPIENT => Key::AddressableEntity(EntityAddr::Account(ACCOUNT_1_ADDR.value())), + ARG_RECIPIENT => account_user_1_key, ARG_AMOUNT => transfer_amount_1, }; - let owner_balance_before = cep18_check_balance_of( - &mut builder, - &cep18_contract_hash, - Key::AddressableEntity(EntityAddr::Account(DEFAULT_ACCOUNT_ADDR.value())), - ); + let owner_balance_before = + cep18_check_balance_of(&mut builder, &cep18_contract_hash, default_account_user_key); assert_eq!(owner_balance_before, initial_supply); - let account_1_balance_before = cep18_check_balance_of( - &mut builder, - &cep18_contract_hash, - Key::AddressableEntity(EntityAddr::Account(ACCOUNT_1_ADDR.value())), - ); + let account_1_balance_before = + cep18_check_balance_of(&mut builder, &cep18_contract_hash, account_user_1_key); assert_eq!(account_1_balance_before, U256::zero()); let token_transfer_request_1 = ExecuteRequestBuilder::contract_call_by_hash( @@ -66,18 +64,12 @@ fn should_transfer_full_owned_amount() { .expect_success() .commit(); - let account_1_balance_after = cep18_check_balance_of( - &mut builder, - &cep18_contract_hash, - Key::AddressableEntity(EntityAddr::Account(ACCOUNT_1_ADDR.value())), - ); + let account_1_balance_after = + cep18_check_balance_of(&mut builder, &cep18_contract_hash, account_user_1_key); assert_eq!(account_1_balance_after, transfer_amount_1); - let owner_balance_after = cep18_check_balance_of( - &mut builder, - &cep18_contract_hash, - Key::AddressableEntity(EntityAddr::Account(DEFAULT_ACCOUNT_ADDR.value())), - ); + let owner_balance_after = + cep18_check_balance_of(&mut builder, &cep18_contract_hash, default_account_user_key); assert_eq!(owner_balance_after, U256::zero()); let total_supply: U256 = builder.get_value( @@ -97,32 +89,30 @@ fn should_not_transfer_more_than_owned_balance() { }, ) = setup(); + let (default_account_user_key, default_account_user_account_hash, _) = + get_test_account("ACCOUNT_USER_0"); + let (account_user_1_key, _, _) = get_test_account("ACCOUNT_USER_1"); + let addressable_cep18_contract_hash = AddressableEntityHash::new(cep18_contract_hash.value()); let initial_supply = U256::from(TOKEN_TOTAL_SUPPLY); let transfer_amount = initial_supply + U256::one(); - let transfer_1_sender = *DEFAULT_ACCOUNT_ADDR; - let transfer_1_recipient = *ACCOUNT_1_ADDR; + let transfer_1_sender = default_account_user_account_hash; + let transfer_1_recipient = account_user_1_key; let cep18_transfer_1_args = runtime_args! { - ARG_RECIPIENT => Key::AddressableEntity(EntityAddr::Account(transfer_1_recipient.value())), + ARG_RECIPIENT => transfer_1_recipient, ARG_AMOUNT => transfer_amount, }; - let owner_balance_before = cep18_check_balance_of( - &mut builder, - &cep18_contract_hash, - Key::AddressableEntity(EntityAddr::Account(DEFAULT_ACCOUNT_ADDR.value())), - ); + let owner_balance_before = + cep18_check_balance_of(&mut builder, &cep18_contract_hash, default_account_user_key); assert_eq!(owner_balance_before, initial_supply); assert!(transfer_amount > owner_balance_before); - let account_1_balance_before = cep18_check_balance_of( - &mut builder, - &cep18_contract_hash, - Key::AddressableEntity(EntityAddr::Account(ACCOUNT_1_ADDR.value())), - ); + let account_1_balance_before = + cep18_check_balance_of(&mut builder, &cep18_contract_hash, account_user_1_key); assert_eq!(account_1_balance_before, U256::zero()); let token_transfer_request_1 = ExecuteRequestBuilder::contract_call_by_hash( @@ -142,18 +132,12 @@ fn should_not_transfer_more_than_owned_balance() { error ); - let account_1_balance_after = cep18_check_balance_of( - &mut builder, - &cep18_contract_hash, - Key::AddressableEntity(EntityAddr::Account(transfer_1_recipient.value())), - ); + let account_1_balance_after = + cep18_check_balance_of(&mut builder, &cep18_contract_hash, account_user_1_key); assert_eq!(account_1_balance_after, account_1_balance_before); - let owner_balance_after = cep18_check_balance_of( - &mut builder, - &cep18_contract_hash, - Key::AddressableEntity(EntityAddr::Account(transfer_1_sender.value())), - ); + let owner_balance_after = + cep18_check_balance_of(&mut builder, &cep18_contract_hash, default_account_user_key); assert_eq!(owner_balance_after, initial_supply); let total_supply: U256 = builder.get_value( @@ -173,31 +157,32 @@ fn should_transfer_from_from_account_to_account() { }, ) = setup(); + let (default_account_user_key, default_account_user_account_hash, _) = + get_test_account("ACCOUNT_USER_0"); + let (account_user_1_key, account_user_1_account_hash, _) = get_test_account("ACCOUNT_USER_1"); + let addressable_cep18_contract_hash = AddressableEntityHash::new(cep18_contract_hash.value()); let initial_supply = U256::from(TOKEN_TOTAL_SUPPLY); let allowance_amount_1 = U256::from(ALLOWANCE_AMOUNT_1); let transfer_from_amount_1 = allowance_amount_1; - let owner = *DEFAULT_ACCOUNT_ADDR; - let spender = *ACCOUNT_1_ADDR; + let owner = default_account_user_account_hash; + let spender = account_user_1_account_hash; let cep18_approve_args = runtime_args! { - ARG_OWNER => Key::AddressableEntity(EntityAddr::Account(owner.value())), - ARG_SPENDER => Key::AddressableEntity(EntityAddr::Account(spender.value())), + ARG_OWNER => default_account_user_key, + ARG_SPENDER => account_user_1_key, ARG_AMOUNT => allowance_amount_1, }; let cep18_transfer_from_args = runtime_args! { - ARG_OWNER => Key::AddressableEntity(EntityAddr::Account(owner.value())), - ARG_RECIPIENT => Key::AddressableEntity(EntityAddr::Account(spender.value())), + ARG_OWNER => default_account_user_key, + ARG_RECIPIENT => account_user_1_key, ARG_AMOUNT => transfer_from_amount_1, }; - let spender_allowance_before = cep18_check_allowance_of( - &mut builder, - Key::AddressableEntity(EntityAddr::Account(owner.value())), - Key::AddressableEntity(EntityAddr::Account(spender.value())), - ); + let spender_allowance_before = + cep18_check_allowance_of(&mut builder, default_account_user_key, account_user_1_key); assert_eq!(spender_allowance_before, U256::zero()); let approve_request_1 = ExecuteRequestBuilder::contract_call_by_hash( @@ -218,18 +203,12 @@ fn should_transfer_from_from_account_to_account() { builder.exec(approve_request_1).expect_success().commit(); - let account_1_balance_before = cep18_check_balance_of( - &mut builder, - &cep18_contract_hash, - Key::AddressableEntity(EntityAddr::Account(owner.value())), - ); + let account_1_balance_before = + cep18_check_balance_of(&mut builder, &cep18_contract_hash, default_account_user_key); assert_eq!(account_1_balance_before, initial_supply); - let account_1_allowance_before = cep18_check_allowance_of( - &mut builder, - Key::AddressableEntity(EntityAddr::Account(owner.value())), - Key::AddressableEntity(EntityAddr::Account(spender.value())), - ); + let account_1_allowance_before = + cep18_check_allowance_of(&mut builder, default_account_user_key, account_user_1_key); assert_eq!(account_1_allowance_before, allowance_amount_1); builder @@ -237,21 +216,15 @@ fn should_transfer_from_from_account_to_account() { .expect_success() .commit(); - let account_1_allowance_after = cep18_check_allowance_of( - &mut builder, - Key::AddressableEntity(EntityAddr::Account(owner.value())), - Key::AddressableEntity(EntityAddr::Account(spender.value())), - ); + let account_1_allowance_after = + cep18_check_allowance_of(&mut builder, default_account_user_key, account_user_1_key); assert_eq!( account_1_allowance_after, account_1_allowance_before - transfer_from_amount_1 ); - let account_1_balance_after = cep18_check_balance_of( - &mut builder, - &cep18_contract_hash, - Key::AddressableEntity(EntityAddr::Account(owner.value())), - ); + let account_1_balance_after = + cep18_check_balance_of(&mut builder, &cep18_contract_hash, default_account_user_key); assert_eq!( account_1_balance_after, account_1_balance_before - transfer_from_amount_1 @@ -269,6 +242,9 @@ fn should_transfer_from_account_by_contract() { }, ) = setup(); + let (default_account_user_key, _, _) = get_test_account("ACCOUNT_USER_0"); + let (account_user_1_key, _, _) = get_test_account("ACCOUNT_USER_1"); + let addressable_cep18_contract_hash = AddressableEntityHash::new(cep18_contract_hash.value()); let initial_supply = U256::from(TOKEN_TOTAL_SUPPLY); @@ -280,25 +256,22 @@ fn should_transfer_from_account_by_contract() { let spender = Key::AddressableEntity(EntityAddr::SmartContract( cep18_test_contract_package.value(), )); - let recipient = Key::AddressableEntity(EntityAddr::Account(ACCOUNT_1_ADDR.value())); + let recipient = account_user_1_key; let cep18_approve_args = runtime_args! { - ARG_OWNER => Key::AddressableEntity(EntityAddr::Account(owner.value())), + ARG_OWNER => default_account_user_key, ARG_SPENDER => spender, ARG_AMOUNT => allowance_amount_1, }; let cep18_transfer_from_args = runtime_args! { ARG_TOKEN_CONTRACT => Key::addressable_entity_key(EntityKindTag::SmartContract, cep18_contract_hash), - ARG_OWNER => Key::AddressableEntity(EntityAddr::Account(owner.value())), + ARG_OWNER => default_account_user_key, ARG_RECIPIENT => recipient, ARG_AMOUNT => transfer_from_amount_1, }; - let spender_allowance_before = cep18_check_allowance_of( - &mut builder, - Key::AddressableEntity(EntityAddr::Account(owner.value())), - spender, - ); + let spender_allowance_before = + cep18_check_allowance_of(&mut builder, default_account_user_key, spender); assert_eq!(spender_allowance_before, U256::zero()); let approve_request_1 = ExecuteRequestBuilder::contract_call_by_hash( @@ -320,18 +293,12 @@ fn should_transfer_from_account_by_contract() { builder.exec(approve_request_1).expect_success().commit(); - let owner_balance_before = cep18_check_balance_of( - &mut builder, - &cep18_contract_hash, - Key::AddressableEntity(EntityAddr::Account(owner.value())), - ); + let owner_balance_before = + cep18_check_balance_of(&mut builder, &cep18_contract_hash, default_account_user_key); assert_eq!(owner_balance_before, initial_supply); - let spender_allowance_before = cep18_check_allowance_of( - &mut builder, - Key::AddressableEntity(EntityAddr::Account(owner.value())), - spender, - ); + let spender_allowance_before = + cep18_check_allowance_of(&mut builder, default_account_user_key, spender); assert_eq!(spender_allowance_before, allowance_amount_1); builder @@ -339,21 +306,15 @@ fn should_transfer_from_account_by_contract() { .expect_success() .commit(); - let spender_allowance_after = cep18_check_allowance_of( - &mut builder, - Key::AddressableEntity(EntityAddr::Account(owner.value())), - spender, - ); + let spender_allowance_after = + cep18_check_allowance_of(&mut builder, default_account_user_key, spender); assert_eq!( spender_allowance_after, spender_allowance_before - transfer_from_amount_1 ); - let owner_balance_after = cep18_check_balance_of( - &mut builder, - &cep18_contract_hash, - Key::AddressableEntity(EntityAddr::Account(owner.value())), - ); + let owner_balance_after = + cep18_check_balance_of(&mut builder, &cep18_contract_hash, default_account_user_key); assert_eq!( owner_balance_after, owner_balance_before - transfer_from_amount_1 @@ -370,8 +331,10 @@ fn should_not_be_able_to_own_transfer() { }, ) = setup(); - let sender = Key::AddressableEntity(EntityAddr::Account(DEFAULT_ACCOUNT_ADDR.value())); - let recipient = Key::AddressableEntity(EntityAddr::Account(DEFAULT_ACCOUNT_ADDR.value())); + let (default_account_user_key, _, _) = get_test_account("ACCOUNT_USER_0"); + + let sender = default_account_user_key; + let recipient = default_account_user_key; let transfer_amount = U256::from(TRANSFER_AMOUNT_1); @@ -404,13 +367,16 @@ fn should_not_be_able_to_own_transfer_from() { }, ) = setup(); + let (default_account_user_key, default_account_account_hash, _) = + get_test_account("ACCOUNT_USER_0"); + let addressable_cep18_contract_hash = AddressableEntityHash::new(cep18_contract_hash.value()); - let sender = *DEFAULT_ACCOUNT_ADDR; - let owner = Key::AddressableEntity(EntityAddr::Account(sender.value())); - let spender = Key::AddressableEntity(EntityAddr::Account(sender.value())); - let sender_key = Key::AddressableEntity(EntityAddr::Account(sender.value())); - let recipient = Key::AddressableEntity(EntityAddr::Account(sender.value())); + let sender = default_account_account_hash; + let owner = default_account_user_key; + let spender = default_account_user_key; + let sender_key = default_account_user_key; + let recipient = default_account_user_key; let allowance_amount = U256::from(ALLOWANCE_AMOUNT_1); let transfer_amount = U256::from(TRANSFER_AMOUNT_1); @@ -469,8 +435,11 @@ fn should_verify_zero_amount_transfer_is_noop() { }, ) = setup(); - let sender = Key::AddressableEntity(EntityAddr::Account(DEFAULT_ACCOUNT_ADDR.value())); - let recipient = Key::AddressableEntity(EntityAddr::Account(ACCOUNT_1_ADDR.value())); + let (default_account_user_key, _, _) = get_test_account("ACCOUNT_USER_0"); + let (account_user_1_key, _, _) = get_test_account("ACCOUNT_USER_1"); + + let sender = default_account_user_key; + let recipient = account_user_1_key; let transfer_amount = U256::zero(); @@ -504,12 +473,16 @@ fn should_verify_zero_amount_transfer_from_is_noop() { }, ) = setup(); + let (default_account_user_key, _, _) = get_test_account("ACCOUNT_USER_0"); + let (account_user_1_key, _, _) = get_test_account("ACCOUNT_USER_1"); + let (account_user_2_key, _, _) = get_test_account("ACCOUNT_USER_2"); + let addressable_cep18_contract_hash = AddressableEntityHash::new(cep18_contract_hash.value()); let owner = *DEFAULT_ACCOUNT_ADDR; - let owner_key = Key::AddressableEntity(EntityAddr::Account(owner.value())); - let spender = Key::AddressableEntity(EntityAddr::Account(ACCOUNT_1_ADDR.value())); - let recipient = Key::AddressableEntity(EntityAddr::Account(ACCOUNT_2_ADDR.value())); + let owner_key = default_account_user_key; + let spender = account_user_1_key; + let recipient = account_user_2_key; let allowance_amount = U256::from(1); let transfer_amount = U256::zero(); @@ -565,7 +538,9 @@ fn should_transfer_contract_to_contract() { .. } = test_context; - let sender1 = Key::AddressableEntity(EntityAddr::Account(DEFAULT_ACCOUNT_ADDR.value())); + let (default_account_user_key, _, _) = get_test_account("ACCOUNT_USER_0"); + + let sender1 = default_account_user_key; let recipient1 = Key::AddressableEntity(EntityAddr::SmartContract( cep18_test_contract_package.value(), )); @@ -592,7 +567,10 @@ fn should_transfer_contract_to_account() { .. } = test_context; - let sender1 = Key::AddressableEntity(EntityAddr::Account(DEFAULT_ACCOUNT_ADDR.value())); + let (default_account_user_key, _, _) = get_test_account("ACCOUNT_USER_0"); + let (account_user_1_key, _, _) = get_test_account("ACCOUNT_USER_1"); + + let sender1 = default_account_user_key; let recipient1 = Key::AddressableEntity(EntityAddr::SmartContract( cep18_test_contract_package.value(), )); @@ -600,7 +578,7 @@ fn should_transfer_contract_to_account() { let sender2 = Key::AddressableEntity(EntityAddr::SmartContract( cep18_test_contract_package.value(), )); - let recipient2 = Key::AddressableEntity(EntityAddr::Account(ACCOUNT_1_ADDR.value())); + let recipient2 = account_user_1_key; test_cep18_transfer( &mut builder, @@ -616,9 +594,12 @@ fn should_transfer_contract_to_account() { fn should_transfer_account_to_contract() { let (mut builder, test_context) = setup(); - let sender1 = Key::AddressableEntity(EntityAddr::Account(DEFAULT_ACCOUNT_ADDR.value())); - let recipient1 = Key::AddressableEntity(EntityAddr::Account(ACCOUNT_1_ADDR.value())); - let sender2 = Key::AddressableEntity(EntityAddr::Account(ACCOUNT_1_ADDR.value())); + let (default_account_user_key, _, _) = get_test_account("ACCOUNT_USER_0"); + let (account_user_1_key, _, _) = get_test_account("ACCOUNT_USER_1"); + + let sender1 = default_account_user_key; + let recipient1 = account_user_1_key; + let sender2 = account_user_1_key; let recipient2 = Key::AddressableEntity(EntityAddr::SmartContract( test_context.cep18_test_contract_package.value(), )); @@ -636,10 +617,15 @@ fn should_transfer_account_to_contract() { #[test] fn should_transfer_account_to_account() { let (mut builder, test_context) = setup(); - let sender1 = Key::AddressableEntity(EntityAddr::Account(DEFAULT_ACCOUNT_ADDR.value())); - let recipient1 = Key::AddressableEntity(EntityAddr::Account(ACCOUNT_1_ADDR.value())); - let sender2 = Key::AddressableEntity(EntityAddr::Account(ACCOUNT_1_ADDR.value())); - let recipient2 = Key::AddressableEntity(EntityAddr::Account(ACCOUNT_2_ADDR.value())); + + let (default_account_user_key, _, _) = get_test_account("ACCOUNT_USER_0"); + let (account_user_1_key, _, _) = get_test_account("ACCOUNT_USER_1"); + let (account_user_2_key, _, _) = get_test_account("ACCOUNT_USER_2"); + + let sender1 = default_account_user_key; + let recipient1 = account_user_1_key; + let sender2 = account_user_1_key; + let recipient2 = account_user_2_key; test_cep18_transfer( &mut builder, diff --git a/tests/src/utility/constants.rs b/tests/src/utility/constants.rs index 9ea30b4..bdeb2c0 100644 --- a/tests/src/utility/constants.rs +++ b/tests/src/utility/constants.rs @@ -1,5 +1,4 @@ -use casper_types::{account::AccountHash, EntityAddr, Key, PublicKey, SecretKey}; -use once_cell::sync::Lazy; +use casper_types::{account::AccountHash, EntityAddr, Key}; pub const CEP18_CONTRACT_WASM: &str = "cep18.wasm"; pub const CEP18_TEST_CONTRACT_WASM: &str = "cep18_test_contract.wasm"; @@ -47,22 +46,6 @@ pub const ARG_ADDRESS: &str = "address"; pub const RESULT_KEY: &str = "result"; pub const CEP18_TEST_CONTRACT_KEY: &str = "cep18_test_contract"; -pub static ACCOUNT_1_SECRET_KEY: Lazy = - Lazy::new(|| SecretKey::secp256k1_from_bytes([221u8; 32]).unwrap()); -pub static ACCOUNT_1_PUBLIC_KEY: Lazy = - Lazy::new(|| PublicKey::from(&*ACCOUNT_1_SECRET_KEY)); -pub static ACCOUNT_1_ADDR: Lazy = Lazy::new(|| ACCOUNT_1_PUBLIC_KEY.to_account_hash()); -pub static _ACCOUNT_1_ENTITY_ADDR_KEY: Lazy = - Lazy::new(|| Key::AddressableEntity(EntityAddr::Account(ACCOUNT_1_ADDR.value()))); - -pub static ACCOUNT_2_SECRET_KEY: Lazy = - Lazy::new(|| SecretKey::secp256k1_from_bytes([212u8; 32]).unwrap()); -pub static ACCOUNT_2_PUBLIC_KEY: Lazy = - Lazy::new(|| PublicKey::from(&*ACCOUNT_2_SECRET_KEY)); -pub static ACCOUNT_2_ADDR: Lazy = Lazy::new(|| ACCOUNT_2_PUBLIC_KEY.to_account_hash()); -pub static _ACCOUNT_2_ENTITY_ADDR_KEY: Lazy = - Lazy::new(|| Key::AddressableEntity(EntityAddr::Account(ACCOUNT_2_ADDR.value()))); - pub const TRANSFER_AMOUNT_1: u64 = 200_001; pub const TRANSFER_AMOUNT_2: u64 = 19_999; pub const ALLOWANCE_AMOUNT_1: u64 = 456_789; diff --git a/tests/src/utility/installer_request_builders.rs b/tests/src/utility/installer_request_builders.rs index f309353..9e36b84 100644 --- a/tests/src/utility/installer_request_builders.rs +++ b/tests/src/utility/installer_request_builders.rs @@ -1,11 +1,10 @@ use casper_engine_test_support::{ utils::create_run_genesis_request, ExecuteRequest, ExecuteRequestBuilder, LmdbWasmTestBuilder, - DEFAULT_ACCOUNT_ADDR, DEFAULT_ACCOUNT_PUBLIC_KEY, + DEFAULT_ACCOUNTS, DEFAULT_ACCOUNT_ADDR, }; use casper_types::{ account::AccountHash, addressable_entity::EntityKindTag, bytesrepr::FromBytes, runtime_args, - AddressableEntityHash, CLTyped, EntityAddr, GenesisAccount, Key, Motes, PackageHash, - RuntimeArgs, U256, U512, + AddressableEntityHash, CLTyped, EntityAddr, Key, PackageHash, PublicKey, RuntimeArgs, U256, }; use crate::utility::constants::{ @@ -13,13 +12,12 @@ use crate::utility::constants::{ }; use super::constants::{ - ACCOUNT_1_PUBLIC_KEY, ACCOUNT_2_PUBLIC_KEY, ARG_ADDRESS, ARG_AMOUNT, ARG_DECIMALS, ARG_NAME, - ARG_OWNER, ARG_RECIPIENT, ARG_SPENDER, ARG_SYMBOL, ARG_TOKEN_CONTRACT, ARG_TOTAL_SUPPLY, - CEP18_CONTRACT_WASM, CEP18_TEST_CONTRACT_KEY, CEP18_TEST_CONTRACT_WASM, - CEP18_TOKEN_CONTRACT_KEY, CHECK_ALLOWANCE_OF_ENTRYPOINT, CHECK_BALANCE_OF_ENTRYPOINT, - CHECK_TOTAL_SUPPLY_ENTRYPOINT, EVENTS_MODE, METHOD_APPROVE, METHOD_APPROVE_AS_STORED_CONTRACT, - METHOD_TRANSFER, METHOD_TRANSFER_AS_STORED_CONTRACT, RESULT_KEY, TOKEN_DECIMALS, TOKEN_NAME, - TOKEN_SYMBOL, TOKEN_TOTAL_SUPPLY, + ARG_ADDRESS, ARG_AMOUNT, ARG_DECIMALS, ARG_NAME, ARG_OWNER, ARG_RECIPIENT, ARG_SPENDER, + ARG_SYMBOL, ARG_TOKEN_CONTRACT, ARG_TOTAL_SUPPLY, CEP18_CONTRACT_WASM, CEP18_TEST_CONTRACT_KEY, + CEP18_TEST_CONTRACT_WASM, CEP18_TOKEN_CONTRACT_KEY, CHECK_ALLOWANCE_OF_ENTRYPOINT, + CHECK_BALANCE_OF_ENTRYPOINT, CHECK_TOTAL_SUPPLY_ENTRYPOINT, EVENTS_MODE, METHOD_APPROVE, + METHOD_APPROVE_AS_STORED_CONTRACT, METHOD_TRANSFER, METHOD_TRANSFER_AS_STORED_CONTRACT, + RESULT_KEY, TOKEN_DECIMALS, TOKEN_NAME, TOKEN_SYMBOL, TOKEN_TOTAL_SUPPLY, }; /// Converts hash addr of Account into Hash, and Hash into Account @@ -61,23 +59,9 @@ pub(crate) fn setup() -> (LmdbWasmTestBuilder, TestContext) { pub(crate) fn setup_with_args(install_args: RuntimeArgs) -> (LmdbWasmTestBuilder, TestContext) { let mut builder = LmdbWasmTestBuilder::default(); - builder.run_genesis(create_run_genesis_request(vec![ - GenesisAccount::Account { - public_key: DEFAULT_ACCOUNT_PUBLIC_KEY.clone(), - balance: Motes::new(U512::from(5_000_000_000_000_u64)), - validator: None, - }, - GenesisAccount::Account { - public_key: ACCOUNT_1_PUBLIC_KEY.clone(), - balance: Motes::new(U512::from(5_000_000_000_000_u64)), - validator: None, - }, - GenesisAccount::Account { - public_key: ACCOUNT_2_PUBLIC_KEY.clone(), - balance: Motes::new(U512::from(5_000_000_000_000_u64)), - validator: None, - }, - ])); + builder + .run_genesis(create_run_genesis_request(DEFAULT_ACCOUNTS.to_vec())) + .commit(); let install_request_1 = ExecuteRequestBuilder::standard(*DEFAULT_ACCOUNT_ADDR, CEP18_CONTRACT_WASM, install_args) @@ -116,6 +100,31 @@ pub(crate) fn setup_with_args(install_args: RuntimeArgs) -> (LmdbWasmTestBuilder (builder, test_context) } +pub(crate) fn get_test_account(ending_string_index: &str) -> (Key, AccountHash, PublicKey) { + let index = ending_string_index + .chars() + .next_back() + .unwrap() + .to_digit(10) + .unwrap_or_default() as usize; + + let accounts = if let Some(account) = DEFAULT_ACCOUNTS.clone().get(index) { + let public_key = account.public_key().clone(); + let account_hash = public_key.to_account_hash(); + let entity_addr = Key::AddressableEntity(EntityAddr::Account(account_hash.value())); + Some((entity_addr, account_hash, public_key)) + } else { + None + }; + + match accounts { + Some(account) => account, + None => { + panic!("No account found for index {}", index); + } + } +} + pub(crate) fn cep18_check_total_supply( builder: &mut LmdbWasmTestBuilder, cep18_contract_hash: &AddressableEntityHash, From 4cfc9465fecd8e46fc87370da742edabc45fe9ab Mon Sep 17 00:00:00 2001 From: Deuszex Date: Wed, 3 Jul 2024 15:02:53 +0200 Subject: [PATCH 17/27] disallow Key::AddressableEntity::SmartContract in favour of Key::Package, should have been this way to begin with --- cep18/src/utils.rs | 2 +- tests/src/allowance.rs | 23 +++---- tests/src/transfer.rs | 26 ++++---- .../src/utility/installer_request_builders.rs | 62 +++++++++---------- 4 files changed, 54 insertions(+), 59 deletions(-) diff --git a/cep18/src/utils.rs b/cep18/src/utils.rs index 1a00c8d..2c1b755 100644 --- a/cep18/src/utils.rs +++ b/cep18/src/utils.rs @@ -55,7 +55,7 @@ fn call_stack_element_to_address(call_stack_element: Caller) -> Key { Key::AddressableEntity(EntityAddr::Account(account_hash.value())) } Caller::Entity { package_hash, .. } => { - Key::AddressableEntity(EntityAddr::SmartContract(package_hash.value())) + Key::Package(package_hash.value()) } } } diff --git a/tests/src/allowance.rs b/tests/src/allowance.rs index 107254b..819339b 100644 --- a/tests/src/allowance.rs +++ b/tests/src/allowance.rs @@ -1,6 +1,5 @@ use casper_engine_test_support::{ExecuteRequestBuilder, DEFAULT_ACCOUNT_ADDR}; -use casper_types::{ - addressable_entity::EntityKindTag, runtime_args, AddressableEntityHash, ApiError, EntityAddr, +use casper_types::{runtime_args, AddressableEntityHash, ApiError, EntityAddr, Key, U256, }; @@ -28,13 +27,9 @@ fn should_approve_funds_contract_to_account() { test_approve_for( &mut builder, &test_context, - Key::addressable_entity_key( - EntityKindTag::SmartContract, - AddressableEntityHash::new(cep18_test_contract_package.value()), + Key::Package(cep18_test_contract_package.value(), ), - Key::addressable_entity_key( - EntityKindTag::SmartContract, - AddressableEntityHash::new(cep18_test_contract_package.value()), + Key::Package(cep18_test_contract_package.value(), ), Key::AddressableEntity(EntityAddr::Account(DEFAULT_ACCOUNT_ADDR.value())), ); @@ -51,13 +46,13 @@ fn should_approve_funds_contract_to_contract() { test_approve_for( &mut builder, &test_context, - Key::AddressableEntity(EntityAddr::SmartContract( + Key::Package( cep18_test_contract_package.value(), - )), - Key::AddressableEntity(EntityAddr::SmartContract( + ), + Key::Package( cep18_test_contract_package.value(), - )), - Key::AddressableEntity(EntityAddr::SmartContract([42; 32])), + ), + Key::Package([42; 32]), ); } @@ -88,7 +83,7 @@ fn should_approve_funds_account_to_contract() { &test_context, default_account_user_key, default_account_user_key, - Key::AddressableEntity(EntityAddr::SmartContract([42; 32])), + Key::Package([42; 32]), ); } diff --git a/tests/src/transfer.rs b/tests/src/transfer.rs index 9abcdc7..5bd7d28 100644 --- a/tests/src/transfer.rs +++ b/tests/src/transfer.rs @@ -253,9 +253,9 @@ fn should_transfer_from_account_by_contract() { let owner = *DEFAULT_ACCOUNT_ADDR; - let spender = Key::AddressableEntity(EntityAddr::SmartContract( + let spender = Key::Package( cep18_test_contract_package.value(), - )); + ); let recipient = account_user_1_key; let cep18_approve_args = runtime_args! { @@ -541,13 +541,13 @@ fn should_transfer_contract_to_contract() { let (default_account_user_key, _, _) = get_test_account("ACCOUNT_USER_0"); let sender1 = default_account_user_key; - let recipient1 = Key::AddressableEntity(EntityAddr::SmartContract( + let recipient1 = Key::Package( cep18_test_contract_package.value(), - )); - let sender2 = Key::AddressableEntity(EntityAddr::SmartContract( + ); + let sender2 = Key::Package( cep18_test_contract_package.value(), - )); - let recipient2 = Key::AddressableEntity(EntityAddr::SmartContract([42; 32])); + ); + let recipient2 = Key::Package([42; 32]); test_cep18_transfer( &mut builder, @@ -571,13 +571,13 @@ fn should_transfer_contract_to_account() { let (account_user_1_key, _, _) = get_test_account("ACCOUNT_USER_1"); let sender1 = default_account_user_key; - let recipient1 = Key::AddressableEntity(EntityAddr::SmartContract( + let recipient1 = Key::Package( cep18_test_contract_package.value(), - )); + ); - let sender2 = Key::AddressableEntity(EntityAddr::SmartContract( + let sender2 = Key::Package( cep18_test_contract_package.value(), - )); + ); let recipient2 = account_user_1_key; test_cep18_transfer( @@ -600,9 +600,9 @@ fn should_transfer_account_to_contract() { let sender1 = default_account_user_key; let recipient1 = account_user_1_key; let sender2 = account_user_1_key; - let recipient2 = Key::AddressableEntity(EntityAddr::SmartContract( + let recipient2 = Key::Package( test_context.cep18_test_contract_package.value(), - )); + ); test_cep18_transfer( &mut builder, diff --git a/tests/src/utility/installer_request_builders.rs b/tests/src/utility/installer_request_builders.rs index 9e36b84..e40dbf5 100644 --- a/tests/src/utility/installer_request_builders.rs +++ b/tests/src/utility/installer_request_builders.rs @@ -30,13 +30,11 @@ pub(crate) fn invert_cep18_address(address: Key) -> Key { Key::Hash(contract_hash) => Key::Account(AccountHash::new(contract_hash)), Key::AddressableEntity(entity_addr) => match entity_addr { EntityAddr::System(_) => panic!("Unsupported Key variant"), - EntityAddr::Account(account) => { - Key::AddressableEntity(EntityAddr::SmartContract(account)) - } - EntityAddr::SmartContract(contract) => { - Key::AddressableEntity(EntityAddr::Account(contract)) - } + EntityAddr::Account(account) => + Key::Package(account), + EntityAddr::SmartContract(_) => panic!("Unsupported Key variant") }, + Key::Package(contract_package_hash) => Key::AddressableEntity(EntityAddr::Account(contract_package_hash)), _ => panic!("Unsupported Key variant"), } } @@ -350,20 +348,21 @@ pub(crate) fn make_cep18_transfer_request( }, ) .build(), - EntityAddr::SmartContract(contract_hash) => ExecuteRequestBuilder::versioned_contract_call_by_hash( - *DEFAULT_ACCOUNT_ADDR, - PackageHash::new(contract_hash), - None, - METHOD_TRANSFER_AS_STORED_CONTRACT, - runtime_args! { - ARG_TOKEN_CONTRACT => Key::addressable_entity_key(EntityKindTag::SmartContract, *cep18_contract_hash), - ARG_AMOUNT => amount, - ARG_RECIPIENT => recipient, - } - ) - .build(), + EntityAddr::SmartContract(_contract_hash) => panic!("invalid variant"), } } + Key::Package(package_hash) => ExecuteRequestBuilder::versioned_contract_call_by_hash( + *DEFAULT_ACCOUNT_ADDR, + PackageHash::new(package_hash), + None, + METHOD_TRANSFER_AS_STORED_CONTRACT, + runtime_args! { + ARG_TOKEN_CONTRACT => Key::addressable_entity_key(EntityKindTag::SmartContract, *cep18_contract_hash), + ARG_AMOUNT => amount, + ARG_RECIPIENT => recipient, + } + ) + .build(), _ => panic!("Unknown variant"), } } @@ -410,20 +409,21 @@ pub(crate) fn make_cep18_approve_request( }, ) .build(), - EntityAddr::SmartContract(contract_hash) => ExecuteRequestBuilder::versioned_contract_call_by_hash( - *DEFAULT_ACCOUNT_ADDR, - PackageHash::new(contract_hash), - None, - METHOD_APPROVE_AS_STORED_CONTRACT, - runtime_args! { - ARG_TOKEN_CONTRACT => Key::addressable_entity_key(EntityKindTag::SmartContract, *cep18_contract_hash), - ARG_SPENDER => spender, - ARG_AMOUNT => amount, - }, - ) - .build(), + EntityAddr::SmartContract(_contract_hash) => panic!("Invalid variant") } - } + }, + Key::Package(contract_package_hash) => ExecuteRequestBuilder::versioned_contract_call_by_hash( + *DEFAULT_ACCOUNT_ADDR, + PackageHash::new(contract_package_hash), + None, + METHOD_APPROVE_AS_STORED_CONTRACT, + runtime_args! { + ARG_TOKEN_CONTRACT => Key::addressable_entity_key(EntityKindTag::SmartContract, *cep18_contract_hash), + ARG_SPENDER => spender, + ARG_AMOUNT => amount, + }, + ) + .build(), _ => panic!("Unknown variant"), } } From ccf239a8f225747147b2898645744b88ae263a59 Mon Sep 17 00:00:00 2001 From: Deuszex Date: Wed, 3 Jul 2024 16:26:04 +0200 Subject: [PATCH 18/27] more event tests --- cep18/src/events.rs | 6 +- cep18/src/main.rs | 4 +- cep18/src/utils.rs | 4 +- tests/src/allowance.rs | 18 +- tests/src/events.rs | 203 +++++++++++++++++- tests/src/install.rs | 15 +- tests/src/transfer.rs | 24 +-- .../src/utility/installer_request_builders.rs | 9 +- tests/src/utility/mod.rs | 1 + tests/src/utility/support.rs | 56 +++++ 10 files changed, 287 insertions(+), 53 deletions(-) create mode 100644 tests/src/utility/support.rs diff --git a/cep18/src/events.rs b/cep18/src/events.rs index 3b30f5e..180b42b 100644 --- a/cep18/src/events.rs +++ b/cep18/src/events.rs @@ -42,7 +42,7 @@ pub enum Event { ChangeSecurity(ChangeSecurity), BalanceMigration(BalanceMigration), AllowanceMigration(AllowanceMigration), - ChangeEventsMode(ChangeEventsMode) + ChangeEventsMode(ChangeEventsMode), } #[derive(Event, Debug, PartialEq, Eq)] @@ -145,7 +145,9 @@ pub fn init_events() { let events_mode: EventsMode = EventsMode::try_from(read_from::(EVENTS_MODE)) .unwrap_or_revert_with(Cep18Error::InvalidEventsMode); - if [EventsMode::CES, EventsMode::NativeNCES].contains(&events_mode) { + if [EventsMode::CES, EventsMode::NativeNCES].contains(&events_mode) + && runtime::get_key(casper_event_standard::EVENTS_DICT).is_none() + { let schemas = Schemas::new() .with::() .with::() diff --git a/cep18/src/main.rs b/cep18/src/main.rs index 78bff26..dbff06d 100644 --- a/cep18/src/main.rs +++ b/cep18/src/main.rs @@ -331,7 +331,7 @@ pub extern "C" fn init() { let minter_list: Option> = utils::get_optional_named_arg_with_user_errors(MINTER_LIST, Cep18Error::InvalidMinterList); - let events_mode: EventsMode = EventsMode::try_from(read_from::(EVENTS_MODE)) + let events_mode: EventsMode = EventsMode::try_from(get_named_arg::(EVENTS_MODE)) .unwrap_or_revert_with(Cep18Error::InvalidEventsMode); if [EventsMode::CES, EventsMode::NativeNCES].contains(&events_mode) { @@ -864,7 +864,7 @@ pub fn install_contract(name: &str) { storage::new_uref(contract_version).into(), ); // Call contract to initialize it - let mut init_args = runtime_args! {TOTAL_SUPPLY => total_supply, PACKAGE_HASH => package_hash, CONTRACT_HASH => contract_hash_key}; + let mut init_args = runtime_args! {TOTAL_SUPPLY => total_supply, PACKAGE_HASH => package_hash, CONTRACT_HASH => contract_hash_key, EVENTS_MODE => events_mode}; if let Some(admin_list) = admin_list { init_args diff --git a/cep18/src/utils.rs b/cep18/src/utils.rs index 2c1b755..a608352 100644 --- a/cep18/src/utils.rs +++ b/cep18/src/utils.rs @@ -54,9 +54,7 @@ fn call_stack_element_to_address(call_stack_element: Caller) -> Key { Caller::Initiator { account_hash } => { Key::AddressableEntity(EntityAddr::Account(account_hash.value())) } - Caller::Entity { package_hash, .. } => { - Key::Package(package_hash.value()) - } + Caller::Entity { package_hash, .. } => Key::Package(package_hash.value()), } } diff --git a/tests/src/allowance.rs b/tests/src/allowance.rs index 819339b..4982ab4 100644 --- a/tests/src/allowance.rs +++ b/tests/src/allowance.rs @@ -1,7 +1,5 @@ use casper_engine_test_support::{ExecuteRequestBuilder, DEFAULT_ACCOUNT_ADDR}; -use casper_types::{runtime_args, AddressableEntityHash, ApiError, EntityAddr, - Key, U256, -}; +use casper_types::{runtime_args, AddressableEntityHash, ApiError, EntityAddr, Key, U256}; use crate::utility::{ constants::{ @@ -27,10 +25,8 @@ fn should_approve_funds_contract_to_account() { test_approve_for( &mut builder, &test_context, - Key::Package(cep18_test_contract_package.value(), - ), - Key::Package(cep18_test_contract_package.value(), - ), + Key::Package(cep18_test_contract_package.value()), + Key::Package(cep18_test_contract_package.value()), Key::AddressableEntity(EntityAddr::Account(DEFAULT_ACCOUNT_ADDR.value())), ); } @@ -46,12 +42,8 @@ fn should_approve_funds_contract_to_contract() { test_approve_for( &mut builder, &test_context, - Key::Package( - cep18_test_contract_package.value(), - ), - Key::Package( - cep18_test_contract_package.value(), - ), + Key::Package(cep18_test_contract_package.value()), + Key::Package(cep18_test_contract_package.value()), Key::Package([42; 32]), ); } diff --git a/tests/src/events.rs b/tests/src/events.rs index 49ee250..0c14162 100644 --- a/tests/src/events.rs +++ b/tests/src/events.rs @@ -1,19 +1,55 @@ -use std::collections::BTreeMap; - use casper_engine_test_support::{ExecuteRequestBuilder, DEFAULT_ACCOUNT_ADDR}; -use casper_types::{contract_messages::Message, runtime_args, AddressableEntityHash, Key, U256}; +use casper_types::{ + addressable_entity::EntityKindTag, bytesrepr::Bytes, contract_messages::Message, runtime_args, + AddressableEntityHash, Key, U256, +}; use crate::utility::{ constants::{ AMOUNT, ARG_DECIMALS, ARG_NAME, ARG_SYMBOL, ARG_TOTAL_SUPPLY, CEP18_TOKEN_CONTRACT_KEY, ENABLE_MINT_BURN, EVENTS, EVENTS_MODE, METHOD_MINT, OWNER, TOKEN_DECIMALS, TOKEN_NAME, - TOKEN_OWNER_ADDRESS_1, TOKEN_OWNER_ADDRESS_1_OLD, TOKEN_OWNER_AMOUNT_1, TOKEN_SYMBOL, - TOKEN_TOTAL_SUPPLY, + TOKEN_OWNER_ADDRESS_1, TOKEN_OWNER_AMOUNT_1, TOKEN_SYMBOL, TOKEN_TOTAL_SUPPLY, }, installer_request_builders::{setup_with_args, TestContext}, message_handlers::{entity, message_topic}, + support::get_dictionary_value_from_key, }; +#[test] +fn should_have_have_no_events() { + let ( + mut builder, + TestContext { + cep18_contract_hash, + .. + }, + ) = setup_with_args(runtime_args! { + ARG_NAME => TOKEN_NAME, + ARG_SYMBOL => TOKEN_SYMBOL, + ARG_DECIMALS => TOKEN_DECIMALS, + ARG_TOTAL_SUPPLY => U256::from(TOKEN_TOTAL_SUPPLY), + EVENTS_MODE => 2_u8, + ENABLE_MINT_BURN => true, + }); + + let addressable_cep18_token = AddressableEntityHash::new(cep18_contract_hash.value()); + let mint_request = ExecuteRequestBuilder::contract_call_by_hash( + *DEFAULT_ACCOUNT_ADDR, + addressable_cep18_token, + METHOD_MINT, + runtime_args! {OWNER => TOKEN_OWNER_ADDRESS_1, AMOUNT => U256::from(TOKEN_OWNER_AMOUNT_1)}, + ) + .build(); + builder.exec(mint_request).expect_success().commit(); + + let entity_with_named_keys = builder + .get_entity_with_named_keys_by_entity_hash(addressable_cep18_token) + .expect("should be named key from entity hash"); + let named_keys = entity_with_named_keys.named_keys(); + assert!(named_keys.get("__events").is_none()); + assert!(named_keys.get("events").is_none()); +} + #[test] fn should_have_native_events() { let ( @@ -60,9 +96,145 @@ fn should_have_native_events() { .next() .expect("should have at least one topic"); - let mut user_map: BTreeMap = BTreeMap::new(); - user_map.insert(Key::Account(*DEFAULT_ACCOUNT_ADDR), true); - user_map.insert(TOKEN_OWNER_ADDRESS_1_OLD, true); + assert_eq!(topic_name, &EVENTS.to_string()); + + let mint_request = ExecuteRequestBuilder::contract_call_by_hash( + *DEFAULT_ACCOUNT_ADDR, + cep18_token, + METHOD_MINT, + runtime_args! {OWNER => TOKEN_OWNER_ADDRESS_1, AMOUNT => U256::from(TOKEN_OWNER_AMOUNT_1)}, + ) + .build(); + + builder.exec(mint_request).expect_success().commit(); + + assert_eq!( + message_topic(&builder, &cep18_token, *message_topic_hash).message_count(), + 3 + ); + + let exec_result = builder.get_exec_result_owned(3).unwrap(); + let messages = exec_result.messages(); + let mint_message = "Mint(Mint { recipient: Key::AddressableEntity(account-2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a), amount: 1000000 })"; + let message = Message::new( + casper_types::EntityAddr::SmartContract(cep18_token.value()), + mint_message.into(), + EVENTS.to_string(), + *message_topic_hash, + 2, + 2, + ); + assert_eq!(messages, &vec![message]); +} + +#[test] +fn should_have_ces_events() { + let ( + mut builder, + TestContext { + cep18_contract_hash, + .. + }, + ) = setup_with_args(runtime_args! { + ARG_NAME => TOKEN_NAME, + ARG_SYMBOL => TOKEN_SYMBOL, + ARG_DECIMALS => TOKEN_DECIMALS, + ARG_TOTAL_SUPPLY => U256::from(TOKEN_TOTAL_SUPPLY), + EVENTS_MODE => 1_u8, + ENABLE_MINT_BURN => true, + }); + + let addressable_cep18_token = AddressableEntityHash::new(cep18_contract_hash.value()); + let mint_request = ExecuteRequestBuilder::contract_call_by_hash( + *DEFAULT_ACCOUNT_ADDR, + addressable_cep18_token, + METHOD_MINT, + runtime_args! {OWNER => TOKEN_OWNER_ADDRESS_1, AMOUNT => U256::from(TOKEN_OWNER_AMOUNT_1)}, + ) + .build(); + builder.exec(mint_request).expect_success().commit(); + + let account = builder + .get_entity_with_named_keys_by_account_hash(*DEFAULT_ACCOUNT_ADDR) + .unwrap(); + let account_named_keys = account.named_keys(); + + let cep18_token = account_named_keys + .get(CEP18_TOKEN_CONTRACT_KEY) + .and_then(|key| key.into_entity_hash()) + .expect("should have contract hash"); + + let mint_request = ExecuteRequestBuilder::contract_call_by_hash( + *DEFAULT_ACCOUNT_ADDR, + cep18_token, + METHOD_MINT, + runtime_args! {OWNER => TOKEN_OWNER_ADDRESS_1, AMOUNT => U256::from(TOKEN_OWNER_AMOUNT_1)}, + ) + .build(); + + builder.exec(mint_request).expect_success().commit(); + + let stored_event: Bytes = get_dictionary_value_from_key( + &builder, + &Key::addressable_entity_key(EntityKindTag::SmartContract, addressable_cep18_token), + "__events", + "1", + ); + assert_eq!( + stored_event, + Bytes::from(vec![ + 10, 0, 0, 0, 101, 118, 101, 110, 116, 95, 77, 105, 110, 116, 17, 1, 42, 42, 42, 42, 42, + 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, + 42, 42, 42, 42, 42, 3, 64, 66, 15 + ]) + ) +} + +#[test] +fn should_have_both_native_and_ces_events() { + let ( + mut builder, + TestContext { + cep18_contract_hash, + .. + }, + ) = setup_with_args(runtime_args! { + ARG_NAME => TOKEN_NAME, + ARG_SYMBOL => TOKEN_SYMBOL, + ARG_DECIMALS => TOKEN_DECIMALS, + ARG_TOTAL_SUPPLY => U256::from(TOKEN_TOTAL_SUPPLY), + EVENTS_MODE => 3_u8, + ENABLE_MINT_BURN => true, + }); + + let addressable_cep18_token = AddressableEntityHash::new(cep18_contract_hash.value()); + let mint_request = ExecuteRequestBuilder::contract_call_by_hash( + *DEFAULT_ACCOUNT_ADDR, + addressable_cep18_token, + METHOD_MINT, + runtime_args! {OWNER => TOKEN_OWNER_ADDRESS_1, AMOUNT => U256::from(TOKEN_OWNER_AMOUNT_1)}, + ) + .build(); + builder.exec(mint_request).expect_success().commit(); + + let account = builder + .get_entity_with_named_keys_by_account_hash(*DEFAULT_ACCOUNT_ADDR) + .unwrap(); + let account_named_keys = account.named_keys(); + + let cep18_token = account_named_keys + .get(CEP18_TOKEN_CONTRACT_KEY) + .and_then(|key| key.into_entity_hash()) + .expect("should have contract hash"); + + // events check + let entity = entity(&builder, &cep18_token); + + let (topic_name, message_topic_hash) = entity + .message_topics() + .iter() + .next() + .expect("should have at least one topic"); assert_eq!(topic_name, &EVENTS.to_string()); @@ -93,4 +265,19 @@ fn should_have_native_events() { 2, ); assert_eq!(messages, &vec![message]); + + let stored_event: Bytes = get_dictionary_value_from_key( + &builder, + &Key::addressable_entity_key(EntityKindTag::SmartContract, addressable_cep18_token), + "__events", + "1", + ); + assert_eq!( + stored_event, + Bytes::from(vec![ + 10, 0, 0, 0, 101, 118, 101, 110, 116, 95, 77, 105, 110, 116, 17, 1, 42, 42, 42, 42, 42, + 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, + 42, 42, 42, 42, 42, 3, 64, 66, 15 + ]) + ) } diff --git a/tests/src/install.rs b/tests/src/install.rs index 9ec5719..0c39a91 100644 --- a/tests/src/install.rs +++ b/tests/src/install.rs @@ -13,7 +13,13 @@ use crate::utility::{ #[test] fn should_have_queryable_properties() { - let (mut builder, TestContext { cep18_contract_hash, .. }) = setup(); + let ( + mut builder, + TestContext { + cep18_contract_hash, + .. + }, + ) = setup(); let cep18_entity_addr = EntityAddr::new_smart_contract(cep18_contract_hash.value()); @@ -34,8 +40,11 @@ fn should_have_queryable_properties() { let owner_balance = cep18_check_balance_of(&mut builder, &cep18_contract_hash, owner_key); assert_eq!(owner_balance, total_supply); - let contract_balance = - cep18_check_balance_of(&mut builder, &cep18_contract_hash, Key::Hash(cep18_contract_hash.value())); + let contract_balance = cep18_check_balance_of( + &mut builder, + &cep18_contract_hash, + Key::Hash(cep18_contract_hash.value()), + ); assert_eq!(contract_balance, U256::zero()); // Ensures that Account and Contract ownership is respected and we're not keying ownership under diff --git a/tests/src/transfer.rs b/tests/src/transfer.rs index 5bd7d28..0c073ed 100644 --- a/tests/src/transfer.rs +++ b/tests/src/transfer.rs @@ -253,9 +253,7 @@ fn should_transfer_from_account_by_contract() { let owner = *DEFAULT_ACCOUNT_ADDR; - let spender = Key::Package( - cep18_test_contract_package.value(), - ); + let spender = Key::Package(cep18_test_contract_package.value()); let recipient = account_user_1_key; let cep18_approve_args = runtime_args! { @@ -541,12 +539,8 @@ fn should_transfer_contract_to_contract() { let (default_account_user_key, _, _) = get_test_account("ACCOUNT_USER_0"); let sender1 = default_account_user_key; - let recipient1 = Key::Package( - cep18_test_contract_package.value(), - ); - let sender2 = Key::Package( - cep18_test_contract_package.value(), - ); + let recipient1 = Key::Package(cep18_test_contract_package.value()); + let sender2 = Key::Package(cep18_test_contract_package.value()); let recipient2 = Key::Package([42; 32]); test_cep18_transfer( @@ -571,13 +565,9 @@ fn should_transfer_contract_to_account() { let (account_user_1_key, _, _) = get_test_account("ACCOUNT_USER_1"); let sender1 = default_account_user_key; - let recipient1 = Key::Package( - cep18_test_contract_package.value(), - ); + let recipient1 = Key::Package(cep18_test_contract_package.value()); - let sender2 = Key::Package( - cep18_test_contract_package.value(), - ); + let sender2 = Key::Package(cep18_test_contract_package.value()); let recipient2 = account_user_1_key; test_cep18_transfer( @@ -600,9 +590,7 @@ fn should_transfer_account_to_contract() { let sender1 = default_account_user_key; let recipient1 = account_user_1_key; let sender2 = account_user_1_key; - let recipient2 = Key::Package( - test_context.cep18_test_contract_package.value(), - ); + let recipient2 = Key::Package(test_context.cep18_test_contract_package.value()); test_cep18_transfer( &mut builder, diff --git a/tests/src/utility/installer_request_builders.rs b/tests/src/utility/installer_request_builders.rs index e40dbf5..52e11af 100644 --- a/tests/src/utility/installer_request_builders.rs +++ b/tests/src/utility/installer_request_builders.rs @@ -30,11 +30,12 @@ pub(crate) fn invert_cep18_address(address: Key) -> Key { Key::Hash(contract_hash) => Key::Account(AccountHash::new(contract_hash)), Key::AddressableEntity(entity_addr) => match entity_addr { EntityAddr::System(_) => panic!("Unsupported Key variant"), - EntityAddr::Account(account) => - Key::Package(account), - EntityAddr::SmartContract(_) => panic!("Unsupported Key variant") + EntityAddr::Account(account) => Key::Package(account), + EntityAddr::SmartContract(_) => panic!("Unsupported Key variant"), }, - Key::Package(contract_package_hash) => Key::AddressableEntity(EntityAddr::Account(contract_package_hash)), + Key::Package(contract_package_hash) => { + Key::AddressableEntity(EntityAddr::Account(contract_package_hash)) + } _ => panic!("Unsupported Key variant"), } } diff --git a/tests/src/utility/mod.rs b/tests/src/utility/mod.rs index 84726d2..e4cbe99 100644 --- a/tests/src/utility/mod.rs +++ b/tests/src/utility/mod.rs @@ -1,3 +1,4 @@ pub mod constants; pub mod installer_request_builders; pub mod message_handlers; +pub mod support; diff --git a/tests/src/utility/support.rs b/tests/src/utility/support.rs new file mode 100644 index 0000000..8bfa458 --- /dev/null +++ b/tests/src/utility/support.rs @@ -0,0 +1,56 @@ +use casper_engine_test_support::LmdbWasmTestBuilder; +use casper_types::{bytesrepr::FromBytes, CLTyped, EntityAddr, Key}; + +pub(crate) fn get_dictionary_value_from_key( + builder: &LmdbWasmTestBuilder, + contract_key: &Key, + dictionary_name: &str, + dictionary_key: &str, +) -> T { + let named_key = match contract_key.into_entity_hash() { + Some(hash) => { + let entity_with_named_keys = builder + .get_entity_with_named_keys_by_entity_hash(hash) + .expect("should be named key from entity hash"); + let named_keys = entity_with_named_keys.named_keys(); + named_keys + .get(dictionary_name) + .expect("must have key") + .to_owned() + } + None => match contract_key.into_account() { + Some(account_hash) => { + let entity_with_named_keys = builder + .get_entity_with_named_keys_by_account_hash(account_hash) + .expect("should be named key from account hash"); + let named_keys = entity_with_named_keys.named_keys(); + named_keys + .get(dictionary_name) + .expect("must have key") + .to_owned() + } + None => { + let named_keys = builder.get_named_keys(EntityAddr::SmartContract( + contract_key + .into_hash_addr() + .expect("should be entity addr"), + )); + named_keys + .get(dictionary_name) + .expect("must have key") + .to_owned() + } + }, + }; + + let seed_uref = named_key.as_uref().expect("must convert to seed uref"); + + builder + .query_dictionary_item(None, *seed_uref, dictionary_key) + .expect("should have dictionary value") + .as_cl_value() + .expect("T should be CLValue") + .to_owned() + .into_t() + .unwrap() +} From a537033270c001023bf26a82ba74b9487366bb0b Mon Sep 17 00:00:00 2001 From: Deuszex Date: Wed, 3 Jul 2024 16:40:18 +0200 Subject: [PATCH 19/27] fix fake positive event test --- tests/src/events.rs | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/tests/src/events.rs b/tests/src/events.rs index 0c14162..79a4f71 100644 --- a/tests/src/events.rs +++ b/tests/src/events.rs @@ -28,7 +28,7 @@ fn should_have_have_no_events() { ARG_SYMBOL => TOKEN_SYMBOL, ARG_DECIMALS => TOKEN_DECIMALS, ARG_TOTAL_SUPPLY => U256::from(TOKEN_TOTAL_SUPPLY), - EVENTS_MODE => 2_u8, + EVENTS_MODE => 0_u8, ENABLE_MINT_BURN => true, }); @@ -42,12 +42,11 @@ fn should_have_have_no_events() { .build(); builder.exec(mint_request).expect_success().commit(); - let entity_with_named_keys = builder - .get_entity_with_named_keys_by_entity_hash(addressable_cep18_token) - .expect("should be named key from entity hash"); - let named_keys = entity_with_named_keys.named_keys(); - assert!(named_keys.get("__events").is_none()); - assert!(named_keys.get("events").is_none()); + let entity_with_named_keys = builder.get_named_keys(casper_types::EntityAddr::SmartContract(addressable_cep18_token.value())); + assert!(entity_with_named_keys.get("__events").is_none()); + let entity = entity(&builder, &addressable_cep18_token); + assert!(entity.message_topics().is_empty()); + } #[test] From 06f68a6e2436035ccdc4e16baf392298e49a48e5 Mon Sep 17 00:00:00 2001 From: Deuszex Date: Wed, 3 Jul 2024 17:23:39 +0200 Subject: [PATCH 20/27] remove unused dictinary location check --- tests/src/events.rs | 5 +++-- tests/src/utility/support.rs | 19 ++++--------------- 2 files changed, 7 insertions(+), 17 deletions(-) diff --git a/tests/src/events.rs b/tests/src/events.rs index 79a4f71..640afcc 100644 --- a/tests/src/events.rs +++ b/tests/src/events.rs @@ -42,11 +42,12 @@ fn should_have_have_no_events() { .build(); builder.exec(mint_request).expect_success().commit(); - let entity_with_named_keys = builder.get_named_keys(casper_types::EntityAddr::SmartContract(addressable_cep18_token.value())); + let entity_with_named_keys = builder.get_named_keys(casper_types::EntityAddr::SmartContract( + addressable_cep18_token.value(), + )); assert!(entity_with_named_keys.get("__events").is_none()); let entity = entity(&builder, &addressable_cep18_token); assert!(entity.message_topics().is_empty()); - } #[test] diff --git a/tests/src/utility/support.rs b/tests/src/utility/support.rs index 8bfa458..21c0f55 100644 --- a/tests/src/utility/support.rs +++ b/tests/src/utility/support.rs @@ -18,27 +18,16 @@ pub(crate) fn get_dictionary_value_from_key( .expect("must have key") .to_owned() } - None => match contract_key.into_account() { - Some(account_hash) => { - let entity_with_named_keys = builder - .get_entity_with_named_keys_by_account_hash(account_hash) - .expect("should be named key from account hash"); - let named_keys = entity_with_named_keys.named_keys(); + None => match contract_key.into_hash_addr() { + Some(contract_key) => { + let named_keys = builder.get_named_keys(EntityAddr::SmartContract(contract_key)); named_keys .get(dictionary_name) .expect("must have key") .to_owned() } None => { - let named_keys = builder.get_named_keys(EntityAddr::SmartContract( - contract_key - .into_hash_addr() - .expect("should be entity addr"), - )); - named_keys - .get(dictionary_name) - .expect("must have key") - .to_owned() + panic!("unsupported dictionary location") } }, }; From b92a2f5ba2c3c9963fd3223db9eaf376e0b12288 Mon Sep 17 00:00:00 2001 From: Deuszex Date: Wed, 3 Jul 2024 17:53:57 +0200 Subject: [PATCH 21/27] failing test --- cep18/src/main.rs | 4 ++-- tests/src/install.rs | 47 ++++++++++++++++++++++++++++++++++++++++---- 2 files changed, 45 insertions(+), 6 deletions(-) diff --git a/cep18/src/main.rs b/cep18/src/main.rs index dbff06d..9eaadaf 100644 --- a/cep18/src/main.rs +++ b/cep18/src/main.rs @@ -767,12 +767,12 @@ pub fn upgrade(name: &str) { storage::disable_contract_version(contract_package_hash, converted_previous_contract_hash) .unwrap_or_revert_with(Cep18Error::FailedToDisableContractVersion); - if let Some(events_mode) = events_mode { + if let Some(events_mode_u8) = events_mode { call_contract::<()>( contract_hash, CHANGE_EVENTS_MODE_ENTRY_POINT_NAME, runtime_args! { - EVENTS_MODE => events_mode + EVENTS_MODE => events_mode_u8 }, ); } diff --git a/tests/src/install.rs b/tests/src/install.rs index 0c39a91..bf3d74a 100644 --- a/tests/src/install.rs +++ b/tests/src/install.rs @@ -1,10 +1,15 @@ -use casper_engine_test_support::DEFAULT_ACCOUNT_ADDR; -use casper_types::{EntityAddr, Key, U256}; +use casper_engine_test_support::{ + utils::create_run_genesis_request, ExecuteRequestBuilder, LmdbWasmTestBuilder, + DEFAULT_ACCOUNTS, DEFAULT_ACCOUNT_ADDR, +}; +use casper_execution_engine::{engine_state::Error as CoreError, execution::ExecError}; +use casper_types::{runtime_args, ApiError, EntityAddr, Key, U256}; use crate::utility::{ constants::{ - ALLOWANCES_KEY, BALANCES_KEY, DECIMALS_KEY, NAME_KEY, SYMBOL_KEY, TOKEN_DECIMALS, - TOKEN_NAME, TOKEN_SYMBOL, TOKEN_TOTAL_SUPPLY, TOTAL_SUPPLY_KEY, + ALLOWANCES_KEY, ARG_DECIMALS, ARG_NAME, ARG_SYMBOL, ARG_TOTAL_SUPPLY, BALANCES_KEY, + CEP18_CONTRACT_WASM, DECIMALS_KEY, ENABLE_MINT_BURN, EVENTS_MODE, NAME_KEY, SYMBOL_KEY, + TOKEN_DECIMALS, TOKEN_NAME, TOKEN_SYMBOL, TOKEN_TOTAL_SUPPLY, TOTAL_SUPPLY_KEY, }, installer_request_builders::{ cep18_check_balance_of, invert_cep18_address, setup, TestContext, @@ -64,3 +69,37 @@ fn should_not_store_balances_or_allowances_under_account_after_install() { assert!(!named_keys.contains(BALANCES_KEY), "{:?}", named_keys); assert!(!named_keys.contains(ALLOWANCES_KEY), "{:?}", named_keys); } + +#[test] +fn should_fail_with_left_over_bytes_converted_into_60006() { + let mut builder = LmdbWasmTestBuilder::default(); + builder + .run_genesis(create_run_genesis_request(DEFAULT_ACCOUNTS.to_vec())) + .commit(); + + let install_request_1 = ExecuteRequestBuilder::standard( + *DEFAULT_ACCOUNT_ADDR, + CEP18_CONTRACT_WASM, + runtime_args! { + ARG_NAME => TOKEN_NAME, + ARG_SYMBOL => TOKEN_SYMBOL, + ARG_DECIMALS => TOKEN_DECIMALS, + ARG_TOTAL_SUPPLY => U256::from(TOKEN_TOTAL_SUPPLY), + EVENTS_MODE => Some(0_u8), + ENABLE_MINT_BURN => true, + }, + ) + .build(); + + builder.exec(install_request_1).expect_failure().commit(); + + let error = builder.get_error().expect("should have error"); + assert!( + matches!( + error, + CoreError::Exec(ExecError::Revert(ApiError::User(60006))) + ), + "{:?}", + error + ); +} From 06325d2c3be293aedd9e6473779b919e344c68dd Mon Sep 17 00:00:00 2001 From: Deuszex Date: Wed, 3 Jul 2024 20:15:24 +0200 Subject: [PATCH 22/27] native expansive error topic --- cep18/src/constants.rs | 2 + cep18/src/entry_points.rs | 15 ++++++- cep18/src/main.rs | 90 ++++++++++++++++++++++----------------- cep18/src/utils.rs | 20 ++++++--- tests/src/events.rs | 63 +++++++++++++++++++++++++-- tests/src/migration.rs | 2 +- 6 files changed, 142 insertions(+), 50 deletions(-) diff --git a/cep18/src/constants.rs b/cep18/src/constants.rs index c324a29..1086052 100644 --- a/cep18/src/constants.rs +++ b/cep18/src/constants.rs @@ -14,6 +14,8 @@ pub const ALLOWANCES: &str = "allowances"; pub const TOTAL_SUPPLY: &str = "total_supply"; pub const EVENTS: &str = "events"; pub const REVERT: &str = "revert"; +pub const ERRORS: &str = "errors"; +pub const CONDOR: &str = "condor"; pub const HASH_KEY_NAME_PREFIX: &str = "cep18_contract_package_"; pub const ACCESS_KEY_NAME_PREFIX: &str = "cep18_contract_package_access_"; diff --git a/cep18/src/entry_points.rs b/cep18/src/entry_points.rs index eb0ecc7..0834a24 100644 --- a/cep18/src/entry_points.rs +++ b/cep18/src/entry_points.rs @@ -9,7 +9,7 @@ use casper_types::{ use crate::constants::{ ADDRESS, ALLOWANCE_ENTRY_POINT_NAME, AMOUNT, APPROVE_ENTRY_POINT_NAME, BALANCE_OF_ENTRY_POINT_NAME, BURN_ENTRY_POINT_NAME, CHANGE_EVENTS_MODE_ENTRY_POINT_NAME, - CHANGE_SECURITY_ENTRY_POINT_NAME, DECIMALS_ENTRY_POINT_NAME, + CHANGE_SECURITY_ENTRY_POINT_NAME, CONDOR, DECIMALS_ENTRY_POINT_NAME, DECREASE_ALLOWANCE_ENTRY_POINT_NAME, EVENTS, EVENTS_MODE, INCREASE_ALLOWANCE_ENTRY_POINT_NAME, INIT_ENTRY_POINT_NAME, MIGRATE_USER_ALLOWANCE_KEYS_ENTRY_POINT_NAME, MIGRATE_USER_BALANCE_KEYS_ENTRY_POINT_NAME, MIGRATE_USER_SEC_KEYS_ENTRY_POINT_NAME, @@ -18,6 +18,18 @@ use crate::constants::{ TRANSFER_FROM_ENTRY_POINT_NAME, }; +/// Returns the `condor` entry point. +pub fn condor() -> EntryPoint { + EntryPoint::new( + String::from(CONDOR), + Vec::new(), + String::cl_type(), + EntryPointAccess::Public, + EntryPointType::Called, + casper_types::EntryPointPayment::Caller, + ) +} + /// Returns the `name` entry point. pub fn name() -> EntryPoint { EntryPoint::new( @@ -297,6 +309,7 @@ pub fn generate_entry_points() -> EntryPoints { let mut entry_points = EntryPoints::new(); entry_points.add_entry_point(init()); entry_points.add_entry_point(name()); + entry_points.add_entry_point(condor()); entry_points.add_entry_point(symbol()); entry_points.add_entry_point(decimals()); entry_points.add_entry_point(total_supply()); diff --git a/cep18/src/main.rs b/cep18/src/main.rs index 9eaadaf..142cc55 100644 --- a/cep18/src/main.rs +++ b/cep18/src/main.rs @@ -38,15 +38,15 @@ use casper_types::{ addressable_entity::{EntityKindTag, NamedKeys}, bytesrepr::ToBytes, contract_messages::MessageTopicOperation, - runtime_args, AddressableEntityHash, CLValue, EntityAddr, Key, PackageHash, U256, + runtime_args, AddressableEntityHash, ApiError, CLValue, EntityAddr, Key, PackageHash, U256, }; use constants::{ ACCESS_KEY_NAME_PREFIX, ADDRESS, ADMIN_LIST, ALLOWANCES, AMOUNT, BALANCES, - CHANGE_EVENTS_MODE_ENTRY_POINT_NAME, CONTRACT_HASH, CONTRACT_NAME_PREFIX, - CONTRACT_VERSION_PREFIX, DECIMALS, ENABLE_MINT_BURN, EVENTS, EVENTS_MODE, HASH_KEY_NAME_PREFIX, - INIT_ENTRY_POINT_NAME, MINTER_LIST, NAME, NONE_LIST, OWNER, PACKAGE_HASH, RECIPIENT, REVERT, - SECURITY_BADGES, SPENDER, SYMBOL, TOTAL_SUPPLY, USER_KEY_MAP, + CHANGE_EVENTS_MODE_ENTRY_POINT_NAME, CONDOR, CONTRACT_HASH, CONTRACT_NAME_PREFIX, + CONTRACT_VERSION_PREFIX, DECIMALS, ENABLE_MINT_BURN, ERRORS, EVENTS, EVENTS_MODE, + HASH_KEY_NAME_PREFIX, INIT_ENTRY_POINT_NAME, MINTER_LIST, NAME, NONE_LIST, OWNER, PACKAGE_HASH, + RECIPIENT, REVERT, SECURITY_BADGES, SPENDER, SYMBOL, TOTAL_SUPPLY, USER_KEY_MAP, }; pub use error::Cep18Error; use events::{ @@ -55,10 +55,18 @@ use events::{ }; use modalities::EventsMode; use utils::{ - get_immediate_caller_address, get_total_supply_uref, read_from, read_total_supply_from, - sec_check, write_total_supply_to, SecurityBadge, + get_immediate_caller_address, get_optional_named_arg_with_user_errors, get_total_supply_uref, + read_from, read_total_supply_from, sec_check, write_total_supply_to, SecurityBadge, }; +#[no_mangle] +pub extern "C" fn condor() { + runtime::ret( + CLValue::from_t(utils::read_from::(CONDOR)) + .unwrap_or_revert_with(Cep18Error::FailedToReturnEntryPointResult), + ); +} + #[no_mangle] pub extern "C" fn name() { runtime::ret( @@ -243,15 +251,24 @@ pub extern "C" fn mint() { let total_supply: U256 = read_total_supply_from(total_supply_uref); total_supply .checked_add(amount) - .ok_or(Cep18Error::Overflow) - .unwrap_or_revert_with(Cep18Error::FailedToChangeTotalSupply) + .ok_or(ApiError::None) + .map_err(|_: ApiError| { + runtime::emit_message( + ERRORS, + &"Cannot add to total_supply as it flow over U256::MAX value".into(), + ) + .unwrap_or_revert(); + Cep18Error::Overflow + }) + .unwrap_or_revert() }; write_balance_to(balances_uref, owner, new_balance); write_total_supply_to(total_supply_uref, new_total_supply); + events::record_event_dictionary(Event::Mint(Mint { recipient: owner, amount, - })) + })); } #[no_mangle] @@ -327,9 +344,9 @@ pub extern "C" fn init() { ); let admin_list: Option> = - utils::get_optional_named_arg_with_user_errors(ADMIN_LIST, Cep18Error::InvalidAdminList); + get_optional_named_arg_with_user_errors(ADMIN_LIST, Cep18Error::InvalidAdminList); let minter_list: Option> = - utils::get_optional_named_arg_with_user_errors(MINTER_LIST, Cep18Error::InvalidMinterList); + get_optional_named_arg_with_user_errors(MINTER_LIST, Cep18Error::InvalidMinterList); let events_mode: EventsMode = EventsMode::try_from(get_named_arg::(EVENTS_MODE)) .unwrap_or_revert_with(Cep18Error::InvalidEventsMode); @@ -384,11 +401,11 @@ pub extern "C" fn change_security() { } sec_check(vec![SecurityBadge::Admin]); let admin_list: Option> = - utils::get_optional_named_arg_with_user_errors(ADMIN_LIST, Cep18Error::InvalidAdminList); + get_optional_named_arg_with_user_errors(ADMIN_LIST, Cep18Error::InvalidAdminList); let minter_list: Option> = - utils::get_optional_named_arg_with_user_errors(MINTER_LIST, Cep18Error::InvalidMinterList); + get_optional_named_arg_with_user_errors(MINTER_LIST, Cep18Error::InvalidMinterList); let none_list: Option> = - utils::get_optional_named_arg_with_user_errors(NONE_LIST, Cep18Error::InvalidNoneList); + get_optional_named_arg_with_user_errors(NONE_LIST, Cep18Error::InvalidNoneList); let mut badge_map: BTreeMap = BTreeMap::new(); if let Some(minter_list) = minter_list { @@ -741,19 +758,16 @@ pub fn upgrade(name: &str) { }; let converted_previous_contract_hash = AddressableEntityHash::new(previous_contract_hash); - let events_mode = utils::get_optional_named_arg_with_user_errors::( - EVENTS_MODE, - Cep18Error::InvalidEventsMode, - ); + let events_mode = + get_optional_named_arg_with_user_errors::(EVENTS_MODE, Cep18Error::InvalidEventsMode); let mut message_topics = BTreeMap::new(); - - if let Some(mode) = events_mode { - let events_mode: EventsMode = mode - .try_into() - .unwrap_or_revert_with(Cep18Error::InvalidEventsMode); - if [EventsMode::NativeNCES, EventsMode::Native].contains(&events_mode) { + match get_key(CONDOR) { + Some(_) => {} + None => { message_topics.insert(EVENTS.to_string(), MessageTopicOperation::Add); + message_topics.insert(ERRORS.to_string(), MessageTopicOperation::Add); + put_key(CONDOR, storage::new_uref(CONDOR).into()); } } @@ -800,19 +814,17 @@ pub fn install_contract(name: &str) { let decimals: u8 = runtime::get_named_arg(DECIMALS); let total_supply: U256 = runtime::get_named_arg(TOTAL_SUPPLY); let events_mode: u8 = - utils::get_optional_named_arg_with_user_errors(EVENTS_MODE, Cep18Error::InvalidEventsMode) + get_optional_named_arg_with_user_errors(EVENTS_MODE, Cep18Error::InvalidEventsMode) .unwrap_or(0u8); let admin_list: Option> = - utils::get_optional_named_arg_with_user_errors(ADMIN_LIST, Cep18Error::InvalidAdminList); + get_optional_named_arg_with_user_errors(ADMIN_LIST, Cep18Error::InvalidAdminList); let minter_list: Option> = - utils::get_optional_named_arg_with_user_errors(MINTER_LIST, Cep18Error::InvalidMinterList); + get_optional_named_arg_with_user_errors(MINTER_LIST, Cep18Error::InvalidMinterList); - let enable_mint_burn: u8 = utils::get_optional_named_arg_with_user_errors( - ENABLE_MINT_BURN, - Cep18Error::InvalidEnableMBFlag, - ) - .unwrap_or(0); + let enable_mint_burn: u8 = + get_optional_named_arg_with_user_errors(ENABLE_MINT_BURN, Cep18Error::InvalidEnableMBFlag) + .unwrap_or(0); let mut named_keys = NamedKeys::new(); named_keys.insert(NAME.to_string(), storage::new_uref(name).into()); @@ -832,14 +844,12 @@ pub fn install_contract(name: &str) { ); let entry_points = generate_entry_points(); - let message_topics = if [EventsMode::Native, EventsMode::NativeNCES] + let mut message_topics = BTreeMap::new(); + message_topics.insert(ERRORS.to_string(), MessageTopicOperation::Add); + if [EventsMode::Native, EventsMode::NativeNCES] .contains(&events_mode.try_into().unwrap_or_default()) { - let mut message_topics = BTreeMap::new(); message_topics.insert(EVENTS.to_string(), MessageTopicOperation::Add); - Some(message_topics) - } else { - None }; let hash_key_name = format!("{HASH_KEY_NAME_PREFIX}{name}"); @@ -848,9 +858,8 @@ pub fn install_contract(name: &str) { Some(named_keys), Some(hash_key_name.clone()), Some(format!("{ACCESS_KEY_NAME_PREFIX}{name}")), - message_topics, + Some(message_topics), ); - let package_hash = runtime::get_key(&hash_key_name).unwrap_or_revert_with(Cep18Error::FailedToGetKey); @@ -877,6 +886,7 @@ pub fn install_contract(name: &str) { .unwrap_or_revert_with(Cep18Error::FailedToInsertToSecurityList); } + put_key(CONDOR, storage::new_uref(CONDOR).into()); runtime::call_contract::<()>(contract_hash, INIT_ENTRY_POINT_NAME, init_args); } diff --git a/cep18/src/utils.rs b/cep18/src/utils.rs index a608352..4135f44 100644 --- a/cep18/src/utils.rs +++ b/cep18/src/utils.rs @@ -1,7 +1,7 @@ //! Implementation details. use core::convert::TryInto; -use alloc::{collections::BTreeMap, vec, vec::Vec}; +use alloc::{collections::BTreeMap, format, vec, vec::Vec}; use casper_contract::{ contract_api::{ self, @@ -20,7 +20,7 @@ use casper_types::{ }; use crate::{ - constants::{SECURITY_BADGES, TOTAL_SUPPLY}, + constants::{ERRORS, SECURITY_BADGES, TOTAL_SUPPLY}, error::Cep18Error, }; @@ -99,7 +99,10 @@ pub fn get_named_arg_size(name: &str) -> Option { match api_error::result_from(ret) { Ok(_) => Some(arg_size), Err(ApiError::MissingArgument) => None, - Err(e) => runtime::revert(e), + Err(e) => { + runtime::emit_message(ERRORS, &format!("{e:?}").into()).unwrap_or_revert(); + runtime::revert(e) + } } } @@ -136,13 +139,20 @@ pub fn get_named_arg_with_user_errors( api_error::result_from(ret).map(|_| data) }; // Assumed to be safe as `get_named_arg_size` checks the argument already - res.unwrap_or_revert_with(Cep18Error::FailedToGetArgBytes) + res.map_err(|err| { + let _ = runtime::emit_message(ERRORS, &format!("{err:?}").into()); + Cep18Error::FailedToGetArgBytes + }) + .unwrap_or_revert() } else { // Avoids allocation with 0 bytes and a call to get_named_arg Vec::new() }; - bytesrepr::deserialize(arg_bytes).map_err(|_| invalid) + bytesrepr::deserialize(arg_bytes).map_err(|err| { + let _ = runtime::emit_message(ERRORS, &format!("{err:?}").into()); + invalid + }) } #[repr(u8)] diff --git a/tests/src/events.rs b/tests/src/events.rs index 640afcc..a4a1548 100644 --- a/tests/src/events.rs +++ b/tests/src/events.rs @@ -47,7 +47,7 @@ fn should_have_have_no_events() { )); assert!(entity_with_named_keys.get("__events").is_none()); let entity = entity(&builder, &addressable_cep18_token); - assert!(entity.message_topics().is_empty()); + assert!(entity.message_topics().len() == 1); } #[test] @@ -93,7 +93,7 @@ fn should_have_native_events() { let (topic_name, message_topic_hash) = entity .message_topics() .iter() - .next() + .last() .expect("should have at least one topic"); assert_eq!(topic_name, &EVENTS.to_string()); @@ -233,7 +233,7 @@ fn should_have_both_native_and_ces_events() { let (topic_name, message_topic_hash) = entity .message_topics() .iter() - .next() + .last() .expect("should have at least one topic"); assert_eq!(topic_name, &EVENTS.to_string()); @@ -281,3 +281,60 @@ fn should_have_both_native_and_ces_events() { ]) ) } + +#[test] +fn should_test_error_message_topic_on_mint_overflow() { + let ( + mut builder, + TestContext { + cep18_contract_hash, + .. + }, + ) = setup_with_args(runtime_args! { + ARG_NAME => TOKEN_NAME, + ARG_SYMBOL => TOKEN_SYMBOL, + ARG_DECIMALS => TOKEN_DECIMALS, + ARG_TOTAL_SUPPLY => U256::from(TOKEN_TOTAL_SUPPLY), + EVENTS_MODE => 0_u8, + ENABLE_MINT_BURN => true, + }); + + let addressable_cep18_token = AddressableEntityHash::new(cep18_contract_hash.value()); + let entity = entity(&builder, &addressable_cep18_token); + + let (topic_name, message_topic_hash) = entity + .message_topics() + .iter() + .last() + .expect("should have at least one topic"); + + assert_eq!(topic_name, "errors"); + + let mint_request = ExecuteRequestBuilder::contract_call_by_hash( + *DEFAULT_ACCOUNT_ADDR, + addressable_cep18_token, + METHOD_MINT, + runtime_args! {OWNER => TOKEN_OWNER_ADDRESS_1, AMOUNT => U256::MAX}, + ) + .build(); + + builder.exec(mint_request).expect_failure().commit(); + + assert_eq!( + message_topic(&builder, &addressable_cep18_token, *message_topic_hash).message_count(), + 1 + ); + + let exec_result = builder.get_exec_result_owned(2).unwrap(); + let messages = exec_result.messages(); + let error_message = "Cannot add to total_supply as it flow over U256::MAX value"; + let message = Message::new( + casper_types::EntityAddr::SmartContract(addressable_cep18_token.value()), + error_message.into(), + "errors".to_string(), + *message_topic_hash, + 0, + 0, + ); + assert_eq!(messages, &vec![message]); +} diff --git a/tests/src/migration.rs b/tests/src/migration.rs index 8574e4e..083ae95 100644 --- a/tests/src/migration.rs +++ b/tests/src/migration.rs @@ -248,7 +248,7 @@ fn should_have_native_events() { let (topic_name, message_topic_hash) = entity .message_topics() .iter() - .next() + .last() .expect("should have at least one topic"); let mut user_map: BTreeMap = BTreeMap::new(); From 785a63dfb351d1b0103832e0235a41d07aa0dece Mon Sep 17 00:00:00 2001 From: Deuszex Date: Mon, 8 Jul 2024 09:45:34 +0200 Subject: [PATCH 23/27] fix allowance migration --- cep18/src/main.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/cep18/src/main.rs b/cep18/src/main.rs index 142cc55..69e8ce5 100644 --- a/cep18/src/main.rs +++ b/cep18/src/main.rs @@ -601,14 +601,14 @@ pub fn migrate_user_allowance_keys() { } }; let old_allowance = - read_allowance_from(allowances_uref, migrated_owner_key, migrated_spender_key); + read_allowance_from(allowances_uref, owner_key, spender_key); if old_allowance > U256::zero() { let new_key_existing_allowance = read_allowance_from(allowances_uref, migrated_owner_key, migrated_spender_key); write_allowance_to( allowances_uref, - migrated_owner_key, - migrated_spender_key, + owner_key, + spender_key, U256::zero(), ); write_allowance_to( From 3243475fe9feb59b7e5e58d9fbec5e88ff09d0ad Mon Sep 17 00:00:00 2001 From: Deuszex Date: Mon, 8 Jul 2024 12:08:17 +0200 Subject: [PATCH 24/27] fix sec migration --- cep18/src/main.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cep18/src/main.rs b/cep18/src/main.rs index 69e8ce5..9987e5b 100644 --- a/cep18/src/main.rs +++ b/cep18/src/main.rs @@ -693,7 +693,7 @@ pub fn migrate_sec_keys() { let sec: SecurityBadge = named_dictionary_get(SECURITY_BADGES, &old_encoded_user_sec_key) .unwrap_or_revert_with(Cep18Error::FailedToGetDictionaryValue) .unwrap_or(SecurityBadge::None); - if ![SecurityBadge::Admin, SecurityBadge::Minter].contains(&sec) { + if [SecurityBadge::Admin, SecurityBadge::Minter].contains(&sec) { named_dictionary_put(SECURITY_BADGES, &migrated_encoded_user_sec_key, sec); } else if event_on { failure_map.insert(old_key, String::from("NoValidBadge")); From 13a48b3aee5f1851bf19f4e2a092c6143430d40d Mon Sep 17 00:00:00 2001 From: Deuszex Date: Tue, 9 Jul 2024 11:38:56 +0200 Subject: [PATCH 25/27] removed custom error to see source behind it --- cep18/src/events.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/cep18/src/events.rs b/cep18/src/events.rs index 180b42b..734ffb4 100644 --- a/cep18/src/events.rs +++ b/cep18/src/events.rs @@ -21,10 +21,10 @@ pub fn record_event_dictionary(event: Event) { EventsMode::NoEvents => {} EventsMode::CES => ces(event), EventsMode::Native => runtime::emit_message(EVENTS, &format!("{event:?}").into()) - .unwrap_or_revert_with(Cep18Error::FailedToWriteMessage), + .unwrap_or_revert(), EventsMode::NativeNCES => { runtime::emit_message(EVENTS, &format!("{event:?}").into()) - .unwrap_or_revert_with(Cep18Error::FailedToWriteMessage); + .unwrap_or_revert(); ces(event); } } From 519d556c08138f6d64b1a710b32298876b431fd5 Mon Sep 17 00:00:00 2001 From: Deuszex Date: Tue, 9 Jul 2024 15:03:00 +0200 Subject: [PATCH 26/27] fixed migration of contract packages --- cep18/src/main.rs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/cep18/src/main.rs b/cep18/src/main.rs index 9987e5b..77d32c3 100644 --- a/cep18/src/main.rs +++ b/cep18/src/main.rs @@ -472,7 +472,7 @@ pub fn migrate_user_balance_keys() { } continue; } - Key::AddressableEntity(EntityAddr::SmartContract(contract_package)) + Key::Package(contract_package) } _ => { if event_on { @@ -547,7 +547,7 @@ pub fn migrate_user_allowance_keys() { revert(Cep18Error::KeyTypeMigrationMismatch) } } - Key::AddressableEntity(EntityAddr::SmartContract(contract_package)) + Key::Package(contract_package) } _ => { if event_on { @@ -586,7 +586,7 @@ pub fn migrate_user_allowance_keys() { revert(Cep18Error::KeyTypeMigrationMismatch) } } - Key::AddressableEntity(EntityAddr::SmartContract(contract_package)) + Key::Package(contract_package) } _ => { if event_on { @@ -669,7 +669,7 @@ pub fn migrate_sec_keys() { } continue; } - Key::AddressableEntity(EntityAddr::SmartContract(contract_package)) + Key::Package(contract_package) } _ => { if event_on { From b591ea19d88de036b0df5523af20fe6c7c5e39e3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zolt=C3=A1n?= <30974986+deuszex@users.noreply.github.com> Date: Thu, 11 Jul 2024 16:38:24 +0200 Subject: [PATCH 27/27] Update utils.rs --- cep18/src/utils.rs | 35 +++++++++++++++++++++++++++++++---- 1 file changed, 31 insertions(+), 4 deletions(-) diff --git a/cep18/src/utils.rs b/cep18/src/utils.rs index 4135f44..5159779 100644 --- a/cep18/src/utils.rs +++ b/cep18/src/utils.rs @@ -51,19 +51,46 @@ where /// case it will use contract package hash as the address. fn call_stack_element_to_address(call_stack_element: Caller) -> Key { match call_stack_element { - Caller::Initiator { account_hash } => { - Key::AddressableEntity(EntityAddr::Account(account_hash.value())) + CallStackElement::Session { account_hash } => Key::from(account_hash), + CallStackElement::StoredSession { account_hash, .. } => { + // Stored session code acts in account's context, so if stored session wants to interact + // with an CEP-18 token caller's address will be used. + Key::from(account_hash) } - Caller::Entity { package_hash, .. } => Key::Package(package_hash.value()), + CallStackElement::StoredContract { + contract_package_hash, + .. + } => Key::from(contract_package_hash), } } +/// Returns the call stack. +pub fn get_call_stack() -> Vec { + let (call_stack_len, result_size) = { + let mut call_stack_len: usize = 0; + let mut result_size: usize = 0; + let ret = unsafe { + ext_ffi::casper_load_call_stack( + &mut call_stack_len as *mut usize, + &mut result_size as *mut usize, + ) + }; + api_error::result_from(ret).unwrap_or_revert(); + (call_stack_len, result_size) + }; + if call_stack_len == 0 { + return Vec::new(); + } + let bytes = read_host_buffer(result_size).unwrap_or_revert(); + bytesrepr::deserialize(bytes).unwrap_or_revert() +} + /// Gets the immediate session caller of the current execution. /// /// This function ensures that Contracts can participate and no middleman (contract) acts for /// users. pub(crate) fn get_immediate_caller_address() -> Result { - let call_stack = runtime::get_call_stack(); + let call_stack = get_call_stack(); call_stack .into_iter() .rev()