--- /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;
}