diff --git a/Cargo.lock b/Cargo.lock index 264215063..a1a77fdfe 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -5079,7 +5079,7 @@ dependencies = [ "pallet-collator-selection", "pallet-ethereum", "pallet-evm", - "pallet-evm-asset-metadata-extender-legacy", + "pallet-evm-asset-metadata-extender", "pallet-evm-chain-id", "pallet-evm-evolution-collection", "pallet-evm-evolution-collection-factory", @@ -5347,7 +5347,6 @@ dependencies = [ "pallet-base-fee", "pallet-ethereum", "pallet-evm", - "pallet-evm-asset-metadata-extender", "pallet-evm-chain-id", "pallet-evm-precompile-blake2", "pallet-evm-precompile-bn128", @@ -6885,10 +6884,16 @@ dependencies = [ name = "pallet-asset-metadata-extender" version = "0.1.0" dependencies = [ + "fp-evm", "frame-benchmarking", "frame-support", "frame-system", + "hex", + "pallet-balances", + "pallet-evm", + "pallet-timestamp", "parity-scale-codec", + "precompile-utils", "scale-info", "sp-core", "sp-io", @@ -7334,30 +7339,6 @@ dependencies = [ "sp-std", ] -[[package]] -name = "pallet-evm-asset-metadata-extender-legacy" -version = "0.1.0" -dependencies = [ - "fp-evm", - "frame-support", - "frame-system", - "hex", - "laos-precompile-utils", - "laos-precompile-utils-macro", - "num_enum", - "pallet-asset-metadata-extender", - "pallet-balances", - "pallet-evm", - "pallet-timestamp", - "parity-scale-codec", - "precompile-utils", - "scale-info", - "sp-core", - "sp-io", - "sp-runtime", - "sp-std", -] - [[package]] name = "pallet-evm-chain-id" version = "1.0.0-dev" diff --git a/Cargo.toml b/Cargo.toml index 0b1e936bd..77f5d9d84 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -256,7 +256,6 @@ pallet-evm-evolution-collection = { path = "./precompile/evolution-collection", pallet-evm-erc721 = { path = "./precompile/erc721", default-features = false } pallet-asset-metadata-extender = { path = "./pallets/asset-metadata-extender", default-features = false } pallet-evm-asset-metadata-extender = { path = "./precompile/asset-metadata-extender", default-features = false } -pallet-evm-asset-metadata-extender-legacy = { path = "./precompile/asset-metadata-extender-legacy", default-features = false } pallet-parachain-staking = { path = "./pallets/parachain-staking", default-features = false } # Precompiles diff --git a/pallets/asset-metadata-extender/Cargo.toml b/pallets/asset-metadata-extender/Cargo.toml index c114b1977..685401894 100644 --- a/pallets/asset-metadata-extender/Cargo.toml +++ b/pallets/asset-metadata-extender/Cargo.toml @@ -18,10 +18,17 @@ sp-runtime = { workspace = true } sp-core = { workspace = true } frame-benchmarking = { optional = true, workspace = true } sp-std = { workspace = true } +fp-evm = { workspace = true } +precompile-utils = { workspace = true, default-features = false } +sp-io = { workspace = true } +pallet-evm = { workspace = true } [dev-dependencies] -sp-io = { workspace = true } sp-runtime = { workspace = true } +precompile-utils = { workspace = true, features = ["testing"] } +pallet-balances = { workspace = true, features = ["std", "insecure_zero_ed"] } +pallet-timestamp = { workspace = true } +hex = { workspace = true } [features] default = ["std"] @@ -34,6 +41,9 @@ std = [ "sp-runtime/std", "frame-benchmarking/std", "sp-std/std", + "fp-evm/std", + "precompile-utils/std", + "pallet-evm/std", ] runtime-benchmarks = [ diff --git a/pallets/asset-metadata-extender/src/lib.rs b/pallets/asset-metadata-extender/src/lib.rs index d5eedbc5a..a431b16dc 100644 --- a/pallets/asset-metadata-extender/src/lib.rs +++ b/pallets/asset-metadata-extender/src/lib.rs @@ -17,12 +17,14 @@ #![cfg_attr(not(feature = "std"), no_std)] mod benchmarking; +pub mod precompiles; pub mod traits; pub mod types; pub mod weights; use frame_support::pallet_prelude::*; pub use pallet::*; +use pallet_evm::GasWeightMapping; use sp_core::H160; use sp_runtime::{ traits::{Convert, One}, @@ -59,6 +61,9 @@ pub mod pallet { /// Converts `Self::AccountId` to `H160` type AccountIdToH160: Convert; + + /// Gas weight mapping + type GasWeightMapping: GasWeightMapping; } /// Extensions counter for a given location diff --git a/pallets/asset-metadata-extender/src/mock.rs b/pallets/asset-metadata-extender/src/mock.rs index f11fa6591..506674818 100644 --- a/pallets/asset-metadata-extender/src/mock.rs +++ b/pallets/asset-metadata-extender/src/mock.rs @@ -15,7 +15,7 @@ // along with LAOS. If not, see . use crate as pallet_asset_metadata_extender; -use frame_support::{derive_impl, parameter_types}; +use frame_support::{derive_impl, pallet_prelude::Weight, parameter_types}; use sp_core::H160; use sp_runtime::{traits::IdentityLookup, BuildStorage}; @@ -49,6 +49,7 @@ impl pallet_asset_metadata_extender::Config for Test { type MaxUniversalLocationLength = MaxUniversalLocationLength; type MaxTokenUriLength = MaxTokenUriLength; type AccountIdToH160 = AccountIdToH160; + type GasWeightMapping = MockGasWeightMapping; } pub struct AccountIdToH160; @@ -59,6 +60,16 @@ impl sp_runtime::traits::Convert for AccountIdToH160 { } } +pub struct MockGasWeightMapping; +impl pallet_evm::GasWeightMapping for MockGasWeightMapping { + fn gas_to_weight(gas: u64, _without_base_weight: bool) -> Weight { + Weight::from_parts(gas, 0) + } + fn weight_to_gas(weight: Weight) -> u64 { + weight.ref_time() + } +} + // Build genesis storage according to the mock runtime. pub fn new_test_ext() -> sp_io::TestExternalities { frame_system::GenesisConfig::::default().build_storage().unwrap().into() diff --git a/precompile/asset-metadata-extender-legacy/src/mock.rs b/pallets/asset-metadata-extender/src/precompiles/asset_metadata_extender/mock.rs similarity index 79% rename from precompile/asset-metadata-extender-legacy/src/mock.rs rename to pallets/asset-metadata-extender/src/precompiles/asset_metadata_extender/mock.rs index 53490dbd9..a44fce5cf 100644 --- a/precompile/asset-metadata-extender-legacy/src/mock.rs +++ b/pallets/asset-metadata-extender/src/precompiles/asset_metadata_extender/mock.rs @@ -15,14 +15,15 @@ // along with LAOS. If not, see . use core::str::FromStr; -use fp_evm::{Precompile, PrecompileHandle}; use frame_support::{ derive_impl, parameter_types, traits::FindAuthor, weights::constants::RocksDbWeight, }; +use precompile_utils::precompile_set::{AddressU64, PrecompileAt, PrecompileSetBuilder}; use sp_core::{H160, U256}; use sp_runtime::{traits::IdentityLookup, BuildStorage, ConsensusEngineId}; -use crate::AssetMetadataExtenderPrecompile; +use super::{AssetMetadataExtenderPrecompile, AssetMetadataExtenderPrecompileCall}; +use crate as pallet_asset_metadata_extender; // Configure a mock runtime to test the pallet. frame_support::construct_runtime!( @@ -69,6 +70,7 @@ impl pallet_asset_metadata_extender::Config for Test { type MaxUniversalLocationLength = MaxUniversalLocationLength; type MaxTokenUriLength = MaxTokenUriLength; type AccountIdToH160 = AccountIdToH160; + type GasWeightMapping = pallet_evm::FixedGasWeightMapping; } pub struct AccountIdToH160; @@ -95,8 +97,8 @@ impl pallet_evm::Config for Test { type AddressMapping = pallet_evm::IdentityAddressMapping; type Currency = Balances; type RuntimeEvent = RuntimeEvent; - type PrecompilesType = MockPrecompileSet; - type PrecompilesValue = MockPrecompiles; + type PrecompilesType = LaosPrecompiles; + type PrecompilesValue = PrecompilesInstance; type ChainId = (); type BlockGasLimit = BlockGasLimit; type Runner = pallet_evm::runner::stack::Runner; @@ -111,12 +113,19 @@ impl pallet_evm::Config for Test { pub const BLOCK_GAS_LIMIT: u64 = 15_000_000; pub const MAX_POV_SIZE: u64 = 5 * 1024 * 1024; +pub type PrecompileCall = AssetMetadataExtenderPrecompileCall; + +pub type LaosPrecompiles = PrecompileSetBuilder< + Test, + (PrecompileAt, AssetMetadataExtenderPrecompile>,), +>; + frame_support::parameter_types! { - pub BlockGasLimit: U256 = U256::from(crate::mock::BLOCK_GAS_LIMIT); - pub const GasLimitPovSizeRatio: u64 = crate::mock::BLOCK_GAS_LIMIT.saturating_div(crate::mock::MAX_POV_SIZE); + pub BlockGasLimit: U256 = U256::from(BLOCK_GAS_LIMIT); + pub const GasLimitPovSizeRatio: u64 = BLOCK_GAS_LIMIT.saturating_div(MAX_POV_SIZE); /// 1 weight to 1 gas, for testing purposes pub WeightPerGas: frame_support::weights::Weight = frame_support::weights::Weight::from_parts(1, 0); - pub MockPrecompiles: MockPrecompileSet = MockPrecompileSet::<_>::new(); + pub PrecompilesInstance: LaosPrecompiles = LaosPrecompiles::new(); } pub struct FindAuthorTruncated; @@ -129,33 +138,6 @@ impl FindAuthor for FindAuthorTruncated { } } -#[derive(Default)] -pub struct MockPrecompileSet(sp_std::marker::PhantomData); - -pub type MockAssetMetadataExtenderPrecompile = AssetMetadataExtenderPrecompile; - -impl MockPrecompileSet -where - Test: pallet_evm::Config, -{ - pub fn new() -> Self { - Self(Default::default()) - } -} - -impl fp_evm::PrecompileSet for MockPrecompileSet -where - Test: pallet_evm::Config + pallet_asset_metadata_extender::Config, -{ - fn execute(&self, handle: &mut impl PrecompileHandle) -> Option { - Some(MockAssetMetadataExtenderPrecompile::execute(handle)) - } - - fn is_precompile(&self, _address: H160, _gas: u64) -> fp_evm::IsPrecompileResult { - fp_evm::IsPrecompileResult::Answer { is_precompile: true, extra_cost: 0 } - } -} - // Pallet Timestamp parameter_types! { pub const MinimumPeriod: u64 = 1000; diff --git a/pallets/asset-metadata-extender/src/precompiles/asset_metadata_extender/mod.rs b/pallets/asset-metadata-extender/src/precompiles/asset_metadata_extender/mod.rs new file mode 100644 index 000000000..575c35725 --- /dev/null +++ b/pallets/asset-metadata-extender/src/precompiles/asset_metadata_extender/mod.rs @@ -0,0 +1,309 @@ +// Copyright 2023-2024 Freeverse.io +// This file is part of LAOS. + +// LAOS is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// LAOS is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with LAOS. If not, see . + +#![cfg_attr(not(feature = "std"), no_std)] +use crate::{ + traits::AssetMetadataExtender as AssetMetadataExtenderT, + weights::{SubstrateWeight as AssetMetadataExtenderWeights, WeightInfo}, + Config, Pallet as AssetMetadataExtender, +}; +use fp_evm::PrecompileHandle; +use frame_support::DefaultNoBound; +use pallet_evm::GasWeightMapping; +use parity_scale_codec::Encode; +use precompile_utils::{ + prelude::{keccak256, log3, Address, EvmResult, LogExt}, + solidity::{self, codec::UnboundedString, revert::revert}, +}; +use scale_info::prelude::{format, string::String}; +use sp_core::H160; +use sp_io::hashing::keccak_256; +use sp_runtime::{BoundedVec, DispatchError}; +use sp_std::{fmt::Debug, marker::PhantomData}; + +/// Solidity selector of the ExtendedULWithExternalURI log, which is the Keccak of the Log +/// signature. +pub const SELECTOR_LOG_EXTENDED_UL_WITH_EXTERNAL_URI: [u8; 32] = + keccak256!("ExtendedULWithExternalURI(address,bytes32,string,string)"); +/// Solidity selector of the UpdatedExtendedULWithExternalURI log, which is the Keccak of the Log +/// signature. +pub const SELECTOR_LOG_UPDATED_EXTENDED_UL_WITH_EXTERNAL_URI: [u8; 32] = + keccak256!("UpdatedExtendedULWithExternalURI(address,bytes32,string,string)"); + +#[derive(DefaultNoBound)] +pub struct AssetMetadataExtenderPrecompile(PhantomData); + +impl AssetMetadataExtenderPrecompile { + pub fn new() -> Self { + Self(PhantomData) + } +} + +#[precompile_utils::precompile] +impl AssetMetadataExtenderPrecompile +where + Runtime: pallet_evm::Config + crate::Config, + Runtime::AccountId: From + Into + Encode + Debug, + AssetMetadataExtender: AssetMetadataExtenderT, +{ + #[precompile::public("extendULWithExternalURI(string,string)")] + fn extend( + handle: &mut impl PrecompileHandle, + universal_location: UnboundedString, + token_uri: UnboundedString, + ) -> EvmResult<()> { + // TODO this might be remove when we have the bounded string as param + let universal_location_bounded: BoundedVec< + u8, + ::MaxUniversalLocationLength, + > = universal_location + .as_bytes() + .to_vec() + .try_into() + .map_err(|_| revert("invalid universal location length"))?; + + // TODO this might be remove when we have the bounded string as param + let token_uri_bounded: BoundedVec::MaxTokenUriLength> = token_uri + .as_bytes() + .to_vec() + .try_into() + .map_err(|_| revert("invalid token uri length"))?; + + AssetMetadataExtender::::create_token_uri_extension( + handle.context().caller.into(), + universal_location_bounded.clone(), + token_uri_bounded.clone(), + ) + .map_err(|err| revert(convert_dispatch_error_to_string(err)))?; + + let consumed_weight = AssetMetadataExtenderWeights::::create_token_uri_extension( + universal_location.as_bytes().to_vec().len() as u32, + token_uri.as_bytes().to_vec().len() as u32, + ); + + let ul_hash = keccak_256(universal_location.as_bytes()); + log3( + handle.context().address, + SELECTOR_LOG_EXTENDED_UL_WITH_EXTERNAL_URI, + handle.context().caller, + ul_hash, + solidity::encode_event_data((universal_location, token_uri)), + ) + .record(handle)?; + + // Record EVM cost + handle.record_cost(::GasWeightMapping::weight_to_gas( + consumed_weight, + ))?; + + // Record Substrate related costs + // TODO: Add `ref_time` when precompiles are benchmarked + handle.record_external_cost(None, Some(consumed_weight.proof_size()))?; + + Ok(()) + } + + #[precompile::public("updateExtendedULWithExternalURI(string,string)")] + fn update( + handle: &mut impl PrecompileHandle, + universal_location: UnboundedString, + token_uri: UnboundedString, + ) -> EvmResult<()> { + // TODO this might be remove when we have the bounded string as param + let universal_location_bounded: BoundedVec< + u8, + ::MaxUniversalLocationLength, + > = universal_location + .as_bytes() + .to_vec() + .try_into() + .map_err(|_| revert("invalid universal location length"))?; + + // TODO this might be remove when we have the bounded string as param + let token_uri_bounded: BoundedVec::MaxTokenUriLength> = token_uri + .as_bytes() + .to_vec() + .try_into() + .map_err(|_| revert("invalid token uri length"))?; + + AssetMetadataExtender::::update_token_uri_extension( + handle.context().caller.into(), + universal_location_bounded.clone(), + token_uri_bounded.clone(), + ) + .map_err(|err| revert(convert_dispatch_error_to_string(err)))?; + + let consumed_weight = AssetMetadataExtenderWeights::::update_token_uri_extension( + universal_location.as_bytes().to_vec().len() as u32, + token_uri.as_bytes().to_vec().len() as u32, + ); + + let ul_hash = keccak_256(universal_location.as_bytes()); + log3( + handle.context().address, + SELECTOR_LOG_UPDATED_EXTENDED_UL_WITH_EXTERNAL_URI, + handle.context().caller, + ul_hash, + solidity::encode_event_data((universal_location, token_uri)), + ) + .record(handle)?; + + // Record EVM cost + handle.record_cost(::GasWeightMapping::weight_to_gas( + consumed_weight, + ))?; + + Ok(()) + } + + #[precompile::public("balanceOfUL(string)")] + fn balance_of( + _handle: &mut impl PrecompileHandle, + universal_location: UnboundedString, + ) -> EvmResult { + // TODO this might be remove when we have the bounded string as param + let universal_location_bounded: BoundedVec< + u8, + ::MaxUniversalLocationLength, + > = universal_location + .as_bytes() + .to_vec() + .try_into() + .map_err(|_| revert("invalid universal location length"))?; + + let balance = AssetMetadataExtender::::balance_of(universal_location_bounded); + + Ok(balance) + } + + #[precompile::public("claimerOfULByIndex(string,uint32)")] + fn claimer_by_index( + _handle: &mut impl PrecompileHandle, + universal_location: UnboundedString, + index: u32, + ) -> EvmResult
{ + // TODO this might be remove when we have the bounded string as param + let universal_location_bounded: BoundedVec< + u8, + ::MaxUniversalLocationLength, + > = universal_location + .as_bytes() + .to_vec() + .try_into() + .map_err(|_| revert("invalid universal location length"))?; + + if AssetMetadataExtender::balance_of(universal_location_bounded.clone()) <= index { + return Err(revert("invalid index")); + } + + let claimer = AssetMetadataExtender::::claimer_by_index( + universal_location_bounded.clone(), + index, + ) + .ok_or_else(|| revert("invalid ul"))?; + + Ok(Address(claimer.into())) + } + + #[precompile::public("extensionOfULByIndex(string,uint32)")] + fn extension_by_index( + _handle: &mut impl PrecompileHandle, + universal_location: UnboundedString, + index: u32, + ) -> EvmResult { + let universal_location_bounded: BoundedVec< + u8, + ::MaxUniversalLocationLength, + > = universal_location + .as_bytes() + .to_vec() + .try_into() + .map_err(|_| revert("invalid universal location length"))?; + + if AssetMetadataExtender::balance_of(universal_location_bounded.clone()) <= index { + return Err(revert("invalid index")); + } + + let token_uri = AssetMetadataExtender::::token_uri_extension_by_index( + universal_location_bounded.clone(), + index, + ) + .ok_or_else(|| revert("invalid ul"))?; + + Ok(token_uri.to_vec().into()) + } + + #[precompile::public("extensionOfULByClaimer(string,address)")] + fn extension_by_location_and_claimer( + _handle: &mut impl PrecompileHandle, + universal_location: UnboundedString, + claimer: Address, + ) -> EvmResult { + let claimer: H160 = claimer.into(); + let universal_location_bounded: BoundedVec< + u8, + ::MaxUniversalLocationLength, + > = universal_location + .as_bytes() + .to_vec() + .try_into() + .map_err(|_| revert("invalid universal location length"))?; + + let token_uri = AssetMetadataExtender::::extension_by_location_and_claimer( + universal_location_bounded.clone(), + claimer.into(), + ) + .ok_or_else(|| revert("invalid ul"))?; + + Ok(token_uri.to_vec().into()) + } + + #[precompile::public("hasExtensionByClaimer(string,address)")] + fn has_extension_by_claimer( + _handle: &mut impl PrecompileHandle, + universal_location: UnboundedString, + claimer: Address, + ) -> EvmResult { + let claimer: H160 = claimer.into(); + let universal_location_bounded: BoundedVec< + u8, + ::MaxUniversalLocationLength, + > = universal_location + .as_bytes() + .to_vec() + .try_into() + .map_err(|_| revert("invalid universal location length"))?; + + let has_extension = AssetMetadataExtender::::has_extension( + universal_location_bounded.clone(), + claimer.into(), + ); + + Ok(has_extension) + } +} + +fn convert_dispatch_error_to_string(err: DispatchError) -> String { + match err { + DispatchError::Module(mod_err) => mod_err.message.unwrap_or("Unknown module error").into(), + _ => format!("{:?}", err), + } +} + +#[cfg(test)] +mod mock; +#[cfg(test)] +mod tests; diff --git a/pallets/asset-metadata-extender/src/precompiles/asset_metadata_extender/tests.rs b/pallets/asset-metadata-extender/src/precompiles/asset_metadata_extender/tests.rs new file mode 100644 index 000000000..23e499ea8 --- /dev/null +++ b/pallets/asset-metadata-extender/src/precompiles/asset_metadata_extender/tests.rs @@ -0,0 +1,530 @@ +// Copyright 2023-2024 Freeverse.io +// This file is part of LAOS. + +// LAOS is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// LAOS is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with LAOS. If not, see . + +use super::*; +use fp_evm::{Context, PrecompileSet}; +use mock::*; +use precompile_utils::{ + prelude::log3, + testing::{Alice, MockHandle, Precompile1, PrecompileTesterExt}, +}; +use sp_core::U256; +use sp_io::hashing::keccak_256; + +/// Get precompiles from the mock. +fn precompiles() -> LaosPrecompiles { + PrecompilesInstance::get() +} + +/// Utility function to extend an universal location. +/// +/// Note: this function is used instead of `PrecompileTesterExt::execute_returns` because the latter +/// does not return the output of the precompile. And `PrecompileTester::execute` is a private +/// function. +fn extend(universal_location: UnboundedString, token_uri: UnboundedString) { + let mut handle = MockHandle::new( + Precompile1.into(), + Context { address: Precompile1.into(), caller: Alice.into(), apparent_value: U256::zero() }, + ); + handle.input = PrecompileCall::extend { universal_location, token_uri }.into(); + + precompiles().execute(&mut handle).unwrap().unwrap(); +} + +#[test] +fn check_log_selectors() { + assert_eq!( + hex::encode(SELECTOR_LOG_EXTENDED_UL_WITH_EXTERNAL_URI), + "f744da499cb735a8fc987aa2a331a1cbeca79e449e4c04eeccfe57c538e79070" + ); + assert_eq!( + hex::encode(SELECTOR_LOG_UPDATED_EXTENDED_UL_WITH_EXTERNAL_URI), + "e7ebe38355126fe0c3eab0ec03eb1b94ff501458a80713c9eb8b737334a651ff" + ); +} + +#[test] +fn selectors() { + assert!(PrecompileCall::extend_selectors().contains(&0xA5FBDF1D)); + assert!(PrecompileCall::update_selectors().contains(&0xCD79C745)); + assert!(PrecompileCall::balance_of_selectors().contains(&0x7B65DED5)); + assert!(PrecompileCall::claimer_by_index_selectors().contains(&0xA565BB04)); + assert!(PrecompileCall::extension_by_index_selectors().contains(&0xB2B7C05A)); +} + +#[test] +fn create_token_uri_extension_should_emit_log() { + new_test_ext().execute_with(|| { + let universal_location: UnboundedString = "my_awesome_universal_location".into(); + let token_uri: UnboundedString = "ciao".into(); + + precompiles() + .prepare_test( + Alice, + Precompile1, + PrecompileCall::extend { + universal_location: universal_location.clone(), + token_uri: token_uri.clone(), + }, + ) + .expect_log(log3( + Precompile1, + SELECTOR_LOG_EXTENDED_UL_WITH_EXTERNAL_URI, + Alice, + keccak_256(universal_location.as_bytes()), + solidity::encode_event_data((universal_location, token_uri)), + )) + .execute_some(); + }); +} + +#[test] +fn create_token_uri_extension_reverts_when_ul_exceeds_length() { + new_test_ext().execute_with(|| { + let unallowed_size = (MaxUniversalLocationLength::get() + 10).try_into().unwrap(); + let universal_location: UnboundedString = vec![b'a'; unallowed_size].into(); + let token_uri: UnboundedString = "ciao".into(); + + precompiles() + .prepare_test( + Alice, + Precompile1, + PrecompileCall::extend { + universal_location: universal_location.clone(), + token_uri: token_uri.clone(), + }, + ) + .execute_reverts(|r| r == b"invalid universal location length"); + }); +} + +#[test] +fn create_token_uri_extension_reverts_when_token_uri_exceeds_length() { + new_test_ext().execute_with(|| { + let unallowed_size = (MaxTokenUriLength::get() + 10).try_into().unwrap(); + let token_uri: UnboundedString = vec![b'a'; unallowed_size].into(); + let universal_location: UnboundedString = "ciao".into(); + precompiles() + .prepare_test( + Alice, + Precompile1, + PrecompileCall::extend { + universal_location: universal_location.clone(), + token_uri: token_uri.clone(), + }, + ) + .execute_reverts(|r| r == b"invalid token uri length"); + }); +} + +#[test] +fn create_token_uri_extension_reverts_when_claimer_already_has_metadata_extension_for_universal_location( +) { + new_test_ext().execute_with(|| { + let universal_location: UnboundedString = "my_awesome_universal_location".into(); + let token_uri: UnboundedString = "ciao".into(); + + precompiles() + .prepare_test( + Alice, + Precompile1, + PrecompileCall::extend { + universal_location: universal_location.clone(), + token_uri: token_uri.clone(), + }, + ) + .execute_returns(()); + + precompiles() + .prepare_test( + Alice, + Precompile1, + PrecompileCall::extend { + universal_location: universal_location.clone(), + token_uri: token_uri.clone(), + }, + ) + .execute_reverts(|r| r == b"ExtensionAlreadyExists"); + }); +} + +#[test] +fn create_token_uri_extension_on_mock_with_nonzero_value_fails() { + new_test_ext().execute_with(|| { + let universal_location: UnboundedString = "my_awesome_universal_location".into(); + let token_uri: UnboundedString = "ciao".into(); + + precompiles() + .prepare_test( + Alice, + Precompile1, + PrecompileCall::extend { + universal_location: universal_location.clone(), + token_uri: token_uri.clone(), + }, + ) + .with_value(U256::from(1)) + .execute_reverts(|r| r == b"Function is not payable"); + }); +} + +#[test] +fn create_token_uri_extension_records_cost() { + new_test_ext().execute_with(|| { + let universal_location: UnboundedString = "my_awesome_universal_location".into(); + let token_uri: UnboundedString = "ciao".into(); + + // Expected weight of the precompile call implementation. + // Since benchmarking precompiles is not supported yet, we are benchmarking + // functions that precompile calls internally. + // + // Following `cost` is calculated as: + // `create_token_uri_extension` weight + log cost + precompiles() + .prepare_test( + Alice, + Precompile1, + PrecompileCall::extend { + universal_location: universal_location.clone(), + token_uri: token_uri.clone(), + }, + ) + .expect_cost(389431817) // [`WeightToGas`] set to 1:1 in mock + .execute_some(); + }) +} + +#[test] +fn update_inexistent_extension_should_fail() { + new_test_ext().execute_with(|| { + let universal_location: UnboundedString = "my_awesome_universal_location".into(); + let token_uri: UnboundedString = "ciao".into(); + + precompiles() + .prepare_test( + Alice, + Precompile1, + PrecompileCall::update { + universal_location: universal_location.clone(), + token_uri: token_uri.clone(), + }, + ) + .execute_reverts(|r| r == b"ExtensionDoesNotExist"); + }); +} + +#[test] +fn update_of_extension_should_succeed() { + new_test_ext().execute_with(|| { + let universal_location: UnboundedString = "my_awesome_universal_location".into(); + let token_uri: UnboundedString = "my_awesome_token_uri".into(); + extend(universal_location.clone(), token_uri.clone()); + + let new_token_uri: UnboundedString = "my_awesome_new_token_uri".into(); + + precompiles() + .prepare_test( + Alice, + Precompile1, + PrecompileCall::update { + universal_location: universal_location.clone(), + token_uri: new_token_uri.clone(), + }, + ) + .execute_returns(()); + }); +} + +#[test] +fn update_token_uri_extension_records_cost() { + new_test_ext().execute_with(|| { + let universal_location: UnboundedString = "my_awesome_universal_location".into(); + let token_uri: UnboundedString = "my_awesome_token_uri".into(); + extend(universal_location.clone(), token_uri.clone()); + + let new_token_uri: UnboundedString = "my_awesome_new_token_uri".into(); + + // Expected weight of the precompile call implementation. + // Since benchmarking precompiles is not supported yet, we are benchmarking + // functions that precompile calls internally. + // + // Following `cost` is calculated as: + // `create_token_uri_extension` weight + log cost + precompiles() + .prepare_test( + Alice, + Precompile1, + PrecompileCall::update { + universal_location: universal_location.clone(), + token_uri: new_token_uri.clone(), + }, + ) + .expect_cost(161656038) // [`WeightToGas`] set to 1:1 in mock + .execute_some(); + }) +} + +#[test] +fn update_of_extension_should_emit_a_log() { + new_test_ext().execute_with(|| { + let universal_location: UnboundedString = "my_awesome_universal_location".into(); + let token_uri: UnboundedString = "my_awesome_token_uri".into(); + extend(universal_location.clone(), token_uri.clone()); + + let new_token_uri: UnboundedString = "my_awesome_new_token_uri".into(); + + precompiles() + .prepare_test( + Alice, + Precompile1, + PrecompileCall::update { + universal_location: universal_location.clone(), + token_uri: new_token_uri.clone(), + }, + ) + .expect_log(log3( + Precompile1, + SELECTOR_LOG_UPDATED_EXTENDED_UL_WITH_EXTERNAL_URI, + Alice, + keccak_256(universal_location.as_bytes()), + solidity::encode_event_data((universal_location, new_token_uri)), + )) + .execute_returns(()); + }); +} + +#[test] +fn claimer_by_index_invalid_index_fails() { + new_test_ext().execute_with(|| { + let universal_location: UnboundedString = "my_awesome_universal_location".into(); + + precompiles() + .prepare_test( + Alice, + Precompile1, + PrecompileCall::claimer_by_index { + universal_location: universal_location.clone(), + index: 2u32, + }, + ) + .execute_reverts(|r| r == b"invalid index"); + + extend(universal_location.clone(), "ciao".into()); + + precompiles() + .prepare_test( + Alice, + Precompile1, + PrecompileCall::claimer_by_index { + universal_location: universal_location.clone(), + index: 1u32, + }, + ) + .execute_reverts(|r| r == b"invalid index"); + }); +} + +#[test] +fn claimer_by_index_works() { + new_test_ext().execute_with(|| { + let universal_location: UnboundedString = "my_awesome_universal_location".into(); + extend(universal_location.clone(), "ciao".into()); + + precompiles() + .prepare_test( + Alice, + Precompile1, + PrecompileCall::claimer_by_index { + universal_location: universal_location.clone(), + index: 0u32, + }, + ) + .execute_returns(Address(Alice.into())); + }); +} + +#[test] +fn extension_by_index_works() { + new_test_ext().execute_with(|| { + let universal_location: UnboundedString = "my_awesome_universal_location".into(); + let token_uri: UnboundedString = "ciao".into(); + extend(universal_location.clone(), token_uri.clone()); + + precompiles() + .prepare_test( + Alice, + Precompile1, + PrecompileCall::extension_by_index { + universal_location: universal_location.clone(), + index: 0u32, + }, + ) + .execute_returns(token_uri); + }); +} + +#[test] +fn extension_by_index_invalid_ul_and_index_fails() { + new_test_ext().execute_with(|| { + let universal_location: UnboundedString = "my_awesome_universal_location".into(); + let token_uri: UnboundedString = "ciao".into(); + + precompiles() + .prepare_test( + Alice, + Precompile1, + PrecompileCall::extension_by_index { + universal_location: universal_location.clone(), + index: 0u32, + }, + ) + .execute_reverts(|r| r == b"invalid index"); + + // now create an extension + extend(universal_location.clone(), token_uri.clone()); + + // now try to get an extension with an invalid index + let other_universal_location: UnboundedString = "some_other_ul".into(); + + // reverts + precompiles() + .prepare_test( + Alice, + Precompile1, + PrecompileCall::extension_by_index { + universal_location: other_universal_location.clone(), + index: 0u32, + }, + ) + .execute_reverts(|r| r == b"invalid index"); + + // now try to get an extension with an invalid index + precompiles() + .prepare_test( + Alice, + Precompile1, + PrecompileCall::extension_by_index { + universal_location: universal_location.clone(), + index: 1u32, + }, + ) + .execute_reverts(|r| r == b"invalid index"); + }); +} + +#[test] +fn balance_of_works() { + new_test_ext().execute_with(|| { + let universal_location: UnboundedString = "my_awesome_universal_location".into(); + + // default balance is 0 + precompiles() + .prepare_test( + Alice, + Precompile1, + PrecompileCall::balance_of { universal_location: universal_location.clone() }, + ) + .execute_returns(0_u32); + + extend(universal_location.clone(), "ciao".into()); + + precompiles() + .prepare_test( + Alice, + Precompile1, + PrecompileCall::balance_of { universal_location: universal_location.clone() }, + ) + .execute_returns(1_u32); + }); +} + +#[test] +fn extension_by_location_and_claimer_works() { + new_test_ext().execute_with(|| { + let universal_location: UnboundedString = "my_awesome_universal_location".into(); + let token_uri: UnboundedString = "ciao".into(); + + extend(universal_location.clone(), token_uri.clone()); + + precompiles() + .prepare_test( + Alice, + Precompile1, + PrecompileCall::extension_by_location_and_claimer { + universal_location: universal_location.clone(), + claimer: Address(Alice.into()), + }, + ) + .execute_returns(token_uri); + }); +} + +#[test] +fn extension_by_location_and_claimer_of_unexistent_claim_reverts() { + new_test_ext().execute_with(|| { + let universal_location: UnboundedString = "my_awesome_universal_location".into(); + + precompiles() + .prepare_test( + Alice, + Precompile1, + PrecompileCall::extension_by_location_and_claimer { + universal_location: universal_location.clone(), + claimer: Address(Alice.into()), + }, + ) + .execute_reverts(|r| r == b"invalid ul"); + }); +} + +#[test] +fn has_extension_by_claim_of_existent_claim_returns_true() { + new_test_ext().execute_with(|| { + let universal_location: UnboundedString = "my_awesome_universal_location".into(); + let token_uri: UnboundedString = "ciao".into(); + + extend(universal_location.clone(), token_uri.clone()); + + precompiles() + .prepare_test( + Alice, + Precompile1, + PrecompileCall::has_extension_by_claimer { + universal_location: universal_location.clone(), + claimer: Address(Alice.into()), + }, + ) + .execute_returns(true); + }); +} + +#[test] +fn has_extension_by_claimer_of_unexistent_claim_returns_false() { + new_test_ext().execute_with(|| { + let universal_location: UnboundedString = "my_awesome_universal_location".into(); + + precompiles() + .prepare_test( + Alice, + Precompile1, + PrecompileCall::has_extension_by_claimer { + universal_location: universal_location.clone(), + claimer: Address(Alice.into()), + }, + ) + .execute_returns(false); + }); +} diff --git a/pallets/asset-metadata-extender/src/precompiles/mod.rs b/pallets/asset-metadata-extender/src/precompiles/mod.rs new file mode 100644 index 000000000..6aff76164 --- /dev/null +++ b/pallets/asset-metadata-extender/src/precompiles/mod.rs @@ -0,0 +1,18 @@ +pub mod asset_metadata_extender; + +use fp_evm::ExitError; +use frame_support::pallet_prelude::Weight; +use pallet_evm::GasWeightMapping; +use precompile_utils::prelude::PrecompileHandle; + +pub fn register_cost( + handle: &mut impl PrecompileHandle, + weight: Weight, +) -> Result<(), ExitError> { + let required_gas = Runtime::GasWeightMapping::weight_to_gas(weight); + let remaining_gas = handle.remaining_gas(); + if required_gas > remaining_gas { + return Err(ExitError::OutOfGas); + } + handle.record_cost(required_gas) +} diff --git a/pallets/laos-evolution/Cargo.toml b/pallets/laos-evolution/Cargo.toml index 744a4acff..5d952d1c2 100644 --- a/pallets/laos-evolution/Cargo.toml +++ b/pallets/laos-evolution/Cargo.toml @@ -47,7 +47,7 @@ std = [ "precompile-utils/std", "fp-evm/std", "pallet-evm/std", - "laos-precompile-utils/std", + "laos-precompile-utils/std", # TODO remove? ] runtime-benchmarks = [ "frame-benchmarking/runtime-benchmarks", diff --git a/precompile/asset-metadata-extender-legacy/Cargo.toml b/precompile/asset-metadata-extender-legacy/Cargo.toml deleted file mode 100644 index e7b5ef9bc..000000000 --- a/precompile/asset-metadata-extender-legacy/Cargo.toml +++ /dev/null @@ -1,60 +0,0 @@ -[package] -name = "pallet-evm-asset-metadata-extender-legacy" -version = "0.1.0" -edition = "2021" -description = "Asset Metadata Extender precompile" -repository = "https://github.com/freeverseio/laos" -homepage.workspace = true -authors.workspace = true - -# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html - -[dependencies] -parity-scale-codec = { workspace = true, features = ["derive"] } -scale-info = { workspace = true, features = ["derive"] } - -# Local pallet -pallet-asset-metadata-extender = { workspace = true } - -# Local precompile utils -laos-precompile-utils-macro = { workspace = true } -laos-precompile-utils = { workspace = true } -num_enum = { workspace = true } - -# Frontier -fp-evm = { workspace = true, features = ["serde"] } -pallet-evm = { workspace = true } - -# Frontier precompile utils -precompile-utils = { workspace = true } - -# Substrate -frame-support = { workspace = true } -sp-core = { workspace = true } -sp-runtime = { workspace = true } -sp-std = { workspace = true } -sp-io = { workspace = true } - -[dev-dependencies] -hex = { workspace = true } -frame-system = { workspace = true } -parity-scale-codec = { workspace = true, features = ["derive"] } -pallet-balances = { workspace = true, features = ["std", "insecure_zero_ed"] } -pallet-timestamp = { workspace = true } -precompile-utils = { workspace = true, features = ["testing"]} - -[features] -default = ["std"] -std = [ - "fp-evm/std", - "pallet-evm/std", - "laos-precompile-utils/std", - "pallet-asset-metadata-extender/std", - "num_enum/std", - "frame-support/std", - "parity-scale-codec/std", - "scale-info/std", - "sp-core/std", - "sp-runtime/std", - "sp-std/std", -] \ No newline at end of file diff --git a/precompile/asset-metadata-extender-legacy/contracts/AssetMetadataExtender.json b/precompile/asset-metadata-extender-legacy/contracts/AssetMetadataExtender.json deleted file mode 100644 index d56575486..000000000 --- a/precompile/asset-metadata-extender-legacy/contracts/AssetMetadataExtender.json +++ /dev/null @@ -1,215 +0,0 @@ -[ - { - "anonymous": false, - "inputs": [ - { - "indexed": true, - "internalType": "address", - "name": "_claimer", - "type": "address" - }, - { - "indexed": true, - "internalType": "bytes32", - "name": "_universalLocationHash", - "type": "bytes32" - }, - { - "indexed": false, - "internalType": "string", - "name": "_universalLocation", - "type": "string" - }, - { - "indexed": false, - "internalType": "string", - "name": "_tokenURI", - "type": "string" - } - ], - "name": "ExtendedULWithExternalURI", - "type": "event" - }, - { - "anonymous": false, - "inputs": [ - { - "indexed": true, - "internalType": "address", - "name": "_claimer", - "type": "address" - }, - { - "indexed": true, - "internalType": "bytes32", - "name": "_universalLocationHash", - "type": "bytes32" - }, - { - "indexed": false, - "internalType": "string", - "name": "_universalLocation", - "type": "string" - }, - { - "indexed": false, - "internalType": "string", - "name": "_tokenURI", - "type": "string" - } - ], - "name": "UpdatedExtendedULWithExternalURI", - "type": "event" - }, - { - "inputs": [ - { - "internalType": "string", - "name": "_uloc", - "type": "string" - } - ], - "name": "balanceOfUL", - "outputs": [ - { - "internalType": "uint32", - "name": "", - "type": "uint32" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "string", - "name": "_uloc", - "type": "string" - }, - { - "internalType": "uint32", - "name": "_index", - "type": "uint32" - } - ], - "name": "claimerOfULByIndex", - "outputs": [ - { - "internalType": "address", - "name": "", - "type": "address" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "string", - "name": "_uloc", - "type": "string" - }, - { - "internalType": "string", - "name": "_tokenURI", - "type": "string" - } - ], - "name": "extendULWithExternalURI", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "string", - "name": "_universalLocation", - "type": "string" - }, - { - "internalType": "address", - "name": "_claimer", - "type": "address" - } - ], - "name": "extensionOfULByClaimer", - "outputs": [ - { - "internalType": "string", - "name": "", - "type": "string" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "string", - "name": "_uloc", - "type": "string" - }, - { - "internalType": "uint32", - "name": "_index", - "type": "uint32" - } - ], - "name": "extensionOfULByIndex", - "outputs": [ - { - "internalType": "string", - "name": "", - "type": "string" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "string", - "name": "_universalLocation", - "type": "string" - }, - { - "internalType": "address", - "name": "_claimer", - "type": "address" - } - ], - "name": "hasExtensionByClaimer", - "outputs": [ - { - "internalType": "bool", - "name": "", - "type": "bool" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "string", - "name": "_uloc", - "type": "string" - }, - { - "internalType": "string", - "name": "_tokenURI", - "type": "string" - } - ], - "name": "updateExtendedULWithExternalURI", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - } -] \ No newline at end of file diff --git a/precompile/asset-metadata-extender-legacy/contracts/AssetMetadataExtender.sol b/precompile/asset-metadata-extender-legacy/contracts/AssetMetadataExtender.sol deleted file mode 100644 index 4a45d9462..000000000 --- a/precompile/asset-metadata-extender-legacy/contracts/AssetMetadataExtender.sol +++ /dev/null @@ -1,92 +0,0 @@ -// SPDX-License-Identifier: GPL-3.0-only -pragma solidity >=0.8.3; - -/// @title Pallet Asset Metadata Extender Interface -/// @author LAOS Team -/// @notice This interface allows Solidity contracts to interact with pallet-asset-metadata-extender -/// @custom:address 0x0000000000000000000000000000000000000405 -interface AssetMetadataExtender { - /// @notice Emitted when a token metadata is extended - /// @param _claimer the address of the caller - /// @param _universalLocationHash keccak256 hash of the universal location - /// @param _universalLocation The universal location of the token - /// @param _tokenURI the extended URI of the token - event ExtendedULWithExternalURI( - address indexed _claimer, - bytes32 indexed _universalLocationHash, - string _universalLocation, - string _tokenURI - ); - - /// @notice Emitted when an extended token's URI is updated - /// @param _claimer the address of the user who updated the token URI - /// @param _universalLocationHash keccak256 hash of the universal location - /// @param _universalLocation the universal location of the token - /// @param _tokenURI the URI of the extension after the update - event UpdatedExtendedULWithExternalURI( - address indexed _claimer, - bytes32 indexed _universalLocationHash, - string _universalLocation, - string _tokenURI - ); - - /// @notice Extends the metadata of a token - /// @dev Emits the ExtendedULWithExternalURI event upon success - /// @dev Reverts if the UL has been extended previously - /// @param _uloc the Universal Location as a string identifying the token - /// @param _tokenURI the URI of the extended metadata - function extendULWithExternalURI( - string calldata _uloc, - string calldata _tokenURI - ) external; - - /// @notice Updates the URI of an extended token - /// @param _uloc The universal location identifier of the token - /// @param _tokenURI The new URI to be set for the token - function updateExtendedULWithExternalURI( - string calldata _uloc, - string calldata _tokenURI - ) external; - - /// @notice Returns the number of extensions made about a UL - /// @param _uloc The Universal Location as a string identifying the asset - /// @return The number of extensions - function balanceOfUL(string calldata _uloc) external view returns (uint32); - - /// @notice Returns the claimer for an extension at a given index - /// @param _uloc The Universal Location string identifying the asset - /// @param _index The index of the extension - /// @return The address of the claimer - function claimerOfULByIndex( - string calldata _uloc, - uint32 _index - ) external view returns (address); - - /// @notice Returns the tokenURI for an extension at a given index - /// @param _uloc The Universal Location string identifying the asset - /// @param _index The index of the extension - /// @return The tokenURI of the extension - function extensionOfULByIndex( - string calldata _uloc, - uint32 _index - ) external view returns (string memory); - - /// @notice Returns the extension of a Universal Location made by a claimer - /// @dev Reverts if the Universal Location has no extension by the provided claimer - /// @param _universalLocation The Universal Location - /// @param _claimer The address of the claimer - /// @return The tokenURI of the extension by the provided claimer - function extensionOfULByClaimer( - string calldata _universalLocation, - address _claimer - ) external view returns (string memory); - - /// @notice Checks if a Universal Location has an extension by a claimer - /// @param _universalLocation The Universal Location - /// @param _claimer The address of the claimer - /// @return True if the Universal Location has an extension by the provided claimer, false otherwise - function hasExtensionByClaimer( - string calldata _universalLocation, - address _claimer - ) external view returns (bool); -} diff --git a/precompile/asset-metadata-extender-legacy/src/lib.rs b/precompile/asset-metadata-extender-legacy/src/lib.rs deleted file mode 100644 index 8a9d908f4..000000000 --- a/precompile/asset-metadata-extender-legacy/src/lib.rs +++ /dev/null @@ -1,309 +0,0 @@ -// Copyright 2023-2024 Freeverse.io -// This file is part of LAOS. - -// LAOS is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. - -// LAOS is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. - -// You should have received a copy of the GNU General Public License -// along with LAOS. If not, see . - -#![cfg_attr(not(feature = "std"), no_std)] -use fp_evm::{Precompile, PrecompileHandle, PrecompileOutput}; -use laos_precompile_utils::{ - keccak256, revert_dispatch_error, succeed, Address, Bytes, EvmDataReader, EvmDataWriter, - EvmResult, FunctionModifier, GasCalculator, LogExt, LogsBuilder, PrecompileHandleExt, -}; -use pallet_asset_metadata_extender::{ - traits::AssetMetadataExtender as AssetMetadataExtenderT, - weights::{SubstrateWeight as AssetMetadataExtenderWeights, WeightInfo}, - Pallet as AssetMetadataExtender, -}; -use parity_scale_codec::Encode; -use precompile_utils::solidity::revert::revert; - -use sp_core::{Get, H160}; -use sp_io::hashing::keccak_256; -use sp_runtime::BoundedVec; -use sp_std::{fmt::Debug, marker::PhantomData}; - -/// Solidity selector of the ExtendedULWithExternalURI log, which is the Keccak of the Log -/// signature. -pub const SELECTOR_LOG_EXTENDED_UL_WITH_EXTERNAL_URI: [u8; 32] = - keccak256!("ExtendedULWithExternalURI(address,bytes32,string,string)"); -/// Solidity selector of the UpdatedExtendedULWithExternalURI log, which is the Keccak of the Log -/// signature. -pub const SELECTOR_LOG_UPDATED_EXTENDED_UL_WITH_EXTERNAL_URI: [u8; 32] = - keccak256!("UpdatedExtendedULWithExternalURI(address,bytes32,string,string)"); - -#[laos_precompile_utils_macro::generate_function_selector] -#[derive(Debug, PartialEq)] -pub enum Action { - /// Extend asset metadata (token uri) - Extend = "extendULWithExternalURI(string,string)", - /// Get extensions balance for a given universal location - Balance = "balanceOfUL(string)", - /// Get claimer of a given universal location using indexation - Claimer = "claimerOfULByIndex(string,uint32)", - /// Get token uri of a given universal location using indexation - Extension = "extensionOfULByIndex(string,uint32)", // TODO rename `extension` for `tokenURI`? - /// Update token uri of a given universal location using indexation - Update = "updateExtendedULWithExternalURI(string,string)", - /// Get extension of a given universal location using claimer - ExtensionOfULByClaimer = "extensionOfULByClaimer(string,address)", - /// Check if a given universal location has an extension - HasExtension = "hasExtensionByClaimer(string,address)", -} - -pub struct AssetMetadataExtenderPrecompile(PhantomData) -where - Runtime: pallet_asset_metadata_extender::Config; - -impl Precompile for AssetMetadataExtenderPrecompile -where - Runtime: pallet_evm::Config + pallet_asset_metadata_extender::Config, - Runtime::AccountId: From + Into + Encode + Debug, - AssetMetadataExtender: AssetMetadataExtenderT, -{ - fn execute(handle: &mut impl PrecompileHandle) -> EvmResult { - let selector = handle.read_selector()?; - - handle.check_function_modifier(match selector { - Action::Extend => FunctionModifier::NonPayable, - Action::Balance => FunctionModifier::View, - Action::Claimer => FunctionModifier::View, - Action::Extension => FunctionModifier::View, - Action::Update => FunctionModifier::NonPayable, - Action::ExtensionOfULByClaimer => FunctionModifier::View, - Action::HasExtension => FunctionModifier::View, - })?; - - match selector { - Action::Extend => Self::extend(handle), - Action::Balance => Self::balance_of(handle), - Action::Claimer => Self::claimer_by_index(handle), - Action::Extension => Self::extension_by_index(handle), - Action::Update => Self::update(handle), - Action::ExtensionOfULByClaimer => Self::extension_by_location_and_claimer(handle), - Action::HasExtension => Self::has_extension_by_claimer(handle), - } - } -} - -impl AssetMetadataExtenderPrecompile -where - Runtime: pallet_evm::Config + pallet_asset_metadata_extender::Config, - Runtime::AccountId: From + Into + Encode + Debug, - AssetMetadataExtender: AssetMetadataExtenderT, -{ - fn extend(handle: &mut impl PrecompileHandle) -> EvmResult { - let context = handle.context(); - let claimer = context.caller; - - let mut input = handle.read_input()?; - input.expect_arguments(2)?; - - // get universal location from input - let universal_location = Self::read_bounded_vec(&mut input) - .map_err(|_| revert("invalid universal location length"))?; - - // get token uri from input - let token_uri = - Self::read_bounded_vec(&mut input).map_err(|_| revert("invalid token uri length"))?; - - AssetMetadataExtender::::create_token_uri_extension( - claimer.into(), - universal_location.clone(), - token_uri.clone(), - ) - .map_err(revert_dispatch_error)?; - - let universal_location_raw = universal_location.into_inner(); - let token_uri_raw = token_uri.into_inner(); - - let consumed_weight = AssetMetadataExtenderWeights::::create_token_uri_extension( - universal_location_raw.len() as u32, - token_uri_raw.len() as u32, - ); - - let ul_hash = keccak_256(&universal_location_raw); - - LogsBuilder::new(context.address) - .log3( - SELECTOR_LOG_EXTENDED_UL_WITH_EXTERNAL_URI, - claimer, - ul_hash, - EvmDataWriter::new() - .write(Bytes(universal_location_raw)) - .write(Bytes(token_uri_raw)) - .build(), - ) - .record(handle)?; - - // Record EVM cost - handle.record_cost(GasCalculator::::weight_to_gas(consumed_weight))?; - - // Record Substrate related costs - // TODO: Add `ref_time` when precompiles are benchmarked - handle.record_external_cost(None, Some(consumed_weight.proof_size()))?; - - Ok(succeed(EvmDataWriter::new().build())) - } - - fn update(handle: &mut impl PrecompileHandle) -> EvmResult { - let context = handle.context(); - - let mut input = handle.read_input()?; - input.expect_arguments(2)?; - - let universal_location = Self::read_bounded_vec(&mut input) - .map_err(|_| revert("invalid universal location length"))?; - - let token_uri = - Self::read_bounded_vec(&mut input).map_err(|_| revert("invalid token uri length"))?; - - let claimer = context.caller; - let universal_location_hash = keccak_256(&universal_location); - - AssetMetadataExtender::::update_token_uri_extension( - claimer.into(), - universal_location.clone(), - token_uri.clone(), - ) - .map_err(revert_dispatch_error)?; - - let universal_location_raw = universal_location.into_inner(); - let token_uri_raw = token_uri.into_inner(); - let consumed_weight = AssetMetadataExtenderWeights::::update_token_uri_extension( - universal_location_raw.len() as u32, - token_uri_raw.len() as u32, - ); - - LogsBuilder::new(context.address) - .log3( - SELECTOR_LOG_UPDATED_EXTENDED_UL_WITH_EXTERNAL_URI, - claimer, - universal_location_hash, - EvmDataWriter::new() - .write(Bytes(universal_location_raw)) - .write(Bytes(token_uri_raw)) - .build(), - ) - .record(handle)?; - - // Record EVM cost - handle.record_cost(GasCalculator::::weight_to_gas(consumed_weight))?; - - Ok(succeed(EvmDataWriter::new().build())) - } - - fn balance_of(handle: &mut impl PrecompileHandle) -> EvmResult { - let mut input = handle.read_input()?; - input.expect_arguments(1)?; - - let universal_location = Self::read_bounded_vec(&mut input) - .map_err(|_| revert("invalid universal location length"))?; - - let balance = AssetMetadataExtender::::balance_of(universal_location); - - Ok(succeed(EvmDataWriter::new().write(balance).build())) - } - - fn claimer_by_index(handle: &mut impl PrecompileHandle) -> EvmResult { - let mut input = handle.read_input()?; - input.expect_arguments(2)?; - - let universal_location = Self::read_bounded_vec(&mut input) - .map_err(|_| revert("invalid universal location length"))?; - - let index = input.read::()?; - - if AssetMetadataExtender::balance_of(universal_location.clone()) <= index { - return Err(revert("invalid index")); - } - - let claimer = - AssetMetadataExtender::::claimer_by_index(universal_location.clone(), index) - .ok_or_else(|| revert("invalid ul"))?; - - Ok(succeed(EvmDataWriter::new().write(Address(claimer.into())).build())) - } - - fn extension_by_index(handle: &mut impl PrecompileHandle) -> EvmResult { - let mut input = handle.read_input()?; - input.expect_arguments(2)?; - - let universal_location = Self::read_bounded_vec(&mut input) - .map_err(|_| revert("invalid universal location length"))?; - - let index = input.read::()?; - - if AssetMetadataExtender::balance_of(universal_location.clone()) <= index { - return Err(revert("invalid index")); - } - - let token_uri = AssetMetadataExtender::::token_uri_extension_by_index( - universal_location.clone(), - index, - ) - .ok_or_else(|| revert("invalid ul"))?; - - Ok(succeed(EvmDataWriter::new().write(Bytes(token_uri.into_inner())).build())) - } - - /// Generic function to read a bounded vector from the input. - fn read_bounded_vec>( - input: &mut EvmDataReader, - ) -> Result, ()> { - let raw_vec = input.read::().map_err(|_| ())?.0; - raw_vec.try_into().map_err(|_| ()) - } - - fn extension_by_location_and_claimer( - handle: &mut impl PrecompileHandle, - ) -> EvmResult { - let mut input = handle.read_input()?; - input.expect_arguments(2)?; - - let universal_location = Self::read_bounded_vec(&mut input) - .map_err(|_| revert("invalid universal location length"))?; - - let claimer = input.read::
().map_err(|_| revert("invalid claimer"))?.0; - - let token_uri = AssetMetadataExtender::::extension_by_location_and_claimer( - universal_location.clone(), - claimer.into(), - ) - .ok_or_else(|| revert("invalid ul"))?; - - Ok(succeed(EvmDataWriter::new().write(Bytes(token_uri.into_inner())).build())) - } - - fn has_extension_by_claimer(handle: &mut impl PrecompileHandle) -> EvmResult { - let mut input = handle.read_input()?; - input.expect_arguments(2)?; - - let universal_location = Self::read_bounded_vec(&mut input) - .map_err(|_| revert("invalid universal location length"))?; - - let claimer = input.read::
().map_err(|_| revert("invalid claimer"))?.0; - - let has_extension = AssetMetadataExtender::::has_extension( - universal_location.clone(), - claimer.into(), - ); - - Ok(succeed(EvmDataWriter::new().write(has_extension).build())) - } -} - -#[cfg(test)] -mod mock; -#[cfg(test)] -mod tests; diff --git a/precompile/asset-metadata-extender-legacy/src/tests.rs b/precompile/asset-metadata-extender-legacy/src/tests.rs deleted file mode 100644 index c3b6568aa..000000000 --- a/precompile/asset-metadata-extender-legacy/src/tests.rs +++ /dev/null @@ -1,615 +0,0 @@ -// Copyright 2023-2024 Freeverse.io -// This file is part of LAOS. - -// LAOS is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. - -// LAOS is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. - -// You should have received a copy of the GNU General Public License -// along with LAOS. If not, see . - -use super::*; -use crate::mock::*; -use core::str::FromStr; -use fp_evm::Log; -use laos_precompile_utils::EvmDataWriter; -use precompile_utils::{solidity::codec::BoundedBytes, testing::PrecompileTesterExt}; -use sp_core::{H160, H256, U256}; - -/// Fixed precompile address for testing. -const PRECOMPILE_ADDRESS: [u8; 20] = [5u8; 20]; - -/// Get precompiles from the mock. -fn precompiles() -> MockPrecompileSet { - MockPrecompiles::get() -} - -const TEST_CLAIMER: &str = "0xf24FF3a9CF04c71Dbc94D0b566f7A27B94566cac"; - -#[test] -fn check_log_selectors() { - assert_eq!( - hex::encode(SELECTOR_LOG_EXTENDED_UL_WITH_EXTERNAL_URI), - "f744da499cb735a8fc987aa2a331a1cbeca79e449e4c04eeccfe57c538e79070" - ); - assert_eq!( - hex::encode(SELECTOR_LOG_UPDATED_EXTENDED_UL_WITH_EXTERNAL_URI), - "e7ebe38355126fe0c3eab0ec03eb1b94ff501458a80713c9eb8b737334a651ff" - ); -} - -#[test] -fn function_selectors() { - assert_eq!(Action::Extend as u32, 0xA5FBDF1D); - assert_eq!(Action::Balance as u32, 0x7B65DED5); - assert_eq!(Action::Claimer as u32, 0xA565BB04); - assert_eq!(Action::Extension as u32, 0xB2B7C05A); - assert_eq!(Action::Update as u32, 0xCD79C745); -} - -#[test] -#[ignore] -fn call_unexistent_selector_should_fail() { - new_test_ext().execute_with(|| { - let input = EvmDataWriter::new_with_selector(0x12345678_u32) - .write(Bytes("my_awesome_universal_location".as_bytes().to_vec())) - .build(); - - precompiles() - .prepare_test(H160::from_str(TEST_CLAIMER).unwrap(), H160(PRECOMPILE_ADDRESS), input) - .execute_reverts(|r| r == b"unknown selector"); - }); -} - -#[test] -fn create_token_uri_extension_should_emit_log() { - new_test_ext().execute_with(|| { - let universal_location = Bytes("my_awesome_universal_location".as_bytes().to_vec()); - let token_uri = Bytes("ciao".as_bytes().to_vec()); - - let input = EvmDataWriter::new_with_selector(Action::Extend) - .write(universal_location.clone()) - .write(token_uri.clone()) - .build(); - - let expected_log = Log { - address: H160(PRECOMPILE_ADDRESS), - topics: vec![ - SELECTOR_LOG_EXTENDED_UL_WITH_EXTERNAL_URI.into(), - H256::from_str( - format!("000000000000000000000000{}", TEST_CLAIMER.trim_start_matches("0x")) - .as_str(), - ) - .unwrap(), - keccak_256(&universal_location.0).into(), - ], - // ul is 29 bytes, so it's prepended with 64 bytes of zeros + ul + 3 bytes to make it 32 - data: hex::decode("00000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000080000000000000000000000000000000000000000000000000000000000000001D6D795F617765736F6D655F756E6976657273616C5F6C6F636174696F6E00000000000000000000000000000000000000000000000000000000000000000000046369616F00000000000000000000000000000000000000000000000000000000") - .unwrap(), - }; - - precompiles() - .prepare_test(H160::from_str(TEST_CLAIMER).unwrap(), H160(PRECOMPILE_ADDRESS), input) - .expect_log(expected_log) - .execute_some(); - }); -} - -#[test] -fn create_token_uri_extension_reverts_when_ul_exceeds_length() { - new_test_ext().execute_with(|| { - let unallowed_size = (MaxUniversalLocationLength::get() + 10).try_into().unwrap(); - let universal_location = Bytes(vec![b'a'; unallowed_size]); - let token_uri = Bytes(vec![b'b'; MaxTokenUriLength::get().try_into().unwrap()]); - - let input = EvmDataWriter::new_with_selector(Action::Extend) - .write(universal_location) - .write(token_uri) - .build(); - - precompiles() - .prepare_test(H160::from_str(TEST_CLAIMER).unwrap(), H160(PRECOMPILE_ADDRESS), input) - .execute_reverts(|r| r == b"invalid universal location length"); - }); -} - -#[test] -fn create_token_uri_extension_reverts_when_token_uri_exceeds_length() { - new_test_ext().execute_with(|| { - let unallowed_size = (MaxTokenUriLength::get() + 1).try_into().unwrap(); - let token_uri = Bytes(vec![b'a'; unallowed_size]); - let universal_location = - Bytes(vec![b'b'; MaxUniversalLocationLength::get().try_into().unwrap()]); - - let input = EvmDataWriter::new_with_selector(Action::Extend) - .write(universal_location) - .write(token_uri) - .build(); - - precompiles() - .prepare_test(H160::from_str(TEST_CLAIMER).unwrap(), H160(PRECOMPILE_ADDRESS), input) - .execute_reverts(|r| r == b"invalid token uri length"); - }); -} - -#[test] -fn create_token_uri_extension_reverts_when_claimer_already_has_metadata_extension_for_universal_location( -) { - new_test_ext().execute_with(|| { - let universal_location = Bytes("my_awesome_universal_location".as_bytes().to_vec()); - let token_uri = Bytes("my_awesome_token_uri".as_bytes().to_vec()); - - let input = EvmDataWriter::new_with_selector(Action::Extend) - .write(universal_location.clone()) - .write(token_uri.clone()) - .build(); - - precompiles() - .prepare_test(H160::from_str(TEST_CLAIMER).unwrap(), H160(PRECOMPILE_ADDRESS), input) - .execute_returns_raw(vec![]); - - let input = EvmDataWriter::new_with_selector(Action::Extend) - .write(universal_location) - .write(token_uri) - .build(); - - precompiles() - .prepare_test(H160::from_str(TEST_CLAIMER).unwrap(), H160(PRECOMPILE_ADDRESS), input) - .execute_reverts(|r| r == b"ExtensionAlreadyExists"); - }); -} - -#[test] -fn create_token_uri_extension_on_mock_with_nonzero_value_fails() { - new_test_ext().execute_with(|| { - let universal_location = Bytes("my_awesome_universal_location".as_bytes().to_vec()); - let token_uri = Bytes("my_awesome_token_uri".as_bytes().to_vec()); - let input = EvmDataWriter::new_with_selector(Action::Extend) - .write(universal_location) - .write(token_uri) - .build(); - - precompiles() - .prepare_test(H160::from_str(TEST_CLAIMER).unwrap(), H160(PRECOMPILE_ADDRESS), input) - .with_value(U256::from(1)) - .execute_reverts(|r| r == b"function is not payable"); - }); -} - -#[test] -fn update_inexistent_extension_should_fail() { - new_test_ext().execute_with(|| { - let universal_location = Bytes("my_awesome_universal_location".as_bytes().to_vec()); - let token_uri = Bytes("my_awesome_token_uri".as_bytes().to_vec()); - - let input = EvmDataWriter::new_with_selector(Action::Update) - .write(universal_location) - .write(token_uri) - .build(); - - precompiles() - .prepare_test(H160::from_str(TEST_CLAIMER).unwrap(), H160(PRECOMPILE_ADDRESS), input) - .execute_reverts(|r| r == b"ExtensionDoesNotExist"); - }); -} - -#[test] -fn create_token_uri_extension_records_cost() { - new_test_ext().execute_with(|| { - let universal_location = Bytes("my_awesome_universal_location".as_bytes().to_vec()); - let token_uri = Bytes("my_awesome_token_uri".as_bytes().to_vec()); - let input = EvmDataWriter::new_with_selector(Action::Extend) - .write(universal_location) - .write(token_uri) - .build(); - - // Expected weight of the precompile call implementation. - // Since benchmarking precompiles is not supported yet, we are benchmarking - // functions that precompile calls internally. - // - // Following `cost` is calculated as: - // `create_token_uri_extension` weight + log cost - precompiles() - .prepare_test(H160::from_str(TEST_CLAIMER).unwrap(), H160(PRECOMPILE_ADDRESS), input) - .expect_cost(364560357) // [`WeightToGas`] set to 1:1 in mock - .execute_some(); - }) -} - -#[test] -fn update_token_uri_extension_records_cost() { - new_test_ext().execute_with(|| { - let universal_location = Bytes("my_awesome_universal_location".as_bytes().to_vec()); - let token_uri = Bytes("my_awesome_token_uri".as_bytes().to_vec()); - - let input = EvmDataWriter::new_with_selector(Action::Extend) - .write(universal_location.clone()) - .write(token_uri.clone()) - .build(); - - precompiles() - .prepare_test(H160::from_str(TEST_CLAIMER).unwrap(), H160(PRECOMPILE_ADDRESS), input) - .execute_returns_raw(vec![]); - - let new_token_uri = Bytes("my_awesome_new_token_uri".as_bytes().to_vec()); - let input = EvmDataWriter::new_with_selector(Action::Update) - .write(universal_location) - .write(new_token_uri) - .build(); - - // Expected weight of the precompile call implementation. - // Since benchmarking precompiles is not supported yet, we are benchmarking - // functions that precompile calls internally. - // - // Following `cost` is calculated as: - // `create_token_uri_extension` weight + log cost - precompiles() - .prepare_test(H160::from_str(TEST_CLAIMER).unwrap(), H160(PRECOMPILE_ADDRESS), input) - .expect_cost(136659074) // [`WeightToGas`] set to 1:1 in mock - .execute_some(); - }) -} - -#[test] -fn update_of_extension_should_succeed() { - new_test_ext().execute_with(|| { - let universal_location = Bytes("my_awesome_universal_location".as_bytes().to_vec()); - let token_uri = Bytes("my_awesome_token_uri".as_bytes().to_vec()); - - let input = EvmDataWriter::new_with_selector(Action::Extend) - .write(universal_location.clone()) - .write(token_uri.clone()) - .build(); - - precompiles() - .prepare_test(H160::from_str(TEST_CLAIMER).unwrap(), H160(PRECOMPILE_ADDRESS), input) - .execute_returns_raw(vec![]); - - let new_token_uri = Bytes("my_awesome_new_token_uri".as_bytes().to_vec()); - - let input = EvmDataWriter::new_with_selector(Action::Update) - .write(universal_location) - .write(new_token_uri) - .build(); - - precompiles() - .prepare_test(H160::from_str(TEST_CLAIMER).unwrap(), H160(PRECOMPILE_ADDRESS), input) - .execute_returns_raw(vec![]); - }); -} - -#[test] -fn update_of_extension_should_emit_a_log() { - new_test_ext().execute_with(|| { - let universal_location = Bytes("my_awesome_universal_location".as_bytes().to_vec()); - let token_uri = Bytes("my_awesome_token_uri".as_bytes().to_vec()); - - let input = EvmDataWriter::new_with_selector(Action::Extend) - .write(universal_location.clone()) - .write(token_uri.clone()) - .build(); - - precompiles() - .prepare_test(H160::from_str(TEST_CLAIMER).unwrap(), H160(PRECOMPILE_ADDRESS), input) - .execute_returns_raw(vec![]); - - let new_token_uri = Bytes("my_awesome_new_token_uri".as_bytes().to_vec()); - - let input = EvmDataWriter::new_with_selector(Action::Update) - .write(universal_location) - .write(new_token_uri) - .build(); - - let expected_log = Log { - address: H160(PRECOMPILE_ADDRESS), - topics: vec![ - SELECTOR_LOG_UPDATED_EXTENDED_UL_WITH_EXTERNAL_URI.into(), - H160::from_str(TEST_CLAIMER).unwrap().into(), - keccak256!("my_awesome_universal_location").into(), - ], - data: hex::decode("00000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000080000000000000000000000000000000000000000000000000000000000000001D6D795F617765736F6D655F756E6976657273616C5F6C6F636174696F6E00000000000000000000000000000000000000000000000000000000000000000000186D795F617765736F6D655F6E65775F746F6B656E5F7572690000000000000000") - .unwrap(), - }; - - precompiles() - .prepare_test(H160::from_str(TEST_CLAIMER).unwrap(), H160(PRECOMPILE_ADDRESS), input) - .expect_log(expected_log) - .execute_returns_raw(vec![]); - }); -} - -#[test] -fn claimer_by_index_invalid_index_fails() { - new_test_ext().execute_with(|| { - let universal_location = Bytes("my_awesome_universal_location".as_bytes().to_vec()); - let input = EvmDataWriter::new_with_selector(Action::Claimer) - .write(universal_location.clone()) - .write(2u32) - .build(); - - precompiles() - .prepare_test( - H160::from_str(TEST_CLAIMER).unwrap(), - H160(PRECOMPILE_ADDRESS), - input.clone(), - ) - .execute_reverts(|r| r == b"invalid index"); - - let input = EvmDataWriter::new_with_selector(Action::Extend) - .write(universal_location.clone()) - .write(Bytes(vec![1u8; 10])) - .build(); - - precompiles() - .prepare_test( - H160::from_str(TEST_CLAIMER).unwrap(), - H160(PRECOMPILE_ADDRESS), - input.clone(), - ) - .execute_returns_raw(sp_std::vec![]); - - let input = EvmDataWriter::new_with_selector(Action::Claimer) - .write(universal_location) - .write(1u32) - .build(); - - precompiles() - .prepare_test(H160::from_str(TEST_CLAIMER).unwrap(), H160(PRECOMPILE_ADDRESS), input) - .execute_reverts(|r| r == b"invalid index"); - }); -} - -#[test] -fn claimer_by_index_works() { - new_test_ext().execute_with(|| { - let universal_location = Bytes("some_universal_location".as_bytes().to_vec()); - - let input = EvmDataWriter::new_with_selector(Action::Extend) - .write(universal_location.clone()) - .write(Bytes("some_token_uri".as_bytes().to_vec())) - .build(); - - precompiles() - .prepare_test(H160::from_str(TEST_CLAIMER).unwrap(), H160(PRECOMPILE_ADDRESS), input) - .execute_returns_raw(vec![]); - - let input = EvmDataWriter::new_with_selector(Action::Claimer) - .write(universal_location.clone()) - .write(0u32) - .build(); - - precompiles() - .prepare_test( - H160::from_str(TEST_CLAIMER).unwrap(), - H160(PRECOMPILE_ADDRESS), - input.clone(), - ) - .execute_returns(precompile_utils::prelude::Address( - H160::from_str(TEST_CLAIMER).unwrap(), - )); - }); -} - -#[test] -fn extension_by_index_works() { - new_test_ext().execute_with(|| { - let universal_location = Bytes("some_universal_location".as_bytes().to_vec()); - - let input = EvmDataWriter::new_with_selector(Action::Extend) - .write(universal_location.clone()) - .write(Bytes(vec![1u8; 10])) - .build(); - - precompiles() - .prepare_test( - H160::from_str(TEST_CLAIMER).unwrap(), - H160(PRECOMPILE_ADDRESS), - input.clone(), - ) - .execute_returns_raw(sp_std::vec![]); - - let input = EvmDataWriter::new_with_selector(Action::Extension) - .write(universal_location.clone()) - .write(0_u32) - .build(); - - precompiles() - .prepare_test(H160::from_str(TEST_CLAIMER).unwrap(), H160(PRECOMPILE_ADDRESS), input) - .execute_returns(BoundedBytes::::from(vec![1u8; 10])); - }); -} - -#[test] -fn extension_by_index_invalid_ul_and_index_fails() { - new_test_ext().execute_with(|| { - let universal_location = Bytes("invalid_universal_location".as_bytes().to_vec()); - - let input = EvmDataWriter::new_with_selector(Action::Extension) - .write(universal_location.clone()) - .write(0_u32) - .build(); - - precompiles() - .prepare_test( - H160::from_str(TEST_CLAIMER).unwrap(), - H160(PRECOMPILE_ADDRESS), - input.clone(), - ) - .execute_reverts(|r| r == b"invalid index"); - - // now create an extension - let input = EvmDataWriter::new_with_selector(Action::Extend) - .write(universal_location.clone()) - .write(Bytes(vec![1u8; 10])) - .build(); - - precompiles() - .prepare_test( - H160::from_str(TEST_CLAIMER).unwrap(), - H160(PRECOMPILE_ADDRESS), - input.clone(), - ) - .execute_returns_raw(sp_std::vec![]); - - // now try to get an extension with an invalid index - let input = EvmDataWriter::new_with_selector(Action::Extension) - .write(Bytes("some_other_ul".as_bytes().to_vec()).clone()) - .write(0_u32) - .build(); - - // reverts - precompiles() - .prepare_test( - H160::from_str(TEST_CLAIMER).unwrap(), - H160(PRECOMPILE_ADDRESS), - input.clone(), - ) - .execute_reverts(|r| r == b"invalid index"); - - // now try to get an extension with an invalid index - let input = EvmDataWriter::new_with_selector(Action::Extension) - .write(universal_location) - .write(1_u32) - .build(); - - // reverts - precompiles() - .prepare_test(H160::from_str(TEST_CLAIMER).unwrap(), H160(PRECOMPILE_ADDRESS), input) - .execute_reverts(|r| r == b"invalid index"); - }); -} - -#[test] -fn balance_of_works() { - new_test_ext().execute_with(|| { - let universal_location = Bytes("some_universal_location".as_bytes().to_vec()); - - let input = EvmDataWriter::new_with_selector(Action::Balance) - .write(universal_location.clone()) - .build(); - - // default balance is 0 - precompiles() - .prepare_test( - H160::from_str(TEST_CLAIMER).unwrap(), - H160(PRECOMPILE_ADDRESS), - input.clone(), - ) - .execute_returns(0_u32); - - let input = EvmDataWriter::new_with_selector(Action::Extend) - .write(universal_location.clone()) - .write(Bytes(vec![1u8; 10])) - .build(); - - precompiles() - .prepare_test( - H160::from_str(TEST_CLAIMER).unwrap(), - H160(PRECOMPILE_ADDRESS), - input.clone(), - ) - .execute_returns_raw(sp_std::vec![]); - - let input = EvmDataWriter::new_with_selector(Action::Balance) - .write(universal_location.clone()) - .build(); - - precompiles() - .prepare_test(H160::from_str(TEST_CLAIMER).unwrap(), H160(PRECOMPILE_ADDRESS), input) - .execute_returns(1_u32); - }); -} - -#[test] -fn extension_by_location_and_claimer_works() { - new_test_ext().execute_with(|| { - let universal_location = Bytes("some_universal_location".as_bytes().to_vec()); - let claimer = H160::from_str(TEST_CLAIMER).unwrap(); - let claim = Bytes(vec![1u8; 10]); - - let input = EvmDataWriter::new_with_selector(Action::Extend) - .write(universal_location.clone()) - .write(claim.clone()) - .build(); - - precompiles() - .prepare_test(claimer, H160(PRECOMPILE_ADDRESS), input.clone()) - .execute_returns_raw(sp_std::vec![]); - - let input = EvmDataWriter::new_with_selector(Action::ExtensionOfULByClaimer) - .write(universal_location.clone()) - .write(Address::from(claimer)) - .build(); - - precompiles() - .prepare_test(claimer, H160(PRECOMPILE_ADDRESS), input) - .execute_returns(BoundedBytes::::from(vec![1u8; 10])); - }); -} - -#[test] -fn extension_by_location_and_claimer_of_unexistent_claim_reverts() { - new_test_ext().execute_with(|| { - let universal_location = Bytes("some_universal_location".as_bytes().to_vec()); - let claimer = H160::from_str(TEST_CLAIMER).unwrap(); - - let input = EvmDataWriter::new_with_selector(Action::ExtensionOfULByClaimer) - .write(universal_location.clone()) - .write(Address::from(claimer)) - .build(); - - precompiles() - .prepare_test(claimer, H160(PRECOMPILE_ADDRESS), input) - .execute_reverts(|r| r == b"invalid ul"); - }); -} - -#[test] -fn has_extension_by_claim_of_existent_claim_returns_true() { - new_test_ext().execute_with(|| { - let universal_location = Bytes("some_universal_location".as_bytes().to_vec()); - let claimer = H160::from_str(TEST_CLAIMER).unwrap(); - let claim = Bytes(vec![1u8; 10]); - - let input = EvmDataWriter::new_with_selector(Action::Extend) - .write(universal_location.clone()) - .write(claim.clone()) - .build(); - - precompiles() - .prepare_test(claimer, H160(PRECOMPILE_ADDRESS), input.clone()) - .execute_returns_raw(sp_std::vec![]); - - let input = EvmDataWriter::new_with_selector(Action::HasExtension) - .write(universal_location.clone()) - .write(Address::from(claimer)) - .build(); - - precompiles() - .prepare_test(claimer, H160(PRECOMPILE_ADDRESS), input) - .execute_returns(true); - }); -} - -#[test] -fn has_extension_by_claimer_of_unexistent_claim_returns_false() { - new_test_ext().execute_with(|| { - let universal_location = Bytes("some_universal_location".as_bytes().to_vec()); - let claimer = H160::from_str(TEST_CLAIMER).unwrap(); - - let input = EvmDataWriter::new_with_selector(Action::HasExtension) - .write(universal_location.clone()) - .write(Address::from(claimer)) - .build(); - - precompiles() - .prepare_test(claimer, H160(PRECOMPILE_ADDRESS), input) - .execute_returns(false); - }); -} diff --git a/precompile/asset-metadata-extender/src/lib.rs b/precompile/asset-metadata-extender/src/lib.rs index ade203095..8a9d908f4 100644 --- a/precompile/asset-metadata-extender/src/lib.rs +++ b/precompile/asset-metadata-extender/src/lib.rs @@ -15,23 +15,22 @@ // along with LAOS. If not, see . #![cfg_attr(not(feature = "std"), no_std)] -use fp_evm::PrecompileHandle; -use frame_support::DefaultNoBound; +use fp_evm::{Precompile, PrecompileHandle, PrecompileOutput}; +use laos_precompile_utils::{ + keccak256, revert_dispatch_error, succeed, Address, Bytes, EvmDataReader, EvmDataWriter, + EvmResult, FunctionModifier, GasCalculator, LogExt, LogsBuilder, PrecompileHandleExt, +}; use pallet_asset_metadata_extender::{ traits::AssetMetadataExtender as AssetMetadataExtenderT, weights::{SubstrateWeight as AssetMetadataExtenderWeights, WeightInfo}, - Config, Pallet as AssetMetadataExtender, + Pallet as AssetMetadataExtender, }; -use pallet_evm::GasWeightMapping; use parity_scale_codec::Encode; -use precompile_utils::{ - prelude::{keccak256, log3, Address, EvmResult, LogExt}, - solidity::{self, codec::UnboundedString, revert::revert}, -}; -use scale_info::prelude::{format, string::String}; -use sp_core::H160; +use precompile_utils::solidity::revert::revert; + +use sp_core::{Get, H160}; use sp_io::hashing::keccak_256; -use sp_runtime::{BoundedVec, DispatchError}; +use sp_runtime::BoundedVec; use sp_std::{fmt::Debug, marker::PhantomData}; /// Solidity selector of the ExtendedULWithExternalURI log, which is the Keccak of the Log @@ -43,263 +42,264 @@ pub const SELECTOR_LOG_EXTENDED_UL_WITH_EXTERNAL_URI: [u8; 32] = pub const SELECTOR_LOG_UPDATED_EXTENDED_UL_WITH_EXTERNAL_URI: [u8; 32] = keccak256!("UpdatedExtendedULWithExternalURI(address,bytes32,string,string)"); -#[derive(DefaultNoBound)] -pub struct AssetMetadataExtenderPrecompile(PhantomData); +#[laos_precompile_utils_macro::generate_function_selector] +#[derive(Debug, PartialEq)] +pub enum Action { + /// Extend asset metadata (token uri) + Extend = "extendULWithExternalURI(string,string)", + /// Get extensions balance for a given universal location + Balance = "balanceOfUL(string)", + /// Get claimer of a given universal location using indexation + Claimer = "claimerOfULByIndex(string,uint32)", + /// Get token uri of a given universal location using indexation + Extension = "extensionOfULByIndex(string,uint32)", // TODO rename `extension` for `tokenURI`? + /// Update token uri of a given universal location using indexation + Update = "updateExtendedULWithExternalURI(string,string)", + /// Get extension of a given universal location using claimer + ExtensionOfULByClaimer = "extensionOfULByClaimer(string,address)", + /// Check if a given universal location has an extension + HasExtension = "hasExtensionByClaimer(string,address)", +} + +pub struct AssetMetadataExtenderPrecompile(PhantomData) +where + Runtime: pallet_asset_metadata_extender::Config; -impl AssetMetadataExtenderPrecompile { - pub fn new() -> Self { - Self(PhantomData) +impl Precompile for AssetMetadataExtenderPrecompile +where + Runtime: pallet_evm::Config + pallet_asset_metadata_extender::Config, + Runtime::AccountId: From + Into + Encode + Debug, + AssetMetadataExtender: AssetMetadataExtenderT, +{ + fn execute(handle: &mut impl PrecompileHandle) -> EvmResult { + let selector = handle.read_selector()?; + + handle.check_function_modifier(match selector { + Action::Extend => FunctionModifier::NonPayable, + Action::Balance => FunctionModifier::View, + Action::Claimer => FunctionModifier::View, + Action::Extension => FunctionModifier::View, + Action::Update => FunctionModifier::NonPayable, + Action::ExtensionOfULByClaimer => FunctionModifier::View, + Action::HasExtension => FunctionModifier::View, + })?; + + match selector { + Action::Extend => Self::extend(handle), + Action::Balance => Self::balance_of(handle), + Action::Claimer => Self::claimer_by_index(handle), + Action::Extension => Self::extension_by_index(handle), + Action::Update => Self::update(handle), + Action::ExtensionOfULByClaimer => Self::extension_by_location_and_claimer(handle), + Action::HasExtension => Self::has_extension_by_claimer(handle), + } } } -#[precompile_utils::precompile] impl AssetMetadataExtenderPrecompile where Runtime: pallet_evm::Config + pallet_asset_metadata_extender::Config, Runtime::AccountId: From + Into + Encode + Debug, AssetMetadataExtender: AssetMetadataExtenderT, { - #[precompile::public("extendULWithExternalURI(string,string)")] - fn extend( - handle: &mut impl PrecompileHandle, - universal_location: UnboundedString, - token_uri: UnboundedString, - ) -> EvmResult<()> { - // TODO this might be remove when we have the bounded string as param - let universal_location_bounded: BoundedVec< - u8, - ::MaxUniversalLocationLength, - > = universal_location - .as_bytes() - .to_vec() - .try_into() + fn extend(handle: &mut impl PrecompileHandle) -> EvmResult { + let context = handle.context(); + let claimer = context.caller; + + let mut input = handle.read_input()?; + input.expect_arguments(2)?; + + // get universal location from input + let universal_location = Self::read_bounded_vec(&mut input) .map_err(|_| revert("invalid universal location length"))?; - // TODO this might be remove when we have the bounded string as param - let token_uri_bounded: BoundedVec::MaxTokenUriLength> = token_uri - .as_bytes() - .to_vec() - .try_into() - .map_err(|_| revert("invalid token uri length"))?; + // get token uri from input + let token_uri = + Self::read_bounded_vec(&mut input).map_err(|_| revert("invalid token uri length"))?; AssetMetadataExtender::::create_token_uri_extension( - handle.context().caller.into(), - universal_location_bounded.clone(), - token_uri_bounded.clone(), + claimer.into(), + universal_location.clone(), + token_uri.clone(), ) - .map_err(|err| revert(convert_dispatch_error_to_string(err)))?; + .map_err(revert_dispatch_error)?; + + let universal_location_raw = universal_location.into_inner(); + let token_uri_raw = token_uri.into_inner(); let consumed_weight = AssetMetadataExtenderWeights::::create_token_uri_extension( - universal_location.as_bytes().to_vec().len() as u32, - token_uri.as_bytes().to_vec().len() as u32, + universal_location_raw.len() as u32, + token_uri_raw.len() as u32, ); - let ul_hash = keccak_256(universal_location.as_bytes()); - log3( - handle.context().address, - SELECTOR_LOG_EXTENDED_UL_WITH_EXTERNAL_URI, - handle.context().caller, - ul_hash, - solidity::encode_event_data((universal_location, token_uri)), - ) - .record(handle)?; + let ul_hash = keccak_256(&universal_location_raw); + + LogsBuilder::new(context.address) + .log3( + SELECTOR_LOG_EXTENDED_UL_WITH_EXTERNAL_URI, + claimer, + ul_hash, + EvmDataWriter::new() + .write(Bytes(universal_location_raw)) + .write(Bytes(token_uri_raw)) + .build(), + ) + .record(handle)?; // Record EVM cost - handle.record_cost(::GasWeightMapping::weight_to_gas( - consumed_weight, - ))?; + handle.record_cost(GasCalculator::::weight_to_gas(consumed_weight))?; // Record Substrate related costs // TODO: Add `ref_time` when precompiles are benchmarked handle.record_external_cost(None, Some(consumed_weight.proof_size()))?; - Ok(()) + Ok(succeed(EvmDataWriter::new().build())) } - #[precompile::public("updateExtendedULWithExternalURI(string,string)")] - fn update( - handle: &mut impl PrecompileHandle, - universal_location: UnboundedString, - token_uri: UnboundedString, - ) -> EvmResult<()> { - // TODO this might be remove when we have the bounded string as param - let universal_location_bounded: BoundedVec< - u8, - ::MaxUniversalLocationLength, - > = universal_location - .as_bytes() - .to_vec() - .try_into() + fn update(handle: &mut impl PrecompileHandle) -> EvmResult { + let context = handle.context(); + + let mut input = handle.read_input()?; + input.expect_arguments(2)?; + + let universal_location = Self::read_bounded_vec(&mut input) .map_err(|_| revert("invalid universal location length"))?; - // TODO this might be remove when we have the bounded string as param - let token_uri_bounded: BoundedVec::MaxTokenUriLength> = token_uri - .as_bytes() - .to_vec() - .try_into() - .map_err(|_| revert("invalid token uri length"))?; + let token_uri = + Self::read_bounded_vec(&mut input).map_err(|_| revert("invalid token uri length"))?; + + let claimer = context.caller; + let universal_location_hash = keccak_256(&universal_location); AssetMetadataExtender::::update_token_uri_extension( - handle.context().caller.into(), - universal_location_bounded.clone(), - token_uri_bounded.clone(), + claimer.into(), + universal_location.clone(), + token_uri.clone(), ) - .map_err(|err| revert(convert_dispatch_error_to_string(err)))?; + .map_err(revert_dispatch_error)?; + let universal_location_raw = universal_location.into_inner(); + let token_uri_raw = token_uri.into_inner(); let consumed_weight = AssetMetadataExtenderWeights::::update_token_uri_extension( - universal_location.as_bytes().to_vec().len() as u32, - token_uri.as_bytes().to_vec().len() as u32, + universal_location_raw.len() as u32, + token_uri_raw.len() as u32, ); - let ul_hash = keccak_256(universal_location.as_bytes()); - log3( - handle.context().address, - SELECTOR_LOG_UPDATED_EXTENDED_UL_WITH_EXTERNAL_URI, - handle.context().caller, - ul_hash, - solidity::encode_event_data((universal_location, token_uri)), - ) - .record(handle)?; + LogsBuilder::new(context.address) + .log3( + SELECTOR_LOG_UPDATED_EXTENDED_UL_WITH_EXTERNAL_URI, + claimer, + universal_location_hash, + EvmDataWriter::new() + .write(Bytes(universal_location_raw)) + .write(Bytes(token_uri_raw)) + .build(), + ) + .record(handle)?; // Record EVM cost - handle.record_cost(::GasWeightMapping::weight_to_gas( - consumed_weight, - ))?; + handle.record_cost(GasCalculator::::weight_to_gas(consumed_weight))?; - Ok(()) + Ok(succeed(EvmDataWriter::new().build())) } - #[precompile::public("balanceOfUL(string)")] - fn balance_of( - _handle: &mut impl PrecompileHandle, - universal_location: UnboundedString, - ) -> EvmResult { - // TODO this might be remove when we have the bounded string as param - let universal_location_bounded: BoundedVec< - u8, - ::MaxUniversalLocationLength, - > = universal_location - .as_bytes() - .to_vec() - .try_into() + fn balance_of(handle: &mut impl PrecompileHandle) -> EvmResult { + let mut input = handle.read_input()?; + input.expect_arguments(1)?; + + let universal_location = Self::read_bounded_vec(&mut input) .map_err(|_| revert("invalid universal location length"))?; - let balance = AssetMetadataExtender::::balance_of(universal_location_bounded); + let balance = AssetMetadataExtender::::balance_of(universal_location); - Ok(balance) + Ok(succeed(EvmDataWriter::new().write(balance).build())) } - #[precompile::public("claimerOfULByIndex(string,uint32)")] - fn claimer_by_index( - _handle: &mut impl PrecompileHandle, - universal_location: UnboundedString, - index: u32, - ) -> EvmResult
{ - // TODO this might be remove when we have the bounded string as param - let universal_location_bounded: BoundedVec< - u8, - ::MaxUniversalLocationLength, - > = universal_location - .as_bytes() - .to_vec() - .try_into() + fn claimer_by_index(handle: &mut impl PrecompileHandle) -> EvmResult { + let mut input = handle.read_input()?; + input.expect_arguments(2)?; + + let universal_location = Self::read_bounded_vec(&mut input) .map_err(|_| revert("invalid universal location length"))?; - if AssetMetadataExtender::balance_of(universal_location_bounded.clone()) <= index { + let index = input.read::()?; + + if AssetMetadataExtender::balance_of(universal_location.clone()) <= index { return Err(revert("invalid index")); } - let claimer = AssetMetadataExtender::::claimer_by_index( - universal_location_bounded.clone(), - index, - ) - .ok_or_else(|| revert("invalid ul"))?; + let claimer = + AssetMetadataExtender::::claimer_by_index(universal_location.clone(), index) + .ok_or_else(|| revert("invalid ul"))?; - Ok(Address(claimer.into())) + Ok(succeed(EvmDataWriter::new().write(Address(claimer.into())).build())) } - #[precompile::public("extensionOfULByIndex(string,uint32)")] - fn extension_by_index( - _handle: &mut impl PrecompileHandle, - universal_location: UnboundedString, - index: u32, - ) -> EvmResult { - let universal_location_bounded: BoundedVec< - u8, - ::MaxUniversalLocationLength, - > = universal_location - .as_bytes() - .to_vec() - .try_into() + fn extension_by_index(handle: &mut impl PrecompileHandle) -> EvmResult { + let mut input = handle.read_input()?; + input.expect_arguments(2)?; + + let universal_location = Self::read_bounded_vec(&mut input) .map_err(|_| revert("invalid universal location length"))?; - if AssetMetadataExtender::balance_of(universal_location_bounded.clone()) <= index { + let index = input.read::()?; + + if AssetMetadataExtender::balance_of(universal_location.clone()) <= index { return Err(revert("invalid index")); } let token_uri = AssetMetadataExtender::::token_uri_extension_by_index( - universal_location_bounded.clone(), + universal_location.clone(), index, ) .ok_or_else(|| revert("invalid ul"))?; - Ok(token_uri.to_vec().into()) + Ok(succeed(EvmDataWriter::new().write(Bytes(token_uri.into_inner())).build())) + } + + /// Generic function to read a bounded vector from the input. + fn read_bounded_vec>( + input: &mut EvmDataReader, + ) -> Result, ()> { + let raw_vec = input.read::().map_err(|_| ())?.0; + raw_vec.try_into().map_err(|_| ()) } - #[precompile::public("extensionOfULByClaimer(string,address)")] fn extension_by_location_and_claimer( - _handle: &mut impl PrecompileHandle, - universal_location: UnboundedString, - claimer: Address, - ) -> EvmResult { - let claimer: H160 = claimer.into(); - let universal_location_bounded: BoundedVec< - u8, - ::MaxUniversalLocationLength, - > = universal_location - .as_bytes() - .to_vec() - .try_into() + handle: &mut impl PrecompileHandle, + ) -> EvmResult { + let mut input = handle.read_input()?; + input.expect_arguments(2)?; + + let universal_location = Self::read_bounded_vec(&mut input) .map_err(|_| revert("invalid universal location length"))?; + let claimer = input.read::
().map_err(|_| revert("invalid claimer"))?.0; + let token_uri = AssetMetadataExtender::::extension_by_location_and_claimer( - universal_location_bounded.clone(), + universal_location.clone(), claimer.into(), ) .ok_or_else(|| revert("invalid ul"))?; - Ok(token_uri.to_vec().into()) + Ok(succeed(EvmDataWriter::new().write(Bytes(token_uri.into_inner())).build())) } - #[precompile::public("hasExtensionByClaimer(string,address)")] - fn has_extension_by_claimer( - _handle: &mut impl PrecompileHandle, - universal_location: UnboundedString, - claimer: Address, - ) -> EvmResult { - let claimer: H160 = claimer.into(); - let universal_location_bounded: BoundedVec< - u8, - ::MaxUniversalLocationLength, - > = universal_location - .as_bytes() - .to_vec() - .try_into() + fn has_extension_by_claimer(handle: &mut impl PrecompileHandle) -> EvmResult { + let mut input = handle.read_input()?; + input.expect_arguments(2)?; + + let universal_location = Self::read_bounded_vec(&mut input) .map_err(|_| revert("invalid universal location length"))?; + let claimer = input.read::
().map_err(|_| revert("invalid claimer"))?.0; + let has_extension = AssetMetadataExtender::::has_extension( - universal_location_bounded.clone(), + universal_location.clone(), claimer.into(), ); - Ok(has_extension) - } -} - -fn convert_dispatch_error_to_string(err: DispatchError) -> String { - match err { - DispatchError::Module(mod_err) => mod_err.message.unwrap_or("Unknown module error").into(), - _ => format!("{:?}", err), + Ok(succeed(EvmDataWriter::new().write(has_extension).build())) } } diff --git a/precompile/asset-metadata-extender/src/mock.rs b/precompile/asset-metadata-extender/src/mock.rs index ebdd429ea..a552ca42f 100644 --- a/precompile/asset-metadata-extender/src/mock.rs +++ b/precompile/asset-metadata-extender/src/mock.rs @@ -15,14 +15,14 @@ // along with LAOS. If not, see . use core::str::FromStr; +use fp_evm::{Precompile, PrecompileHandle}; use frame_support::{ derive_impl, parameter_types, traits::FindAuthor, weights::constants::RocksDbWeight, }; -use precompile_utils::precompile_set::{AddressU64, PrecompileAt, PrecompileSetBuilder}; use sp_core::{H160, U256}; use sp_runtime::{traits::IdentityLookup, BuildStorage, ConsensusEngineId}; -use crate::{AssetMetadataExtenderPrecompile, AssetMetadataExtenderPrecompileCall}; +use crate::AssetMetadataExtenderPrecompile; // Configure a mock runtime to test the pallet. frame_support::construct_runtime!( @@ -69,6 +69,7 @@ impl pallet_asset_metadata_extender::Config for Test { type MaxUniversalLocationLength = MaxUniversalLocationLength; type MaxTokenUriLength = MaxTokenUriLength; type AccountIdToH160 = AccountIdToH160; + type GasWeightMapping = pallet_evm::FixedGasWeightMapping; } pub struct AccountIdToH160; @@ -95,8 +96,8 @@ impl pallet_evm::Config for Test { type AddressMapping = pallet_evm::IdentityAddressMapping; type Currency = Balances; type RuntimeEvent = RuntimeEvent; - type PrecompilesType = LaosPrecompiles; - type PrecompilesValue = PrecompilesInstance; + type PrecompilesType = MockPrecompileSet; + type PrecompilesValue = MockPrecompiles; type ChainId = (); type BlockGasLimit = BlockGasLimit; type Runner = pallet_evm::runner::stack::Runner; @@ -111,19 +112,12 @@ impl pallet_evm::Config for Test { pub const BLOCK_GAS_LIMIT: u64 = 15_000_000; pub const MAX_POV_SIZE: u64 = 5 * 1024 * 1024; -pub type PrecompileCall = AssetMetadataExtenderPrecompileCall; - -pub type LaosPrecompiles = PrecompileSetBuilder< - Test, - (PrecompileAt, AssetMetadataExtenderPrecompile>,), ->; - frame_support::parameter_types! { pub BlockGasLimit: U256 = U256::from(crate::mock::BLOCK_GAS_LIMIT); pub const GasLimitPovSizeRatio: u64 = crate::mock::BLOCK_GAS_LIMIT.saturating_div(crate::mock::MAX_POV_SIZE); /// 1 weight to 1 gas, for testing purposes pub WeightPerGas: frame_support::weights::Weight = frame_support::weights::Weight::from_parts(1, 0); - pub PrecompilesInstance: LaosPrecompiles = LaosPrecompiles::new(); + pub MockPrecompiles: MockPrecompileSet = MockPrecompileSet::<_>::new(); } pub struct FindAuthorTruncated; @@ -136,6 +130,33 @@ impl FindAuthor for FindAuthorTruncated { } } +#[derive(Default)] +pub struct MockPrecompileSet(sp_std::marker::PhantomData); + +pub type MockAssetMetadataExtenderPrecompile = AssetMetadataExtenderPrecompile; + +impl MockPrecompileSet +where + Test: pallet_evm::Config, +{ + pub fn new() -> Self { + Self(Default::default()) + } +} + +impl fp_evm::PrecompileSet for MockPrecompileSet +where + Test: pallet_evm::Config + pallet_asset_metadata_extender::Config, +{ + fn execute(&self, handle: &mut impl PrecompileHandle) -> Option { + Some(MockAssetMetadataExtenderPrecompile::execute(handle)) + } + + fn is_precompile(&self, _address: H160, _gas: u64) -> fp_evm::IsPrecompileResult { + fp_evm::IsPrecompileResult::Answer { is_precompile: true, extra_cost: 0 } + } +} + // Pallet Timestamp parameter_types! { pub const MinimumPeriod: u64 = 1000; diff --git a/precompile/asset-metadata-extender/src/tests.rs b/precompile/asset-metadata-extender/src/tests.rs index 043794bfb..c3b6568aa 100644 --- a/precompile/asset-metadata-extender/src/tests.rs +++ b/precompile/asset-metadata-extender/src/tests.rs @@ -16,33 +16,21 @@ use super::*; use crate::mock::*; -use fp_evm::{Context, PrecompileSet}; -use precompile_utils::{ - prelude::log3, - testing::{Alice, MockHandle, Precompile1, PrecompileTesterExt}, -}; -use sp_core::U256; -use sp_io::hashing::keccak_256; +use core::str::FromStr; +use fp_evm::Log; +use laos_precompile_utils::EvmDataWriter; +use precompile_utils::{solidity::codec::BoundedBytes, testing::PrecompileTesterExt}; +use sp_core::{H160, H256, U256}; + +/// Fixed precompile address for testing. +const PRECOMPILE_ADDRESS: [u8; 20] = [5u8; 20]; /// Get precompiles from the mock. -fn precompiles() -> LaosPrecompiles { - PrecompilesInstance::get() +fn precompiles() -> MockPrecompileSet { + MockPrecompiles::get() } -/// Utility function to extend an universal location. -/// -/// Note: this function is used instead of `PrecompileTesterExt::execute_returns` because the latter -/// does not return the output of the precompile. And `PrecompileTester::execute` is a private -/// function. -fn extend(universal_location: UnboundedString, token_uri: UnboundedString) { - let mut handle = MockHandle::new( - Precompile1.into(), - Context { address: Precompile1.into(), caller: Alice.into(), apparent_value: U256::zero() }, - ); - handle.input = PrecompileCall::extend { universal_location, token_uri }.into(); - - precompiles().execute(&mut handle).unwrap().unwrap(); -} +const TEST_CLAIMER: &str = "0xf24FF3a9CF04c71Dbc94D0b566f7A27B94566cac"; #[test] fn check_log_selectors() { @@ -57,36 +45,58 @@ fn check_log_selectors() { } #[test] -fn selectors() { - assert!(PrecompileCall::extend_selectors().contains(&0xA5FBDF1D)); - assert!(PrecompileCall::update_selectors().contains(&0xCD79C745)); - assert!(PrecompileCall::balance_of_selectors().contains(&0x7B65DED5)); - assert!(PrecompileCall::claimer_by_index_selectors().contains(&0xA565BB04)); - assert!(PrecompileCall::extension_by_index_selectors().contains(&0xB2B7C05A)); +fn function_selectors() { + assert_eq!(Action::Extend as u32, 0xA5FBDF1D); + assert_eq!(Action::Balance as u32, 0x7B65DED5); + assert_eq!(Action::Claimer as u32, 0xA565BB04); + assert_eq!(Action::Extension as u32, 0xB2B7C05A); + assert_eq!(Action::Update as u32, 0xCD79C745); +} + +#[test] +#[ignore] +fn call_unexistent_selector_should_fail() { + new_test_ext().execute_with(|| { + let input = EvmDataWriter::new_with_selector(0x12345678_u32) + .write(Bytes("my_awesome_universal_location".as_bytes().to_vec())) + .build(); + + precompiles() + .prepare_test(H160::from_str(TEST_CLAIMER).unwrap(), H160(PRECOMPILE_ADDRESS), input) + .execute_reverts(|r| r == b"unknown selector"); + }); } #[test] fn create_token_uri_extension_should_emit_log() { new_test_ext().execute_with(|| { - let universal_location: UnboundedString = "my_awesome_universal_location".into(); - let token_uri: UnboundedString = "ciao".into(); + let universal_location = Bytes("my_awesome_universal_location".as_bytes().to_vec()); + let token_uri = Bytes("ciao".as_bytes().to_vec()); + + let input = EvmDataWriter::new_with_selector(Action::Extend) + .write(universal_location.clone()) + .write(token_uri.clone()) + .build(); + + let expected_log = Log { + address: H160(PRECOMPILE_ADDRESS), + topics: vec![ + SELECTOR_LOG_EXTENDED_UL_WITH_EXTERNAL_URI.into(), + H256::from_str( + format!("000000000000000000000000{}", TEST_CLAIMER.trim_start_matches("0x")) + .as_str(), + ) + .unwrap(), + keccak_256(&universal_location.0).into(), + ], + // ul is 29 bytes, so it's prepended with 64 bytes of zeros + ul + 3 bytes to make it 32 + data: hex::decode("00000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000080000000000000000000000000000000000000000000000000000000000000001D6D795F617765736F6D655F756E6976657273616C5F6C6F636174696F6E00000000000000000000000000000000000000000000000000000000000000000000046369616F00000000000000000000000000000000000000000000000000000000") + .unwrap(), + }; precompiles() - .prepare_test( - Alice, - Precompile1, - PrecompileCall::extend { - universal_location: universal_location.clone(), - token_uri: token_uri.clone(), - }, - ) - .expect_log(log3( - Precompile1, - SELECTOR_LOG_EXTENDED_UL_WITH_EXTERNAL_URI, - Alice, - keccak_256(universal_location.as_bytes()), - solidity::encode_event_data((universal_location, token_uri)), - )) + .prepare_test(H160::from_str(TEST_CLAIMER).unwrap(), H160(PRECOMPILE_ADDRESS), input) + .expect_log(expected_log) .execute_some(); }); } @@ -95,18 +105,16 @@ fn create_token_uri_extension_should_emit_log() { fn create_token_uri_extension_reverts_when_ul_exceeds_length() { new_test_ext().execute_with(|| { let unallowed_size = (MaxUniversalLocationLength::get() + 10).try_into().unwrap(); - let universal_location: UnboundedString = vec![b'a'; unallowed_size].into(); - let token_uri: UnboundedString = "ciao".into(); + let universal_location = Bytes(vec![b'a'; unallowed_size]); + let token_uri = Bytes(vec![b'b'; MaxTokenUriLength::get().try_into().unwrap()]); + + let input = EvmDataWriter::new_with_selector(Action::Extend) + .write(universal_location) + .write(token_uri) + .build(); precompiles() - .prepare_test( - Alice, - Precompile1, - PrecompileCall::extend { - universal_location: universal_location.clone(), - token_uri: token_uri.clone(), - }, - ) + .prepare_test(H160::from_str(TEST_CLAIMER).unwrap(), H160(PRECOMPILE_ADDRESS), input) .execute_reverts(|r| r == b"invalid universal location length"); }); } @@ -114,18 +122,18 @@ fn create_token_uri_extension_reverts_when_ul_exceeds_length() { #[test] fn create_token_uri_extension_reverts_when_token_uri_exceeds_length() { new_test_ext().execute_with(|| { - let unallowed_size = (MaxTokenUriLength::get() + 10).try_into().unwrap(); - let token_uri: UnboundedString = vec![b'a'; unallowed_size].into(); - let universal_location: UnboundedString = "ciao".into(); + let unallowed_size = (MaxTokenUriLength::get() + 1).try_into().unwrap(); + let token_uri = Bytes(vec![b'a'; unallowed_size]); + let universal_location = + Bytes(vec![b'b'; MaxUniversalLocationLength::get().try_into().unwrap()]); + + let input = EvmDataWriter::new_with_selector(Action::Extend) + .write(universal_location) + .write(token_uri) + .build(); + precompiles() - .prepare_test( - Alice, - Precompile1, - PrecompileCall::extend { - universal_location: universal_location.clone(), - token_uri: token_uri.clone(), - }, - ) + .prepare_test(H160::from_str(TEST_CLAIMER).unwrap(), H160(PRECOMPILE_ADDRESS), input) .execute_reverts(|r| r == b"invalid token uri length"); }); } @@ -134,29 +142,25 @@ fn create_token_uri_extension_reverts_when_token_uri_exceeds_length() { fn create_token_uri_extension_reverts_when_claimer_already_has_metadata_extension_for_universal_location( ) { new_test_ext().execute_with(|| { - let universal_location: UnboundedString = "my_awesome_universal_location".into(); - let token_uri: UnboundedString = "ciao".into(); + let universal_location = Bytes("my_awesome_universal_location".as_bytes().to_vec()); + let token_uri = Bytes("my_awesome_token_uri".as_bytes().to_vec()); + + let input = EvmDataWriter::new_with_selector(Action::Extend) + .write(universal_location.clone()) + .write(token_uri.clone()) + .build(); precompiles() - .prepare_test( - Alice, - Precompile1, - PrecompileCall::extend { - universal_location: universal_location.clone(), - token_uri: token_uri.clone(), - }, - ) - .execute_returns(()); + .prepare_test(H160::from_str(TEST_CLAIMER).unwrap(), H160(PRECOMPILE_ADDRESS), input) + .execute_returns_raw(vec![]); + + let input = EvmDataWriter::new_with_selector(Action::Extend) + .write(universal_location) + .write(token_uri) + .build(); precompiles() - .prepare_test( - Alice, - Precompile1, - PrecompileCall::extend { - universal_location: universal_location.clone(), - token_uri: token_uri.clone(), - }, - ) + .prepare_test(H160::from_str(TEST_CLAIMER).unwrap(), H160(PRECOMPILE_ADDRESS), input) .execute_reverts(|r| r == b"ExtensionAlreadyExists"); }); } @@ -164,28 +168,46 @@ fn create_token_uri_extension_reverts_when_claimer_already_has_metadata_extensio #[test] fn create_token_uri_extension_on_mock_with_nonzero_value_fails() { new_test_ext().execute_with(|| { - let universal_location: UnboundedString = "my_awesome_universal_location".into(); - let token_uri: UnboundedString = "ciao".into(); + let universal_location = Bytes("my_awesome_universal_location".as_bytes().to_vec()); + let token_uri = Bytes("my_awesome_token_uri".as_bytes().to_vec()); + let input = EvmDataWriter::new_with_selector(Action::Extend) + .write(universal_location) + .write(token_uri) + .build(); precompiles() - .prepare_test( - Alice, - Precompile1, - PrecompileCall::extend { - universal_location: universal_location.clone(), - token_uri: token_uri.clone(), - }, - ) + .prepare_test(H160::from_str(TEST_CLAIMER).unwrap(), H160(PRECOMPILE_ADDRESS), input) .with_value(U256::from(1)) - .execute_reverts(|r| r == b"Function is not payable"); + .execute_reverts(|r| r == b"function is not payable"); + }); +} + +#[test] +fn update_inexistent_extension_should_fail() { + new_test_ext().execute_with(|| { + let universal_location = Bytes("my_awesome_universal_location".as_bytes().to_vec()); + let token_uri = Bytes("my_awesome_token_uri".as_bytes().to_vec()); + + let input = EvmDataWriter::new_with_selector(Action::Update) + .write(universal_location) + .write(token_uri) + .build(); + + precompiles() + .prepare_test(H160::from_str(TEST_CLAIMER).unwrap(), H160(PRECOMPILE_ADDRESS), input) + .execute_reverts(|r| r == b"ExtensionDoesNotExist"); }); } #[test] fn create_token_uri_extension_records_cost() { new_test_ext().execute_with(|| { - let universal_location: UnboundedString = "my_awesome_universal_location".into(); - let token_uri: UnboundedString = "ciao".into(); + let universal_location = Bytes("my_awesome_universal_location".as_bytes().to_vec()); + let token_uri = Bytes("my_awesome_token_uri".as_bytes().to_vec()); + let input = EvmDataWriter::new_with_selector(Action::Extend) + .write(universal_location) + .write(token_uri) + .build(); // Expected weight of the precompile call implementation. // Since benchmarking precompiles is not supported yet, we are benchmarking @@ -194,68 +216,32 @@ fn create_token_uri_extension_records_cost() { // Following `cost` is calculated as: // `create_token_uri_extension` weight + log cost precompiles() - .prepare_test( - Alice, - Precompile1, - PrecompileCall::extend { - universal_location: universal_location.clone(), - token_uri: token_uri.clone(), - }, - ) - .expect_cost(389431817) // [`WeightToGas`] set to 1:1 in mock + .prepare_test(H160::from_str(TEST_CLAIMER).unwrap(), H160(PRECOMPILE_ADDRESS), input) + .expect_cost(364560357) // [`WeightToGas`] set to 1:1 in mock .execute_some(); }) } #[test] -fn update_inexistent_extension_should_fail() { - new_test_ext().execute_with(|| { - let universal_location: UnboundedString = "my_awesome_universal_location".into(); - let token_uri: UnboundedString = "ciao".into(); - - precompiles() - .prepare_test( - Alice, - Precompile1, - PrecompileCall::update { - universal_location: universal_location.clone(), - token_uri: token_uri.clone(), - }, - ) - .execute_reverts(|r| r == b"ExtensionDoesNotExist"); - }); -} - -#[test] -fn update_of_extension_should_succeed() { +fn update_token_uri_extension_records_cost() { new_test_ext().execute_with(|| { - let universal_location: UnboundedString = "my_awesome_universal_location".into(); - let token_uri: UnboundedString = "my_awesome_token_uri".into(); - extend(universal_location.clone(), token_uri.clone()); + let universal_location = Bytes("my_awesome_universal_location".as_bytes().to_vec()); + let token_uri = Bytes("my_awesome_token_uri".as_bytes().to_vec()); - let new_token_uri: UnboundedString = "my_awesome_new_token_uri".into(); + let input = EvmDataWriter::new_with_selector(Action::Extend) + .write(universal_location.clone()) + .write(token_uri.clone()) + .build(); precompiles() - .prepare_test( - Alice, - Precompile1, - PrecompileCall::update { - universal_location: universal_location.clone(), - token_uri: new_token_uri.clone(), - }, - ) - .execute_returns(()); - }); -} - -#[test] -fn update_token_uri_extension_records_cost() { - new_test_ext().execute_with(|| { - let universal_location: UnboundedString = "my_awesome_universal_location".into(); - let token_uri: UnboundedString = "my_awesome_token_uri".into(); - extend(universal_location.clone(), token_uri.clone()); + .prepare_test(H160::from_str(TEST_CLAIMER).unwrap(), H160(PRECOMPILE_ADDRESS), input) + .execute_returns_raw(vec![]); - let new_token_uri: UnboundedString = "my_awesome_new_token_uri".into(); + let new_token_uri = Bytes("my_awesome_new_token_uri".as_bytes().to_vec()); + let input = EvmDataWriter::new_with_selector(Action::Update) + .write(universal_location) + .write(new_token_uri) + .build(); // Expected weight of the precompile call implementation. // Since benchmarking precompiles is not supported yet, we are benchmarking @@ -264,75 +250,117 @@ fn update_token_uri_extension_records_cost() { // Following `cost` is calculated as: // `create_token_uri_extension` weight + log cost precompiles() - .prepare_test( - Alice, - Precompile1, - PrecompileCall::update { - universal_location: universal_location.clone(), - token_uri: new_token_uri.clone(), - }, - ) - .expect_cost(161656038) // [`WeightToGas`] set to 1:1 in mock + .prepare_test(H160::from_str(TEST_CLAIMER).unwrap(), H160(PRECOMPILE_ADDRESS), input) + .expect_cost(136659074) // [`WeightToGas`] set to 1:1 in mock .execute_some(); }) } +#[test] +fn update_of_extension_should_succeed() { + new_test_ext().execute_with(|| { + let universal_location = Bytes("my_awesome_universal_location".as_bytes().to_vec()); + let token_uri = Bytes("my_awesome_token_uri".as_bytes().to_vec()); + + let input = EvmDataWriter::new_with_selector(Action::Extend) + .write(universal_location.clone()) + .write(token_uri.clone()) + .build(); + + precompiles() + .prepare_test(H160::from_str(TEST_CLAIMER).unwrap(), H160(PRECOMPILE_ADDRESS), input) + .execute_returns_raw(vec![]); + + let new_token_uri = Bytes("my_awesome_new_token_uri".as_bytes().to_vec()); + + let input = EvmDataWriter::new_with_selector(Action::Update) + .write(universal_location) + .write(new_token_uri) + .build(); + + precompiles() + .prepare_test(H160::from_str(TEST_CLAIMER).unwrap(), H160(PRECOMPILE_ADDRESS), input) + .execute_returns_raw(vec![]); + }); +} + #[test] fn update_of_extension_should_emit_a_log() { new_test_ext().execute_with(|| { - let universal_location: UnboundedString = "my_awesome_universal_location".into(); - let token_uri: UnboundedString = "my_awesome_token_uri".into(); - extend(universal_location.clone(), token_uri.clone()); + let universal_location = Bytes("my_awesome_universal_location".as_bytes().to_vec()); + let token_uri = Bytes("my_awesome_token_uri".as_bytes().to_vec()); - let new_token_uri: UnboundedString = "my_awesome_new_token_uri".into(); + let input = EvmDataWriter::new_with_selector(Action::Extend) + .write(universal_location.clone()) + .write(token_uri.clone()) + .build(); precompiles() - .prepare_test( - Alice, - Precompile1, - PrecompileCall::update { - universal_location: universal_location.clone(), - token_uri: new_token_uri.clone(), - }, - ) - .expect_log(log3( - Precompile1, - SELECTOR_LOG_UPDATED_EXTENDED_UL_WITH_EXTERNAL_URI, - Alice, - keccak_256(universal_location.as_bytes()), - solidity::encode_event_data((universal_location, new_token_uri)), - )) - .execute_returns(()); + .prepare_test(H160::from_str(TEST_CLAIMER).unwrap(), H160(PRECOMPILE_ADDRESS), input) + .execute_returns_raw(vec![]); + + let new_token_uri = Bytes("my_awesome_new_token_uri".as_bytes().to_vec()); + + let input = EvmDataWriter::new_with_selector(Action::Update) + .write(universal_location) + .write(new_token_uri) + .build(); + + let expected_log = Log { + address: H160(PRECOMPILE_ADDRESS), + topics: vec![ + SELECTOR_LOG_UPDATED_EXTENDED_UL_WITH_EXTERNAL_URI.into(), + H160::from_str(TEST_CLAIMER).unwrap().into(), + keccak256!("my_awesome_universal_location").into(), + ], + data: hex::decode("00000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000080000000000000000000000000000000000000000000000000000000000000001D6D795F617765736F6D655F756E6976657273616C5F6C6F636174696F6E00000000000000000000000000000000000000000000000000000000000000000000186D795F617765736F6D655F6E65775F746F6B656E5F7572690000000000000000") + .unwrap(), + }; + + precompiles() + .prepare_test(H160::from_str(TEST_CLAIMER).unwrap(), H160(PRECOMPILE_ADDRESS), input) + .expect_log(expected_log) + .execute_returns_raw(vec![]); }); } #[test] fn claimer_by_index_invalid_index_fails() { new_test_ext().execute_with(|| { - let universal_location: UnboundedString = "my_awesome_universal_location".into(); + let universal_location = Bytes("my_awesome_universal_location".as_bytes().to_vec()); + let input = EvmDataWriter::new_with_selector(Action::Claimer) + .write(universal_location.clone()) + .write(2u32) + .build(); precompiles() .prepare_test( - Alice, - Precompile1, - PrecompileCall::claimer_by_index { - universal_location: universal_location.clone(), - index: 2u32, - }, + H160::from_str(TEST_CLAIMER).unwrap(), + H160(PRECOMPILE_ADDRESS), + input.clone(), ) .execute_reverts(|r| r == b"invalid index"); - extend(universal_location.clone(), "ciao".into()); + let input = EvmDataWriter::new_with_selector(Action::Extend) + .write(universal_location.clone()) + .write(Bytes(vec![1u8; 10])) + .build(); precompiles() .prepare_test( - Alice, - Precompile1, - PrecompileCall::claimer_by_index { - universal_location: universal_location.clone(), - index: 1u32, - }, + H160::from_str(TEST_CLAIMER).unwrap(), + H160(PRECOMPILE_ADDRESS), + input.clone(), ) + .execute_returns_raw(sp_std::vec![]); + + let input = EvmDataWriter::new_with_selector(Action::Claimer) + .write(universal_location) + .write(1u32) + .build(); + + precompiles() + .prepare_test(H160::from_str(TEST_CLAIMER).unwrap(), H160(PRECOMPILE_ADDRESS), input) .execute_reverts(|r| r == b"invalid index"); }); } @@ -340,87 +368,119 @@ fn claimer_by_index_invalid_index_fails() { #[test] fn claimer_by_index_works() { new_test_ext().execute_with(|| { - let universal_location: UnboundedString = "my_awesome_universal_location".into(); - extend(universal_location.clone(), "ciao".into()); + let universal_location = Bytes("some_universal_location".as_bytes().to_vec()); + + let input = EvmDataWriter::new_with_selector(Action::Extend) + .write(universal_location.clone()) + .write(Bytes("some_token_uri".as_bytes().to_vec())) + .build(); + + precompiles() + .prepare_test(H160::from_str(TEST_CLAIMER).unwrap(), H160(PRECOMPILE_ADDRESS), input) + .execute_returns_raw(vec![]); + + let input = EvmDataWriter::new_with_selector(Action::Claimer) + .write(universal_location.clone()) + .write(0u32) + .build(); precompiles() .prepare_test( - Alice, - Precompile1, - PrecompileCall::claimer_by_index { - universal_location: universal_location.clone(), - index: 0u32, - }, + H160::from_str(TEST_CLAIMER).unwrap(), + H160(PRECOMPILE_ADDRESS), + input.clone(), ) - .execute_returns(Address(Alice.into())); + .execute_returns(precompile_utils::prelude::Address( + H160::from_str(TEST_CLAIMER).unwrap(), + )); }); } #[test] fn extension_by_index_works() { new_test_ext().execute_with(|| { - let universal_location: UnboundedString = "my_awesome_universal_location".into(); - let token_uri: UnboundedString = "ciao".into(); - extend(universal_location.clone(), token_uri.clone()); + let universal_location = Bytes("some_universal_location".as_bytes().to_vec()); + + let input = EvmDataWriter::new_with_selector(Action::Extend) + .write(universal_location.clone()) + .write(Bytes(vec![1u8; 10])) + .build(); precompiles() .prepare_test( - Alice, - Precompile1, - PrecompileCall::extension_by_index { - universal_location: universal_location.clone(), - index: 0u32, - }, + H160::from_str(TEST_CLAIMER).unwrap(), + H160(PRECOMPILE_ADDRESS), + input.clone(), ) - .execute_returns(token_uri); + .execute_returns_raw(sp_std::vec![]); + + let input = EvmDataWriter::new_with_selector(Action::Extension) + .write(universal_location.clone()) + .write(0_u32) + .build(); + + precompiles() + .prepare_test(H160::from_str(TEST_CLAIMER).unwrap(), H160(PRECOMPILE_ADDRESS), input) + .execute_returns(BoundedBytes::::from(vec![1u8; 10])); }); } #[test] fn extension_by_index_invalid_ul_and_index_fails() { new_test_ext().execute_with(|| { - let universal_location: UnboundedString = "my_awesome_universal_location".into(); - let token_uri: UnboundedString = "ciao".into(); + let universal_location = Bytes("invalid_universal_location".as_bytes().to_vec()); + + let input = EvmDataWriter::new_with_selector(Action::Extension) + .write(universal_location.clone()) + .write(0_u32) + .build(); precompiles() .prepare_test( - Alice, - Precompile1, - PrecompileCall::extension_by_index { - universal_location: universal_location.clone(), - index: 0u32, - }, + H160::from_str(TEST_CLAIMER).unwrap(), + H160(PRECOMPILE_ADDRESS), + input.clone(), ) .execute_reverts(|r| r == b"invalid index"); // now create an extension - extend(universal_location.clone(), token_uri.clone()); + let input = EvmDataWriter::new_with_selector(Action::Extend) + .write(universal_location.clone()) + .write(Bytes(vec![1u8; 10])) + .build(); + + precompiles() + .prepare_test( + H160::from_str(TEST_CLAIMER).unwrap(), + H160(PRECOMPILE_ADDRESS), + input.clone(), + ) + .execute_returns_raw(sp_std::vec![]); // now try to get an extension with an invalid index - let other_universal_location: UnboundedString = "some_other_ul".into(); + let input = EvmDataWriter::new_with_selector(Action::Extension) + .write(Bytes("some_other_ul".as_bytes().to_vec()).clone()) + .write(0_u32) + .build(); // reverts precompiles() .prepare_test( - Alice, - Precompile1, - PrecompileCall::extension_by_index { - universal_location: other_universal_location.clone(), - index: 0u32, - }, + H160::from_str(TEST_CLAIMER).unwrap(), + H160(PRECOMPILE_ADDRESS), + input.clone(), ) .execute_reverts(|r| r == b"invalid index"); // now try to get an extension with an invalid index + let input = EvmDataWriter::new_with_selector(Action::Extension) + .write(universal_location) + .write(1_u32) + .build(); + + // reverts precompiles() - .prepare_test( - Alice, - Precompile1, - PrecompileCall::extension_by_index { - universal_location: universal_location.clone(), - index: 1u32, - }, - ) + .prepare_test(H160::from_str(TEST_CLAIMER).unwrap(), H160(PRECOMPILE_ADDRESS), input) .execute_reverts(|r| r == b"invalid index"); }); } @@ -428,25 +488,40 @@ fn extension_by_index_invalid_ul_and_index_fails() { #[test] fn balance_of_works() { new_test_ext().execute_with(|| { - let universal_location: UnboundedString = "my_awesome_universal_location".into(); + let universal_location = Bytes("some_universal_location".as_bytes().to_vec()); + + let input = EvmDataWriter::new_with_selector(Action::Balance) + .write(universal_location.clone()) + .build(); // default balance is 0 precompiles() .prepare_test( - Alice, - Precompile1, - PrecompileCall::balance_of { universal_location: universal_location.clone() }, + H160::from_str(TEST_CLAIMER).unwrap(), + H160(PRECOMPILE_ADDRESS), + input.clone(), ) .execute_returns(0_u32); - extend(universal_location.clone(), "ciao".into()); + let input = EvmDataWriter::new_with_selector(Action::Extend) + .write(universal_location.clone()) + .write(Bytes(vec![1u8; 10])) + .build(); precompiles() .prepare_test( - Alice, - Precompile1, - PrecompileCall::balance_of { universal_location: universal_location.clone() }, + H160::from_str(TEST_CLAIMER).unwrap(), + H160(PRECOMPILE_ADDRESS), + input.clone(), ) + .execute_returns_raw(sp_std::vec![]); + + let input = EvmDataWriter::new_with_selector(Action::Balance) + .write(universal_location.clone()) + .build(); + + precompiles() + .prepare_test(H160::from_str(TEST_CLAIMER).unwrap(), H160(PRECOMPILE_ADDRESS), input) .execute_returns(1_u32); }); } @@ -454,38 +529,43 @@ fn balance_of_works() { #[test] fn extension_by_location_and_claimer_works() { new_test_ext().execute_with(|| { - let universal_location: UnboundedString = "my_awesome_universal_location".into(); - let token_uri: UnboundedString = "ciao".into(); + let universal_location = Bytes("some_universal_location".as_bytes().to_vec()); + let claimer = H160::from_str(TEST_CLAIMER).unwrap(); + let claim = Bytes(vec![1u8; 10]); - extend(universal_location.clone(), token_uri.clone()); + let input = EvmDataWriter::new_with_selector(Action::Extend) + .write(universal_location.clone()) + .write(claim.clone()) + .build(); precompiles() - .prepare_test( - Alice, - Precompile1, - PrecompileCall::extension_by_location_and_claimer { - universal_location: universal_location.clone(), - claimer: Address(Alice.into()), - }, - ) - .execute_returns(token_uri); + .prepare_test(claimer, H160(PRECOMPILE_ADDRESS), input.clone()) + .execute_returns_raw(sp_std::vec![]); + + let input = EvmDataWriter::new_with_selector(Action::ExtensionOfULByClaimer) + .write(universal_location.clone()) + .write(Address::from(claimer)) + .build(); + + precompiles() + .prepare_test(claimer, H160(PRECOMPILE_ADDRESS), input) + .execute_returns(BoundedBytes::::from(vec![1u8; 10])); }); } #[test] fn extension_by_location_and_claimer_of_unexistent_claim_reverts() { new_test_ext().execute_with(|| { - let universal_location: UnboundedString = "my_awesome_universal_location".into(); + let universal_location = Bytes("some_universal_location".as_bytes().to_vec()); + let claimer = H160::from_str(TEST_CLAIMER).unwrap(); + + let input = EvmDataWriter::new_with_selector(Action::ExtensionOfULByClaimer) + .write(universal_location.clone()) + .write(Address::from(claimer)) + .build(); precompiles() - .prepare_test( - Alice, - Precompile1, - PrecompileCall::extension_by_location_and_claimer { - universal_location: universal_location.clone(), - claimer: Address(Alice.into()), - }, - ) + .prepare_test(claimer, H160(PRECOMPILE_ADDRESS), input) .execute_reverts(|r| r == b"invalid ul"); }); } @@ -493,20 +573,26 @@ fn extension_by_location_and_claimer_of_unexistent_claim_reverts() { #[test] fn has_extension_by_claim_of_existent_claim_returns_true() { new_test_ext().execute_with(|| { - let universal_location: UnboundedString = "my_awesome_universal_location".into(); - let token_uri: UnboundedString = "ciao".into(); + let universal_location = Bytes("some_universal_location".as_bytes().to_vec()); + let claimer = H160::from_str(TEST_CLAIMER).unwrap(); + let claim = Bytes(vec![1u8; 10]); - extend(universal_location.clone(), token_uri.clone()); + let input = EvmDataWriter::new_with_selector(Action::Extend) + .write(universal_location.clone()) + .write(claim.clone()) + .build(); precompiles() - .prepare_test( - Alice, - Precompile1, - PrecompileCall::has_extension_by_claimer { - universal_location: universal_location.clone(), - claimer: Address(Alice.into()), - }, - ) + .prepare_test(claimer, H160(PRECOMPILE_ADDRESS), input.clone()) + .execute_returns_raw(sp_std::vec![]); + + let input = EvmDataWriter::new_with_selector(Action::HasExtension) + .write(universal_location.clone()) + .write(Address::from(claimer)) + .build(); + + precompiles() + .prepare_test(claimer, H160(PRECOMPILE_ADDRESS), input) .execute_returns(true); }); } @@ -514,17 +600,16 @@ fn has_extension_by_claim_of_existent_claim_returns_true() { #[test] fn has_extension_by_claimer_of_unexistent_claim_returns_false() { new_test_ext().execute_with(|| { - let universal_location: UnboundedString = "my_awesome_universal_location".into(); + let universal_location = Bytes("some_universal_location".as_bytes().to_vec()); + let claimer = H160::from_str(TEST_CLAIMER).unwrap(); + + let input = EvmDataWriter::new_with_selector(Action::HasExtension) + .write(universal_location.clone()) + .write(Address::from(claimer)) + .build(); precompiles() - .prepare_test( - Alice, - Precompile1, - PrecompileCall::has_extension_by_claimer { - universal_location: universal_location.clone(), - claimer: Address(Alice.into()), - }, - ) + .prepare_test(claimer, H160(PRECOMPILE_ADDRESS), input) .execute_returns(false); }); } diff --git a/runtime/klaos/Cargo.toml b/runtime/klaos/Cargo.toml index 91c2afdeb..69fb88095 100644 --- a/runtime/klaos/Cargo.toml +++ b/runtime/klaos/Cargo.toml @@ -56,7 +56,7 @@ pallet-laos-evolution = { workspace = true } pallet-evm-evolution-collection-factory = { workspace = true } pallet-evm-evolution-collection = { workspace = true } -pallet-evm-asset-metadata-extender-legacy = { workspace = true } +pallet-evm-asset-metadata-extender = { workspace = true } # Polkadot pallet-xcm = { workspace = true } @@ -169,7 +169,7 @@ std = [ "pallet-ethereum/std", "pallet-evm/std", "pallet-evm-evolution-collection-factory/std", - "pallet-evm-asset-metadata-extender-legacy/std", + "pallet-evm-asset-metadata-extender/std", "pallet-evm-evolution-collection/std", "pallet-evm-chain-id/std", "pallet-evm-precompile-modexp/std", diff --git a/runtime/klaos/src/lib.rs b/runtime/klaos/src/lib.rs index 753c868a2..0c4347aab 100644 --- a/runtime/klaos/src/lib.rs +++ b/runtime/klaos/src/lib.rs @@ -518,6 +518,7 @@ impl pallet_asset_metadata_extender::Config for Runtime { type AccountIdToH160 = AccountIdToH160; type MaxTokenUriLength = MaxTokenUriLength; type MaxUniversalLocationLength = MaxUniversalLocationLength; + type GasWeightMapping = ::GasWeightMapping; } impl pallet_sudo::Config for Runtime { diff --git a/runtime/klaos/src/precompiles/mod.rs b/runtime/klaos/src/precompiles/mod.rs index 6c37f609c..e160ad3f3 100644 --- a/runtime/klaos/src/precompiles/mod.rs +++ b/runtime/klaos/src/precompiles/mod.rs @@ -23,7 +23,7 @@ use pallet_evm::{ use sp_core::H160; use sp_std::marker::PhantomData; -use pallet_evm_asset_metadata_extender_legacy::AssetMetadataExtenderPrecompile; +use pallet_evm_asset_metadata_extender::AssetMetadataExtenderPrecompile; use pallet_evm_evolution_collection::EvolutionCollectionPrecompile; use pallet_evm_evolution_collection_factory::EvolutionCollectionFactoryPrecompile; use pallet_evm_precompile_blake2::Blake2F; diff --git a/runtime/laos/Cargo.toml b/runtime/laos/Cargo.toml index 660a386c4..35268e2dc 100644 --- a/runtime/laos/Cargo.toml +++ b/runtime/laos/Cargo.toml @@ -59,7 +59,6 @@ sp-staking = { workspace = true } pallet-asset-metadata-extender = { workspace = true } pallet-laos-evolution = { workspace = true } -pallet-evm-asset-metadata-extender = { workspace = true } # Polkadot pallet-xcm = { workspace = true } @@ -175,7 +174,6 @@ std = [ "pallet-base-fee/std", "pallet-ethereum/std", "pallet-evm/std", - "pallet-evm-asset-metadata-extender/std", "pallet-evm-chain-id/std", "pallet-evm-precompile-modexp/std", "pallet-evm-precompile-simple/std", diff --git a/runtime/laos/src/configs/asset_metadata_extender.rs b/runtime/laos/src/configs/asset_metadata_extender.rs index 999de58fd..dd98b5e6f 100644 --- a/runtime/laos/src/configs/asset_metadata_extender.rs +++ b/runtime/laos/src/configs/asset_metadata_extender.rs @@ -28,4 +28,5 @@ impl pallet_asset_metadata_extender::Config for Runtime { type AccountIdToH160 = AccountIdToH160; type MaxTokenUriLength = MaxTokenUriLength; type MaxUniversalLocationLength = MaxUniversalLocationLength; + type GasWeightMapping = ::GasWeightMapping; } diff --git a/runtime/laos/src/precompiles/mod.rs b/runtime/laos/src/precompiles/mod.rs index 7ecbf6372..d6f5c4e2a 100644 --- a/runtime/laos/src/precompiles/mod.rs +++ b/runtime/laos/src/precompiles/mod.rs @@ -18,7 +18,7 @@ use frame_support::parameter_types; -use pallet_evm_asset_metadata_extender::AssetMetadataExtenderPrecompile; +use pallet_asset_metadata_extender::precompiles::asset_metadata_extender::AssetMetadataExtenderPrecompile; use pallet_evm_precompile_blake2::Blake2F; use pallet_evm_precompile_bn128::{Bn128Add, Bn128Mul, Bn128Pairing}; use pallet_evm_precompile_modexp::Modexp;