--- /dev/null +++ b/pallets/unique/src/eth/mod.rs @@ -0,0 +1,288 @@ +// Copyright 2019-2022 Unique Network (Gibraltar) Ltd. +// This file is part of Unique Network. + +// Unique Network 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. + +// Unique Network 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 Unique Network. If not, see . + +pub mod sponsoring; + +use fp_evm::PrecompileResult; +use pallet_common::{ + CollectionById, + erc::CommonEvmHandler, + eth::{map_eth_to_id, map_eth_to_token_id}, +}; +use pallet_fungible::FungibleHandle; +use pallet_nonfungible::NonfungibleHandle; +use pallet_refungible::{RefungibleHandle, erc::RefungibleTokenHandle}; +use sp_std::borrow::ToOwned; +use sp_std::vec::Vec; +use sp_core::{H160, U256}; +use crate::{CollectionMode, Config, dispatch::Dispatched}; +use pallet_common::CollectionHandle; + +pub struct UniqueErcSupport(core::marker::PhantomData); + +impl pallet_evm::OnMethodCall for UniqueErcSupport { + fn is_reserved(target: &H160) -> bool { + map_eth_to_id(target).is_some() + } + fn is_used(target: &H160) -> bool { + map_eth_to_id(target) + .map(>::contains_key) + .unwrap_or(false) + } + fn get_code(target: &H160) -> Option> { + if let Some(collection_id) = map_eth_to_id(target) { + let collection = >::get(collection_id)?; + Some( + match collection.mode { + CollectionMode::NFT => >::CODE, + CollectionMode::Fungible(_) => >::CODE, + CollectionMode::ReFungible => >::CODE, + } + .to_owned(), + ) + } else if let Some((collection_id, _token_id)) = map_eth_to_token_id(target) { + let collection = >::get(collection_id)?; + if collection.mode != CollectionMode::ReFungible { + return None; + } + // TODO: check token existence + Some(>::CODE.to_owned()) + } else { + None + } + } + fn call( + source: &H160, + target: &H160, + gas_limit: u64, + input: &[u8], + value: U256, + ) -> Option { + if let Some(collection_id) = map_eth_to_id(target) { + let collection = >::new_with_gas_limit(collection_id, gas_limit)?; + let dispatched = Dispatched::dispatch(collection); + + match dispatched { + Dispatched::Fungible(h) => h.call(source, input, value), + Dispatched::Nonfungible(h) => h.call(source, input, value), + Dispatched::Refungible(h) => h.call(source, input, value), + } + } else if let Some((collection_id, token_id)) = map_eth_to_token_id(target) { + let collection = >::new_with_gas_limit(collection_id, gas_limit)?; + if collection.mode != CollectionMode::ReFungible { + return None; + } + + let handle = RefungibleHandle::cast(collection); + // TODO: check token existence + RefungibleTokenHandle(handle, token_id).call(source, input, value) + } else { + None + } + } +} + +pub mod evm_collection { + use core::marker::PhantomData; + use evm_coder::{execution::*, generate_stubgen, solidity_interface, types::*, ToLog}; + use ethereum as _; + use pallet_evm_coder_substrate::{SubstrateRecorder, WithRecorder}; + use pallet_evm::{OnMethodCall, PrecompileResult, account::CrossAccountId}; + use up_data_structs::{ + CreateCollectionData, MAX_COLLECTION_DESCRIPTION_LENGTH, MAX_TOKEN_PREFIX_LENGTH, + MAX_COLLECTION_NAME_LENGTH, + }; + use frame_support::traits::Get; + use sp_core::H160; + use pallet_common::{CollectionHandle, save_eth}; + + use sp_std::{vec::Vec, rc::Rc}; + use alloc::format; + + // #[pallet::config] + pub trait Config: + frame_system::Config + + pallet_evm_coder_substrate::Config + + pallet_evm::account::Config + + pallet_nonfungible::Config + { + type ContractAddress: Get; + } + + struct EvmCollection(Rc>); + impl WithRecorder for EvmCollection { + fn recorder(&self) -> &SubstrateRecorder { + &self.0 + } + + fn into_recorder(self) -> Rc> { + self.0 + } + } + + #[derive(ToLog)] + pub enum EthCollectionEvent { + CollectionCreated { + #[indexed] + owner: address, + #[indexed] + collection_id: address, + }, + } + + #[solidity_interface(name = "Collection")] + impl EvmCollection { + fn create_721_collection( + &self, + caller: caller, + name: string, + description: string, + token_prefix: string, + ) -> Result
{ + let caller = T::CrossAccountId::from_eth(caller); + let name = name + .encode_utf16() + .collect::>() + .try_into() + .map_err(|_| error_feild_too_long(stringify!(name), MAX_COLLECTION_NAME_LENGTH))?; + let description = description + .encode_utf16() + .collect::>() + .try_into() + .map_err(|_| { + error_feild_too_long(stringify!(description), MAX_COLLECTION_DESCRIPTION_LENGTH) + })?; + let token_prefix = token_prefix + .into_bytes() + .try_into() + .map_err(|_| error_feild_too_long(stringify!(token_prefix), MAX_TOKEN_PREFIX_LENGTH))?; + + let data = CreateCollectionData { + name, + description, + token_prefix, + ..Default::default() + }; + + let collection_id = + >::init_collection(caller.as_sub().clone(), data) + .map_err(pallet_evm_coder_substrate::dispatch_to_evm::)?; + + let address = pallet_common::eth::collection_id_to_address(collection_id); + self.0.log_mirrored(EthCollectionEvent::CollectionCreated { + owner: *caller.as_eth(), + collection_id: address, + }); + Ok(address) + } + + fn set_sponsor( + &self, + caller: caller, + collection_address: address, + sponsor: address, + ) -> Result { + let mut collection = collection_from_address(collection_address, &self.0)?; + check_is_owner(caller, &collection)?; + + let sponsor = T::CrossAccountId::from_eth(sponsor); + collection.set_sponsor(sponsor.as_sub().clone()); + save_eth(collection) + } + + fn confirm_sponsorship(&self, caller: caller, collection_address: address) -> Result { + let mut collection = collection_from_address(collection_address, &self.0)?; + let caller = T::CrossAccountId::from_eth(caller); + if !collection.confirm_sponsorship(caller.as_sub()) { + return Err(Error::Revert("Caller is not set as sponsor".into())); + } + save_eth(collection) + } + + fn set_limits( + &self, + caller: caller, + collection_address: address, + limits_json: string, + ) -> Result { + let mut collection = collection_from_address(collection_address, &self.0)?; + check_is_owner(caller, &collection)?; + + let limits = serde_json_core::from_str(limits_json.as_ref()) + .map_err(|e| Error::Revert(format!("Parse JSON error: {}", e)))?; + collection.limits = limits.0; + save_eth(collection) + } + } + + fn error_feild_too_long(feild: &str, bound: u32) -> Error { + Error::Revert(format!("{} is too long. Max length is {}.", feild, bound)) + } + + fn collection_from_address( + collection_address: address, + recorder: &Rc>, + ) -> Result> { + let collection_id = pallet_common::eth::map_eth_to_id(&collection_address) + .ok_or(Error::Revert("Contract is not an unique collection".into()))?; + let collection = + pallet_common::CollectionHandle::new_with_recorder(collection_id, recorder.clone()) + .ok_or(Error::Revert("Create collection handle error".into()))?; + Ok(collection) + } + + fn check_is_owner(caller: caller, collection: &CollectionHandle) -> Result<()> { + let caller = T::CrossAccountId::from_eth(caller); + collection + .check_is_owner(&caller) + .map_err(pallet_evm_coder_substrate::dispatch_to_evm::)?; + Ok(()) + } + + pub struct CollectionOnMethodCall(PhantomData<*const T>); + impl OnMethodCall for CollectionOnMethodCall { + fn is_reserved(contract: &sp_core::H160) -> bool { + contract == &T::ContractAddress::get() + } + + fn is_used(contract: &sp_core::H160) -> bool { + contract == &T::ContractAddress::get() + } + + fn call( + source: &sp_core::H160, + target: &sp_core::H160, + gas_left: u64, + input: &[u8], + value: sp_core::U256, + ) -> Option { + if target != &T::ContractAddress::get() { + return None; + } + + let helpers = EvmCollection::(Rc::new(SubstrateRecorder::::new(*target, gas_left))); + pallet_evm_coder_substrate::call(*source, helpers, value, input) + } + + fn get_code(contract: &sp_core::H160) -> Option> { + (contract == &T::ContractAddress::get()) + .then(|| include_bytes!("./stubs/Collection.raw").to_vec()) + } + } + + generate_stubgen!(collection_impl, CollectionCall<()>, true); + generate_stubgen!(collection_iface, CollectionCall<()>, false); +} --- a/pallets/unique/src/lib.rs +++ b/pallets/unique/src/lib.rs @@ -46,7 +46,7 @@ CollectionHandle, Pallet as PalletCommon, CommonWeightInfo, dispatch::dispatch_call, dispatch::CollectionDispatch, }; -pub use eth::pallet_evm_collection; +pub use eth::evm_collection; #[cfg(feature = "runtime-benchmarks")] mod benchmarking; --- a/runtime/opal/src/lib.rs +++ b/runtime/opal/src/lib.rs @@ -80,7 +80,7 @@ }; use smallvec::smallvec; use codec::{Encode, Decode}; -use pallet_unique::pallet_evm_collection; +use pallet_unique::eth::evm_collection; use fp_rpc::TransactionStatus; use sp_runtime::{ traits::{BlockNumberProvider, Dispatchable, PostDispatchInfoOf, Saturating}, @@ -307,7 +307,7 @@ pallet_evm_migration::OnMethodCall, pallet_evm_contract_helpers::HelpersOnMethodCall, CollectionDispatchT, - pallet_evm_collection::CollectionOnMethodCall, + evm_collection::CollectionOnMethodCall, ); type OnCreate = pallet_evm_contract_helpers::HelpersOnCreate; type ChainId = ChainId; @@ -988,7 +988,7 @@ type DefaultSponsoringRateLimit = DefaultSponsoringRateLimit; } -impl pallet_evm_collection::Config for Runtime { +impl evm_collection::Config for Runtime { type ContractAddress = EvmCollectionAddress; } --- a/runtime/quartz/src/lib.rs +++ b/runtime/quartz/src/lib.rs @@ -80,7 +80,7 @@ use smallvec::smallvec; use codec::{Encode, Decode}; use pallet_evm::{Account as EVMAccount, FeeCalculator, GasWeightMapping, OnMethodCall}; -use pallet_unique::pallet_evm_collection; +use pallet_unique::evm_collection; use fp_rpc::TransactionStatus; use sp_runtime::{ traits::{BlockNumberProvider, Dispatchable, PostDispatchInfoOf, Saturating}, @@ -279,7 +279,7 @@ pallet_evm_migration::OnMethodCall, pallet_evm_contract_helpers::HelpersOnMethodCall, CollectionDispatchT, - pallet_evm_collection::CollectionOnMethodCall, + evm_collection::CollectionOnMethodCall, ); type OnCreate = pallet_evm_contract_helpers::HelpersOnCreate; type ChainId = ChainId; @@ -965,7 +965,7 @@ type DefaultSponsoringRateLimit = DefaultSponsoringRateLimit; } -impl pallet_evm_collection::Config for Runtime { +impl evm_collection::Config for Runtime { type ContractAddress = EvmCollectionAddress; } --- a/runtime/unique/src/lib.rs +++ b/runtime/unique/src/lib.rs @@ -85,7 +85,7 @@ use smallvec::smallvec; use codec::{Encode, Decode}; use pallet_evm::{Account as EVMAccount, FeeCalculator, GasWeightMapping, OnMethodCall}; -use pallet_unique::pallet_evm_collection; +use pallet_unique::evm_collection; use fp_rpc::TransactionStatus; use sp_runtime::{ traits::{BlockNumberProvider, Dispatchable, PostDispatchInfoOf, Saturating}, @@ -283,7 +283,7 @@ pallet_evm_migration::OnMethodCall, pallet_evm_contract_helpers::HelpersOnMethodCall, CollectionDispatchT, - pallet_evm_collection::CollectionOnMethodCall, + evm_collection::CollectionOnMethodCall, ); type OnCreate = pallet_evm_contract_helpers::HelpersOnCreate; type ChainId = ChainId; @@ -970,7 +970,7 @@ type DefaultSponsoringRateLimit = DefaultSponsoringRateLimit; } -impl pallet_evm_collection::Config for Runtime { +impl evm_collection::Config for Runtime { type ContractAddress = EvmCollectionAddress; }