From 719cd9f59bf3ece5ff5f9298f0c882c0bebc592b Mon Sep 17 00:00:00 2001 From: gRoussac Date: Mon, 18 Aug 2025 17:39:04 +0200 Subject: [PATCH 01/11] First commit entities falg --- contracts/contract/src/allowances.rs | 11 +- contracts/contract/src/balances.rs | 28 ++--- contracts/contract/src/utils.rs | 108 ++++++++++++++++-- tests/src/migration.rs | 4 +- .../src/utility/installer_request_builders.rs | 17 ++- 5 files changed, 127 insertions(+), 41 deletions(-) diff --git a/contracts/contract/src/allowances.rs b/contracts/contract/src/allowances.rs index e5fd80c7..5b625bad 100644 --- a/contracts/contract/src/allowances.rs +++ b/contracts/contract/src/allowances.rs @@ -2,19 +2,24 @@ use crate::{ constants::DICT_ALLOWANCES, utils::{ - get_dictionary_value_from_key, make_dictionary_item_key, set_dictionary_value_for_key, + get_dictionary_value_from_key, make_dictionary_item_key_value, + set_dictionary_value_for_key, to_legacy_key, }, }; use casper_types::{Key, U256}; /// Writes an allowance for owner and spender for a specific amount. pub fn write_allowance_to(owner: Key, spender: Key, amount: U256) { - let dictionary_item_key = make_dictionary_item_key(&owner, &spender); + let owner = to_legacy_key(owner); + let spender = to_legacy_key(spender); + let dictionary_item_key = make_dictionary_item_key_value(&owner, &spender); set_dictionary_value_for_key(DICT_ALLOWANCES, &dictionary_item_key, &amount) } /// Reads an allowance for a owner and spender pub fn read_allowance_from(owner: Key, spender: Key) -> U256 { - let dictionary_item_key = make_dictionary_item_key(&owner, &spender); + let owner = to_legacy_key(owner); + let spender = to_legacy_key(spender); + let dictionary_item_key = make_dictionary_item_key_value(&owner, &spender); get_dictionary_value_from_key(DICT_ALLOWANCES, &dictionary_item_key).unwrap_or_default() } diff --git a/contracts/contract/src/balances.rs b/contracts/contract/src/balances.rs index 6829a944..aee01798 100644 --- a/contracts/contract/src/balances.rs +++ b/contracts/contract/src/balances.rs @@ -2,31 +2,16 @@ use crate::{ constants::DICT_BALANCES, error::Cep18Error, - utils::{base64_encode, get_dictionary_value_from_key, set_dictionary_value_for_key}, + utils::{ + get_dictionary_value_from_key, make_dictionary_item_key, set_dictionary_value_for_key, + to_legacy_key, + }, }; -use alloc::string::String; -use casper_contract::unwrap_or_revert::UnwrapOrRevert; -use casper_types::{bytesrepr::ToBytes, Key, U256}; - -/// Creates a dictionary item key for a dictionary item, by base64 encoding the Key argument -/// since stringified Keys are too long to be used as dictionary keys. -#[inline] -/// ! TODO GR check hex::encode with utils::make_dictionary_item_key or else -fn make_dictionary_item_key(owner: Key) -> String { - 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 - // characters. - // Even if the preimage increased in size we still have extra space but even in case of much - // larger preimage we can switch to base85 which has ratio of 4:5. - base64_encode(preimage) -} +use casper_types::{Key, U256}; /// Writes token balance of a specified account into a dictionary. pub fn write_balance_to(address: Key, amount: U256) { + let address = to_legacy_key(address); let dictionary_item_key = make_dictionary_item_key(address); set_dictionary_value_for_key(DICT_BALANCES, &dictionary_item_key, &amount) } @@ -35,6 +20,7 @@ pub fn write_balance_to(address: Key, amount: U256) { /// /// If a given account does not have balances in the system, then a 0 is returned. pub fn read_balance_from(address: Key) -> U256 { + let address = to_legacy_key(address); let dictionary_item_key = make_dictionary_item_key(address); get_dictionary_value_from_key(DICT_BALANCES, &dictionary_item_key).unwrap_or_default() } diff --git a/contracts/contract/src/utils.rs b/contracts/contract/src/utils.rs index ec19bbcd..e104ef87 100644 --- a/contracts/contract/src/utils.rs +++ b/contracts/contract/src/utils.rs @@ -18,12 +18,33 @@ use casper_types::{ api_error, bytesrepr::{self, FromBytes, ToBytes}, contracts::{ContractPackageHash, ContractVersionKey}, - ApiError, CLTyped, EntityAddr, Key, URef, U256, + ApiError, CLTyped, Key, PackageHash, URef, U256, }; use core::convert::TryInto; +/// Retrieves the immediate caller of the current contract execution context as a [`Key`]. +/// +/// This function abstracts over the different kinds of entities that may invoke a contract: +/// legacy accounts, legacy contract packages, or new entities. +/// +/// # Behavior +/// +/// * **ACCOUNT (legacy or new entity account)** Returns a `Key::Account` wrapping the +/// `AccountHash`. +/// * **CONTRACT (legacy contract package)** Returns a `Key::Hash` wrapping the +/// `ContractPackageHash`. +/// * **ENTITY (new entity)** Returns a `Key::Hash` wrapping the `PackageHash`. +/// * **Other / unexpected kinds** Reverts with [`Cep18Error::InvalidContext`]. +/// +/// # Example +/// +/// ```ignore +/// let caller: Key = get_immediate_caller(); +/// // `caller` can now be converted to a legacy key via `to_legacy_key` if needed +/// ``` pub fn get_immediate_caller() -> Key { const ACCOUNT: u8 = 0; + const PACKAGE: u8 = 1; const CONTRACT_PACKAGE: u8 = 2; const ENTITY: u8 = 3; const CONTRACT: u8 = 4; @@ -31,6 +52,7 @@ pub fn get_immediate_caller() -> Key { let caller_info = casper_get_immediate_caller().unwrap_or_revert(); match caller_info.kind() { + // Legacy or new entity account returns AccountHash ACCOUNT => caller_info .get_field_by_index(ACCOUNT) .unwrap() @@ -38,17 +60,19 @@ pub fn get_immediate_caller() -> Key { .unwrap_or_revert() .unwrap_or_revert_with(Cep18Error::InvalidContext) .into(), - CONTRACT => caller_info - .get_field_by_index(CONTRACT_PACKAGE) + // New entity returns PackageHash + ENTITY => caller_info + .get_field_by_index(PACKAGE) .unwrap() - .to_t::>() + .to_t::>() .unwrap_or_revert() .unwrap_or_revert_with(Cep18Error::InvalidContext) .into(), - ENTITY => caller_info - .get_field_by_index(ENTITY) + // Legacy returns ContractPackageHash + CONTRACT => caller_info + .get_field_by_index(CONTRACT_PACKAGE) .unwrap() - .to_t::>() + .to_t::>() .unwrap_or_revert() .unwrap_or_revert_with(Cep18Error::InvalidContext) .into(), @@ -56,6 +80,57 @@ pub fn get_immediate_caller() -> Key { } } +/// Converts a new-style [`Key`] returned by [`get_immediate_caller`] into a legacy-compatible +/// [`Key`]. +/// +/// This function ensures backward compatibility with legacy CEP-18 expectations, +/// where callers were identified only as `Account` or `Hash`. +/// +/// # Behavior +/// +/// * **`Key::AddressableEntity`** +/// - If the entity is an **account**, returns a legacy [`Key::Account`]. +/// - If the entity is not an account (e.g., smart contract or system entity), returns a legacy +/// [`Key::Hash`]. This case should not normally occur in CEP-18, where contracts calling are +/// expected to be identified as contract packages, but it is handled for consistency. +/// * **`Key::SmartContract`** Converts directly into a legacy [`Key::Hash`] using the +/// [`PackageHash`]. +/// * **Other legacy keys** (`Key::Account`, `Key::Hash`) Returned unchanged. +/// +/// # Notes +/// +/// - This function is mostly used in CEP-18 context where contract logic needs to interact with +/// storage or access controls (balances, allowances, etc.). +/// +/// # Example +/// +/// ```ignore +/// let caller = get_immediate_caller(); +/// let legacy_caller = to_legacy_key(caller); +/// // legacy_caller is now safe to use in CEP-18 storage lookups +/// ``` +pub fn to_legacy_key(key: Key) -> Key { + match key { + Key::AddressableEntity(entity_addr) => { + if entity_addr.is_account() { + let account_hash = AccountHash::new(entity_addr.value()); + Key::Account(account_hash) + } else { + // This case should in theory never happen, since caller returned by + // `get_immediate_caller` is expected to be either an Account or a + // Package. We keep consistency by returning a legacy Key::Hash + // instead of reverting here. + Key::Hash(entity_addr.value()) + } + } + // Manage PackageHash from `get_immediate_caller` ENTITY case + Key::SmartContract(package_addr) => Key::Hash(package_addr.into()), + // Legacy cases Account + ContractPackageHash from `get_immediate_caller` ACCOUNT + CONTRACT + // cases + legacy => legacy, + } +} + pub fn get_contract_version_key(contract_version: u32) -> ContractVersionKey { let (major, _, _) = get_protocol_version().destructure(); ContractVersionKey::new(major, contract_version) @@ -126,7 +201,24 @@ pub fn get_optional_named_arg_with_user_errors( } } -pub fn make_dictionary_item_key( +/// Creates a dictionary item key for a dictionary item, by base64 encoding the Key argument +/// since stringified Keys are too long to be used as dictionary keys. +#[inline] +pub fn make_dictionary_item_key(owner: Key) -> String { + 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 + // characters. + // Even if the preimage increased in size we still have extra space but even in case of much + // larger preimage we can switch to base85 which has ratio of 4:5. + base64_encode(preimage) +} + +#[inline] +pub fn make_dictionary_item_key_value( key: &T, value: &V, ) -> String { diff --git a/tests/src/migration.rs b/tests/src/migration.rs index a63e6b65..28003571 100644 --- a/tests/src/migration.rs +++ b/tests/src/migration.rs @@ -34,10 +34,8 @@ pub fn upgrade_v1_5_6_fixture_to_v2_0_0_ee( let mut upgrade_config = UpgradeRequestBuilder::new() .with_current_protocol_version(lmdb_fixture_state.genesis_protocol_version()) .with_new_protocol_version(ProtocolVersion::V2_0_0) - // TODO GR - // .with_migrate_legacy_accounts(true) - // .with_migrate_legacy_contracts(true) .with_activation_point(EraId::new(1)) + .with_enable_addressable_entity(true) .build(); builder diff --git a/tests/src/utility/installer_request_builders.rs b/tests/src/utility/installer_request_builders.rs index 137c99c8..401c9331 100644 --- a/tests/src/utility/installer_request_builders.rs +++ b/tests/src/utility/installer_request_builders.rs @@ -6,8 +6,8 @@ use crate::utility::constants::{ AMOUNT_ALLOWANCE_1, AMOUNT_ALLOWANCE_2, AMOUNT_TRANSFER_1, AMOUNT_TRANSFER_2, }; use casper_engine_test_support::{ - utils::create_run_genesis_request, ExecuteRequest, ExecuteRequestBuilder, LmdbWasmTestBuilder, - DEFAULT_ACCOUNTS, DEFAULT_ACCOUNT_ADDR, + utils::create_run_genesis_request_with_chainspec_config, ChainspecConfig, ExecuteRequest, + ExecuteRequestBuilder, LmdbWasmTestBuilder, DEFAULT_ACCOUNTS, DEFAULT_ACCOUNT_ADDR, }; use casper_types::{ account::AccountHash, bytesrepr::FromBytes, runtime_args, AddressableEntityHash, CLTyped, @@ -63,9 +63,14 @@ pub(crate) fn setup() -> (LmdbWasmTestBuilder, TestContext) { } pub(crate) fn setup_with_args(install_args: RuntimeArgs) -> (LmdbWasmTestBuilder, TestContext) { - let mut builder = LmdbWasmTestBuilder::default(); + let chainspec = ChainspecConfig::default().with_enable_addressable_entity(true); + + let mut builder = LmdbWasmTestBuilder::new_temporary_with_config(chainspec.clone()); builder - .run_genesis(create_run_genesis_request(DEFAULT_ACCOUNTS.to_vec())) + .run_genesis(create_run_genesis_request_with_chainspec_config( + DEFAULT_ACCOUNTS.to_vec(), + chainspec, + )) .commit(); let install_request_1 = @@ -229,7 +234,7 @@ pub(crate) fn cep18_check_allowance_of( .and_then(|key| key.into_package_hash()) .expect("should have test contract hash"); - let check_balance_args = runtime_args! { + let check_allowance_args = runtime_args! { ARG_TOKEN_CONTRACT => Key::Hash(cep18_contract_hash.value()), ARG_OWNER => owner, ARG_SPENDER => spender, @@ -239,7 +244,7 @@ pub(crate) fn cep18_check_allowance_of( cep18_test_contract_package, None, ENTRY_POINT_CHECK_ALLOWANCE_OF, - check_balance_args, + check_allowance_args, ) .build(); builder.exec(exec_request).expect_success().commit(); From 95ff7dc46525aba1ca3c0097dc06d9c53362d492 Mon Sep 17 00:00:00 2001 From: gRoussac Date: Mon, 18 Aug 2025 18:01:49 +0200 Subject: [PATCH 02/11] Add TEST_ENABLE_ADDRESSABLE_ENTITY env var --- tests/Cargo.toml | 3 +++ tests/src/migration.rs | 6 ++++-- tests/src/utility/constants.rs | 2 ++ tests/src/utility/installer_request_builders.rs | 15 +++++++++++++-- 4 files changed, 22 insertions(+), 4 deletions(-) diff --git a/tests/Cargo.toml b/tests/Cargo.toml index f1ab98b4..7ebefd91 100644 --- a/tests/Cargo.toml +++ b/tests/Cargo.toml @@ -23,3 +23,6 @@ casper-fixtures = { git = "https://github.com/casper-network/casper-fixtures", b casper-engine-test-support = { version = "8.1.0", default-features = false } casper-execution-engine = { version = "8.1.0", default-features = false } casper-event-standard.workspace = true + +[dev-dependencies] +dotenv = "*" diff --git a/tests/src/migration.rs b/tests/src/migration.rs index 28003571..ebbb75d3 100644 --- a/tests/src/migration.rs +++ b/tests/src/migration.rs @@ -15,7 +15,9 @@ use crate::utility::{ AMOUNT_1, CEP18_CONTRACT_WASM, CEP18_TEST_CONTRACT_WASM, CEP18_TEST_TOKEN_CONTRACT_NAME, CEP18_TEST_TOKEN_CONTRACT_VERSION, TOKEN_NAME, }, - installer_request_builders::{cep18_check_balance_of, get_test_account}, + installer_request_builders::{ + cep18_check_balance_of, get_enable_addressable_entity, get_test_account, + }, message_handlers::{message_summary, message_topic}, support::query_stored_value, }; @@ -35,7 +37,7 @@ pub fn upgrade_v1_5_6_fixture_to_v2_0_0_ee( .with_current_protocol_version(lmdb_fixture_state.genesis_protocol_version()) .with_new_protocol_version(ProtocolVersion::V2_0_0) .with_activation_point(EraId::new(1)) - .with_enable_addressable_entity(true) + .with_enable_addressable_entity(get_enable_addressable_entity()) .build(); builder diff --git a/tests/src/utility/constants.rs b/tests/src/utility/constants.rs index f6849bf7..ba70368c 100644 --- a/tests/src/utility/constants.rs +++ b/tests/src/utility/constants.rs @@ -4,6 +4,8 @@ pub const CEP18_TEST_CONTRACT_WASM: &str = "cep18_test_contract.wasm"; pub const CEP18_TEST_TOKEN_CONTRACT_NAME: &str = "cep18_contract_hash_CasperTest"; pub const CEP18_TEST_TOKEN_CONTRACT_VERSION: &str = "cep18_contract_version_CasperTest"; +pub const TEST_ENABLE_ADDRESSABLE_ENTITY: &str = "TEST_ENABLE_ADDRESSABLE_ENTITY"; + pub const TOKEN_NAME: &str = "CasperTest"; pub const TOKEN_SYMBOL: &str = "CSPRT"; pub const TOKEN_DECIMALS: u8 = 100; diff --git a/tests/src/utility/installer_request_builders.rs b/tests/src/utility/installer_request_builders.rs index 401c9331..88094317 100644 --- a/tests/src/utility/installer_request_builders.rs +++ b/tests/src/utility/installer_request_builders.rs @@ -4,6 +4,7 @@ use super::constants::{ }; use crate::utility::constants::{ AMOUNT_ALLOWANCE_1, AMOUNT_ALLOWANCE_2, AMOUNT_TRANSFER_1, AMOUNT_TRANSFER_2, + TEST_ENABLE_ADDRESSABLE_ENTITY, }; use casper_engine_test_support::{ utils::create_run_genesis_request_with_chainspec_config, ChainspecConfig, ExecuteRequest, @@ -25,6 +26,15 @@ use cep18_test_contract::constants::{ ENTRY_POINT_CHECK_ALLOWANCE_OF, ENTRY_POINT_CHECK_BALANCE_OF, ENTRY_POINT_CHECK_TOTAL_SUPPLY, ENTRY_POINT_TRANSFER_AS_STORED_CONTRACT, RESULT_KEY, }; +use dotenv::dotenv; +use std::env; + +pub(crate) fn get_enable_addressable_entity() -> bool { + let _ = dotenv(); + env::var(TEST_ENABLE_ADDRESSABLE_ENTITY) + .map(|v| v == "true") + .unwrap_or(false) +} /// Converts hash addr of Account into Hash, and Hash into Account /// @@ -58,12 +68,13 @@ pub(crate) fn setup() -> (LmdbWasmTestBuilder, TestContext) { ARG_SYMBOL => TOKEN_SYMBOL, ARG_DECIMALS => TOKEN_DECIMALS, ARG_TOTAL_SUPPLY => U256::from(TOKEN_TOTAL_SUPPLY), - ARG_EVENTS_MODE => EventsMode::Native as u8 + ARG_EVENTS_MODE => EventsMode::Native as u8, }) } pub(crate) fn setup_with_args(install_args: RuntimeArgs) -> (LmdbWasmTestBuilder, TestContext) { - let chainspec = ChainspecConfig::default().with_enable_addressable_entity(true); + let chainspec = + ChainspecConfig::default().with_enable_addressable_entity(get_enable_addressable_entity()); let mut builder = LmdbWasmTestBuilder::new_temporary_with_config(chainspec.clone()); builder From fb809ba77f3bd900212100b7a8a57e2ebc522ad3 Mon Sep 17 00:00:00 2001 From: gRoussac Date: Mon, 18 Aug 2025 18:20:37 +0200 Subject: [PATCH 03/11] Lint --- contracts/contract/src/utils.rs | 2 +- tests/Cargo.toml | 2 -- 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/contracts/contract/src/utils.rs b/contracts/contract/src/utils.rs index e104ef87..06933ac4 100644 --- a/contracts/contract/src/utils.rs +++ b/contracts/contract/src/utils.rs @@ -124,7 +124,7 @@ pub fn to_legacy_key(key: Key) -> Key { } } // Manage PackageHash from `get_immediate_caller` ENTITY case - Key::SmartContract(package_addr) => Key::Hash(package_addr.into()), + Key::SmartContract(package_addr) => Key::Hash(package_addr), // Legacy cases Account + ContractPackageHash from `get_immediate_caller` ACCOUNT + CONTRACT // cases legacy => legacy, diff --git a/tests/Cargo.toml b/tests/Cargo.toml index 7ebefd91..634f4c14 100644 --- a/tests/Cargo.toml +++ b/tests/Cargo.toml @@ -23,6 +23,4 @@ casper-fixtures = { git = "https://github.com/casper-network/casper-fixtures", b casper-engine-test-support = { version = "8.1.0", default-features = false } casper-execution-engine = { version = "8.1.0", default-features = false } casper-event-standard.workspace = true - -[dev-dependencies] dotenv = "*" From d92aa2963076fc8ebaac6fa1dbae9a5b277752f6 Mon Sep 17 00:00:00 2001 From: gRoussac Date: Tue, 19 Aug 2025 18:30:12 +0200 Subject: [PATCH 04/11] Remove dotenv, switch to feature config in test --- Makefile | 1 + tests/Cargo.toml | 4 +++- tests/src/utility/constants.rs | 2 -- tests/src/utility/installer_request_builders.rs | 8 +------- 4 files changed, 5 insertions(+), 10 deletions(-) diff --git a/Makefile b/Makefile index 873a316a..cb7642ea 100644 --- a/Makefile +++ b/Makefile @@ -35,6 +35,7 @@ native-test: setup-test test: setup-test cargo test -p tests --lib + cargo test -p tests --lib --features enable-addressable-entity clippy: cargo +$(PINNED_TOOLCHAIN) clippy --release -p cep18 --bins --target wasm32-unknown-unknown $(CARGO_BUILD_FLAGS) -- -D warnings diff --git a/tests/Cargo.toml b/tests/Cargo.toml index 634f4c14..77b2dc08 100644 --- a/tests/Cargo.toml +++ b/tests/Cargo.toml @@ -23,4 +23,6 @@ casper-fixtures = { git = "https://github.com/casper-network/casper-fixtures", b casper-engine-test-support = { version = "8.1.0", default-features = false } casper-execution-engine = { version = "8.1.0", default-features = false } casper-event-standard.workspace = true -dotenv = "*" + +[features] +enable-addressable-entity = [] diff --git a/tests/src/utility/constants.rs b/tests/src/utility/constants.rs index ba70368c..f6849bf7 100644 --- a/tests/src/utility/constants.rs +++ b/tests/src/utility/constants.rs @@ -4,8 +4,6 @@ pub const CEP18_TEST_CONTRACT_WASM: &str = "cep18_test_contract.wasm"; pub const CEP18_TEST_TOKEN_CONTRACT_NAME: &str = "cep18_contract_hash_CasperTest"; pub const CEP18_TEST_TOKEN_CONTRACT_VERSION: &str = "cep18_contract_version_CasperTest"; -pub const TEST_ENABLE_ADDRESSABLE_ENTITY: &str = "TEST_ENABLE_ADDRESSABLE_ENTITY"; - pub const TOKEN_NAME: &str = "CasperTest"; pub const TOKEN_SYMBOL: &str = "CSPRT"; pub const TOKEN_DECIMALS: u8 = 100; diff --git a/tests/src/utility/installer_request_builders.rs b/tests/src/utility/installer_request_builders.rs index 88094317..add82166 100644 --- a/tests/src/utility/installer_request_builders.rs +++ b/tests/src/utility/installer_request_builders.rs @@ -4,7 +4,6 @@ use super::constants::{ }; use crate::utility::constants::{ AMOUNT_ALLOWANCE_1, AMOUNT_ALLOWANCE_2, AMOUNT_TRANSFER_1, AMOUNT_TRANSFER_2, - TEST_ENABLE_ADDRESSABLE_ENTITY, }; use casper_engine_test_support::{ utils::create_run_genesis_request_with_chainspec_config, ChainspecConfig, ExecuteRequest, @@ -26,14 +25,9 @@ use cep18_test_contract::constants::{ ENTRY_POINT_CHECK_ALLOWANCE_OF, ENTRY_POINT_CHECK_BALANCE_OF, ENTRY_POINT_CHECK_TOTAL_SUPPLY, ENTRY_POINT_TRANSFER_AS_STORED_CONTRACT, RESULT_KEY, }; -use dotenv::dotenv; -use std::env; pub(crate) fn get_enable_addressable_entity() -> bool { - let _ = dotenv(); - env::var(TEST_ENABLE_ADDRESSABLE_ENTITY) - .map(|v| v == "true") - .unwrap_or(false) + cfg!(feature = "enable-addressable-entity") } /// Converts hash addr of Account into Hash, and Hash into Account From 1d3d0674a78b2ff7009173a42bab9dd12e4154e5 Mon Sep 17 00:00:00 2001 From: gRoussac Date: Tue, 19 Aug 2025 18:39:11 +0200 Subject: [PATCH 05/11] naming to_account_or_pachage_hash --- contracts/contract/src/allowances.rs | 10 +++++----- contracts/contract/src/balances.rs | 6 +++--- contracts/contract/src/utils.rs | 6 +++--- 3 files changed, 11 insertions(+), 11 deletions(-) diff --git a/contracts/contract/src/allowances.rs b/contracts/contract/src/allowances.rs index 5b625bad..fca8ebd5 100644 --- a/contracts/contract/src/allowances.rs +++ b/contracts/contract/src/allowances.rs @@ -3,23 +3,23 @@ use crate::{ constants::DICT_ALLOWANCES, utils::{ get_dictionary_value_from_key, make_dictionary_item_key_value, - set_dictionary_value_for_key, to_legacy_key, + set_dictionary_value_for_key, to_account_or_pachage_hash, }, }; use casper_types::{Key, U256}; /// Writes an allowance for owner and spender for a specific amount. pub fn write_allowance_to(owner: Key, spender: Key, amount: U256) { - let owner = to_legacy_key(owner); - let spender = to_legacy_key(spender); + let owner = to_account_or_pachage_hash(owner); + let spender = to_account_or_pachage_hash(spender); let dictionary_item_key = make_dictionary_item_key_value(&owner, &spender); set_dictionary_value_for_key(DICT_ALLOWANCES, &dictionary_item_key, &amount) } /// Reads an allowance for a owner and spender pub fn read_allowance_from(owner: Key, spender: Key) -> U256 { - let owner = to_legacy_key(owner); - let spender = to_legacy_key(spender); + let owner = to_account_or_pachage_hash(owner); + let spender = to_account_or_pachage_hash(spender); let dictionary_item_key = make_dictionary_item_key_value(&owner, &spender); get_dictionary_value_from_key(DICT_ALLOWANCES, &dictionary_item_key).unwrap_or_default() } diff --git a/contracts/contract/src/balances.rs b/contracts/contract/src/balances.rs index aee01798..56a87f7b 100644 --- a/contracts/contract/src/balances.rs +++ b/contracts/contract/src/balances.rs @@ -4,14 +4,14 @@ use crate::{ error::Cep18Error, utils::{ get_dictionary_value_from_key, make_dictionary_item_key, set_dictionary_value_for_key, - to_legacy_key, + to_account_or_pachage_hash, }, }; use casper_types::{Key, U256}; /// Writes token balance of a specified account into a dictionary. pub fn write_balance_to(address: Key, amount: U256) { - let address = to_legacy_key(address); + let address = to_account_or_pachage_hash(address); let dictionary_item_key = make_dictionary_item_key(address); set_dictionary_value_for_key(DICT_BALANCES, &dictionary_item_key, &amount) } @@ -20,7 +20,7 @@ pub fn write_balance_to(address: Key, amount: U256) { /// /// If a given account does not have balances in the system, then a 0 is returned. pub fn read_balance_from(address: Key) -> U256 { - let address = to_legacy_key(address); + let address = to_account_or_pachage_hash(address); let dictionary_item_key = make_dictionary_item_key(address); get_dictionary_value_from_key(DICT_BALANCES, &dictionary_item_key).unwrap_or_default() } diff --git a/contracts/contract/src/utils.rs b/contracts/contract/src/utils.rs index 06933ac4..59cdcd66 100644 --- a/contracts/contract/src/utils.rs +++ b/contracts/contract/src/utils.rs @@ -40,7 +40,7 @@ use core::convert::TryInto; /// /// ```ignore /// let caller: Key = get_immediate_caller(); -/// // `caller` can now be converted to a legacy key via `to_legacy_key` if needed +/// // `caller` can now be converted to a legacy key via `to_account_or_pachage_hash` if needed /// ``` pub fn get_immediate_caller() -> Key { const ACCOUNT: u8 = 0; @@ -106,10 +106,10 @@ pub fn get_immediate_caller() -> Key { /// /// ```ignore /// let caller = get_immediate_caller(); -/// let legacy_caller = to_legacy_key(caller); +/// let legacy_caller = to_account_or_pachage_hash(caller); /// // legacy_caller is now safe to use in CEP-18 storage lookups /// ``` -pub fn to_legacy_key(key: Key) -> Key { +pub fn to_account_or_pachage_hash(key: Key) -> Key { match key { Key::AddressableEntity(entity_addr) => { if entity_addr.is_account() { From 9b8e5736f731995748ff870b399fa2a50fd05057 Mon Sep 17 00:00:00 2001 From: gRoussac Date: Tue, 19 Aug 2025 18:44:33 +0200 Subject: [PATCH 06/11] naming feature --- Makefile | 2 +- tests/Cargo.toml | 2 +- tests/src/utility/installer_request_builders.rs | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Makefile b/Makefile index cb7642ea..71d1176b 100644 --- a/Makefile +++ b/Makefile @@ -35,7 +35,7 @@ native-test: setup-test test: setup-test cargo test -p tests --lib - cargo test -p tests --lib --features enable-addressable-entity + cargo test -p tests --lib --features test-enable-addressable-entity clippy: cargo +$(PINNED_TOOLCHAIN) clippy --release -p cep18 --bins --target wasm32-unknown-unknown $(CARGO_BUILD_FLAGS) -- -D warnings diff --git a/tests/Cargo.toml b/tests/Cargo.toml index 77b2dc08..0097477e 100644 --- a/tests/Cargo.toml +++ b/tests/Cargo.toml @@ -25,4 +25,4 @@ casper-execution-engine = { version = "8.1.0", default-features = false } casper-event-standard.workspace = true [features] -enable-addressable-entity = [] +test-enable-addressable-entity = [] diff --git a/tests/src/utility/installer_request_builders.rs b/tests/src/utility/installer_request_builders.rs index add82166..da047909 100644 --- a/tests/src/utility/installer_request_builders.rs +++ b/tests/src/utility/installer_request_builders.rs @@ -27,7 +27,7 @@ use cep18_test_contract::constants::{ }; pub(crate) fn get_enable_addressable_entity() -> bool { - cfg!(feature = "enable-addressable-entity") + cfg!(feature = "test-enable-addressable-entity") } /// Converts hash addr of Account into Hash, and Hash into Account From c46793a1b456005839594a8e7675680c7656fbac Mon Sep 17 00:00:00 2001 From: gRoussac Date: Mon, 25 Aug 2025 20:44:12 +0200 Subject: [PATCH 07/11] Call to_account_or_pachage_hash into get_immediate_caller for caller comparison cases --- contracts/contract/src/utils.rs | 20 +++----------------- 1 file changed, 3 insertions(+), 17 deletions(-) diff --git a/contracts/contract/src/utils.rs b/contracts/contract/src/utils.rs index 59cdcd66..1f69377a 100644 --- a/contracts/contract/src/utils.rs +++ b/contracts/contract/src/utils.rs @@ -35,13 +35,6 @@ use core::convert::TryInto; /// `ContractPackageHash`. /// * **ENTITY (new entity)** Returns a `Key::Hash` wrapping the `PackageHash`. /// * **Other / unexpected kinds** Reverts with [`Cep18Error::InvalidContext`]. -/// -/// # Example -/// -/// ```ignore -/// let caller: Key = get_immediate_caller(); -/// // `caller` can now be converted to a legacy key via `to_account_or_pachage_hash` if needed -/// ``` pub fn get_immediate_caller() -> Key { const ACCOUNT: u8 = 0; const PACKAGE: u8 = 1; @@ -51,7 +44,7 @@ pub fn get_immediate_caller() -> Key { let caller_info = casper_get_immediate_caller().unwrap_or_revert(); - match caller_info.kind() { + let caller = match caller_info.kind() { // Legacy or new entity account returns AccountHash ACCOUNT => caller_info .get_field_by_index(ACCOUNT) @@ -77,7 +70,8 @@ pub fn get_immediate_caller() -> Key { .unwrap_or_revert_with(Cep18Error::InvalidContext) .into(), _ => revert(Cep18Error::InvalidContext), - } + }; + to_account_or_pachage_hash(caller) } /// Converts a new-style [`Key`] returned by [`get_immediate_caller`] into a legacy-compatible @@ -101,14 +95,6 @@ pub fn get_immediate_caller() -> Key { /// /// - This function is mostly used in CEP-18 context where contract logic needs to interact with /// storage or access controls (balances, allowances, etc.). -/// -/// # Example -/// -/// ```ignore -/// let caller = get_immediate_caller(); -/// let legacy_caller = to_account_or_pachage_hash(caller); -/// // legacy_caller is now safe to use in CEP-18 storage lookups -/// ``` pub fn to_account_or_pachage_hash(key: Key) -> Key { match key { Key::AddressableEntity(entity_addr) => { From 1b411536ba88bab9a41f0b7f0a5a9f5f5050661b Mon Sep 17 00:00:00 2001 From: gRoussac Date: Mon, 25 Aug 2025 20:56:55 +0200 Subject: [PATCH 08/11] naming --- contracts/contract/src/allowances.rs | 10 +++++----- contracts/contract/src/balances.rs | 6 +++--- contracts/contract/src/utils.rs | 4 ++-- 3 files changed, 10 insertions(+), 10 deletions(-) diff --git a/contracts/contract/src/allowances.rs b/contracts/contract/src/allowances.rs index fca8ebd5..5156234a 100644 --- a/contracts/contract/src/allowances.rs +++ b/contracts/contract/src/allowances.rs @@ -3,23 +3,23 @@ use crate::{ constants::DICT_ALLOWANCES, utils::{ get_dictionary_value_from_key, make_dictionary_item_key_value, - set_dictionary_value_for_key, to_account_or_pachage_hash, + set_dictionary_value_for_key, to_account_or_pachage_hash_key, }, }; use casper_types::{Key, U256}; /// Writes an allowance for owner and spender for a specific amount. pub fn write_allowance_to(owner: Key, spender: Key, amount: U256) { - let owner = to_account_or_pachage_hash(owner); - let spender = to_account_or_pachage_hash(spender); + let owner = to_account_or_pachage_hash_key(owner); + let spender = to_account_or_pachage_hash_key(spender); let dictionary_item_key = make_dictionary_item_key_value(&owner, &spender); set_dictionary_value_for_key(DICT_ALLOWANCES, &dictionary_item_key, &amount) } /// Reads an allowance for a owner and spender pub fn read_allowance_from(owner: Key, spender: Key) -> U256 { - let owner = to_account_or_pachage_hash(owner); - let spender = to_account_or_pachage_hash(spender); + let owner = to_account_or_pachage_hash_key(owner); + let spender = to_account_or_pachage_hash_key(spender); let dictionary_item_key = make_dictionary_item_key_value(&owner, &spender); get_dictionary_value_from_key(DICT_ALLOWANCES, &dictionary_item_key).unwrap_or_default() } diff --git a/contracts/contract/src/balances.rs b/contracts/contract/src/balances.rs index 56a87f7b..1c9aaa7c 100644 --- a/contracts/contract/src/balances.rs +++ b/contracts/contract/src/balances.rs @@ -4,14 +4,14 @@ use crate::{ error::Cep18Error, utils::{ get_dictionary_value_from_key, make_dictionary_item_key, set_dictionary_value_for_key, - to_account_or_pachage_hash, + to_account_or_pachage_hash_key, }, }; use casper_types::{Key, U256}; /// Writes token balance of a specified account into a dictionary. pub fn write_balance_to(address: Key, amount: U256) { - let address = to_account_or_pachage_hash(address); + let address = to_account_or_pachage_hash_key(address); let dictionary_item_key = make_dictionary_item_key(address); set_dictionary_value_for_key(DICT_BALANCES, &dictionary_item_key, &amount) } @@ -20,7 +20,7 @@ pub fn write_balance_to(address: Key, amount: U256) { /// /// If a given account does not have balances in the system, then a 0 is returned. pub fn read_balance_from(address: Key) -> U256 { - let address = to_account_or_pachage_hash(address); + let address = to_account_or_pachage_hash_key(address); let dictionary_item_key = make_dictionary_item_key(address); get_dictionary_value_from_key(DICT_BALANCES, &dictionary_item_key).unwrap_or_default() } diff --git a/contracts/contract/src/utils.rs b/contracts/contract/src/utils.rs index 1f69377a..734b030d 100644 --- a/contracts/contract/src/utils.rs +++ b/contracts/contract/src/utils.rs @@ -71,7 +71,7 @@ pub fn get_immediate_caller() -> Key { .into(), _ => revert(Cep18Error::InvalidContext), }; - to_account_or_pachage_hash(caller) + to_account_or_pachage_hash_key(caller) } /// Converts a new-style [`Key`] returned by [`get_immediate_caller`] into a legacy-compatible @@ -95,7 +95,7 @@ pub fn get_immediate_caller() -> Key { /// /// - This function is mostly used in CEP-18 context where contract logic needs to interact with /// storage or access controls (balances, allowances, etc.). -pub fn to_account_or_pachage_hash(key: Key) -> Key { +pub fn to_account_or_pachage_hash_key(key: Key) -> Key { match key { Key::AddressableEntity(entity_addr) => { if entity_addr.is_account() { From a85088077c2dfa39e9921067c423f5f2d4565942 Mon Sep 17 00:00:00 2001 From: gRoussac Date: Mon, 25 Aug 2025 20:58:51 +0200 Subject: [PATCH 09/11] naming --- contracts/contract/src/allowances.rs | 12 ++++++------ contracts/contract/src/balances.rs | 6 +++--- contracts/contract/src/utils.rs | 4 ++-- 3 files changed, 11 insertions(+), 11 deletions(-) diff --git a/contracts/contract/src/allowances.rs b/contracts/contract/src/allowances.rs index 5156234a..ec9dd839 100644 --- a/contracts/contract/src/allowances.rs +++ b/contracts/contract/src/allowances.rs @@ -2,24 +2,24 @@ use crate::{ constants::DICT_ALLOWANCES, utils::{ - get_dictionary_value_from_key, make_dictionary_item_key_value, - set_dictionary_value_for_key, to_account_or_pachage_hash_key, + get_dictionary_value_from_key, key_as_account_or_package, make_dictionary_item_key_value, + set_dictionary_value_for_key, }, }; use casper_types::{Key, U256}; /// Writes an allowance for owner and spender for a specific amount. pub fn write_allowance_to(owner: Key, spender: Key, amount: U256) { - let owner = to_account_or_pachage_hash_key(owner); - let spender = to_account_or_pachage_hash_key(spender); + let owner = key_as_account_or_package(owner); + let spender = key_as_account_or_package(spender); let dictionary_item_key = make_dictionary_item_key_value(&owner, &spender); set_dictionary_value_for_key(DICT_ALLOWANCES, &dictionary_item_key, &amount) } /// Reads an allowance for a owner and spender pub fn read_allowance_from(owner: Key, spender: Key) -> U256 { - let owner = to_account_or_pachage_hash_key(owner); - let spender = to_account_or_pachage_hash_key(spender); + let owner = key_as_account_or_package(owner); + let spender = key_as_account_or_package(spender); let dictionary_item_key = make_dictionary_item_key_value(&owner, &spender); get_dictionary_value_from_key(DICT_ALLOWANCES, &dictionary_item_key).unwrap_or_default() } diff --git a/contracts/contract/src/balances.rs b/contracts/contract/src/balances.rs index 1c9aaa7c..2550bd56 100644 --- a/contracts/contract/src/balances.rs +++ b/contracts/contract/src/balances.rs @@ -4,14 +4,14 @@ use crate::{ error::Cep18Error, utils::{ get_dictionary_value_from_key, make_dictionary_item_key, set_dictionary_value_for_key, - to_account_or_pachage_hash_key, + key_as_account_or_package, }, }; use casper_types::{Key, U256}; /// Writes token balance of a specified account into a dictionary. pub fn write_balance_to(address: Key, amount: U256) { - let address = to_account_or_pachage_hash_key(address); + let address = key_as_account_or_package(address); let dictionary_item_key = make_dictionary_item_key(address); set_dictionary_value_for_key(DICT_BALANCES, &dictionary_item_key, &amount) } @@ -20,7 +20,7 @@ pub fn write_balance_to(address: Key, amount: U256) { /// /// If a given account does not have balances in the system, then a 0 is returned. pub fn read_balance_from(address: Key) -> U256 { - let address = to_account_or_pachage_hash_key(address); + let address = key_as_account_or_package(address); let dictionary_item_key = make_dictionary_item_key(address); get_dictionary_value_from_key(DICT_BALANCES, &dictionary_item_key).unwrap_or_default() } diff --git a/contracts/contract/src/utils.rs b/contracts/contract/src/utils.rs index 734b030d..840ecc55 100644 --- a/contracts/contract/src/utils.rs +++ b/contracts/contract/src/utils.rs @@ -71,7 +71,7 @@ pub fn get_immediate_caller() -> Key { .into(), _ => revert(Cep18Error::InvalidContext), }; - to_account_or_pachage_hash_key(caller) + key_as_account_or_package(caller) } /// Converts a new-style [`Key`] returned by [`get_immediate_caller`] into a legacy-compatible @@ -95,7 +95,7 @@ pub fn get_immediate_caller() -> Key { /// /// - This function is mostly used in CEP-18 context where contract logic needs to interact with /// storage or access controls (balances, allowances, etc.). -pub fn to_account_or_pachage_hash_key(key: Key) -> Key { +pub fn key_as_account_or_package(key: Key) -> Key { match key { Key::AddressableEntity(entity_addr) => { if entity_addr.is_account() { From 0c3b20bbf86feb718973647dabebcdbaff833aef Mon Sep 17 00:00:00 2001 From: gRoussac Date: Mon, 25 Aug 2025 21:25:43 +0200 Subject: [PATCH 10/11] Reinforce Key = runtime::get_named_arg to call key_as_account_or_package --- contracts/contract/src/main.rs | 24 ++++++++++++------------ contracts/contract/src/utils.rs | 12 ++++++++++++ 2 files changed, 24 insertions(+), 12 deletions(-) diff --git a/contracts/contract/src/main.rs b/contracts/contract/src/main.rs index 7546d42b..3c7e8560 100644 --- a/contracts/contract/src/main.rs +++ b/contracts/contract/src/main.rs @@ -43,7 +43,7 @@ use cep18::{ utils::{ base64_encode, get_contract_version_key, get_immediate_caller, get_optional_named_arg_with_user_errors, get_stored_value, get_uref_with_user_errors, - write_total_supply_to, + write_total_supply_to, UpsertTransform, }, }; @@ -81,7 +81,7 @@ pub extern "C" fn total_supply() { #[no_mangle] pub extern "C" fn balance_of() { - let address: Key = runtime::get_named_arg(ARG_ADDRESS); + let address: Key = runtime::get_named_arg::(ARG_ADDRESS).key_as_account_or_package(); let balance = read_balance_from(address); runtime::ret( CLValue::from_t(balance).unwrap_or_revert_with(Cep18Error::FailedToReturnEntryPointResult), @@ -90,8 +90,8 @@ pub extern "C" fn balance_of() { #[no_mangle] pub extern "C" fn allowance() { - let spender: Key = runtime::get_named_arg(ARG_SPENDER); - let owner: Key = runtime::get_named_arg(ARG_OWNER); + let spender: Key = runtime::get_named_arg::(ARG_SPENDER).key_as_account_or_package(); + let owner: Key = runtime::get_named_arg::(ARG_OWNER).key_as_account_or_package(); let val: U256 = read_allowance_from(owner, spender); runtime::ret( CLValue::from_t(val).unwrap_or_revert_with(Cep18Error::FailedToReturnEntryPointResult), @@ -101,7 +101,7 @@ pub extern "C" fn allowance() { #[no_mangle] pub extern "C" fn approve() { let caller = get_immediate_caller(); - let spender: Key = runtime::get_named_arg(ARG_SPENDER); + let spender: Key = runtime::get_named_arg::(ARG_SPENDER).key_as_account_or_package(); if spender == caller { revert(Cep18Error::CannotTargetSelfUser); } @@ -117,7 +117,7 @@ pub extern "C" fn approve() { #[no_mangle] pub extern "C" fn decrease_allowance() { let caller = get_immediate_caller(); - let spender: Key = runtime::get_named_arg(ARG_SPENDER); + let spender: Key = runtime::get_named_arg::(ARG_SPENDER).key_as_account_or_package(); if spender == caller { revert(Cep18Error::CannotTargetSelfUser); } @@ -136,7 +136,7 @@ pub extern "C" fn decrease_allowance() { #[no_mangle] pub extern "C" fn increase_allowance() { let caller = get_immediate_caller(); - let spender: Key = runtime::get_named_arg(ARG_SPENDER); + let spender: Key = runtime::get_named_arg::(ARG_SPENDER).key_as_account_or_package(); if spender == caller { revert(Cep18Error::CannotTargetSelfUser); } @@ -155,7 +155,7 @@ pub extern "C" fn increase_allowance() { #[no_mangle] pub extern "C" fn transfer() { let caller = get_immediate_caller(); - let recipient: Key = runtime::get_named_arg(ARG_RECIPIENT); + let recipient: Key = runtime::get_named_arg::(ARG_RECIPIENT).key_as_account_or_package(); if caller == recipient { revert(Cep18Error::CannotTargetSelfUser); } @@ -171,8 +171,8 @@ pub extern "C" fn transfer() { #[no_mangle] pub extern "C" fn transfer_from() { let caller = get_immediate_caller(); - let recipient: Key = runtime::get_named_arg(ARG_RECIPIENT); - let owner: Key = runtime::get_named_arg(ARG_OWNER); + let recipient: Key = runtime::get_named_arg::(ARG_RECIPIENT).key_as_account_or_package(); + let owner: Key = runtime::get_named_arg::(ARG_OWNER).key_as_account_or_package(); if owner == recipient { revert(Cep18Error::CannotTargetSelfUser); } @@ -204,7 +204,7 @@ pub extern "C" fn mint() { sec_check(vec![SecurityBadge::Admin, SecurityBadge::Minter]); - let owner: Key = runtime::get_named_arg(ARG_OWNER); + let owner: Key = runtime::get_named_arg::(ARG_OWNER).key_as_account_or_package(); let amount: U256 = runtime::get_named_arg(ARG_AMOUNT); let new_balance = { @@ -236,7 +236,7 @@ pub extern "C" fn burn() { revert(Cep18Error::MintBurnDisabled); } - let owner: Key = runtime::get_named_arg(ARG_OWNER); + let owner: Key = runtime::get_named_arg::(ARG_OWNER).key_as_account_or_package(); let caller = get_immediate_caller(); if owner != caller { revert(Cep18Error::InvalidBurnTarget); diff --git a/contracts/contract/src/utils.rs b/contracts/contract/src/utils.rs index 840ecc55..5d64d8c6 100644 --- a/contracts/contract/src/utils.rs +++ b/contracts/contract/src/utils.rs @@ -117,6 +117,18 @@ pub fn key_as_account_or_package(key: Key) -> Key { } } +pub trait UpsertTransform: Sized { + fn key_as_account_or_package(self) -> Self { + self + } +} + +impl UpsertTransform for Key { + fn key_as_account_or_package(self) -> Self { + key_as_account_or_package(self) + } +} + pub fn get_contract_version_key(contract_version: u32) -> ContractVersionKey { let (major, _, _) = get_protocol_version().destructure(); ContractVersionKey::new(major, contract_version) From 0af4d2e197a5201d3b8f191878a78335c833cc2b Mon Sep 17 00:00:00 2001 From: gRoussac Date: Mon, 25 Aug 2025 22:19:34 +0200 Subject: [PATCH 11/11] Doc comment --- contracts/contract/src/utils.rs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/contracts/contract/src/utils.rs b/contracts/contract/src/utils.rs index 5d64d8c6..43726aaa 100644 --- a/contracts/contract/src/utils.rs +++ b/contracts/contract/src/utils.rs @@ -71,6 +71,12 @@ pub fn get_immediate_caller() -> Key { .into(), _ => revert(Cep18Error::InvalidContext), }; + + // Transform the caller Key to a legacy-compatible form (Account or Hash) for consistent + // on-chain usage. + // ⚠️ Strongly recommended: apply `key_as_account_or_package()` to any `Key` retrieved from + // named arguments before comparing with the caller. This ensures consistent normalization + // between user input and immediate caller, preventing mismatches. key_as_account_or_package(caller) }