difftreelog
misk: Remove some warnings. Add over_max_size test
in: master
12 files changed
Cargo.lockdiffbeforeafterboth--- a/Cargo.lock
+++ b/Cargo.lock
@@ -2347,6 +2347,7 @@
"sha3-const",
"similar-asserts",
"sp-std",
+ "trybuild",
]
[[package]]
@@ -12671,6 +12672,21 @@
]
[[package]]
+name = "trybuild"
+version = "1.0.71"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ea496675d71016e9bc76aa42d87f16aefd95447cc5818e671e12b2d7e269075d"
+dependencies = [
+ "glob",
+ "once_cell",
+ "serde",
+ "serde_derive",
+ "serde_json",
+ "termcolor",
+ "toml",
+]
+
+[[package]]
name = "tt-call"
version = "1.0.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
crates/evm-coder/Cargo.tomldiffbeforeafterboth--- a/crates/evm-coder/Cargo.toml
+++ b/crates/evm-coder/Cargo.toml
@@ -29,6 +29,7 @@
hex-literal = "0.3.4"
similar-asserts = "1.4.2"
concat-idents = "1.1.3"
+trybuild = "1.0"
[features]
default = ["std"]
crates/evm-coder/src/custom_signature.rsdiffbeforeafterboth--- a/crates/evm-coder/src/custom_signature.rs
+++ b/crates/evm-coder/src/custom_signature.rs
@@ -423,15 +423,6 @@
assert_eq!(<MaxSize>::name(), "!".repeat(SIGNATURE_SIZE_LIMIT));
}
- // This test must NOT compile with "index out of bounds"!
- // #[test]
- // fn over_max_size() {
- // assert_eq!(
- // <Vec<MaxSize>>::name(),
- // "!".repeat(SIGNATURE_SIZE_LIMIT) + "[]"
- // );
- // }
-
#[test]
fn make_func_without_args() {
const SIG: FunctionSignature = make_signature!(
@@ -498,4 +489,10 @@
fn shift() {
assert_eq!(<(u32,)>::name(), "(uint32)");
}
+
+ #[test]
+ fn over_max_size() {
+ let t = trybuild::TestCases::new();
+ t.compile_fail("tests/build_failed/custom_signature_over_max_size.rs");
+ }
}
crates/evm-coder/tests/build_failed/custom_signature_over_max_size.rsdiffbeforeafterboth--- /dev/null
+++ b/crates/evm-coder/tests/build_failed/custom_signature_over_max_size.rs
@@ -0,0 +1,33 @@
+#![allow(dead_code)]
+use std::str::from_utf8;
+
+use evm_coder::{
+ make_signature,
+ custom_signature::{SignatureUnit, SIGNATURE_SIZE_LIMIT},
+};
+
+trait Name {
+ const SIGNATURE: SignatureUnit;
+
+ fn name() -> &'static str {
+ from_utf8(&Self::SIGNATURE.data[..Self::SIGNATURE.len]).expect("bad utf-8")
+ }
+}
+
+impl<T: Name> Name for Vec<T> {
+ evm_coder::make_signature!(new nameof(T) fixed("[]"));
+}
+
+struct MaxSize();
+impl Name for MaxSize {
+ const SIGNATURE: SignatureUnit = SignatureUnit {
+ data: [b'!'; SIGNATURE_SIZE_LIMIT],
+ len: SIGNATURE_SIZE_LIMIT,
+ };
+}
+
+const NAME: SignatureUnit = <Vec<MaxSize>>::SIGNATURE;
+
+fn main() {
+ assert!(false);
+}
crates/evm-coder/tests/build_failed/custom_signature_over_max_size.stderrdiffbeforeafterboth--- /dev/null
+++ b/crates/evm-coder/tests/build_failed/custom_signature_over_max_size.stderr
@@ -0,0 +1,19 @@
+error: any use of this value will cause an error
+ --> tests/build_failed/custom_signature_over_max_size.rs:18:2
+ |
+18 | evm_coder::make_signature!(new nameof(T) fixed("[]"));
+ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ index out of bounds: the length is 256 but the index is 256
+ |
+ = note: `#[deny(const_err)]` on by default
+ = warning: this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release!
+ = note: for more information, see issue #71800 <https://github.com/rust-lang/rust/issues/71800>
+ = note: this error originates in the macro `make_signature` which comes from the expansion of the macro `evm_coder::make_signature` (in Nightly builds, run with -Z macro-backtrace for more info)
+
+error: any use of this value will cause an error
+ --> tests/build_failed/custom_signature_over_max_size.rs:29:29
+ |
+29 | const NAME: SignatureUnit = <Vec<MaxSize>>::SIGNATURE;
+ | ------------------------- ^^^^^^^^^^^^^^^^^^^^^^^^^ referenced constant has errors
+ |
+ = warning: this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release!
+ = note: for more information, see issue #71800 <https://github.com/rust-lang/rust/issues/71800>
pallets/common/src/erc.rsdiffbeforeafterboth--- a/pallets/common/src/erc.rs
+++ b/pallets/common/src/erc.rs
@@ -35,10 +35,7 @@
use crate::{
Pallet, CollectionHandle, Config, CollectionProperties, SelfWeightOf,
- eth::{
- convert_cross_account_to_uint256, convert_cross_account_to_tuple,
- convert_tuple_to_cross_account,
- },
+ eth::{convert_cross_account_to_uint256, convert_tuple_to_cross_account},
weights::WeightInfo,
};
pallets/evm-contract-helpers/src/eth.rsdiffbeforeafterboth--- a/pallets/evm-contract-helpers/src/eth.rs
+++ b/pallets/evm-contract-helpers/src/eth.rs
@@ -17,7 +17,6 @@
//! Implementation of magic contract
extern crate alloc;
-use alloc::string::ToString;
use core::marker::PhantomData;
use evm_coder::{
abi::AbiWriter,
pallets/fungible/src/erc.rsdiffbeforeafterboth--- a/pallets/fungible/src/erc.rs
+++ b/pallets/fungible/src/erc.rs
@@ -28,7 +28,6 @@
custom_signature::{SignatureUnit, FunctionSignature, SignaturePreferences},
make_signature,
};
-use pallet_common::eth::convert_tuple_to_cross_account;
use up_data_structs::CollectionMode;
use pallet_common::erc::{CommonEvmHandler, PrecompileResult};
use sp_std::vec::Vec;
pallets/refungible/src/erc.rsdiffbeforeafterboth1// Copyright 2019-2022 Unique Network (Gibraltar) Ltd.2// This file is part of Unique Network.34// Unique Network is free software: you can redistribute it and/or modify5// it under the terms of the GNU General Public License as published by6// the Free Software Foundation, either version 3 of the License, or7// (at your option) any later version.89// Unique Network is distributed in the hope that it will be useful,10// but WITHOUT ANY WARRANTY; without even the implied warranty of11// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the12// GNU General Public License for more details.1314// You should have received a copy of the GNU General Public License15// along with Unique Network. If not, see <http://www.gnu.org/licenses/>.1617//! # Refungible Pallet EVM API for tokens18//!19//! Provides ERC-721 standart support implementation and EVM API for unique extensions for Refungible Pallet.20//! Method implementations are mostly doing parameter conversion and calling Refungible Pallet methods.2122extern crate alloc;2324use alloc::string::ToString;25use core::{26 char::{REPLACEMENT_CHARACTER, decode_utf16},27 convert::TryInto,28};29use evm_coder::{30 ToLog,31 execution::*,32 generate_stubgen, solidity, solidity_interface,33 types::*,34 weight,35 custom_signature::{SignatureUnit, FunctionSignature, SignaturePreferences},36 make_signature,37};38use frame_support::{BoundedBTreeMap, BoundedVec};39use pallet_common::{40 CollectionHandle, CollectionPropertyPermissions,41 erc::{CommonEvmHandler, CollectionCall, static_property::key},42 eth::convert_tuple_to_cross_account,43};44use pallet_evm::{account::CrossAccountId, PrecompileHandle};45use pallet_evm_coder_substrate::{call, dispatch_to_evm};46use pallet_structure::{SelfWeightOf as StructureWeight, weights::WeightInfo as _};47use sp_core::H160;48use sp_std::{collections::btree_map::BTreeMap, vec::Vec, vec};49use up_data_structs::{50 CollectionId, CollectionPropertiesVec, mapping::TokenAddressMapping, Property, PropertyKey,51 PropertyKeyPermission, PropertyPermission, TokenId,52};5354use crate::{55 AccountBalance, Balance, Config, CreateItemData, Pallet, RefungibleHandle, SelfWeightOf,56 TokenProperties, TokensMinted, TotalSupply, weights::WeightInfo,57};5859pub const ADDRESS_FOR_PARTIALLY_OWNED_TOKENS: H160 = H160::repeat_byte(0xff);6061/// @title A contract that allows to set and delete token properties and change token property permissions.62#[solidity_interface(name = TokenProperties)]63impl<T: Config> RefungibleHandle<T> {64 /// @notice Set permissions for token property.65 /// @dev Throws error if `msg.sender` is not admin or owner of the collection.66 /// @param key Property key.67 /// @param isMutable Permission to mutate property.68 /// @param collectionAdmin Permission to mutate property by collection admin if property is mutable.69 /// @param tokenOwner Permission to mutate property by token owner if property is mutable.70 fn set_token_property_permission(71 &mut self,72 caller: caller,73 key: string,74 is_mutable: bool,75 collection_admin: bool,76 token_owner: bool,77 ) -> Result<()> {78 let caller = T::CrossAccountId::from_eth(caller);79 <Pallet<T>>::set_token_property_permissions(80 self,81 &caller,82 vec![PropertyKeyPermission {83 key: <Vec<u8>>::from(key)84 .try_into()85 .map_err(|_| "too long key")?,86 permission: PropertyPermission {87 mutable: is_mutable,88 collection_admin,89 token_owner,90 },91 }],92 )93 .map_err(dispatch_to_evm::<T>)94 }9596 /// @notice Set token property value.97 /// @dev Throws error if `msg.sender` has no permission to edit the property.98 /// @param tokenId ID of the token.99 /// @param key Property key.100 /// @param value Property value.101 fn set_property(102 &mut self,103 caller: caller,104 token_id: uint256,105 key: string,106 value: bytes,107 ) -> Result<()> {108 let caller = T::CrossAccountId::from_eth(caller);109 let token_id: u32 = token_id.try_into().map_err(|_| "token id overflow")?;110 let key = <Vec<u8>>::from(key)111 .try_into()112 .map_err(|_| "key too long")?;113 let value = value.0.try_into().map_err(|_| "value too long")?;114115 let nesting_budget = self116 .recorder117 .weight_calls_budget(<StructureWeight<T>>::find_parent());118119 <Pallet<T>>::set_token_property(120 self,121 &caller,122 TokenId(token_id),123 Property { key, value },124 &nesting_budget,125 )126 .map_err(dispatch_to_evm::<T>)127 }128129 /// @notice Set token properties value.130 /// @dev Throws error if `msg.sender` has no permission to edit the property.131 /// @param tokenId ID of the token.132 /// @param properties settable properties133 fn set_properties(134 &mut self,135 caller: caller,136 token_id: uint256,137 properties: Vec<(string, bytes)>,138 ) -> Result<()> {139 let caller = T::CrossAccountId::from_eth(caller);140 let token_id: u32 = token_id.try_into().map_err(|_| "token id overflow")?;141142 let nesting_budget = self143 .recorder144 .weight_calls_budget(<StructureWeight<T>>::find_parent());145146 let properties = properties147 .into_iter()148 .map(|(key, value)| {149 let key = <Vec<u8>>::from(key)150 .try_into()151 .map_err(|_| "key too large")?;152153 let value = value.0.try_into().map_err(|_| "value too large")?;154155 Ok(Property { key, value })156 })157 .collect::<Result<Vec<_>>>()?;158159 <Pallet<T>>::set_token_properties(160 self,161 &caller,162 TokenId(token_id),163 properties.into_iter(),164 <Pallet<T>>::token_exists(&self, TokenId(token_id)),165 &nesting_budget,166 )167 .map_err(dispatch_to_evm::<T>)168 }169170 /// @notice Delete token property value.171 /// @dev Throws error if `msg.sender` has no permission to edit the property.172 /// @param tokenId ID of the token.173 /// @param key Property key.174 fn delete_property(&mut self, token_id: uint256, caller: caller, key: string) -> Result<()> {175 let caller = T::CrossAccountId::from_eth(caller);176 let token_id: u32 = token_id.try_into().map_err(|_| "token id overflow")?;177 let key = <Vec<u8>>::from(key)178 .try_into()179 .map_err(|_| "key too long")?;180181 let nesting_budget = self182 .recorder183 .weight_calls_budget(<StructureWeight<T>>::find_parent());184185 <Pallet<T>>::delete_token_property(self, &caller, TokenId(token_id), key, &nesting_budget)186 .map_err(dispatch_to_evm::<T>)187 }188189 /// @notice Get token property value.190 /// @dev Throws error if key not found191 /// @param tokenId ID of the token.192 /// @param key Property key.193 /// @return Property value bytes194 fn property(&self, token_id: uint256, key: string) -> Result<bytes> {195 let token_id: u32 = token_id.try_into().map_err(|_| "token id overflow")?;196 let key = <Vec<u8>>::from(key)197 .try_into()198 .map_err(|_| "key too long")?;199200 let props = <TokenProperties<T>>::get((self.id, token_id));201 let prop = props.get(&key).ok_or("key not found")?;202203 Ok(prop.to_vec().into())204 }205}206207#[derive(ToLog)]208pub enum ERC721Events {209 /// @dev This event emits when NFTs are created (`from` == 0) and destroyed210 /// (`to` == 0). Exception: during contract creation, any number of RFTs211 /// may be created and assigned without emitting Transfer.212 Transfer {213 #[indexed]214 from: address,215 #[indexed]216 to: address,217 #[indexed]218 token_id: uint256,219 },220 /// @dev Not supported221 Approval {222 #[indexed]223 owner: address,224 #[indexed]225 approved: address,226 #[indexed]227 token_id: uint256,228 },229 /// @dev Not supported230 #[allow(dead_code)]231 ApprovalForAll {232 #[indexed]233 owner: address,234 #[indexed]235 operator: address,236 approved: bool,237 },238}239240#[derive(ToLog)]241pub enum ERC721UniqueMintableEvents {242 /// @dev Not supported243 #[allow(dead_code)]244 MintingFinished {},245}246247#[solidity_interface(name = ERC721Metadata)]248impl<T: Config> RefungibleHandle<T>249where250 T::AccountId: From<[u8; 32]>,251{252 /// @notice A descriptive name for a collection of NFTs in this contract253 /// @dev real implementation of this function lies in `ERC721UniqueExtensions`254 #[solidity(hide, rename_selector = "name")]255 fn name_proxy(&self) -> Result<string> {256 self.name()257 }258259 /// @notice An abbreviated name for NFTs in this contract260 /// @dev real implementation of this function lies in `ERC721UniqueExtensions`261 #[solidity(hide, rename_selector = "symbol")]262 fn symbol_proxy(&self) -> Result<string> {263 self.symbol()264 }265266 /// @notice A distinct Uniform Resource Identifier (URI) for a given asset.267 ///268 /// @dev If the token has a `url` property and it is not empty, it is returned.269 /// Else If the collection does not have a property with key `schemaName` or its value is not equal to `ERC721Metadata`, it return an error `tokenURI not set`.270 /// If the collection property `baseURI` is empty or absent, return "" (empty string)271 /// otherwise, if token property `suffix` present and is non-empty, return concatenation of baseURI and suffix272 /// otherwise, return concatenation of `baseURI` and stringified token id (decimal stringifying, without paddings).273 ///274 /// @return token's const_metadata275 #[solidity(rename_selector = "tokenURI")]276 fn token_uri(&self, token_id: uint256) -> Result<string> {277 let token_id_u32: u32 = token_id.try_into().map_err(|_| "token id overflow")?;278279 match get_token_property(self, token_id_u32, &key::url()).as_deref() {280 Err(_) | Ok("") => (),281 Ok(url) => {282 return Ok(url.into());283 }284 };285286 let base_uri =287 pallet_common::Pallet::<T>::get_collection_property(self.id, &key::base_uri())288 .map(BoundedVec::into_inner)289 .map(string::from_utf8)290 .transpose()291 .map_err(|e| {292 Error::Revert(alloc::format!(293 "Can not convert value \"baseURI\" to string with error \"{}\"",294 e295 ))296 })?;297298 let base_uri = match base_uri.as_deref() {299 None | Some("") => {300 return Ok("".into());301 }302 Some(base_uri) => base_uri.into(),303 };304305 Ok(306 match get_token_property(self, token_id_u32, &key::suffix()).as_deref() {307 Err(_) | Ok("") => base_uri,308 Ok(suffix) => base_uri + suffix,309 },310 )311 }312}313314/// @title ERC-721 Non-Fungible Token Standard, optional enumeration extension315/// @dev See https://eips.ethereum.org/EIPS/eip-721316#[solidity_interface(name = ERC721Enumerable)]317impl<T: Config> RefungibleHandle<T> {318 /// @notice Enumerate valid RFTs319 /// @param index A counter less than `totalSupply()`320 /// @return The token identifier for the `index`th NFT,321 /// (sort order not specified)322 fn token_by_index(&self, index: uint256) -> Result<uint256> {323 Ok(index)324 }325326 /// Not implemented327 fn token_of_owner_by_index(&self, _owner: address, _index: uint256) -> Result<uint256> {328 // TODO: Not implemetable329 Err("not implemented".into())330 }331332 /// @notice Count RFTs tracked by this contract333 /// @return A count of valid RFTs tracked by this contract, where each one of334 /// them has an assigned and queryable owner not equal to the zero address335 fn total_supply(&self) -> Result<uint256> {336 self.consume_store_reads(1)?;337 Ok(<Pallet<T>>::total_supply(self).into())338 }339}340341/// @title ERC-721 Non-Fungible Token Standard342/// @dev See https://github.com/ethereum/EIPs/blob/master/EIPS/eip-721.md343#[solidity_interface(name = ERC721, events(ERC721Events))]344impl<T: Config> RefungibleHandle<T> {345 /// @notice Count all RFTs assigned to an owner346 /// @dev RFTs assigned to the zero address are considered invalid, and this347 /// function throws for queries about the zero address.348 /// @param owner An address for whom to query the balance349 /// @return The number of RFTs owned by `owner`, possibly zero350 fn balance_of(&self, owner: address) -> Result<uint256> {351 self.consume_store_reads(1)?;352 let owner = T::CrossAccountId::from_eth(owner);353 let balance = <AccountBalance<T>>::get((self.id, owner));354 Ok(balance.into())355 }356357 /// @notice Find the owner of an RFT358 /// @dev RFTs assigned to zero address are considered invalid, and queries359 /// about them do throw.360 /// Returns special 0xffffffffffffffffffffffffffffffffffffffff address for361 /// the tokens that are partially owned.362 /// @param tokenId The identifier for an RFT363 /// @return The address of the owner of the RFT364 fn owner_of(&self, token_id: uint256) -> Result<address> {365 self.consume_store_reads(2)?;366 let token = token_id.try_into()?;367 let owner = <Pallet<T>>::token_owner(self.id, token);368 Ok(owner369 .map(|address| *address.as_eth())370 .unwrap_or_else(|| ADDRESS_FOR_PARTIALLY_OWNED_TOKENS))371 }372373 /// @dev Not implemented374 fn safe_transfer_from_with_data(375 &mut self,376 _from: address,377 _to: address,378 _token_id: uint256,379 _data: bytes,380 ) -> Result<void> {381 // TODO: Not implemetable382 Err("not implemented".into())383 }384385 /// @dev Not implemented386 fn safe_transfer_from(387 &mut self,388 _from: address,389 _to: address,390 _token_id: uint256,391 ) -> Result<void> {392 // TODO: Not implemetable393 Err("not implemented".into())394 }395396 /// @notice Transfer ownership of an RFT -- THE CALLER IS RESPONSIBLE397 /// TO CONFIRM THAT `to` IS CAPABLE OF RECEIVING NFTS OR ELSE398 /// THEY MAY BE PERMANENTLY LOST399 /// @dev Throws unless `msg.sender` is the current owner or an authorized400 /// operator for this RFT. Throws if `from` is not the current owner. Throws401 /// if `to` is the zero address. Throws if `tokenId` is not a valid RFT.402 /// Throws if RFT pieces have multiple owners.403 /// @param from The current owner of the NFT404 /// @param to The new owner405 /// @param tokenId The NFT to transfer406 #[weight(<SelfWeightOf<T>>::transfer_from_creating_removing())]407 fn transfer_from(408 &mut self,409 caller: caller,410 from: address,411 to: address,412 token_id: uint256,413 ) -> Result<void> {414 let caller = T::CrossAccountId::from_eth(caller);415 let from = T::CrossAccountId::from_eth(from);416 let to = T::CrossAccountId::from_eth(to);417 let token = token_id.try_into()?;418 let budget = self419 .recorder420 .weight_calls_budget(<StructureWeight<T>>::find_parent());421422 let balance = balance(&self, token, &from)?;423 ensure_single_owner(&self, token, balance)?;424425 <Pallet<T>>::transfer_from(self, &caller, &from, &to, token, balance, &budget)426 .map_err(dispatch_to_evm::<T>)?;427428 Ok(())429 }430431 /// @dev Not implemented432 fn approve(&mut self, _caller: caller, _approved: address, _token_id: uint256) -> Result<void> {433 Err("not implemented".into())434 }435436 /// @dev Not implemented437 fn set_approval_for_all(438 &mut self,439 _caller: caller,440 _operator: address,441 _approved: bool,442 ) -> Result<void> {443 // TODO: Not implemetable444 Err("not implemented".into())445 }446447 /// @dev Not implemented448 fn get_approved(&self, _token_id: uint256) -> Result<address> {449 // TODO: Not implemetable450 Err("not implemented".into())451 }452453 /// @dev Not implemented454 fn is_approved_for_all(&self, _owner: address, _operator: address) -> Result<address> {455 // TODO: Not implemetable456 Err("not implemented".into())457 }458}459460/// Returns amount of pieces of `token` that `owner` have461pub fn balance<T: Config>(462 collection: &RefungibleHandle<T>,463 token: TokenId,464 owner: &T::CrossAccountId,465) -> Result<u128> {466 collection.consume_store_reads(1)?;467 let balance = <Balance<T>>::get((collection.id, token, &owner));468 Ok(balance)469}470471/// Throws if `owner_balance` is lower than total amount of `token` pieces472pub fn ensure_single_owner<T: Config>(473 collection: &RefungibleHandle<T>,474 token: TokenId,475 owner_balance: u128,476) -> Result<()> {477 collection.consume_store_reads(1)?;478 let total_supply = <TotalSupply<T>>::get((collection.id, token));479 if total_supply != owner_balance {480 return Err("token has multiple owners".into());481 }482 Ok(())483}484485/// @title ERC721 Token that can be irreversibly burned (destroyed).486#[solidity_interface(name = ERC721Burnable)]487impl<T: Config> RefungibleHandle<T> {488 /// @notice Burns a specific ERC721 token.489 /// @dev Throws unless `msg.sender` is the current RFT owner, or an authorized490 /// operator of the current owner.491 /// @param tokenId The RFT to approve492 #[weight(<SelfWeightOf<T>>::burn_item_fully())]493 fn burn(&mut self, caller: caller, token_id: uint256) -> Result<void> {494 let caller = T::CrossAccountId::from_eth(caller);495 let token = token_id.try_into()?;496497 let balance = balance(&self, token, &caller)?;498 ensure_single_owner(&self, token, balance)?;499500 <Pallet<T>>::burn(self, &caller, token, balance).map_err(dispatch_to_evm::<T>)?;501 Ok(())502 }503}504505/// @title ERC721 minting logic.506#[solidity_interface(name = ERC721UniqueMintable, events(ERC721UniqueMintableEvents))]507impl<T: Config> RefungibleHandle<T> {508 fn minting_finished(&self) -> Result<bool> {509 Ok(false)510 }511512 /// @notice Function to mint token.513 /// @param to The new owner514 /// @return uint256 The id of the newly minted token515 #[weight(<SelfWeightOf<T>>::create_item())]516 fn mint(&mut self, caller: caller, to: address) -> Result<uint256> {517 let token_id: uint256 = <TokensMinted<T>>::get(self.id)518 .checked_add(1)519 .ok_or("item id overflow")?520 .into();521 self.mint_check_id(caller, to, token_id)?;522 Ok(token_id)523 }524525 /// @notice Function to mint token.526 /// @dev `tokenId` should be obtained with `nextTokenId` method,527 /// unlike standard, you can't specify it manually528 /// @param to The new owner529 /// @param tokenId ID of the minted RFT530 #[solidity(hide, rename_selector = "mint")]531 #[weight(<SelfWeightOf<T>>::create_item())]532 fn mint_check_id(&mut self, caller: caller, to: address, token_id: uint256) -> Result<bool> {533 let caller = T::CrossAccountId::from_eth(caller);534 let to = T::CrossAccountId::from_eth(to);535 let token_id: u32 = token_id.try_into()?;536 let budget = self537 .recorder538 .weight_calls_budget(<StructureWeight<T>>::find_parent());539540 if <TokensMinted<T>>::get(self.id)541 .checked_add(1)542 .ok_or("item id overflow")?543 != token_id544 {545 return Err("item id should be next".into());546 }547548 let users = [(to.clone(), 1)]549 .into_iter()550 .collect::<BTreeMap<_, _>>()551 .try_into()552 .unwrap();553 <Pallet<T>>::create_item(554 self,555 &caller,556 CreateItemData::<T::CrossAccountId> {557 users,558 properties: CollectionPropertiesVec::default(),559 },560 &budget,561 )562 .map_err(dispatch_to_evm::<T>)?;563564 Ok(true)565 }566567 /// @notice Function to mint token with the given tokenUri.568 /// @param to The new owner569 /// @param tokenUri Token URI that would be stored in the NFT properties570 /// @return uint256 The id of the newly minted token571 #[solidity(rename_selector = "mintWithTokenURI")]572 #[weight(<SelfWeightOf<T>>::create_item())]573 fn mint_with_token_uri(574 &mut self,575 caller: caller,576 to: address,577 token_uri: string,578 ) -> Result<uint256> {579 let token_id: uint256 = <TokensMinted<T>>::get(self.id)580 .checked_add(1)581 .ok_or("item id overflow")?582 .into();583 self.mint_with_token_uri_check_id(caller, to, token_id, token_uri)?;584 Ok(token_id)585 }586587 /// @notice Function to mint token with the given tokenUri.588 /// @dev `tokenId` should be obtained with `nextTokenId` method,589 /// unlike standard, you can't specify it manually590 /// @param to The new owner591 /// @param tokenId ID of the minted RFT592 /// @param tokenUri Token URI that would be stored in the RFT properties593 #[solidity(hide, rename_selector = "mintWithTokenURI")]594 #[weight(<SelfWeightOf<T>>::create_item())]595 fn mint_with_token_uri_check_id(596 &mut self,597 caller: caller,598 to: address,599 token_id: uint256,600 token_uri: string,601 ) -> Result<bool> {602 let key = key::url();603 let permission = get_token_permission::<T>(self.id, &key)?;604 if !permission.collection_admin {605 return Err("Operation is not allowed".into());606 }607608 let caller = T::CrossAccountId::from_eth(caller);609 let to = T::CrossAccountId::from_eth(to);610 let token_id: u32 = token_id.try_into().map_err(|_| "amount overflow")?;611 let budget = self612 .recorder613 .weight_calls_budget(<StructureWeight<T>>::find_parent());614615 if <TokensMinted<T>>::get(self.id)616 .checked_add(1)617 .ok_or("item id overflow")?618 != token_id619 {620 return Err("item id should be next".into());621 }622623 let mut properties = CollectionPropertiesVec::default();624 properties625 .try_push(Property {626 key,627 value: token_uri628 .into_bytes()629 .try_into()630 .map_err(|_| "token uri is too long")?,631 })632 .map_err(|e| Error::Revert(alloc::format!("Can't add property: {:?}", e)))?;633634 let users = [(to.clone(), 1)]635 .into_iter()636 .collect::<BTreeMap<_, _>>()637 .try_into()638 .unwrap();639 <Pallet<T>>::create_item(640 self,641 &caller,642 CreateItemData::<T::CrossAccountId> { users, properties },643 &budget,644 )645 .map_err(dispatch_to_evm::<T>)?;646 Ok(true)647 }648649 /// @dev Not implemented650 fn finish_minting(&mut self, _caller: caller) -> Result<bool> {651 Err("not implementable".into())652 }653}654655fn get_token_property<T: Config>(656 collection: &CollectionHandle<T>,657 token_id: u32,658 key: &up_data_structs::PropertyKey,659) -> Result<string> {660 collection.consume_store_reads(1)?;661 let properties = <TokenProperties<T>>::try_get((collection.id, token_id))662 .map_err(|_| Error::Revert("Token properties not found".into()))?;663 if let Some(property) = properties.get(key) {664 return Ok(string::from_utf8_lossy(property).into());665 }666667 Err("Property tokenURI not found".into())668}669670fn get_token_permission<T: Config>(671 collection_id: CollectionId,672 key: &PropertyKey,673) -> Result<PropertyPermission> {674 let token_property_permissions = CollectionPropertyPermissions::<T>::try_get(collection_id)675 .map_err(|_| Error::Revert("No permissions for collection".into()))?;676 let a = token_property_permissions677 .get(key)678 .map(Clone::clone)679 .ok_or_else(|| {680 let key = string::from_utf8(key.clone().into_inner()).unwrap_or_default();681 Error::Revert(alloc::format!("No permission for key {}", key))682 })?;683 Ok(a)684}685686/// @title Unique extensions for ERC721.687#[solidity_interface(name = ERC721UniqueExtensions)]688impl<T: Config> RefungibleHandle<T>689where690 T::AccountId: From<[u8; 32]>,691{692 /// @notice A descriptive name for a collection of NFTs in this contract693 fn name(&self) -> Result<string> {694 Ok(decode_utf16(self.name.iter().copied())695 .map(|r| r.unwrap_or(REPLACEMENT_CHARACTER))696 .collect::<string>())697 }698699 /// @notice An abbreviated name for NFTs in this contract700 fn symbol(&self) -> Result<string> {701 Ok(string::from_utf8_lossy(&self.token_prefix).into())702 }703704 /// @notice Transfer ownership of an RFT705 /// @dev Throws unless `msg.sender` is the current owner. Throws if `to`706 /// is the zero address. Throws if `tokenId` is not a valid RFT.707 /// Throws if RFT pieces have multiple owners.708 /// @param to The new owner709 /// @param tokenId The RFT to transfer710 #[weight(<SelfWeightOf<T>>::transfer_creating_removing())]711 fn transfer(&mut self, caller: caller, to: address, token_id: uint256) -> Result<void> {712 let caller = T::CrossAccountId::from_eth(caller);713 let to = T::CrossAccountId::from_eth(to);714 let token = token_id.try_into()?;715 let budget = self716 .recorder717 .weight_calls_budget(<StructureWeight<T>>::find_parent());718719 let balance = balance(self, token, &caller)?;720 ensure_single_owner(self, token, balance)?;721722 <Pallet<T>>::transfer(self, &caller, &to, token, balance, &budget)723 .map_err(dispatch_to_evm::<T>)?;724 Ok(())725 }726727 /// @notice Transfer ownership of an RFT728 /// @dev Throws unless `msg.sender` is the current owner. Throws if `to`729 /// is the zero address. Throws if `tokenId` is not a valid RFT.730 /// Throws if RFT pieces have multiple owners.731 /// @param to The new owner732 /// @param tokenId The RFT to transfer733 #[weight(<SelfWeightOf<T>>::transfer_creating_removing())]734 fn transfer_from_cross(735 &mut self,736 caller: caller,737 from: EthCrossAccount,738 to: EthCrossAccount,739 token_id: uint256,740 ) -> Result<void> {741 let caller = T::CrossAccountId::from_eth(caller);742 let from = from.into_sub_cross_account::<T>()?;743 let to = to.into_sub_cross_account::<T>()?;744 let token_id = token_id.try_into()?;745 let budget = self746 .recorder747 .weight_calls_budget(<StructureWeight<T>>::find_parent());748749 let balance = balance(self, token_id, &from)?;750 ensure_single_owner(self, token_id, balance)?;751752 Pallet::<T>::transfer_from(self, &caller, &from, &to, token_id, balance, &budget)753 .map_err(dispatch_to_evm::<T>)?;754 Ok(())755 }756757 /// @notice Burns a specific ERC721 token.758 /// @dev Throws unless `msg.sender` is the current owner or an authorized759 /// operator for this RFT. Throws if `from` is not the current owner. Throws760 /// if `to` is the zero address. Throws if `tokenId` is not a valid RFT.761 /// Throws if RFT pieces have multiple owners.762 /// @param from The current owner of the RFT763 /// @param tokenId The RFT to transfer764 #[weight(<SelfWeightOf<T>>::burn_from())]765 fn burn_from(&mut self, caller: caller, from: address, token_id: uint256) -> Result<void> {766 let caller = T::CrossAccountId::from_eth(caller);767 let from = T::CrossAccountId::from_eth(from);768 let token = token_id.try_into()?;769 let budget = self770 .recorder771 .weight_calls_budget(<StructureWeight<T>>::find_parent());772773 let balance = balance(self, token, &from)?;774 ensure_single_owner(self, token, balance)?;775776 <Pallet<T>>::burn_from(self, &caller, &from, token, balance, &budget)777 .map_err(dispatch_to_evm::<T>)?;778 Ok(())779 }780781 /// @notice Burns a specific ERC721 token.782 /// @dev Throws unless `msg.sender` is the current owner or an authorized783 /// operator for this RFT. Throws if `from` is not the current owner. Throws784 /// if `to` is the zero address. Throws if `tokenId` is not a valid RFT.785 /// Throws if RFT pieces have multiple owners.786 /// @param from The current owner of the RFT787 /// @param tokenId The RFT to transfer788 #[weight(<SelfWeightOf<T>>::burn_from())]789 fn burn_from_cross(790 &mut self,791 caller: caller,792 from: EthCrossAccount,793 token_id: uint256,794 ) -> Result<void> {795 let caller = T::CrossAccountId::from_eth(caller);796 let from = from.into_sub_cross_account::<T>()?;797 let token = token_id.try_into()?;798 let budget = self799 .recorder800 .weight_calls_budget(<StructureWeight<T>>::find_parent());801802 let balance = balance(self, token, &from)?;803 ensure_single_owner(self, token, balance)?;804805 <Pallet<T>>::burn_from(self, &caller, &from, token, balance, &budget)806 .map_err(dispatch_to_evm::<T>)?;807 Ok(())808 }809810 /// @notice Returns next free RFT ID.811 fn next_token_id(&self) -> Result<uint256> {812 self.consume_store_reads(1)?;813 Ok(<TokensMinted<T>>::get(self.id)814 .checked_add(1)815 .ok_or("item id overflow")?816 .into())817 }818819 /// @notice Function to mint multiple tokens.820 /// @dev `tokenIds` should be an array of consecutive numbers and first number821 /// should be obtained with `nextTokenId` method822 /// @param to The new owner823 /// @param tokenIds IDs of the minted RFTs824 #[solidity(hide)]825 #[weight(<SelfWeightOf<T>>::create_multiple_items(token_ids.len() as u32))]826 fn mint_bulk(&mut self, caller: caller, to: address, token_ids: Vec<uint256>) -> Result<bool> {827 let caller = T::CrossAccountId::from_eth(caller);828 let to = T::CrossAccountId::from_eth(to);829 let mut expected_index = <TokensMinted<T>>::get(self.id)830 .checked_add(1)831 .ok_or("item id overflow")?;832 let budget = self833 .recorder834 .weight_calls_budget(<StructureWeight<T>>::find_parent());835836 let total_tokens = token_ids.len();837 for id in token_ids.into_iter() {838 let id: u32 = id.try_into().map_err(|_| "token id overflow")?;839 if id != expected_index {840 return Err("item id should be next".into());841 }842 expected_index = expected_index.checked_add(1).ok_or("item id overflow")?;843 }844 let users = [(to.clone(), 1)]845 .into_iter()846 .collect::<BTreeMap<_, _>>()847 .try_into()848 .unwrap();849 let create_item_data = CreateItemData::<T::CrossAccountId> {850 users,851 properties: CollectionPropertiesVec::default(),852 };853 let data = (0..total_tokens)854 .map(|_| create_item_data.clone())855 .collect();856857 <Pallet<T>>::create_multiple_items(self, &caller, data, &budget)858 .map_err(dispatch_to_evm::<T>)?;859 Ok(true)860 }861862 /// @notice Function to mint multiple tokens with the given tokenUris.863 /// @dev `tokenIds` is array of pairs of token ID and token URI. Token IDs should be consecutive864 /// numbers and first number should be obtained with `nextTokenId` method865 /// @param to The new owner866 /// @param tokens array of pairs of token ID and token URI for minted tokens867 #[solidity(hide, rename_selector = "mintBulkWithTokenURI")]868 #[weight(<SelfWeightOf<T>>::create_multiple_items(tokens.len() as u32))]869 fn mint_bulk_with_token_uri(870 &mut self,871 caller: caller,872 to: address,873 tokens: Vec<(uint256, string)>,874 ) -> Result<bool> {875 let key = key::url();876 let caller = T::CrossAccountId::from_eth(caller);877 let to = T::CrossAccountId::from_eth(to);878 let mut expected_index = <TokensMinted<T>>::get(self.id)879 .checked_add(1)880 .ok_or("item id overflow")?;881 let budget = self882 .recorder883 .weight_calls_budget(<StructureWeight<T>>::find_parent());884885 let mut data = Vec::with_capacity(tokens.len());886 let users: BoundedBTreeMap<_, _, _> = [(to.clone(), 1)]887 .into_iter()888 .collect::<BTreeMap<_, _>>()889 .try_into()890 .unwrap();891 for (id, token_uri) in tokens {892 let id: u32 = id.try_into().map_err(|_| "token id overflow")?;893 if id != expected_index {894 return Err("item id should be next".into());895 }896 expected_index = expected_index.checked_add(1).ok_or("item id overflow")?;897898 let mut properties = CollectionPropertiesVec::default();899 properties900 .try_push(Property {901 key: key.clone(),902 value: token_uri903 .into_bytes()904 .try_into()905 .map_err(|_| "token uri is too long")?,906 })907 .map_err(|e| Error::Revert(alloc::format!("Can't add property: {:?}", e)))?;908909 let create_item_data = CreateItemData::<T::CrossAccountId> {910 users: users.clone(),911 properties,912 };913 data.push(create_item_data);914 }915916 <Pallet<T>>::create_multiple_items(self, &caller, data, &budget)917 .map_err(dispatch_to_evm::<T>)?;918 Ok(true)919 }920921 /// Returns EVM address for refungible token922 ///923 /// @param token ID of the token924 fn token_contract_address(&self, token: uint256) -> Result<address> {925 Ok(T::EvmTokenAddressMapping::token_to_address(926 self.id,927 token.try_into().map_err(|_| "token id overflow")?,928 ))929 }930}931932#[solidity_interface(933 name = UniqueRefungible,934 is(935 ERC721,936 ERC721Enumerable,937 ERC721UniqueExtensions,938 ERC721UniqueMintable,939 ERC721Burnable,940 ERC721Metadata(if(this.flags.erc721metadata)),941 Collection(via(common_mut returns CollectionHandle<T>)),942 TokenProperties,943 )944)]945impl<T: Config> RefungibleHandle<T> where T::AccountId: From<[u8; 32]> + AsRef<[u8; 32]> {}946947// Not a tests, but code generators948generate_stubgen!(gen_impl, UniqueRefungibleCall<()>, true);949generate_stubgen!(gen_iface, UniqueRefungibleCall<()>, false);950951impl<T: Config> CommonEvmHandler for RefungibleHandle<T>952where953 T::AccountId: From<[u8; 32]> + AsRef<[u8; 32]>,954{955 const CODE: &'static [u8] = include_bytes!("./stubs/UniqueRefungible.raw");956 fn call(957 self,958 handle: &mut impl PrecompileHandle,959 ) -> Option<pallet_common::erc::PrecompileResult> {960 call::<T, UniqueRefungibleCall<T>, _, _>(handle, self)961 }962}1// Copyright 2019-2022 Unique Network (Gibraltar) Ltd.2// This file is part of Unique Network.34// Unique Network is free software: you can redistribute it and/or modify5// it under the terms of the GNU General Public License as published by6// the Free Software Foundation, either version 3 of the License, or7// (at your option) any later version.89// Unique Network is distributed in the hope that it will be useful,10// but WITHOUT ANY WARRANTY; without even the implied warranty of11// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the12// GNU General Public License for more details.1314// You should have received a copy of the GNU General Public License15// along with Unique Network. If not, see <http://www.gnu.org/licenses/>.1617//! # Refungible Pallet EVM API for tokens18//!19//! Provides ERC-721 standart support implementation and EVM API for unique extensions for Refungible Pallet.20//! Method implementations are mostly doing parameter conversion and calling Refungible Pallet methods.2122extern crate alloc;2324use core::{25 char::{REPLACEMENT_CHARACTER, decode_utf16},26 convert::TryInto,27};28use evm_coder::{29 ToLog,30 execution::*,31 generate_stubgen, solidity, solidity_interface,32 types::*,33 weight,34 custom_signature::{SignatureUnit, FunctionSignature, SignaturePreferences},35 make_signature,36};37use frame_support::{BoundedBTreeMap, BoundedVec};38use pallet_common::{39 CollectionHandle, CollectionPropertyPermissions,40 erc::{CommonEvmHandler, CollectionCall, static_property::key},41};42use pallet_evm::{account::CrossAccountId, PrecompileHandle};43use pallet_evm_coder_substrate::{call, dispatch_to_evm};44use pallet_structure::{SelfWeightOf as StructureWeight, weights::WeightInfo as _};45use sp_core::H160;46use sp_std::{collections::btree_map::BTreeMap, vec::Vec, vec};47use up_data_structs::{48 CollectionId, CollectionPropertiesVec, mapping::TokenAddressMapping, Property, PropertyKey,49 PropertyKeyPermission, PropertyPermission, TokenId,50};5152use crate::{53 AccountBalance, Balance, Config, CreateItemData, Pallet, RefungibleHandle, SelfWeightOf,54 TokenProperties, TokensMinted, TotalSupply, weights::WeightInfo,55};5657pub const ADDRESS_FOR_PARTIALLY_OWNED_TOKENS: H160 = H160::repeat_byte(0xff);5859/// @title A contract that allows to set and delete token properties and change token property permissions.60#[solidity_interface(name = TokenProperties)]61impl<T: Config> RefungibleHandle<T> {62 /// @notice Set permissions for token property.63 /// @dev Throws error if `msg.sender` is not admin or owner of the collection.64 /// @param key Property key.65 /// @param isMutable Permission to mutate property.66 /// @param collectionAdmin Permission to mutate property by collection admin if property is mutable.67 /// @param tokenOwner Permission to mutate property by token owner if property is mutable.68 fn set_token_property_permission(69 &mut self,70 caller: caller,71 key: string,72 is_mutable: bool,73 collection_admin: bool,74 token_owner: bool,75 ) -> Result<()> {76 let caller = T::CrossAccountId::from_eth(caller);77 <Pallet<T>>::set_token_property_permissions(78 self,79 &caller,80 vec![PropertyKeyPermission {81 key: <Vec<u8>>::from(key)82 .try_into()83 .map_err(|_| "too long key")?,84 permission: PropertyPermission {85 mutable: is_mutable,86 collection_admin,87 token_owner,88 },89 }],90 )91 .map_err(dispatch_to_evm::<T>)92 }9394 /// @notice Set token property value.95 /// @dev Throws error if `msg.sender` has no permission to edit the property.96 /// @param tokenId ID of the token.97 /// @param key Property key.98 /// @param value Property value.99 fn set_property(100 &mut self,101 caller: caller,102 token_id: uint256,103 key: string,104 value: bytes,105 ) -> Result<()> {106 let caller = T::CrossAccountId::from_eth(caller);107 let token_id: u32 = token_id.try_into().map_err(|_| "token id overflow")?;108 let key = <Vec<u8>>::from(key)109 .try_into()110 .map_err(|_| "key too long")?;111 let value = value.0.try_into().map_err(|_| "value too long")?;112113 let nesting_budget = self114 .recorder115 .weight_calls_budget(<StructureWeight<T>>::find_parent());116117 <Pallet<T>>::set_token_property(118 self,119 &caller,120 TokenId(token_id),121 Property { key, value },122 &nesting_budget,123 )124 .map_err(dispatch_to_evm::<T>)125 }126127 /// @notice Set token properties value.128 /// @dev Throws error if `msg.sender` has no permission to edit the property.129 /// @param tokenId ID of the token.130 /// @param properties settable properties131 fn set_properties(132 &mut self,133 caller: caller,134 token_id: uint256,135 properties: Vec<(string, bytes)>,136 ) -> Result<()> {137 let caller = T::CrossAccountId::from_eth(caller);138 let token_id: u32 = token_id.try_into().map_err(|_| "token id overflow")?;139140 let nesting_budget = self141 .recorder142 .weight_calls_budget(<StructureWeight<T>>::find_parent());143144 let properties = properties145 .into_iter()146 .map(|(key, value)| {147 let key = <Vec<u8>>::from(key)148 .try_into()149 .map_err(|_| "key too large")?;150151 let value = value.0.try_into().map_err(|_| "value too large")?;152153 Ok(Property { key, value })154 })155 .collect::<Result<Vec<_>>>()?;156157 <Pallet<T>>::set_token_properties(158 self,159 &caller,160 TokenId(token_id),161 properties.into_iter(),162 <Pallet<T>>::token_exists(&self, TokenId(token_id)),163 &nesting_budget,164 )165 .map_err(dispatch_to_evm::<T>)166 }167168 /// @notice Delete token property value.169 /// @dev Throws error if `msg.sender` has no permission to edit the property.170 /// @param tokenId ID of the token.171 /// @param key Property key.172 fn delete_property(&mut self, token_id: uint256, caller: caller, key: string) -> Result<()> {173 let caller = T::CrossAccountId::from_eth(caller);174 let token_id: u32 = token_id.try_into().map_err(|_| "token id overflow")?;175 let key = <Vec<u8>>::from(key)176 .try_into()177 .map_err(|_| "key too long")?;178179 let nesting_budget = self180 .recorder181 .weight_calls_budget(<StructureWeight<T>>::find_parent());182183 <Pallet<T>>::delete_token_property(self, &caller, TokenId(token_id), key, &nesting_budget)184 .map_err(dispatch_to_evm::<T>)185 }186187 /// @notice Get token property value.188 /// @dev Throws error if key not found189 /// @param tokenId ID of the token.190 /// @param key Property key.191 /// @return Property value bytes192 fn property(&self, token_id: uint256, key: string) -> Result<bytes> {193 let token_id: u32 = token_id.try_into().map_err(|_| "token id overflow")?;194 let key = <Vec<u8>>::from(key)195 .try_into()196 .map_err(|_| "key too long")?;197198 let props = <TokenProperties<T>>::get((self.id, token_id));199 let prop = props.get(&key).ok_or("key not found")?;200201 Ok(prop.to_vec().into())202 }203}204205#[derive(ToLog)]206pub enum ERC721Events {207 /// @dev This event emits when NFTs are created (`from` == 0) and destroyed208 /// (`to` == 0). Exception: during contract creation, any number of RFTs209 /// may be created and assigned without emitting Transfer.210 Transfer {211 #[indexed]212 from: address,213 #[indexed]214 to: address,215 #[indexed]216 token_id: uint256,217 },218 /// @dev Not supported219 Approval {220 #[indexed]221 owner: address,222 #[indexed]223 approved: address,224 #[indexed]225 token_id: uint256,226 },227 /// @dev Not supported228 #[allow(dead_code)]229 ApprovalForAll {230 #[indexed]231 owner: address,232 #[indexed]233 operator: address,234 approved: bool,235 },236}237238#[derive(ToLog)]239pub enum ERC721UniqueMintableEvents {240 /// @dev Not supported241 #[allow(dead_code)]242 MintingFinished {},243}244245#[solidity_interface(name = ERC721Metadata)]246impl<T: Config> RefungibleHandle<T>247where248 T::AccountId: From<[u8; 32]>,249{250 /// @notice A descriptive name for a collection of NFTs in this contract251 /// @dev real implementation of this function lies in `ERC721UniqueExtensions`252 #[solidity(hide, rename_selector = "name")]253 fn name_proxy(&self) -> Result<string> {254 self.name()255 }256257 /// @notice An abbreviated name for NFTs in this contract258 /// @dev real implementation of this function lies in `ERC721UniqueExtensions`259 #[solidity(hide, rename_selector = "symbol")]260 fn symbol_proxy(&self) -> Result<string> {261 self.symbol()262 }263264 /// @notice A distinct Uniform Resource Identifier (URI) for a given asset.265 ///266 /// @dev If the token has a `url` property and it is not empty, it is returned.267 /// Else If the collection does not have a property with key `schemaName` or its value is not equal to `ERC721Metadata`, it return an error `tokenURI not set`.268 /// If the collection property `baseURI` is empty or absent, return "" (empty string)269 /// otherwise, if token property `suffix` present and is non-empty, return concatenation of baseURI and suffix270 /// otherwise, return concatenation of `baseURI` and stringified token id (decimal stringifying, without paddings).271 ///272 /// @return token's const_metadata273 #[solidity(rename_selector = "tokenURI")]274 fn token_uri(&self, token_id: uint256) -> Result<string> {275 let token_id_u32: u32 = token_id.try_into().map_err(|_| "token id overflow")?;276277 match get_token_property(self, token_id_u32, &key::url()).as_deref() {278 Err(_) | Ok("") => (),279 Ok(url) => {280 return Ok(url.into());281 }282 };283284 let base_uri =285 pallet_common::Pallet::<T>::get_collection_property(self.id, &key::base_uri())286 .map(BoundedVec::into_inner)287 .map(string::from_utf8)288 .transpose()289 .map_err(|e| {290 Error::Revert(alloc::format!(291 "Can not convert value \"baseURI\" to string with error \"{}\"",292 e293 ))294 })?;295296 let base_uri = match base_uri.as_deref() {297 None | Some("") => {298 return Ok("".into());299 }300 Some(base_uri) => base_uri.into(),301 };302303 Ok(304 match get_token_property(self, token_id_u32, &key::suffix()).as_deref() {305 Err(_) | Ok("") => base_uri,306 Ok(suffix) => base_uri + suffix,307 },308 )309 }310}311312/// @title ERC-721 Non-Fungible Token Standard, optional enumeration extension313/// @dev See https://eips.ethereum.org/EIPS/eip-721314#[solidity_interface(name = ERC721Enumerable)]315impl<T: Config> RefungibleHandle<T> {316 /// @notice Enumerate valid RFTs317 /// @param index A counter less than `totalSupply()`318 /// @return The token identifier for the `index`th NFT,319 /// (sort order not specified)320 fn token_by_index(&self, index: uint256) -> Result<uint256> {321 Ok(index)322 }323324 /// Not implemented325 fn token_of_owner_by_index(&self, _owner: address, _index: uint256) -> Result<uint256> {326 // TODO: Not implemetable327 Err("not implemented".into())328 }329330 /// @notice Count RFTs tracked by this contract331 /// @return A count of valid RFTs tracked by this contract, where each one of332 /// them has an assigned and queryable owner not equal to the zero address333 fn total_supply(&self) -> Result<uint256> {334 self.consume_store_reads(1)?;335 Ok(<Pallet<T>>::total_supply(self).into())336 }337}338339/// @title ERC-721 Non-Fungible Token Standard340/// @dev See https://github.com/ethereum/EIPs/blob/master/EIPS/eip-721.md341#[solidity_interface(name = ERC721, events(ERC721Events))]342impl<T: Config> RefungibleHandle<T> {343 /// @notice Count all RFTs assigned to an owner344 /// @dev RFTs assigned to the zero address are considered invalid, and this345 /// function throws for queries about the zero address.346 /// @param owner An address for whom to query the balance347 /// @return The number of RFTs owned by `owner`, possibly zero348 fn balance_of(&self, owner: address) -> Result<uint256> {349 self.consume_store_reads(1)?;350 let owner = T::CrossAccountId::from_eth(owner);351 let balance = <AccountBalance<T>>::get((self.id, owner));352 Ok(balance.into())353 }354355 /// @notice Find the owner of an RFT356 /// @dev RFTs assigned to zero address are considered invalid, and queries357 /// about them do throw.358 /// Returns special 0xffffffffffffffffffffffffffffffffffffffff address for359 /// the tokens that are partially owned.360 /// @param tokenId The identifier for an RFT361 /// @return The address of the owner of the RFT362 fn owner_of(&self, token_id: uint256) -> Result<address> {363 self.consume_store_reads(2)?;364 let token = token_id.try_into()?;365 let owner = <Pallet<T>>::token_owner(self.id, token);366 Ok(owner367 .map(|address| *address.as_eth())368 .unwrap_or_else(|| ADDRESS_FOR_PARTIALLY_OWNED_TOKENS))369 }370371 /// @dev Not implemented372 fn safe_transfer_from_with_data(373 &mut self,374 _from: address,375 _to: address,376 _token_id: uint256,377 _data: bytes,378 ) -> Result<void> {379 // TODO: Not implemetable380 Err("not implemented".into())381 }382383 /// @dev Not implemented384 fn safe_transfer_from(385 &mut self,386 _from: address,387 _to: address,388 _token_id: uint256,389 ) -> Result<void> {390 // TODO: Not implemetable391 Err("not implemented".into())392 }393394 /// @notice Transfer ownership of an RFT -- THE CALLER IS RESPONSIBLE395 /// TO CONFIRM THAT `to` IS CAPABLE OF RECEIVING NFTS OR ELSE396 /// THEY MAY BE PERMANENTLY LOST397 /// @dev Throws unless `msg.sender` is the current owner or an authorized398 /// operator for this RFT. Throws if `from` is not the current owner. Throws399 /// if `to` is the zero address. Throws if `tokenId` is not a valid RFT.400 /// Throws if RFT pieces have multiple owners.401 /// @param from The current owner of the NFT402 /// @param to The new owner403 /// @param tokenId The NFT to transfer404 #[weight(<SelfWeightOf<T>>::transfer_from_creating_removing())]405 fn transfer_from(406 &mut self,407 caller: caller,408 from: address,409 to: address,410 token_id: uint256,411 ) -> Result<void> {412 let caller = T::CrossAccountId::from_eth(caller);413 let from = T::CrossAccountId::from_eth(from);414 let to = T::CrossAccountId::from_eth(to);415 let token = token_id.try_into()?;416 let budget = self417 .recorder418 .weight_calls_budget(<StructureWeight<T>>::find_parent());419420 let balance = balance(&self, token, &from)?;421 ensure_single_owner(&self, token, balance)?;422423 <Pallet<T>>::transfer_from(self, &caller, &from, &to, token, balance, &budget)424 .map_err(dispatch_to_evm::<T>)?;425426 Ok(())427 }428429 /// @dev Not implemented430 fn approve(&mut self, _caller: caller, _approved: address, _token_id: uint256) -> Result<void> {431 Err("not implemented".into())432 }433434 /// @dev Not implemented435 fn set_approval_for_all(436 &mut self,437 _caller: caller,438 _operator: address,439 _approved: bool,440 ) -> Result<void> {441 // TODO: Not implemetable442 Err("not implemented".into())443 }444445 /// @dev Not implemented446 fn get_approved(&self, _token_id: uint256) -> Result<address> {447 // TODO: Not implemetable448 Err("not implemented".into())449 }450451 /// @dev Not implemented452 fn is_approved_for_all(&self, _owner: address, _operator: address) -> Result<address> {453 // TODO: Not implemetable454 Err("not implemented".into())455 }456}457458/// Returns amount of pieces of `token` that `owner` have459pub fn balance<T: Config>(460 collection: &RefungibleHandle<T>,461 token: TokenId,462 owner: &T::CrossAccountId,463) -> Result<u128> {464 collection.consume_store_reads(1)?;465 let balance = <Balance<T>>::get((collection.id, token, &owner));466 Ok(balance)467}468469/// Throws if `owner_balance` is lower than total amount of `token` pieces470pub fn ensure_single_owner<T: Config>(471 collection: &RefungibleHandle<T>,472 token: TokenId,473 owner_balance: u128,474) -> Result<()> {475 collection.consume_store_reads(1)?;476 let total_supply = <TotalSupply<T>>::get((collection.id, token));477 if total_supply != owner_balance {478 return Err("token has multiple owners".into());479 }480 Ok(())481}482483/// @title ERC721 Token that can be irreversibly burned (destroyed).484#[solidity_interface(name = ERC721Burnable)]485impl<T: Config> RefungibleHandle<T> {486 /// @notice Burns a specific ERC721 token.487 /// @dev Throws unless `msg.sender` is the current RFT owner, or an authorized488 /// operator of the current owner.489 /// @param tokenId The RFT to approve490 #[weight(<SelfWeightOf<T>>::burn_item_fully())]491 fn burn(&mut self, caller: caller, token_id: uint256) -> Result<void> {492 let caller = T::CrossAccountId::from_eth(caller);493 let token = token_id.try_into()?;494495 let balance = balance(&self, token, &caller)?;496 ensure_single_owner(&self, token, balance)?;497498 <Pallet<T>>::burn(self, &caller, token, balance).map_err(dispatch_to_evm::<T>)?;499 Ok(())500 }501}502503/// @title ERC721 minting logic.504#[solidity_interface(name = ERC721UniqueMintable, events(ERC721UniqueMintableEvents))]505impl<T: Config> RefungibleHandle<T> {506 fn minting_finished(&self) -> Result<bool> {507 Ok(false)508 }509510 /// @notice Function to mint token.511 /// @param to The new owner512 /// @return uint256 The id of the newly minted token513 #[weight(<SelfWeightOf<T>>::create_item())]514 fn mint(&mut self, caller: caller, to: address) -> Result<uint256> {515 let token_id: uint256 = <TokensMinted<T>>::get(self.id)516 .checked_add(1)517 .ok_or("item id overflow")?518 .into();519 self.mint_check_id(caller, to, token_id)?;520 Ok(token_id)521 }522523 /// @notice Function to mint token.524 /// @dev `tokenId` should be obtained with `nextTokenId` method,525 /// unlike standard, you can't specify it manually526 /// @param to The new owner527 /// @param tokenId ID of the minted RFT528 #[solidity(hide, rename_selector = "mint")]529 #[weight(<SelfWeightOf<T>>::create_item())]530 fn mint_check_id(&mut self, caller: caller, to: address, token_id: uint256) -> Result<bool> {531 let caller = T::CrossAccountId::from_eth(caller);532 let to = T::CrossAccountId::from_eth(to);533 let token_id: u32 = token_id.try_into()?;534 let budget = self535 .recorder536 .weight_calls_budget(<StructureWeight<T>>::find_parent());537538 if <TokensMinted<T>>::get(self.id)539 .checked_add(1)540 .ok_or("item id overflow")?541 != token_id542 {543 return Err("item id should be next".into());544 }545546 let users = [(to.clone(), 1)]547 .into_iter()548 .collect::<BTreeMap<_, _>>()549 .try_into()550 .unwrap();551 <Pallet<T>>::create_item(552 self,553 &caller,554 CreateItemData::<T::CrossAccountId> {555 users,556 properties: CollectionPropertiesVec::default(),557 },558 &budget,559 )560 .map_err(dispatch_to_evm::<T>)?;561562 Ok(true)563 }564565 /// @notice Function to mint token with the given tokenUri.566 /// @param to The new owner567 /// @param tokenUri Token URI that would be stored in the NFT properties568 /// @return uint256 The id of the newly minted token569 #[solidity(rename_selector = "mintWithTokenURI")]570 #[weight(<SelfWeightOf<T>>::create_item())]571 fn mint_with_token_uri(572 &mut self,573 caller: caller,574 to: address,575 token_uri: string,576 ) -> Result<uint256> {577 let token_id: uint256 = <TokensMinted<T>>::get(self.id)578 .checked_add(1)579 .ok_or("item id overflow")?580 .into();581 self.mint_with_token_uri_check_id(caller, to, token_id, token_uri)?;582 Ok(token_id)583 }584585 /// @notice Function to mint token with the given tokenUri.586 /// @dev `tokenId` should be obtained with `nextTokenId` method,587 /// unlike standard, you can't specify it manually588 /// @param to The new owner589 /// @param tokenId ID of the minted RFT590 /// @param tokenUri Token URI that would be stored in the RFT properties591 #[solidity(hide, rename_selector = "mintWithTokenURI")]592 #[weight(<SelfWeightOf<T>>::create_item())]593 fn mint_with_token_uri_check_id(594 &mut self,595 caller: caller,596 to: address,597 token_id: uint256,598 token_uri: string,599 ) -> Result<bool> {600 let key = key::url();601 let permission = get_token_permission::<T>(self.id, &key)?;602 if !permission.collection_admin {603 return Err("Operation is not allowed".into());604 }605606 let caller = T::CrossAccountId::from_eth(caller);607 let to = T::CrossAccountId::from_eth(to);608 let token_id: u32 = token_id.try_into().map_err(|_| "amount overflow")?;609 let budget = self610 .recorder611 .weight_calls_budget(<StructureWeight<T>>::find_parent());612613 if <TokensMinted<T>>::get(self.id)614 .checked_add(1)615 .ok_or("item id overflow")?616 != token_id617 {618 return Err("item id should be next".into());619 }620621 let mut properties = CollectionPropertiesVec::default();622 properties623 .try_push(Property {624 key,625 value: token_uri626 .into_bytes()627 .try_into()628 .map_err(|_| "token uri is too long")?,629 })630 .map_err(|e| Error::Revert(alloc::format!("Can't add property: {:?}", e)))?;631632 let users = [(to.clone(), 1)]633 .into_iter()634 .collect::<BTreeMap<_, _>>()635 .try_into()636 .unwrap();637 <Pallet<T>>::create_item(638 self,639 &caller,640 CreateItemData::<T::CrossAccountId> { users, properties },641 &budget,642 )643 .map_err(dispatch_to_evm::<T>)?;644 Ok(true)645 }646647 /// @dev Not implemented648 fn finish_minting(&mut self, _caller: caller) -> Result<bool> {649 Err("not implementable".into())650 }651}652653fn get_token_property<T: Config>(654 collection: &CollectionHandle<T>,655 token_id: u32,656 key: &up_data_structs::PropertyKey,657) -> Result<string> {658 collection.consume_store_reads(1)?;659 let properties = <TokenProperties<T>>::try_get((collection.id, token_id))660 .map_err(|_| Error::Revert("Token properties not found".into()))?;661 if let Some(property) = properties.get(key) {662 return Ok(string::from_utf8_lossy(property).into());663 }664665 Err("Property tokenURI not found".into())666}667668fn get_token_permission<T: Config>(669 collection_id: CollectionId,670 key: &PropertyKey,671) -> Result<PropertyPermission> {672 let token_property_permissions = CollectionPropertyPermissions::<T>::try_get(collection_id)673 .map_err(|_| Error::Revert("No permissions for collection".into()))?;674 let a = token_property_permissions675 .get(key)676 .map(Clone::clone)677 .ok_or_else(|| {678 let key = string::from_utf8(key.clone().into_inner()).unwrap_or_default();679 Error::Revert(alloc::format!("No permission for key {}", key))680 })?;681 Ok(a)682}683684/// @title Unique extensions for ERC721.685#[solidity_interface(name = ERC721UniqueExtensions)]686impl<T: Config> RefungibleHandle<T>687where688 T::AccountId: From<[u8; 32]>,689{690 /// @notice A descriptive name for a collection of NFTs in this contract691 fn name(&self) -> Result<string> {692 Ok(decode_utf16(self.name.iter().copied())693 .map(|r| r.unwrap_or(REPLACEMENT_CHARACTER))694 .collect::<string>())695 }696697 /// @notice An abbreviated name for NFTs in this contract698 fn symbol(&self) -> Result<string> {699 Ok(string::from_utf8_lossy(&self.token_prefix).into())700 }701702 /// @notice Transfer ownership of an RFT703 /// @dev Throws unless `msg.sender` is the current owner. Throws if `to`704 /// is the zero address. Throws if `tokenId` is not a valid RFT.705 /// Throws if RFT pieces have multiple owners.706 /// @param to The new owner707 /// @param tokenId The RFT to transfer708 #[weight(<SelfWeightOf<T>>::transfer_creating_removing())]709 fn transfer(&mut self, caller: caller, to: address, token_id: uint256) -> Result<void> {710 let caller = T::CrossAccountId::from_eth(caller);711 let to = T::CrossAccountId::from_eth(to);712 let token = token_id.try_into()?;713 let budget = self714 .recorder715 .weight_calls_budget(<StructureWeight<T>>::find_parent());716717 let balance = balance(self, token, &caller)?;718 ensure_single_owner(self, token, balance)?;719720 <Pallet<T>>::transfer(self, &caller, &to, token, balance, &budget)721 .map_err(dispatch_to_evm::<T>)?;722 Ok(())723 }724725 /// @notice Transfer ownership of an RFT726 /// @dev Throws unless `msg.sender` is the current owner. Throws if `to`727 /// is the zero address. Throws if `tokenId` is not a valid RFT.728 /// Throws if RFT pieces have multiple owners.729 /// @param to The new owner730 /// @param tokenId The RFT to transfer731 #[weight(<SelfWeightOf<T>>::transfer_creating_removing())]732 fn transfer_from_cross(733 &mut self,734 caller: caller,735 from: EthCrossAccount,736 to: EthCrossAccount,737 token_id: uint256,738 ) -> Result<void> {739 let caller = T::CrossAccountId::from_eth(caller);740 let from = from.into_sub_cross_account::<T>()?;741 let to = to.into_sub_cross_account::<T>()?;742 let token_id = token_id.try_into()?;743 let budget = self744 .recorder745 .weight_calls_budget(<StructureWeight<T>>::find_parent());746747 let balance = balance(self, token_id, &from)?;748 ensure_single_owner(self, token_id, balance)?;749750 Pallet::<T>::transfer_from(self, &caller, &from, &to, token_id, balance, &budget)751 .map_err(dispatch_to_evm::<T>)?;752 Ok(())753 }754755 /// @notice Burns a specific ERC721 token.756 /// @dev Throws unless `msg.sender` is the current owner or an authorized757 /// operator for this RFT. Throws if `from` is not the current owner. Throws758 /// if `to` is the zero address. Throws if `tokenId` is not a valid RFT.759 /// Throws if RFT pieces have multiple owners.760 /// @param from The current owner of the RFT761 /// @param tokenId The RFT to transfer762 #[weight(<SelfWeightOf<T>>::burn_from())]763 fn burn_from(&mut self, caller: caller, from: address, token_id: uint256) -> Result<void> {764 let caller = T::CrossAccountId::from_eth(caller);765 let from = T::CrossAccountId::from_eth(from);766 let token = token_id.try_into()?;767 let budget = self768 .recorder769 .weight_calls_budget(<StructureWeight<T>>::find_parent());770771 let balance = balance(self, token, &from)?;772 ensure_single_owner(self, token, balance)?;773774 <Pallet<T>>::burn_from(self, &caller, &from, token, balance, &budget)775 .map_err(dispatch_to_evm::<T>)?;776 Ok(())777 }778779 /// @notice Burns a specific ERC721 token.780 /// @dev Throws unless `msg.sender` is the current owner or an authorized781 /// operator for this RFT. Throws if `from` is not the current owner. Throws782 /// if `to` is the zero address. Throws if `tokenId` is not a valid RFT.783 /// Throws if RFT pieces have multiple owners.784 /// @param from The current owner of the RFT785 /// @param tokenId The RFT to transfer786 #[weight(<SelfWeightOf<T>>::burn_from())]787 fn burn_from_cross(788 &mut self,789 caller: caller,790 from: EthCrossAccount,791 token_id: uint256,792 ) -> Result<void> {793 let caller = T::CrossAccountId::from_eth(caller);794 let from = from.into_sub_cross_account::<T>()?;795 let token = token_id.try_into()?;796 let budget = self797 .recorder798 .weight_calls_budget(<StructureWeight<T>>::find_parent());799800 let balance = balance(self, token, &from)?;801 ensure_single_owner(self, token, balance)?;802803 <Pallet<T>>::burn_from(self, &caller, &from, token, balance, &budget)804 .map_err(dispatch_to_evm::<T>)?;805 Ok(())806 }807808 /// @notice Returns next free RFT ID.809 fn next_token_id(&self) -> Result<uint256> {810 self.consume_store_reads(1)?;811 Ok(<TokensMinted<T>>::get(self.id)812 .checked_add(1)813 .ok_or("item id overflow")?814 .into())815 }816817 /// @notice Function to mint multiple tokens.818 /// @dev `tokenIds` should be an array of consecutive numbers and first number819 /// should be obtained with `nextTokenId` method820 /// @param to The new owner821 /// @param tokenIds IDs of the minted RFTs822 #[solidity(hide)]823 #[weight(<SelfWeightOf<T>>::create_multiple_items(token_ids.len() as u32))]824 fn mint_bulk(&mut self, caller: caller, to: address, token_ids: Vec<uint256>) -> Result<bool> {825 let caller = T::CrossAccountId::from_eth(caller);826 let to = T::CrossAccountId::from_eth(to);827 let mut expected_index = <TokensMinted<T>>::get(self.id)828 .checked_add(1)829 .ok_or("item id overflow")?;830 let budget = self831 .recorder832 .weight_calls_budget(<StructureWeight<T>>::find_parent());833834 let total_tokens = token_ids.len();835 for id in token_ids.into_iter() {836 let id: u32 = id.try_into().map_err(|_| "token id overflow")?;837 if id != expected_index {838 return Err("item id should be next".into());839 }840 expected_index = expected_index.checked_add(1).ok_or("item id overflow")?;841 }842 let users = [(to.clone(), 1)]843 .into_iter()844 .collect::<BTreeMap<_, _>>()845 .try_into()846 .unwrap();847 let create_item_data = CreateItemData::<T::CrossAccountId> {848 users,849 properties: CollectionPropertiesVec::default(),850 };851 let data = (0..total_tokens)852 .map(|_| create_item_data.clone())853 .collect();854855 <Pallet<T>>::create_multiple_items(self, &caller, data, &budget)856 .map_err(dispatch_to_evm::<T>)?;857 Ok(true)858 }859860 /// @notice Function to mint multiple tokens with the given tokenUris.861 /// @dev `tokenIds` is array of pairs of token ID and token URI. Token IDs should be consecutive862 /// numbers and first number should be obtained with `nextTokenId` method863 /// @param to The new owner864 /// @param tokens array of pairs of token ID and token URI for minted tokens865 #[solidity(hide, rename_selector = "mintBulkWithTokenURI")]866 #[weight(<SelfWeightOf<T>>::create_multiple_items(tokens.len() as u32))]867 fn mint_bulk_with_token_uri(868 &mut self,869 caller: caller,870 to: address,871 tokens: Vec<(uint256, string)>,872 ) -> Result<bool> {873 let key = key::url();874 let caller = T::CrossAccountId::from_eth(caller);875 let to = T::CrossAccountId::from_eth(to);876 let mut expected_index = <TokensMinted<T>>::get(self.id)877 .checked_add(1)878 .ok_or("item id overflow")?;879 let budget = self880 .recorder881 .weight_calls_budget(<StructureWeight<T>>::find_parent());882883 let mut data = Vec::with_capacity(tokens.len());884 let users: BoundedBTreeMap<_, _, _> = [(to.clone(), 1)]885 .into_iter()886 .collect::<BTreeMap<_, _>>()887 .try_into()888 .unwrap();889 for (id, token_uri) in tokens {890 let id: u32 = id.try_into().map_err(|_| "token id overflow")?;891 if id != expected_index {892 return Err("item id should be next".into());893 }894 expected_index = expected_index.checked_add(1).ok_or("item id overflow")?;895896 let mut properties = CollectionPropertiesVec::default();897 properties898 .try_push(Property {899 key: key.clone(),900 value: token_uri901 .into_bytes()902 .try_into()903 .map_err(|_| "token uri is too long")?,904 })905 .map_err(|e| Error::Revert(alloc::format!("Can't add property: {:?}", e)))?;906907 let create_item_data = CreateItemData::<T::CrossAccountId> {908 users: users.clone(),909 properties,910 };911 data.push(create_item_data);912 }913914 <Pallet<T>>::create_multiple_items(self, &caller, data, &budget)915 .map_err(dispatch_to_evm::<T>)?;916 Ok(true)917 }918919 /// Returns EVM address for refungible token920 ///921 /// @param token ID of the token922 fn token_contract_address(&self, token: uint256) -> Result<address> {923 Ok(T::EvmTokenAddressMapping::token_to_address(924 self.id,925 token.try_into().map_err(|_| "token id overflow")?,926 ))927 }928}929930#[solidity_interface(931 name = UniqueRefungible,932 is(933 ERC721,934 ERC721Enumerable,935 ERC721UniqueExtensions,936 ERC721UniqueMintable,937 ERC721Burnable,938 ERC721Metadata(if(this.flags.erc721metadata)),939 Collection(via(common_mut returns CollectionHandle<T>)),940 TokenProperties,941 )942)]943impl<T: Config> RefungibleHandle<T> where T::AccountId: From<[u8; 32]> + AsRef<[u8; 32]> {}944945// Not a tests, but code generators946generate_stubgen!(gen_impl, UniqueRefungibleCall<()>, true);947generate_stubgen!(gen_iface, UniqueRefungibleCall<()>, false);948949impl<T: Config> CommonEvmHandler for RefungibleHandle<T>950where951 T::AccountId: From<[u8; 32]> + AsRef<[u8; 32]>,952{953 const CODE: &'static [u8] = include_bytes!("./stubs/UniqueRefungible.raw");954 fn call(955 self,956 handle: &mut impl PrecompileHandle,957 ) -> Option<pallet_common::erc::PrecompileResult> {958 call::<T, UniqueRefungibleCall<T>, _, _>(handle, self)959 }960}pallets/unique/src/eth/mod.rsdiffbeforeafterboth--- a/pallets/unique/src/eth/mod.rs
+++ b/pallets/unique/src/eth/mod.rs
@@ -42,10 +42,7 @@
CreateCollectionData,
};
-use crate::{
- weights::WeightInfo, Config, SelfWeightOf, NftTransferBasket, FungibleTransferBasket,
- ReFungibleTransferBasket, NftApproveBasket, FungibleApproveBasket, RefungibleApproveBasket,
-};
+use crate::{weights::WeightInfo, Config, SelfWeightOf};
use alloc::format;
use sp_std::vec::Vec;
runtime/common/config/pallets/scheduler.rsdiffbeforeafterboth--- a/runtime/common/config/pallets/scheduler.rs
+++ b/runtime/common/config/pallets/scheduler.rs
@@ -25,7 +25,7 @@
use codec::Decode;
use crate::{
runtime_common::{scheduler::SchedulerPaymentExecutor, config::substrate::RuntimeBlockWeights},
- Runtime, RuntimeCall, RuntimeEvent, RuntimeOrigin, OriginCaller, Balances,
+ Runtime, RuntimeCall, RuntimeEvent, RuntimeOrigin, OriginCaller,
};
use pallet_unique_scheduler_v2::ScheduledEnsureOriginSuccess;
use up_common::types::AccountId;
runtime/common/scheduler.rsdiffbeforeafterboth--- a/runtime/common/scheduler.rs
+++ b/runtime/common/scheduler.rs
@@ -14,19 +14,16 @@
// You should have received a copy of the GNU General Public License
// along with Unique Network. If not, see <http://www.gnu.org/licenses/>.
-use frame_support::{
- traits::NamedReservableCurrency,
- dispatch::{GetDispatchInfo, PostDispatchInfo, DispatchInfo},
-};
+use frame_support::dispatch::{GetDispatchInfo, PostDispatchInfo, DispatchInfo};
use sp_runtime::{
traits::{Dispatchable, Applyable, Member},
generic::Era,
transaction_validity::TransactionValidityError,
- DispatchErrorWithPostInfo, DispatchError,
+ DispatchErrorWithPostInfo,
};
use codec::Encode;
-use crate::{Runtime, RuntimeCall, RuntimeOrigin, Balances};
-use up_common::types::{AccountId, Balance};
+use crate::{Runtime, RuntimeCall, RuntimeOrigin};
+use up_common::types::AccountId;
use fp_self_contained::SelfContainedCall;
use pallet_unique_scheduler_v2::DispatchCall;
use pallet_transaction_payment::ChargeTransactionPayment;