difftreelog
feat ethereum method handling
in: master
8 files changed
pallets/nft/src/eth/mod.rsdiffbeforeafterboth--- a/pallets/nft/src/eth/mod.rs
+++ b/pallets/nft/src/eth/mod.rs
@@ -4,3 +4,304 @@
use abi::{AbiReader, AbiWriter};
pub mod log;
+use sp_std::borrow::ToOwned;
+use sp_std::vec::Vec;
+use sp_std::convert::TryInto;
+
+use codec::{Decode, Encode};
+use pallet_evm::{AddressMapping, PrecompileLog, PrecompileOutput, ExitReason, ExitRevert, ExitSucceed};
+use sp_core::{H160, H256};
+use frame_support::storage::{StorageMap, StorageDoubleMap};
+
+use crate::{Allowances, NftItemList, Module, Balance, Config, CollectionById, CollectionHandle, CollectionId, CollectionMode};
+
+pub struct NftErcSupport<T: Config>(core::marker::PhantomData<T>);
+
+// 0x17c4e6453Cc49AAAaEACA894e6D9683e00000001 - collection
+// TODO: Unhardcode prefix
+const ETH_ACCOUNT_PREFIX: [u8; 16] = [0x17, 0xc4, 0xe6, 0x45, 0x3c, 0xc4, 0x9a, 0xaa, 0xae, 0xac, 0xa8, 0x94, 0xe6, 0xd9, 0x68, 0x3e];
+
+fn map_eth_to_id(eth: &H160) -> Option<CollectionId> {
+ if ð[0..16] != ETH_ACCOUNT_PREFIX {
+ return None;
+ }
+ let mut id_bytes = [0; 4];
+ id_bytes.copy_from_slice(ð[16..20]);
+ Some(u32::from_be_bytes(id_bytes))
+}
+pub fn collection_id_to_address(id: u32) -> H160 {
+ let mut out = [0; 20];
+ out[0..16].copy_from_slice(Ð_ACCOUNT_PREFIX);
+ out[16..20].copy_from_slice(&u32::to_be_bytes(id));
+ H160(out)
+}
+
+fn result_to_output(result: Result<AbiWriter, Option<&'static str>>, logs: Vec<PrecompileLog>) -> PrecompileOutput {
+ sp_io::storage::start_transaction();
+ match result {
+ Ok(result) => {
+ sp_io::storage::commit_transaction();
+ // TODO: weight
+ PrecompileOutput(ExitReason::Succeed(ExitSucceed::Returned), result.finish(), 0, logs)
+ }
+ Err(Some(s)) => {
+ sp_io::storage::rollback_transaction();
+ // Error(string)
+ let mut out = AbiWriter::new_call(0x08c379a0);
+ out.string(&s);
+ PrecompileOutput(ExitReason::Revert(ExitRevert::Reverted), out.finish(), 0, Vec::new())
+ }
+ Err(None) => {
+ sp_io::storage::rollback_transaction();
+ PrecompileOutput(ExitReason::Revert(ExitRevert::Reverted), Vec::new(), 0, Vec::new())
+ }
+ }
+}
+
+fn call_internal<T: Config>(sender: H160, collection: &CollectionHandle<T>, method_id: u32, mut input: AbiReader) -> Result<AbiWriter, Option<&'static str>> {
+ let erc20 = matches!(collection.mode, CollectionMode::Fungible(_));
+ let erc721 = matches!(collection.mode, CollectionMode::ReFungible);
+
+ Ok(match method_id {
+ // function name() external view returns (string memory)
+ 0x06fdde03 => {
+ let name = collection.name.iter()
+ .map(|&e| e.try_into().ok() as Option<u8>)
+ .collect::<Option<Vec<u8>>>()
+ .ok_or(Some("non-ascii name"))?;
+
+ crate::abi_encode!(memory(&name))
+ }
+ // function symbol() external view returns (string memory)
+ 0x95d89b41 => {
+ let name = collection.token_prefix.iter()
+ .map(|&e| e.is_ascii_uppercase().then(|| e))
+ .collect::<Option<Vec<u8>>>()
+ .ok_or(Some("non-uppercase prefix"))?;
+
+ crate::abi_encode!(memory(&name))
+ }
+ // function decimals() external view returns (uint8 decimals)
+ 0x313ce567 if erc20 => {
+ if let CollectionMode::Fungible(decimals) = &collection.mode {
+ crate::abi_encode!(uint8(*decimals))
+ } else {
+ unreachable!()
+ }
+ }
+ // function totalSupply() external view returns (uint256)
+ 0x18160ddd if erc20 => {
+ // TODO: can't be implemented, as we don't track total amount of fungibles
+ crate::abi_encode!(uint256(0))
+ }
+ // function balanceOf(address account) external view returns (uint256)
+ 0x70a08231 if erc20 || erc721 => {
+ crate::abi_decode!(input, account: address);
+ let account = T::EvmAddressMapping::into_account_id(account);
+ let balance = <Balance<T>>::get(collection.id, account);
+ crate::abi_encode!(uint256(balance))
+ }
+ // function ownerOf(uint256 tokenId) external view returns (address)
+ 0x6352211e if erc721 => {
+ crate::abi_decode!(input, token_id: uint256);
+ let token_id: u32 = token_id.try_into().map_err(|_| "bad token id")?;
+
+ let token = <NftItemList<T>>::get(collection.id, token_id).ok_or("unknown token")?;
+
+ crate::abi_encode!(address(token.owner.as_eth().clone()))
+ }
+ // function transfer(address recipient, uint256 amount) external returns (bool) {
+ 0xa9059cbb if erc20 => {
+ crate::abi_decode!(input, recipient: address, amount: uint256);
+ let sender = T::CrossAccountId::from_eth(sender);
+ let recipient = T::CrossAccountId::from_eth(recipient);
+
+ <Module<T>>::transfer_internal(
+ sender,
+ recipient,
+ &collection,
+ 1,
+ amount,
+ ).map_err(|_| "transfer error")?;
+
+ crate::abi_encode!(bool(true))
+ }
+ // function allowance(address owner, address spender) external view returns (uint256)
+ 0xdd62ed3e if erc20 => {
+ crate::abi_decode!(input, owner: address, spender: address);
+ let owner = T::EvmAddressMapping::into_account_id(owner);
+ let spender = T::EvmAddressMapping::into_account_id(spender);
+ let allowance = <Allowances<T>>::get(collection.id, (1, &owner, &spender));
+ crate::abi_encode!(uint256(allowance))
+ }
+ // function approve(address spender, uint256 amount) external returns (bool)
+ // FIXME: All current implementations resets amount to specified value, ours - adds it
+ // FIXME: Our implementation doesn't handle resets (approve with zero amount)
+ 0x095ea7b3 if erc20 => {
+ crate::abi_decode!(input, spender: address, amount: uint256);
+ let sender = T::CrossAccountId::from_eth(sender);
+ let spender = T::CrossAccountId::from_eth(spender);
+
+ <Module<T>>::approve_internal(
+ sender,
+ spender,
+ &collection,
+ 1,
+ amount,
+ ).map_err(|_| "approve error")?;
+
+ crate::abi_encode!(bool(true))
+ }
+ // function approve(address approved, uint256 tokenId) external payable
+ 0x095ea7b3 if erc721 => {
+ crate::abi_decode!(input, approved: address, token_id: uint256);
+ let sender = T::CrossAccountId::from_eth(sender);
+ let approved = T::CrossAccountId::from_eth(approved);
+ let token_id = token_id.try_into().map_err(|_| "bad token id")?;
+
+ <Module<T>>::approve_internal(
+ sender,
+ approved,
+ &collection,
+ token_id,
+ 1,
+ ).map_err(|_| "approve error")?;
+ crate::abi_encode!()
+ }
+ // function transferFrom(address sender, address recipient, uint256 amount) external returns (bool)
+ 0x23b872dd if erc20 => {
+ crate::abi_decode!(input, from: address, recipient: address, amount: uint256);
+ let sender = T::CrossAccountId::from_eth(sender);
+ let from = T::CrossAccountId::from_eth(from);
+ let recipient = T::CrossAccountId::from_eth(recipient);
+
+ <Module<T>>::transfer_from_internal(
+ sender,
+ from,
+ recipient,
+ &collection,
+ 1,
+ amount,
+ ).map_err(|_| "transfer_from error")?;
+
+ crate::abi_encode!(bool(true))
+ }
+ // function transferFrom(address from, address to, uint256 tokenId) external payable
+ 0x23b872dd if erc721 => {
+ crate::abi_decode!(input, from: address, recipient: address, token_id: uint256);
+ let sender = T::CrossAccountId::from_eth(sender);
+ let from = T::CrossAccountId::from_eth(from);
+ let recipient = T::CrossAccountId::from_eth(recipient);
+ let token_id = token_id.try_into().map_err(|_| "bad token id")?;
+
+ <Module<T>>::transfer_from_internal(
+ sender,
+ from,
+ recipient,
+ &collection,
+ token_id,
+ 1,
+ ).map_err(|_| "transfer_from error")?;
+
+ crate::abi_encode!()
+ }
+ // function supportsInterface(bytes4 interfaceID) public pure returns (bool)
+ 0x01ffc9a7 => {
+ crate::abi_decode!(input, interface_id: uint32);
+ let supports = match interface_id {
+ // ERC165
+ 0x01ffc9a7 => true,
+ // ERC20
+ 0x36372b07 if erc20 => true,
+ // ERC721
+ 0x80ac58cd if erc721 => true,
+ _ => false,
+ };
+ crate::abi_encode!(bool(supports))
+ }
+ _ => return Err(None)
+ })
+}
+
+impl<T: Config> pallet_evm::OnMethodCall<T> for NftErcSupport<T> {
+ 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(<CollectionById<T>>::contains_key)
+ .unwrap_or(false)
+ }
+ fn get_code(target: &H160) -> Option<Vec<u8>> {
+ map_eth_to_id(&target)
+ .and_then(<CollectionById<T>>::get)
+ .map(|collection| {
+ match collection.mode {
+ CollectionMode::NFT => include_bytes!("stubs/ERC721.bin") as &[u8],
+ CollectionMode::Fungible(_) => include_bytes!("stubs/ERC20.bin") as &[u8],
+ CollectionMode::ReFungible => include_bytes!("stubs/ERC1633.bin") as &[u8],
+ CollectionMode::Invalid => include_bytes!("stubs/Invalid.bin") as &[u8],
+ }.to_owned()
+ })
+ }
+ fn call(
+ source: &H160,
+ target: &H160,
+ input: &[u8],
+ ) -> Option<PrecompileOutput> {
+ let collection = map_eth_to_id(&target)
+ .and_then(<CollectionHandle<T>>::get)?;
+ let (method_id, input) = AbiReader::new_call(input).unwrap();
+ let result = call_internal(*source, &collection, method_id, input);
+ Some(result_to_output(result, collection.logs.retrieve_logs_for_contract(*target)))
+ }
+}
+
+/// event Transfer(address indexed from, address indexed to, uint256 indexed tokenId);
+pub const TRANSFER_NFT_TOPIC: H256 = H256(hex_literal::hex!("ddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef"));
+/// event Approval(address indexed owner, address indexed approved, uint256 indexed tokenId);
+pub const APPROVAL_NFT_TOPIC: H256 = H256(hex_literal::hex!("8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b925"));
+// TODO: event ApprovalForAll(address indexed owner, address indexed operator, bool approved);
+
+/// event Transfer(address indexed from, address indexed to, uint256 indexed amount);
+pub const TRANSFER_FUNGIBLE_TOPIC: H256 = H256(hex_literal::hex!("ddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef"));
+/// event Approval(address indexed owner, address indexed approved, uint256 indexed amount);
+pub const APPROVAL_FUNGIBLE_TOPIC: H256 = H256(hex_literal::hex!("8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b925"));
+
+pub fn address_to_topic(address: &H160) -> H256 {
+ let mut output = [0; 32];
+ output[12..32].copy_from_slice(&address.0);
+ H256(output)
+}
+
+
+// TODO: This function is slow, and output can be memoized
+pub fn generate_transaction(collection_id: u32, chain_id: u64) -> ethereum::Transaction {
+ let contract = collection_id_to_address(collection_id);
+
+ // TODO: Make it work without native runtime by forking ethereum_tx_sign, and
+ // switching to pure-rust implementation of secp256k1
+ #[cfg(feature = "std")]
+ {
+ let signed = ethereum_tx_sign::RawTransaction {
+ nonce: 0.into(),
+ to: Some(contract.0.into()),
+ value: 0.into(),
+ gas_price: 0.into(),
+ gas: 0.into(),
+ // zero selector, this transaction always have same sender, so all data should be acquired from logs
+ data: Vec::from([0, 0, 0, 0]),
+ }.sign(
+ // TODO: move to pallet config
+ // 0xF70631E55faff9f3FD3681545aa6c724226a3853
+ // 9dbaef9b3ebc00e53f67c6a77bcfbf2c4f2aebe4d70d94af4f2df01744b7a91a
+ &hex_literal::hex!("9dbaef9b3ebc00e53f67c6a77bcfbf2c4f2aebe4d70d94af4f2df01744b7a91a").into(),
+ &chain_id
+ );
+ rlp::decode::<ethereum::Transaction>(&signed).expect("transaction is just created, it can't be broken")
+ }
+ #[cfg(not(feature = "std"))]
+ {
+ panic!("transaction generation not yet supported by wasm runtime")
+ }
+}
\ No newline at end of file
pallets/nft/src/eth/stubs/ERC1633.bindiffbeforeafterboth--- /dev/null
+++ b/pallets/nft/src/eth/stubs/ERC1633.bin
@@ -0,0 +1 @@
+TODO
\ No newline at end of file
pallets/nft/src/eth/stubs/ERC20.bindiffbeforeafterbothbinary blob — no preview
pallets/nft/src/eth/stubs/ERC20.soldiffbeforeafterboth--- /dev/null
+++ b/pallets/nft/src/eth/stubs/ERC20.sol
@@ -0,0 +1,69 @@
+// SPDX-License-Identifier: OTHER
+
+pragma solidity >=0.8.0 <0.9.0;
+
+contract ERC20 {
+ uint8 _dummy = 0;
+ string stub_error = "this contract does not exists, code for collections is implemented at pallet side";
+
+ // 0x18160ddd
+ function totalSupply() external view returns (uint256) {
+ require(false, stub_error);
+ _dummy;
+ return 0;
+ }
+
+ // 0x70a08231
+ function balanceOf(address account) external view returns (uint256) {
+ require(false, stub_error);
+ account;
+ _dummy;
+ return 0;
+ }
+
+ // 0xa9059cbb
+ function transfer(address recipient, uint256 amount) external returns (bool) {
+ require(false, stub_error);
+ recipient;
+ amount;
+ _dummy = 0;
+ return false;
+ }
+
+ // 0xdd62ed3e
+ function allowance(address owner, address spender) external view returns (uint256) {
+ require(false, stub_error);
+ owner;
+ spender;
+ return _dummy;
+ }
+
+ // 0x095ea7b3
+ function approve(address spender, uint256 amount) external returns (bool) {
+ require(false, stub_error);
+ spender;
+ amount;
+ _dummy = 0;
+ return false;
+ }
+
+ // 0x23b872dd
+ function transferFrom(address sender, address recipient, uint256 amount) external returns (bool) {
+ require(false, stub_error);
+ sender;
+ recipient;
+ amount;
+ _dummy = 0;
+ return false;
+ }
+
+ // While ERC165 is not required by spec of ERC20, better implement it
+ // 0x01ffc9a7
+ function supportsInterface(bytes4 interfaceID) public pure returns (bool) {
+ return
+ // ERC20
+ interfaceID == 0x36372b07 ||
+ // ERC165
+ interfaceID == 0x01ffc9a7;
+ }
+}
\ No newline at end of file
pallets/nft/src/eth/stubs/ERC721.bindiffbeforeafterboth--- /dev/null
+++ b/pallets/nft/src/eth/stubs/ERC721.bin
@@ -0,0 +1 @@
+0x608060405260008060006101000a81548160ff021916908360ff16021790555060008060016101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff16021790555060405180608001604052806051815260200162000cd6605191396001908051906020019061008f9291906100a2565b5034801561009c57600080fd5b506101a6565b8280546100ae90610145565b90600052602060002090601f0160209004810192826100d05760008555610117565b82601f106100e957805160ff1916838001178555610117565b82800160010185558215610117579182015b828111156101165782518255916020019190600101906100fb565b5b5090506101249190610128565b5090565b5b80821115610141576000816000905550600101610129565b5090565b6000600282049050600182168061015d57607f821691505b6020821081141561017157610170610177565b5b50919050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052602260045260246000fd5b610b2080620001b66000396000f3fe6080604052600436106100915760003560e01c80636352211e116100595780636352211e1461016457806370a08231146101a1578063a22cb465146101de578063b88d4fde14610207578063e985e9c51461022357610091565b806301ffc9a714610096578063081812fc146100d3578063095ea7b31461011057806323b872dd1461012c57806342842e0e14610148575b600080fd5b3480156100a257600080fd5b506100bd60048036038101906100b89190610821565b610260565b6040516100ca919061093b565b60405180910390f35b3480156100df57600080fd5b506100fa60048036038101906100f5919061084a565b6102c2565b6040516101079190610920565b60405180910390f35b61012a600480360381019061012591906107e5565b610333565b005b610146600480360381019061014191906106da565b61037d565b005b610162600480360381019061015d91906106da565b6103c8565b005b34801561017057600080fd5b5061018b6004803603810190610186919061084a565b610413565b6040516101989190610920565b60405180910390f35b3480156101ad57600080fd5b506101c860048036038101906101c39190610675565b610484565b6040516101d59190610978565b60405180910390f35b3480156101ea57600080fd5b50610205600480360381019061020091906107a9565b6104d4565b005b610221600480360381019061021c9190610729565b610539565b005b34801561022f57600080fd5b5061024a6004803603810190610245919061069e565b610586565b604051610257919061093b565b60405180910390f35b60006380ac58cd60e01b827bffffffffffffffffffffffffffffffffffffffffffffffffffffffff191614806102bb57506301ffc9a760e01b827bffffffffffffffffffffffffffffffffffffffffffffffffffffffff1916145b9050919050565b600080600190610308576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016102ff9190610956565b60405180910390fd5b50600060019054906101000a900473ffffffffffffffffffffffffffffffffffffffff169050919050565b6000600190610378576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040161036f9190610956565b60405180910390fd5b505050565b60006001906103c2576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016103b99190610956565b60405180910390fd5b50505050565b600060019061040d576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016104049190610956565b60405180910390fd5b50505050565b600080600190610459576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016104509190610956565b60405180910390fd5b50600060019054906101000a900473ffffffffffffffffffffffffffffffffffffffff169050919050565b6000806001906104ca576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016104c19190610956565b60405180910390fd5b5060009050919050565b6000600190610519576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016105109190610956565b60405180910390fd5b5060008060006101000a81548160ff021916908360ff1602179055505050565b600060019061057e576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016105759190610956565b60405180910390fd5b505050505050565b6000806001906105cc576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016105c39190610956565b60405180910390fd5b506000905092915050565b6000813590506105e681610a8e565b92915050565b6000813590506105fb81610aa5565b92915050565b60008135905061061081610abc565b92915050565b60008083601f84011261062857600080fd5b8235905067ffffffffffffffff81111561064157600080fd5b60208301915083600182028301111561065957600080fd5b9250929050565b60008135905061066f81610ad3565b92915050565b60006020828403121561068757600080fd5b6000610695848285016105d7565b91505092915050565b600080604083850312156106b157600080fd5b60006106bf858286016105d7565b92505060206106d0858286016105d7565b9150509250929050565b6000806000606084860312156106ef57600080fd5b60006106fd868287016105d7565b935050602061070e868287016105d7565b925050604061071f86828701610660565b9150509250925092565b60008060008060006080868803121561074157600080fd5b600061074f888289016105d7565b9550506020610760888289016105d7565b945050604061077188828901610660565b935050606086013567ffffffffffffffff81111561078e57600080fd5b61079a88828901610616565b92509250509295509295909350565b600080604083850312156107bc57600080fd5b60006107ca858286016105d7565b92505060206107db858286016105ec565b9150509250929050565b600080604083850312156107f857600080fd5b6000610806858286016105d7565b925050602061081785828601610660565b9150509250929050565b60006020828403121561083357600080fd5b600061084184828501610601565b91505092915050565b60006020828403121561085c57600080fd5b600061086a84828501610660565b91505092915050565b61087c816109b9565b82525050565b61088b816109cb565b82525050565b6000815461089e81610a2d565b6108a881866109a8565b945060018216600081146108c357600181146108d557610908565b60ff1983168652602086019350610908565b6108de85610993565b60005b83811015610900578154818901526001820191506020810190506108e1565b808801955050505b50505092915050565b61091a81610a23565b82525050565b60006020820190506109356000830184610873565b92915050565b60006020820190506109506000830184610882565b92915050565b600060208201905081810360008301526109708184610891565b905092915050565b600060208201905061098d6000830184610911565b92915050565b60008190508160005260206000209050919050565b600082825260208201905092915050565b60006109c482610a03565b9050919050565b60008115159050919050565b60007fffffffff0000000000000000000000000000000000000000000000000000000082169050919050565b600073ffffffffffffffffffffffffffffffffffffffff82169050919050565b6000819050919050565b60006002820490506001821680610a4557607f821691505b60208210811415610a5957610a58610a5f565b5b50919050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052602260045260246000fd5b610a97816109b9565b8114610aa257600080fd5b50565b610aae816109cb565b8114610ab957600080fd5b50565b610ac5816109d7565b8114610ad057600080fd5b50565b610adc81610a23565b8114610ae757600080fd5b5056fea26469706673582212206e53fcc41e6b23f5bd49a262462ecf5ff647f480649658ee8b67c91a8f733ba864736f6c634300080100337468697320636f6e747261637420646f6573206e6f74206578697374732c20636f646520666f7220636f6c6c656374696f6e7320697320696d706c656d656e7465642061742070616c6c65742073696465
\ No newline at end of file
pallets/nft/src/eth/stubs/ERC721.soldiffbeforeafterboth--- /dev/null
+++ b/pallets/nft/src/eth/stubs/ERC721.sol
@@ -0,0 +1,163 @@
+// SPDX-License-Identifier: OTHER
+
+pragma solidity >=0.8.0 <0.9.0;
+
+contract ERC721 {
+ uint8 _dummy = 0;
+ address _dummy_addr = 0x0000000000000000000000000000000000000000;
+ string _dummy_string = "";
+ string stub_error =
+ "this contract does not exists, code for collections is implemented at pallet side";
+
+ event Transfer(
+ address indexed from,
+ address indexed to,
+ uint256 indexed tokenId
+ );
+
+ event Approval(
+ address indexed owner,
+ address indexed approved,
+ uint256 indexed tokenId
+ );
+
+ event ApprovalForAll(
+ address indexed owner,
+ address indexed operator,
+ bool approved
+ );
+
+ // 0x18160ddd
+ function totalSupply() external view returns (uint256) {
+ require(false, stub_error);
+ return 0;
+ }
+
+ function name() external view returns (string memory res_name) {
+ require(false, stub_error);
+ res_name = _dummy_string;
+ }
+
+ function symbol() external view returns (string memory res_symbol) {
+ require(false, stub_error);
+ res_symbol = _dummy_string;
+ }
+
+ function tokenURI(uint256 tokenId) external view returns (string memory) {
+ require(false, stub_error);
+ tokenId;
+ return _dummy_string;
+ }
+
+ function tokenByIndex(uint256 index) external view returns (uint256) {
+ require(false, stub_error);
+ index;
+ return 0;
+ }
+
+ function tokenOfOwnerByIndex(address owner, uint256 index) external view returns (uint256) {
+ require(false, stub_error);
+ owner;
+ index;
+ return 0;
+ }
+
+ // 0x70a08231
+ function balanceOf(address owner) external view returns (uint256) {
+ require(false, stub_error);
+ owner;
+ return 0;
+ }
+
+ // 0x6352211e
+ function ownerOf(uint256 tokenId) external view returns (address) {
+ require(false, stub_error);
+ tokenId;
+ return _dummy_addr;
+ }
+
+ // 0xb88d4fde
+ function safeTransferFrom(
+ address from,
+ address to,
+ uint256 tokenId,
+ bytes calldata data
+ ) external payable {
+ require(false, stub_error);
+ from;
+ to;
+ tokenId;
+ data;
+ }
+
+ // 0x42842e0e
+ function safeTransferFrom(
+ address from,
+ address to,
+ uint256 tokenId
+ ) external payable {
+ require(false, stub_error);
+ from;
+ to;
+ tokenId;
+ }
+
+ // 0x23b872dd
+ function transferFrom(
+ address from,
+ address to,
+ uint256 tokenId
+ ) external payable {
+ require(false, stub_error);
+ from;
+ to;
+ tokenId;
+ }
+
+ // 0x095ea7b3
+ function approve(address approved, uint256 tokenId) external payable {
+ require(false, stub_error);
+ approved;
+ tokenId;
+ }
+
+ // 0xa22cb465
+ function setApprovalForAll(address operator, bool approved) external {
+ require(false, stub_error);
+ operator;
+ approved;
+ _dummy = 0;
+ }
+
+ // 0x081812fc
+ function getApproved(uint256 tokenId) external view returns (address) {
+ require(false, stub_error);
+ tokenId;
+ return _dummy_addr;
+ }
+
+ // 0xe985e9c5
+ function isApprovedForAll(address owner, address operator)
+ external
+ view
+ returns (bool)
+ {
+ require(false, stub_error);
+ owner;
+ operator;
+ return false;
+ }
+
+ // 0x01ffc9a7
+ function supportsInterface(bytes4 interfaceID) public pure returns (bool) {
+ return
+ // ERC721
+ interfaceID == 0x80ac58cd ||
+ // ERC721Metadata
+ interfaceID == 0x5b5e139f ||
+ // ERC721Enumerable
+ interfaceID == 0x780e9d63 ||
+ // ERC165
+ interfaceID == 0x01ffc9a7;
+ }
+}
pallets/nft/src/eth/stubs/Invalid.bindiffbeforeafterboth--- /dev/null
+++ b/pallets/nft/src/eth/stubs/Invalid.bin
@@ -0,0 +1 @@
+TODO
\ No newline at end of file
pallets/nft/src/lib.rsdiffbeforeafterboth1//2// This file is subject to the terms and conditions defined in3// file 'LICENSE', which is part of this source code package.4//56#![recursion_limit = "1024"]78#![cfg_attr(not(feature = "std"), no_std)]910#[cfg(feature = "std")]11pub use serde::*;1213use core::ops::{Deref, DerefMut};14use codec::{Decode, Encode};15pub use frame_support::{16 construct_runtime, decl_event, decl_module, decl_storage, decl_error,17 dispatch::DispatchResult,18 ensure, fail, parameter_types,19 traits::{20 Currency, ExistenceRequirement, Get, Imbalance, KeyOwnerProofSystem, OnUnbalanced,21 Randomness, IsSubType, WithdrawReasons,22 },23 weights::{24 constants::{BlockExecutionWeight, ExtrinsicBaseWeight, RocksDbWeight, WEIGHT_PER_SECOND},25 DispatchInfo, GetDispatchInfo, IdentityFee, Pays, PostDispatchInfo, Weight,26 WeightToFeePolynomial, DispatchClass,27 },28 StorageValue,29 transactional,30};3132use frame_system::{self as system, ensure_signed, ensure_root};33use sp_core::{H160, H256};34use sp_runtime::sp_std::prelude::Vec;35use sp_runtime::{36 traits::{37 Hash, DispatchInfoOf, Dispatchable, PostDispatchInfoOf, Saturating, SaturatedConversion, SignedExtension, Zero,38 },39 transaction_validity::{40 TransactionPriority, InvalidTransaction, TransactionValidity, TransactionValidityError, ValidTransaction,41 },42 FixedPointOperand, FixedU128,43};44use sp_runtime::traits::StaticLookup;45use pallet_contracts::chain_extension::UncheckedFrom;46use pallet_ethereum::EthereumTransactionSender;47use pallet_transaction_payment::OnChargeTransaction;4849#[cfg(test)]50mod mock;5152#[cfg(test)]53mod tests;5455mod default_weights;56mod eth;5758pub use eth::account::*;5960pub const MAX_DECIMAL_POINTS: DecimalPoints = 30;61pub const MAX_REFUNGIBLE_PIECES: u128 = 1_000_000_000_000_000_000_000;62pub const MAX_SPONSOR_TIMEOUT: u32 = 10_368_000;63pub const MAX_TOKEN_OWNERSHIP: u32 = 10_000_000;6465// Structs66// #region6768pub type CollectionId = u32;69pub type TokenId = u32;70pub type DecimalPoints = u8;7172#[derive(Encode, Decode, Eq, Debug, Clone, PartialEq)]73#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]74pub enum CollectionMode {75 Invalid,76 NFT,77 // decimal points78 Fungible(DecimalPoints),79 ReFungible,80}8182impl Default for CollectionMode {83 fn default() -> Self {84 Self::Invalid85 }86}8788impl Into<u8> for CollectionMode {89 fn into(self) -> u8 {90 match self {91 CollectionMode::Invalid => 0,92 CollectionMode::NFT => 1,93 CollectionMode::Fungible(_) => 2,94 CollectionMode::ReFungible => 3,95 }96 }97}9899#[derive(Encode, Decode, Eq, Debug, Clone, PartialEq)]100#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]101pub enum AccessMode {102 Normal,103 WhiteList,104}105impl Default for AccessMode {106 fn default() -> Self {107 Self::Normal108 }109}110111#[derive(Encode, Decode, Eq, Debug, Clone, PartialEq)]112#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]113pub enum SchemaVersion {114 ImageURL,115 Unique,116}117impl Default for SchemaVersion {118 fn default() -> Self {119 Self::ImageURL120 }121}122123#[derive(Encode, Decode, Default, Debug, Clone, PartialEq)]124#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]125pub struct Ownership<AccountId> {126 pub owner: AccountId,127 pub fraction: u128,128}129130#[derive(Encode, Decode, Debug, Clone, PartialEq)]131#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]132pub enum SponsorshipState<AccountId> {133 /// The fees are applied to the transaction sender134 Disabled,135 Unconfirmed(AccountId),136 /// Transactions are sponsored by specified account137 Confirmed(AccountId),138}139140impl<AccountId> SponsorshipState<AccountId> {141 fn sponsor(&self) -> Option<&AccountId> {142 match self {143 Self::Confirmed(sponsor) => Some(sponsor),144 _ => None,145 }146 }147148 fn pending_sponsor(&self) -> Option<&AccountId> {149 match self {150 Self::Unconfirmed(sponsor) | Self::Confirmed(sponsor) => Some(sponsor),151 _ => None,152 }153 }154155 fn confirmed(&self) -> bool {156 matches!(self, Self::Confirmed(_))157 }158}159160impl<T> Default for SponsorshipState<T> {161 fn default() -> Self {162 Self::Disabled163 }164}165166#[derive(Encode, Decode, Clone, PartialEq)]167#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]168pub struct Collection<T: Config> {169 pub owner: T::CrossAccountId,170 pub mode: CollectionMode,171 pub access: AccessMode,172 pub decimal_points: DecimalPoints,173 pub name: Vec<u16>, // 64 include null escape char174 pub description: Vec<u16>, // 256 include null escape char175 pub token_prefix: Vec<u8>, // 16 include null escape char176 pub mint_mode: bool,177 pub offchain_schema: Vec<u8>,178 pub schema_version: SchemaVersion,179 pub sponsorship: SponsorshipState<T::AccountId>,180 pub limits: CollectionLimits<T::BlockNumber>, // Collection private restrictions 181 pub variable_on_chain_schema: Vec<u8>, //182 pub const_on_chain_schema: Vec<u8>, //183}184185pub struct CollectionHandle<T: Config> {186 pub id: CollectionId,187 collection: Collection<T>,188 logs: eth::log::LogRecorder,189}190impl<T: Config> CollectionHandle<T> {191 pub fn get(id: CollectionId) -> Option<Self> {192 <CollectionById<T>>::get(id)193 .map(|collection| Self {194 id,195 collection,196 logs: eth::log::LogRecorder::default(),197 })198 }199 pub fn log(&self, topics: Vec<H256>, data: eth::abi::AbiWriter) {200 self.logs.log(topics, data)201 }202 pub fn into_inner(self) -> Collection<T> {203 self.collection.clone()204 }205}206207impl<T: Config> Deref for CollectionHandle<T> {208 type Target = Collection<T>;209210 fn deref(&self) -> &Self::Target {211 &self.collection212 }213}214215impl<T: Config> DerefMut for CollectionHandle<T> {216 fn deref_mut(&mut self) -> &mut Self::Target {217 &mut self.collection218 }219}220221#[derive(Encode, Decode, Debug, Clone, PartialEq)]222#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]223pub struct NftItemType<AccountId> {224 pub owner: AccountId,225 pub const_data: Vec<u8>,226 pub variable_data: Vec<u8>,227}228229#[derive(Encode, Decode, Default, Debug, Clone, PartialEq)]230#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]231pub struct FungibleItemType {232 pub value: u128,233}234235#[derive(Encode, Decode, Debug, Clone, PartialEq)]236#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]237pub struct ReFungibleItemType<AccountId> {238 pub owner: Vec<Ownership<AccountId>>,239 pub const_data: Vec<u8>,240 pub variable_data: Vec<u8>,241}242243// #[derive(Encode, Decode, Default, Debug, Clone, PartialEq)]244// #[cfg_attr(feature = "std", derive(Serialize, Deserialize))]245// pub struct VestingItem<AccountId, Moment> {246// pub sender: AccountId,247// pub recipient: AccountId,248// pub collection_id: CollectionId,249// pub item_id: TokenId,250// pub amount: u64,251// pub vesting_date: Moment,252// }253254#[derive(Encode, Decode, Debug, Clone, PartialEq)]255#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]256pub struct CollectionLimits<BlockNumber: Encode + Decode> {257 pub account_token_ownership_limit: u32,258 pub sponsored_data_size: u32,259 /// None - setVariableMetadata is not sponsored260 /// Some(v) - setVariableMetadata is sponsored 261 /// if there is v block between txs262 pub sponsored_data_rate_limit: Option<BlockNumber>,263 pub token_limit: u32,264265 // Timeouts for item types in passed blocks266 pub sponsor_transfer_timeout: u32,267 pub owner_can_transfer: bool,268 pub owner_can_destroy: bool,269}270271impl<BlockNumber: Encode + Decode> Default for CollectionLimits<BlockNumber> {272 fn default() -> Self {273 Self { 274 account_token_ownership_limit: 10_000_000, 275 token_limit: u32::max_value(),276 sponsored_data_size: u32::MAX, 277 sponsored_data_rate_limit: None,278 sponsor_transfer_timeout: 14400,279 owner_can_transfer: true,280 owner_can_destroy: true281 }282 }283}284285#[derive(Encode, Decode, Default, Debug, Clone, PartialEq)]286#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]287pub struct ChainLimits {288 pub collection_numbers_limit: u32,289 pub account_token_ownership_limit: u32,290 pub collections_admins_limit: u64,291 pub custom_data_limit: u32,292293 // Timeouts for item types in passed blocks294 pub nft_sponsor_transfer_timeout: u32,295 pub fungible_sponsor_transfer_timeout: u32,296 pub refungible_sponsor_transfer_timeout: u32,297298 // Schema limits299 pub offchain_schema_limit: u32,300 pub variable_on_chain_schema_limit: u32,301 pub const_on_chain_schema_limit: u32,302}303304pub trait WeightInfo {305 fn create_collection() -> Weight;306 fn destroy_collection() -> Weight;307 fn add_to_white_list() -> Weight;308 fn remove_from_white_list() -> Weight;309 fn set_public_access_mode() -> Weight;310 fn set_mint_permission() -> Weight;311 fn change_collection_owner() -> Weight;312 fn add_collection_admin() -> Weight;313 fn remove_collection_admin() -> Weight;314 fn set_collection_sponsor() -> Weight;315 fn confirm_sponsorship() -> Weight;316 fn remove_collection_sponsor() -> Weight;317 fn create_item(s: usize) -> Weight;318 fn burn_item() -> Weight;319 fn transfer() -> Weight;320 fn approve() -> Weight;321 fn transfer_from() -> Weight;322 fn set_offchain_schema() -> Weight;323 fn set_const_on_chain_schema() -> Weight;324 fn set_variable_on_chain_schema() -> Weight;325 fn set_variable_meta_data() -> Weight;326 fn enable_contract_sponsoring() -> Weight;327 fn set_schema_version() -> Weight;328 fn set_chain_limits() -> Weight;329 fn set_contract_sponsoring_rate_limit() -> Weight;330 fn set_variable_meta_data_sponsoring_rate_limit() -> Weight;331 fn toggle_contract_white_list() -> Weight;332 fn add_to_contract_white_list() -> Weight;333 fn remove_from_contract_white_list() -> Weight;334 fn set_collection_limits() -> Weight;335}336337#[derive(Encode, Decode, Default, Debug, Clone, PartialEq)]338#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]339pub struct CreateNftData {340 pub const_data: Vec<u8>,341 pub variable_data: Vec<u8>,342}343344#[derive(Encode, Decode, Default, Debug, Clone, PartialEq)]345#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]346pub struct CreateFungibleData {347 pub value: u128,348}349350#[derive(Encode, Decode, Default, Debug, Clone, PartialEq)]351#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]352pub struct CreateReFungibleData {353 pub const_data: Vec<u8>,354 pub variable_data: Vec<u8>,355 pub pieces: u128,356}357358#[derive(Encode, Decode, Debug, Clone, PartialEq)]359#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]360pub enum CreateItemData {361 NFT(CreateNftData),362 Fungible(CreateFungibleData),363 ReFungible(CreateReFungibleData),364}365366impl CreateItemData {367 pub fn len(&self) -> usize {368 let len = match self {369 CreateItemData::NFT(data) => data.variable_data.len() + data.const_data.len(),370 CreateItemData::ReFungible(data) => data.variable_data.len() + data.const_data.len(),371 _ => 0372 };373 374 return len;375 }376}377378impl From<CreateNftData> for CreateItemData {379 fn from(item: CreateNftData) -> Self {380 CreateItemData::NFT(item)381 }382}383384impl From<CreateReFungibleData> for CreateItemData {385 fn from(item: CreateReFungibleData) -> Self {386 CreateItemData::ReFungible(item)387 }388}389390impl From<CreateFungibleData> for CreateItemData {391 fn from(item: CreateFungibleData) -> Self {392 CreateItemData::Fungible(item)393 }394}395396397decl_error! {398 /// Error for non-fungible-token module.399 pub enum Error for Module<T: Config> {400 /// Total collections bound exceeded.401 TotalCollectionsLimitExceeded,402 /// Decimal_points parameter must be lower than MAX_DECIMAL_POINTS constant, currently it is 30.403 CollectionDecimalPointLimitExceeded, 404 /// Collection name can not be longer than 63 char.405 CollectionNameLimitExceeded, 406 /// Collection description can not be longer than 255 char.407 CollectionDescriptionLimitExceeded, 408 /// Token prefix can not be longer than 15 char.409 CollectionTokenPrefixLimitExceeded,410 /// This collection does not exist.411 CollectionNotFound,412 /// Item not exists.413 TokenNotFound,414 /// Admin not found415 AdminNotFound,416 /// Arithmetic calculation overflow.417 NumOverflow, 418 /// Account already has admin role.419 AlreadyAdmin, 420 /// You do not own this collection.421 NoPermission,422 /// This address is not set as sponsor, use setCollectionSponsor first.423 ConfirmUnsetSponsorFail,424 /// Collection is not in mint mode.425 PublicMintingNotAllowed,426 /// Sender parameter and item owner must be equal.427 MustBeTokenOwner,428 /// Item balance not enough.429 TokenValueTooLow,430 /// Size of item is too large.431 NftSizeLimitExceeded,432 /// No approve found433 ApproveNotFound,434 /// Requested value more than approved.435 TokenValueNotEnough,436 /// Only approved addresses can call this method.437 ApproveRequired,438 /// Address is not in white list.439 AddresNotInWhiteList,440 /// Number of collection admins bound exceeded.441 CollectionAdminsLimitExceeded,442 /// Owned tokens by a single address bound exceeded.443 AddressOwnershipLimitExceeded,444 /// Length of items properties must be greater than 0.445 EmptyArgument,446 /// const_data exceeded data limit.447 TokenConstDataLimitExceeded,448 /// variable_data exceeded data limit.449 TokenVariableDataLimitExceeded,450 /// Not NFT item data used to mint in NFT collection.451 NotNftDataUsedToMintNftCollectionToken,452 /// Not Fungible item data used to mint in Fungible collection.453 NotFungibleDataUsedToMintFungibleCollectionToken,454 /// Not Re Fungible item data used to mint in Re Fungible collection.455 NotReFungibleDataUsedToMintReFungibleCollectionToken,456 /// Unexpected collection type.457 UnexpectedCollectionType,458 /// Can't store metadata in fungible tokens.459 CantStoreMetadataInFungibleTokens,460 /// Collection token limit exceeded461 CollectionTokenLimitExceeded,462 /// Account token limit exceeded per collection463 AccountTokenLimitExceeded,464 /// Collection limit bounds per collection exceeded465 CollectionLimitBoundsExceeded,466 /// Tried to enable permissions which are only permitted to be disabled467 OwnerPermissionsCantBeReverted,468 /// Schema data size limit bound exceeded469 SchemaDataLimitExceeded,470 /// Maximum refungibility exceeded471 WrongRefungiblePieces,472 /// createRefungible should be called with one owner473 BadCreateRefungibleCall,474 }475}476477pub trait Config: system::Config + Sized + pallet_transaction_payment::Config + pallet_contracts::Config {478 type Event: From<Event<Self>> + Into<<Self as system::Config>::Event>;479480 /// Weight information for extrinsics in this pallet.481 type WeightInfo: WeightInfo;482483 type EvmAddressMapping: pallet_evm::AddressMapping<Self::AccountId>;484 type EvmBackwardsAddressMapping: EvmBackwardsAddressMapping<Self::AccountId>;485 type EvmWithdrawOrigin: pallet_evm::EnsureAddressOrigin<Self::Origin, Success = Self::AccountId>;486487 type CrossAccountId: CrossAccountId<Self::AccountId>;488 type Currency: Currency<Self::AccountId>;489 type CollectionCreationPrice: Get<<<Self as Config>::Currency as Currency<Self::AccountId>>::Balance>;490 type TreasuryAccountId: Get<Self::AccountId>;491492 type EthereumChainId: Get<u64>;493 type EthereumTransactionSender: pallet_ethereum::EthereumTransactionSender;494}495496#[cfg(feature = "runtime-benchmarks")]497mod benchmarking;498499// #endregion500501// # Used definitions502//503// ## User control levels504//505// chain-controlled - key is uncontrolled by user506// i.e autoincrementing index507// can use non-cryptographic hash508// real - key is controlled by user509// but it is hard to generate enough colliding values, i.e owner of signed txs510// can use non-cryptographic hash511// controlled - key is completly controlled by users512// i.e maps with mutable keys513// should use cryptographic hash514//515// ## User control level downgrade reasons516//517// ?1 - chain-controlled -> controlled518// collections/tokens can be destroyed, resulting in massive holes519// ?2 - chain-controlled -> controlled520// same as ?1, but can be only added, resulting in easier exploitation521// ?3 - real -> controlled522// no confirmation required, so addresses can be easily generated523decl_storage! {524 trait Store for Module<T: Config> as Nft {525526 //#region Private members527 /// Id of next collection528 CreatedCollectionCount: u32;529 /// Used for migrations530 ChainVersion: u64;531 /// Id of last collection token532 /// Collection id (controlled?1)533 ItemListIndex: map hasher(blake2_128_concat) CollectionId => TokenId;534 //#endregion535536 //#region Chain limits struct537 pub ChainLimit get(fn chain_limit) config(): ChainLimits;538 //#endregion539540 //#region Bound counters541 /// Amount of collections destroyed, used for total amount tracking with542 /// CreatedCollectionCount543 DestroyedCollectionCount: u32;544 /// Total amount of account owned tokens (NFTs + RFTs + unique fungibles)545 /// Account id (real)546 pub AccountItemCount get(fn account_item_count): map hasher(twox_64_concat) T::AccountId => u32;547 //#endregion548549 //#region Basic collections550 /// Collection info551 /// Collection id (controlled?1)552 pub CollectionById get(fn collection_id) config(): map hasher(blake2_128_concat) CollectionId => Option<Collection<T>> = None;553 /// List of collection admins554 /// Collection id (controlled?2)555 pub AdminList get(fn admin_list_collection): map hasher(blake2_128_concat) CollectionId => Vec<T::CrossAccountId>;556 /// Whitelisted collection users557 /// Collection id (controlled?2), user id (controlled?3)558 pub WhiteList get(fn white_list): double_map hasher(blake2_128_concat) CollectionId, hasher(blake2_128_concat) T::AccountId => bool;559 //#endregion560561 /// How many of collection items user have562 /// Collection id (controlled?2), account id (real)563 pub Balance get(fn balance_count): double_map hasher(blake2_128_concat) CollectionId, hasher(twox_64_concat) T::AccountId => u128;564565 /// Amount of items which spender can transfer out of owners account (via transferFrom)566 /// Collection id (controlled?2), (token id (controlled ?2) + owner account id (real) + spender account id (controlled?3))567 /// TODO: Off chain worker should remove from this map when token gets removed568 pub Allowances get(fn approved): double_map hasher(blake2_128_concat) CollectionId, hasher(blake2_128_concat) (TokenId, T::AccountId, T::AccountId) => u128;569570 //#region Item collections571 /// Collection id (controlled?2), token id (controlled?1)572 pub NftItemList get(fn nft_item_id) config(): double_map hasher(blake2_128_concat) CollectionId, hasher(blake2_128_concat) TokenId => Option<NftItemType<T::CrossAccountId>>;573 /// Collection id (controlled?2), owner (controlled?2)574 pub FungibleItemList get(fn fungible_item_id) config(): double_map hasher(blake2_128_concat) CollectionId, hasher(blake2_128_concat) T::AccountId => FungibleItemType;575 /// Collection id (controlled?2), token id (controlled?1)576 pub ReFungibleItemList get(fn refungible_item_id) config(): double_map hasher(blake2_128_concat) CollectionId, hasher(blake2_128_concat) TokenId => Option<ReFungibleItemType<T::CrossAccountId>>;577 //#endregion578579 //#region Index list580 /// Collection id (controlled?2), tokens owner (controlled?2)581 pub AddressTokens get(fn address_tokens): double_map hasher(blake2_128_concat) CollectionId, hasher(blake2_128_concat) T::AccountId => Vec<TokenId>;582 //#endregion583584 //#region Tokens transfer rate limit baskets585 /// (Collection id (controlled?2), who created (real))586 /// TODO: Off chain worker should remove from this map when collection gets removed587 pub CreateItemBasket get(fn create_item_basket): map hasher(blake2_128_concat) (CollectionId, T::AccountId) => T::BlockNumber;588 /// Collection id (controlled?2), token id (controlled?2)589 pub NftTransferBasket get(fn nft_transfer_basket): double_map hasher(blake2_128_concat) CollectionId, hasher(blake2_128_concat) TokenId => T::BlockNumber;590 /// Collection id (controlled?2), owning user (real)591 pub FungibleTransferBasket get(fn fungible_transfer_basket): double_map hasher(blake2_128_concat) CollectionId, hasher(twox_64_concat) T::AccountId => T::BlockNumber;592 /// Collection id (controlled?2), token id (controlled?2)593 pub ReFungibleTransferBasket get(fn refungible_transfer_basket): double_map hasher(blake2_128_concat) CollectionId, hasher(blake2_128_concat) TokenId => T::BlockNumber;594 //#endregion595596 /// Variable metadata sponsoring597 /// Collection id (controlled?2), token id (controlled?2)598 pub VariableMetaDataBasket get(fn variable_meta_data_basket): double_map hasher(blake2_128_concat) CollectionId, hasher(blake2_128_concat) TokenId => Option<T::BlockNumber> = None;599 600 //#region Contract Sponsorship and Ownership601 /// Contract address (real)602 pub ContractOwner get(fn contract_owner): map hasher(twox_64_concat) T::AccountId => Option<T::AccountId>;603 /// Contract address (real)604 pub ContractSelfSponsoring get(fn contract_self_sponsoring): map hasher(twox_64_concat) T::AccountId => bool;605 /// (Contract address(real), caller (real))606 pub ContractSponsorBasket get(fn contract_sponsor_basket): map hasher(twox_64_concat) (T::AccountId, T::AccountId) => T::BlockNumber;607 /// Contract address (real)608 pub ContractSponsoringRateLimit get(fn contract_sponsoring_rate_limit): map hasher(twox_64_concat) T::AccountId => T::BlockNumber;609 /// Contract address (real)610 pub ContractWhiteListEnabled get(fn contract_white_list_enabled): map hasher(twox_64_concat) T::AccountId => bool; 611 /// Contract address (real) => Whitelisted user (controlled?3)612 pub ContractWhiteList get(fn contract_white_list): double_map hasher(twox_64_concat) T::AccountId, hasher(blake2_128_concat) T::AccountId => bool; 613 //#endregion614 }615 add_extra_genesis {616 build(|config: &GenesisConfig<T>| {617 // Modification of storage618 for (_num, _c) in &config.collection_id {619 <Module<T>>::init_collection(_c);620 }621622 for (_num, _c, _i) in &config.nft_item_id {623 <Module<T>>::init_nft_token(*_c, _i);624 }625626 for (collection_id, account_id, fungible_item) in &config.fungible_item_id {627 <Module<T>>::init_fungible_token(*collection_id, &T::CrossAccountId::from_sub(account_id.clone()), fungible_item);628 }629630 for (_num, _c, _i) in &config.refungible_item_id {631 <Module<T>>::init_refungible_token(*_c, _i);632 }633 })634 }635}636637decl_event!(638 pub enum Event<T>639 where640 CrossAccountId = <T as Config>::CrossAccountId,641 {642 /// New collection was created643 /// 644 /// # Arguments645 /// 646 /// * collection_id: Globally unique identifier of newly created collection.647 /// 648 /// * mode: [CollectionMode] converted into u8.649 /// 650 /// * account_id: Collection owner.651 CollectionCreated(CollectionId, u8, CrossAccountId),652653 /// New item was created.654 /// 655 /// # Arguments656 /// 657 /// * collection_id: Id of the collection where item was created.658 /// 659 /// * item_id: Id of an item. Unique within the collection.660 ///661 /// * recipient: Owner of newly created item 662 ItemCreated(CollectionId, TokenId, CrossAccountId),663664 /// Collection item was burned.665 /// 666 /// # Arguments667 /// 668 /// collection_id.669 /// 670 /// item_id: Identifier of burned NFT.671 ItemDestroyed(CollectionId, TokenId),672673 /// Item was transferred674 ///675 /// * collection_id: Id of collection to which item is belong676 ///677 /// * item_id: Id of an item678 ///679 /// * sender: Original owner of item680 ///681 /// * recipient: New owner of item682 ///683 /// * amount: Always 1 for NFT684 Transfer(CollectionId, TokenId, CrossAccountId, CrossAccountId, u128),685686 /// * collection_id687 ///688 /// * item_id689 ///690 /// * sender691 ///692 /// * spender693 ///694 /// * amount695 Approved(CollectionId, TokenId, CrossAccountId, CrossAccountId, u128),696 }697);698699decl_module! {700 pub struct Module<T: Config> for enum Call 701 where 702 origin: T::Origin703 {704 fn deposit_event() = default;705 type Error = Error<T>;706707 fn on_initialize(now: T::BlockNumber) -> Weight {708 0709 }710711 /// This method creates a Collection of NFTs. Each Token may have multiple properties encoded as an array of bytes of certain length. The initial owner and admin of the collection are set to the address that signed the transaction. Both addresses can be changed later.712 /// 713 /// # Permissions714 /// 715 /// * Anyone.716 /// 717 /// # Arguments718 /// 719 /// * collection_name: UTF-16 string with collection name (limit 64 characters), will be stored as zero-terminated.720 /// 721 /// * collection_description: UTF-16 string with collection description (limit 256 characters), will be stored as zero-terminated.722 /// 723 /// * token_prefix: UTF-8 string with token prefix.724 /// 725 /// * mode: [CollectionMode] collection type and type dependent data.726 // returns collection ID727 #[weight = <T as Config>::WeightInfo::create_collection()]728 #[transactional]729 pub fn create_collection(origin,730 collection_name: Vec<u16>,731 collection_description: Vec<u16>,732 token_prefix: Vec<u8>,733 mode: CollectionMode) -> DispatchResult {734735 // Anyone can create a collection736 let who = T::CrossAccountId::from_sub(ensure_signed(origin)?);737738 // Take a (non-refundable) deposit of collection creation739 let mut imbalance = <<<T as Config>::Currency as Currency<T::AccountId>>::PositiveImbalance>::zero();740 imbalance.subsume(<<T as Config>::Currency as Currency<T::AccountId>>::deposit_creating(741 &T::TreasuryAccountId::get(),742 T::CollectionCreationPrice::get(),743 ));744 <T as Config>::Currency::settle(745 who.as_sub(),746 imbalance,747 WithdrawReasons::TRANSFER,748 ExistenceRequirement::KeepAlive,749 ).map_err(|_| Error::<T>::NoPermission)?;750751 let decimal_points = match mode {752 CollectionMode::Fungible(points) => points,753 _ => 0754 };755756 let chain_limit = ChainLimit::get();757758 let created_count = CreatedCollectionCount::get();759 let destroyed_count = DestroyedCollectionCount::get();760761 // bound Total number of collections762 ensure!(created_count - destroyed_count < chain_limit.collection_numbers_limit, Error::<T>::TotalCollectionsLimitExceeded);763764 // check params765 ensure!(decimal_points <= MAX_DECIMAL_POINTS, Error::<T>::CollectionDecimalPointLimitExceeded);766 ensure!(collection_name.len() <= 64, Error::<T>::CollectionNameLimitExceeded);767 ensure!(collection_description.len() <= 256, Error::<T>::CollectionDescriptionLimitExceeded);768 ensure!(token_prefix.len() <= 16, Error::<T>::CollectionTokenPrefixLimitExceeded);769770 // Generate next collection ID771 let next_id = created_count772 .checked_add(1)773 .ok_or(Error::<T>::NumOverflow)?;774775 CreatedCollectionCount::put(next_id);776777 let limits = CollectionLimits {778 sponsored_data_size: chain_limit.custom_data_limit,779 ..Default::default()780 };781782 // Create new collection783 let new_collection = Collection {784 owner: who.clone(),785 name: collection_name,786 mode: mode.clone(),787 mint_mode: false,788 access: AccessMode::Normal,789 description: collection_description,790 decimal_points: decimal_points,791 token_prefix: token_prefix,792 offchain_schema: Vec::new(),793 schema_version: SchemaVersion::ImageURL,794 sponsorship: SponsorshipState::Disabled,795 variable_on_chain_schema: Vec::new(),796 const_on_chain_schema: Vec::new(),797 limits,798 };799800 // Add new collection to map801 <CollectionById<T>>::insert(next_id, new_collection);802803 // call event804 Self::deposit_event(RawEvent::CollectionCreated(next_id, mode.into(), who));805806 Ok(())807 }808809 /// **DANGEROUS**: Destroys collection and all NFTs within this collection. Users irrecoverably lose their assets and may lose real money.810 /// 811 /// # Permissions812 /// 813 /// * Collection Owner.814 /// 815 /// # Arguments816 /// 817 /// * collection_id: collection to destroy.818 #[weight = <T as Config>::WeightInfo::destroy_collection()]819 #[transactional]820 pub fn destroy_collection(origin, collection_id: CollectionId) -> DispatchResult {821822 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);823 let collection = Self::get_collection(collection_id)?;824 Self::check_owner_permissions(&collection, &sender)?;825 if !collection.limits.owner_can_destroy {826 fail!(Error::<T>::NoPermission);827 }828829 <AddressTokens<T>>::remove_prefix(collection_id);830 <Allowances<T>>::remove_prefix(collection_id);831 <Balance<T>>::remove_prefix(collection_id);832 <ItemListIndex>::remove(collection_id);833 <AdminList<T>>::remove(collection_id);834 <CollectionById<T>>::remove(collection_id);835 <WhiteList<T>>::remove_prefix(collection_id);836837 <NftItemList<T>>::remove_prefix(collection_id);838 <FungibleItemList<T>>::remove_prefix(collection_id);839 <ReFungibleItemList<T>>::remove_prefix(collection_id);840841 <NftTransferBasket<T>>::remove_prefix(collection_id);842 <FungibleTransferBasket<T>>::remove_prefix(collection_id);843 <ReFungibleTransferBasket<T>>::remove_prefix(collection_id);844845 <VariableMetaDataBasket<T>>::remove_prefix(collection_id);846847 DestroyedCollectionCount::put(DestroyedCollectionCount::get()848 .checked_add(1)849 .ok_or(Error::<T>::NumOverflow)?);850851 Ok(())852 }853854 /// Add an address to white list.855 /// 856 /// # Permissions857 /// 858 /// * Collection Owner859 /// * Collection Admin860 /// 861 /// # Arguments862 /// 863 /// * collection_id.864 /// 865 /// * address.866 #[weight = <T as Config>::WeightInfo::add_to_white_list()]867 #[transactional]868 pub fn add_to_white_list(origin, collection_id: CollectionId, address: T::CrossAccountId) -> DispatchResult{869870 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);871 let collection = Self::get_collection(collection_id)?;872 Self::check_owner_or_admin_permissions(&collection, &sender)?;873874 <WhiteList<T>>::insert(collection_id, address.as_sub(), true);875 876 Ok(())877 }878879 /// Remove an address from white list.880 /// 881 /// # Permissions882 /// 883 /// * Collection Owner884 /// * Collection Admin885 /// 886 /// # Arguments887 /// 888 /// * collection_id.889 /// 890 /// * address.891 #[weight = <T as Config>::WeightInfo::remove_from_white_list()]892 #[transactional]893 pub fn remove_from_white_list(origin, collection_id: CollectionId, address: T::CrossAccountId) -> DispatchResult{894895 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);896 let collection = Self::get_collection(collection_id)?;897 Self::check_owner_or_admin_permissions(&collection, &sender)?;898899 <WhiteList<T>>::remove(collection_id, address.as_sub());900901 Ok(())902 }903904 /// Toggle between normal and white list access for the methods with access for `Anyone`.905 /// 906 /// # Permissions907 /// 908 /// * Collection Owner.909 /// 910 /// # Arguments911 /// 912 /// * collection_id.913 /// 914 /// * mode: [AccessMode]915 #[weight = <T as Config>::WeightInfo::set_public_access_mode()]916 #[transactional]917 pub fn set_public_access_mode(origin, collection_id: CollectionId, mode: AccessMode) -> DispatchResult918 {919 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);920921 let mut target_collection = Self::get_collection(collection_id)?;922 Self::check_owner_permissions(&target_collection, &sender)?;923 target_collection.access = mode;924 Self::save_collection(target_collection);925926 Ok(())927 }928929 /// Allows Anyone to create tokens if:930 /// * White List is enabled, and931 /// * Address is added to white list, and932 /// * This method was called with True parameter933 /// 934 /// # Permissions935 /// * Collection Owner936 ///937 /// # Arguments938 /// 939 /// * collection_id.940 /// 941 /// * mint_permission: Boolean parameter. If True, allows minting to Anyone with conditions above.942 #[weight = <T as Config>::WeightInfo::set_mint_permission()]943 #[transactional]944 pub fn set_mint_permission(origin, collection_id: CollectionId, mint_permission: bool) -> DispatchResult945 {946 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);947948 let mut target_collection = Self::get_collection(collection_id)?;949 Self::check_owner_permissions(&target_collection, &sender)?;950 target_collection.mint_mode = mint_permission;951 Self::save_collection(target_collection);952953 Ok(())954 }955956 /// Change the owner of the collection.957 /// 958 /// # Permissions959 /// 960 /// * Collection Owner.961 /// 962 /// # Arguments963 /// 964 /// * collection_id.965 /// 966 /// * new_owner.967 #[weight = <T as Config>::WeightInfo::change_collection_owner()]968 #[transactional]969 pub fn change_collection_owner(origin, collection_id: CollectionId, new_owner: T::CrossAccountId) -> DispatchResult {970971 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);972 let mut target_collection = Self::get_collection(collection_id)?;973 Self::check_owner_permissions(&target_collection, &sender)?;974 target_collection.owner = new_owner;975 Self::save_collection(target_collection);976977 Ok(())978 }979980 /// Adds an admin of the Collection.981 /// NFT Collection can be controlled by multiple admin addresses (some which can also be servers, for example). Admins can issue and burn NFTs, as well as add and remove other admins, but cannot change NFT or Collection ownership. 982 /// 983 /// # Permissions984 /// 985 /// * Collection Owner.986 /// * Collection Admin.987 /// 988 /// # Arguments989 /// 990 /// * collection_id: ID of the Collection to add admin for.991 /// 992 /// * new_admin_id: Address of new admin to add.993 #[weight = <T as Config>::WeightInfo::add_collection_admin()]994 #[transactional]995 pub fn add_collection_admin(origin, collection_id: CollectionId, new_admin_id: T::CrossAccountId) -> DispatchResult {996 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);997 let collection = Self::get_collection(collection_id)?;998 Self::check_owner_or_admin_permissions(&collection, &sender)?;999 let mut admin_arr = <AdminList<T>>::get(collection_id);10001001 match admin_arr.binary_search(&new_admin_id) {1002 Ok(_) => {},1003 Err(idx) => {1004 let limits = ChainLimit::get();1005 ensure!(admin_arr.len() < limits.collections_admins_limit as usize, Error::<T>::CollectionAdminsLimitExceeded);1006 admin_arr.insert(idx, new_admin_id);1007 <AdminList<T>>::insert(collection_id, admin_arr);1008 }1009 }1010 Ok(())1011 }10121013 /// Remove admin address of the Collection. An admin address can remove itself. List of admins may become empty, in which case only Collection Owner will be able to add an Admin.1014 ///1015 /// # Permissions1016 /// 1017 /// * Collection Owner.1018 /// * Collection Admin.1019 /// 1020 /// # Arguments1021 /// 1022 /// * collection_id: ID of the Collection to remove admin for.1023 /// 1024 /// * account_id: Address of admin to remove.1025 #[weight = <T as Config>::WeightInfo::remove_collection_admin()]1026 #[transactional]1027 pub fn remove_collection_admin(origin, collection_id: CollectionId, account_id: T::CrossAccountId) -> DispatchResult {1028 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);1029 let collection = Self::get_collection(collection_id)?;1030 Self::check_owner_or_admin_permissions(&collection, &sender)?;1031 let mut admin_arr = <AdminList<T>>::get(collection_id);10321033 match admin_arr.binary_search(&account_id) {1034 Ok(idx) => {1035 admin_arr.remove(idx);1036 <AdminList<T>>::insert(collection_id, admin_arr);1037 },1038 Err(_) => {}1039 }1040 Ok(())1041 }10421043 /// # Permissions1044 /// 1045 /// * Collection Owner1046 /// 1047 /// # Arguments1048 /// 1049 /// * collection_id.1050 /// 1051 /// * new_sponsor.1052 #[weight = <T as Config>::WeightInfo::set_collection_sponsor()]1053 #[transactional]1054 pub fn set_collection_sponsor(origin, collection_id: CollectionId, new_sponsor: T::AccountId) -> DispatchResult {1055 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);1056 let mut target_collection = Self::get_collection(collection_id)?;1057 Self::check_owner_permissions(&target_collection, &sender)?;10581059 target_collection.sponsorship = SponsorshipState::Unconfirmed(new_sponsor);1060 Self::save_collection(target_collection);10611062 Ok(())1063 }10641065 /// # Permissions1066 /// 1067 /// * Sponsor.1068 /// 1069 /// # Arguments1070 /// 1071 /// * collection_id.1072 #[weight = <T as Config>::WeightInfo::confirm_sponsorship()]1073 #[transactional]1074 pub fn confirm_sponsorship(origin, collection_id: CollectionId) -> DispatchResult {1075 let sender = ensure_signed(origin)?;10761077 let mut target_collection = Self::get_collection(collection_id)?;1078 ensure!(1079 target_collection.sponsorship.pending_sponsor() == Some(&sender),1080 Error::<T>::ConfirmUnsetSponsorFail1081 );10821083 target_collection.sponsorship = SponsorshipState::Confirmed(sender);1084 Self::save_collection(target_collection);10851086 Ok(())1087 }10881089 /// Switch back to pay-per-own-transaction model.1090 ///1091 /// # Permissions1092 ///1093 /// * Collection owner.1094 /// 1095 /// # Arguments1096 /// 1097 /// * collection_id.1098 #[weight = <T as Config>::WeightInfo::remove_collection_sponsor()]1099 #[transactional]1100 pub fn remove_collection_sponsor(origin, collection_id: CollectionId) -> DispatchResult {1101 let sender = ensure_signed(origin)?;11021103 let mut target_collection = Self::get_collection(collection_id)?;1104 Self::check_owner_permissions(&target_collection, &T::CrossAccountId::from_sub(sender))?;11051106 target_collection.sponsorship = SponsorshipState::Disabled;1107 Self::save_collection(target_collection);11081109 Ok(())1110 }11111112 /// This method creates a concrete instance of NFT Collection created with CreateCollection method.1113 /// 1114 /// # Permissions1115 /// 1116 /// * Collection Owner.1117 /// * Collection Admin.1118 /// * Anyone if1119 /// * White List is enabled, and1120 /// * Address is added to white list, and1121 /// * MintPermission is enabled (see SetMintPermission method)1122 /// 1123 /// # Arguments1124 /// 1125 /// * collection_id: ID of the collection.1126 /// 1127 /// * owner: Address, initial owner of the NFT.1128 ///1129 /// * data: Token data to store on chain.1130 // #[weight =1131 // (130_000_000 as Weight)1132 // .saturating_add((2135 as Weight).saturating_mul((properties.len() as u64) as Weight))1133 // .saturating_add(RocksDbWeight::get().reads(10 as Weight))1134 // .saturating_add(RocksDbWeight::get().writes(8 as Weight))]11351136 #[weight = <T as Config>::WeightInfo::create_item(data.len())]1137 #[transactional]1138 pub fn create_item(origin, collection_id: CollectionId, owner: T::CrossAccountId, data: CreateItemData) -> DispatchResult {11391140 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);11411142 let target_collection = Self::get_collection(collection_id)?;11431144 Self::can_create_items_in_collection(&target_collection, &sender, &owner, 1)?;1145 Self::validate_create_item_args(&target_collection, &data)?;1146 Self::create_item_no_validation(&target_collection, owner, data)?;11471148 Self::submit_logs(target_collection)?;1149 Ok(())1150 }11511152 /// This method creates multiple items in a collection created with CreateCollection method.1153 /// 1154 /// # Permissions1155 /// 1156 /// * Collection Owner.1157 /// * Collection Admin.1158 /// * Anyone if1159 /// * White List is enabled, and1160 /// * Address is added to white list, and1161 /// * MintPermission is enabled (see SetMintPermission method)1162 /// 1163 /// # Arguments1164 /// 1165 /// * collection_id: ID of the collection.1166 /// 1167 /// * itemsData: Array items properties. Each property is an array of bytes itself, see [create_item].1168 /// 1169 /// * owner: Address, initial owner of the NFT.1170 #[weight = <T as Config>::WeightInfo::create_item(items_data.into_iter()1171 .map(|data| { data.len() })1172 .sum())]1173 #[transactional]1174 pub fn create_multiple_items(origin, collection_id: CollectionId, owner: T::CrossAccountId, items_data: Vec<CreateItemData>) -> DispatchResult {11751176 ensure!(items_data.len() > 0, Error::<T>::EmptyArgument);1177 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);1178 let collection = Self::get_collection(collection_id)?;11791180 Self::create_multiple_items_internal(sender, &collection, owner, items_data)?;11811182 Self::submit_logs(collection)?;1183 Ok(())1184 }11851186 /// Destroys a concrete instance of NFT.1187 /// 1188 /// # Permissions1189 /// 1190 /// * Collection Owner.1191 /// * Collection Admin.1192 /// * Current NFT Owner.1193 /// 1194 /// # Arguments1195 /// 1196 /// * collection_id: ID of the collection.1197 /// 1198 /// * item_id: ID of NFT to burn.1199 #[weight = <T as Config>::WeightInfo::burn_item()]1200 #[transactional]1201 pub fn burn_item(origin, collection_id: CollectionId, item_id: TokenId, value: u128) -> DispatchResult {12021203 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);1204 let target_collection = Self::get_collection(collection_id)?;12051206 Self::burn_item_internal(&sender, &target_collection, item_id, value)?;12071208 Self::submit_logs(target_collection)?;1209 Ok(())1210 }12111212 /// Change ownership of the token.1213 /// 1214 /// # Permissions1215 /// 1216 /// * Collection Owner1217 /// * Collection Admin1218 /// * Current NFT owner1219 ///1220 /// # Arguments1221 /// 1222 /// * recipient: Address of token recipient.1223 /// 1224 /// * collection_id.1225 /// 1226 /// * item_id: ID of the item1227 /// * Non-Fungible Mode: Required.1228 /// * Fungible Mode: Ignored.1229 /// * Re-Fungible Mode: Required.1230 /// 1231 /// * value: Amount to transfer.1232 /// * Non-Fungible Mode: Ignored1233 /// * Fungible Mode: Must specify transferred amount1234 /// * Re-Fungible Mode: Must specify transferred portion (between 0 and 1)1235 #[weight = <T as Config>::WeightInfo::transfer()]1236 #[transactional]1237 pub fn transfer(origin, recipient: T::CrossAccountId, collection_id: CollectionId, item_id: TokenId, value: u128) -> DispatchResult {1238 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);1239 let collection = Self::get_collection(collection_id)?;12401241 Self::transfer_internal(sender, recipient, &collection, item_id, value)?;12421243 Self::submit_logs(collection)?;1244 Ok(())1245 }12461247 /// Set, change, or remove approved address to transfer the ownership of the NFT.1248 /// 1249 /// # Permissions1250 /// 1251 /// * Collection Owner1252 /// * Collection Admin1253 /// * Current NFT owner1254 /// 1255 /// # Arguments1256 /// 1257 /// * approved: Address that is approved to transfer this NFT or zero (if needed to remove approval).1258 /// 1259 /// * collection_id.1260 /// 1261 /// * item_id: ID of the item.1262 #[weight = <T as Config>::WeightInfo::approve()]1263 #[transactional]1264 pub fn approve(origin, spender: T::CrossAccountId, collection_id: CollectionId, item_id: TokenId, amount: u128) -> DispatchResult {12651266 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);1267 let collection = Self::get_collection(collection_id)?;12681269 Self::approve_internal(sender, spender, &collection, item_id, amount)?;12701271 Self::submit_logs(collection)?;1272 Ok(())1273 }1274 1275 /// Change ownership of a NFT on behalf of the owner. See Approve method for additional information. After this method executes, the approval is removed so that the approved address will not be able to transfer this NFT again from this owner.1276 /// 1277 /// # Permissions1278 /// * Collection Owner1279 /// * Collection Admin1280 /// * Current NFT owner1281 /// * Address approved by current NFT owner1282 /// 1283 /// # Arguments1284 /// 1285 /// * from: Address that owns token.1286 /// 1287 /// * recipient: Address of token recipient.1288 /// 1289 /// * collection_id.1290 /// 1291 /// * item_id: ID of the item.1292 /// 1293 /// * value: Amount to transfer.1294 #[weight = <T as Config>::WeightInfo::transfer_from()]1295 #[transactional]1296 pub fn transfer_from(origin, from: T::CrossAccountId, recipient: T::CrossAccountId, collection_id: CollectionId, item_id: TokenId, value: u128 ) -> DispatchResult {12971298 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);1299 let collection = Self::get_collection(collection_id)?;13001301 Self::transfer_from_internal(sender, from, recipient, &collection, item_id, value)?;13021303 Self::submit_logs(collection)?;1304 Ok(())1305 }13061307 // #[weight = 0]1308 // pub fn safe_transfer_from(origin, collection_id: CollectionId, item_id: TokenId, new_owner: T::AccountId) -> DispatchResult {13091310 // // let no_perm_mes = "You do not have permissions to modify this collection";1311 // // ensure!(<ApprovedList<T>>::contains_key((collection_id, item_id)), no_perm_mes);1312 // // let list_itm = <ApprovedList<T>>::get((collection_id, item_id));1313 // // ensure!(list_itm.contains(&new_owner.clone()), no_perm_mes);13141315 // // // on_nft_received call13161317 // // Self::transfer(origin, collection_id, item_id, new_owner)?;13181319 // Ok(())1320 // }13211322 /// Set off-chain data schema.1323 /// 1324 /// # Permissions1325 /// 1326 /// * Collection Owner1327 /// * Collection Admin1328 /// 1329 /// # Arguments1330 /// 1331 /// * collection_id.1332 /// 1333 /// * schema: String representing the offchain data schema.1334 #[weight = <T as Config>::WeightInfo::set_variable_meta_data()]1335 #[transactional]1336 pub fn set_variable_meta_data (1337 origin,1338 collection_id: CollectionId,1339 item_id: TokenId,1340 data: Vec<u8>1341 ) -> DispatchResult {1342 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);1343 1344 let target_collection = Self::get_collection(collection_id)?;1345 Self::set_variable_meta_data_internal(sender, &target_collection, item_id, data)?;13461347 Ok(())1348 }1349 1350 /// Set schema standard1351 /// ImageURL1352 /// Unique1353 /// 1354 /// # Permissions1355 /// 1356 /// * Collection Owner1357 /// * Collection Admin1358 /// 1359 /// # Arguments1360 /// 1361 /// * collection_id.1362 /// 1363 /// * schema: SchemaVersion: enum1364 #[weight = <T as Config>::WeightInfo::set_schema_version()]1365 #[transactional]1366 pub fn set_schema_version(1367 origin,1368 collection_id: CollectionId,1369 version: SchemaVersion1370 ) -> DispatchResult {1371 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);1372 let mut target_collection = Self::get_collection(collection_id)?;1373 Self::check_owner_or_admin_permissions(&target_collection, &sender)?;1374 target_collection.schema_version = version;1375 Self::save_collection(target_collection);13761377 Ok(())1378 }13791380 /// Set off-chain data schema.1381 /// 1382 /// # Permissions1383 /// 1384 /// * Collection Owner1385 /// * Collection Admin1386 /// 1387 /// # Arguments1388 /// 1389 /// * collection_id.1390 /// 1391 /// * schema: String representing the offchain data schema.1392 #[weight = <T as Config>::WeightInfo::set_offchain_schema()]1393 #[transactional]1394 pub fn set_offchain_schema(1395 origin,1396 collection_id: CollectionId,1397 schema: Vec<u8>1398 ) -> DispatchResult {1399 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);1400 let mut target_collection = Self::get_collection(collection_id)?;1401 Self::check_owner_or_admin_permissions(&target_collection, &sender)?;14021403 // check schema limit1404 ensure!(schema.len() as u32 <= ChainLimit::get().offchain_schema_limit, "");14051406 target_collection.offchain_schema = schema;1407 Self::save_collection(target_collection);14081409 Ok(())1410 }14111412 /// Set const on-chain data schema.1413 /// 1414 /// # Permissions1415 /// 1416 /// * Collection Owner1417 /// * Collection Admin1418 /// 1419 /// # Arguments1420 /// 1421 /// * collection_id.1422 /// 1423 /// * schema: String representing the const on-chain data schema.1424 #[weight = <T as Config>::WeightInfo::set_const_on_chain_schema()]1425 #[transactional]1426 pub fn set_const_on_chain_schema (1427 origin,1428 collection_id: CollectionId,1429 schema: Vec<u8>1430 ) -> DispatchResult {1431 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);1432 let mut target_collection = Self::get_collection(collection_id)?;1433 Self::check_owner_or_admin_permissions(&target_collection, &sender)?;14341435 // check schema limit1436 ensure!(schema.len() as u32 <= ChainLimit::get().const_on_chain_schema_limit, "");14371438 target_collection.const_on_chain_schema = schema;1439 Self::save_collection(target_collection);14401441 Ok(())1442 }14431444 /// Set variable on-chain data schema.1445 /// 1446 /// # Permissions1447 /// 1448 /// * Collection Owner1449 /// * Collection Admin1450 /// 1451 /// # Arguments1452 /// 1453 /// * collection_id.1454 /// 1455 /// * schema: String representing the variable on-chain data schema.1456 #[weight = <T as Config>::WeightInfo::set_const_on_chain_schema()]1457 #[transactional]1458 pub fn set_variable_on_chain_schema (1459 origin,1460 collection_id: CollectionId,1461 schema: Vec<u8>1462 ) -> DispatchResult {1463 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);1464 let mut target_collection = Self::get_collection(collection_id)?;1465 Self::check_owner_or_admin_permissions(&target_collection, &sender)?;14661467 // check schema limit1468 ensure!(schema.len() as u32 <= ChainLimit::get().variable_on_chain_schema_limit, "");14691470 target_collection.variable_on_chain_schema = schema;1471 Self::save_collection(target_collection);14721473 Ok(())1474 }14751476 // Sudo permissions function1477 #[weight = <T as Config>::WeightInfo::set_chain_limits()]1478 #[transactional]1479 pub fn set_chain_limits(1480 origin,1481 limits: ChainLimits1482 ) -> DispatchResult {14831484 #[cfg(not(feature = "runtime-benchmarks"))]1485 ensure_root(origin)?;14861487 <ChainLimit>::put(limits);1488 Ok(())1489 }14901491 /// Enable smart contract self-sponsoring.1492 /// 1493 /// # Permissions1494 /// 1495 /// * Contract Owner1496 /// 1497 /// # Arguments1498 /// 1499 /// * contract address1500 /// * enable flag1501 /// 1502 #[weight = <T as Config>::WeightInfo::enable_contract_sponsoring()]1503 #[transactional]1504 pub fn enable_contract_sponsoring(1505 origin,1506 contract_address: T::AccountId,1507 enable: bool1508 ) -> DispatchResult {15091510 let sender = ensure_signed(origin)?;15111512 #[cfg(feature = "runtime-benchmarks")]1513 <ContractOwner<T>>::insert(contract_address.clone(), sender.clone());15141515 Self::ensure_contract_owned(sender, &contract_address)?;15161517 <ContractSelfSponsoring<T>>::insert(contract_address, enable);1518 Ok(())1519 }15201521 /// Set the rate limit for contract sponsoring to specified number of blocks.1522 /// 1523 /// If not set (has the default value of 0 blocks), the sponsoring will be disabled. 1524 /// If set to the number B (for blocks), the transactions will be sponsored with a rate 1525 /// limit of B, i.e. fees for every transaction sent to this smart contract will be paid 1526 /// from contract endowment if there are at least B blocks between such transactions. 1527 /// Nonetheless, if transactions are sent more frequently, the fees are paid by the sender.1528 /// 1529 /// # Permissions1530 /// 1531 /// * Contract Owner1532 /// 1533 /// # Arguments1534 /// 1535 /// -`contract_address`: Address of the contract to sponsor1536 /// -`rate_limit`: Number of blocks to wait until the next sponsored transaction is allowed1537 /// 1538 #[weight = <T as Config>::WeightInfo::set_contract_sponsoring_rate_limit()]1539 #[transactional]1540 pub fn set_contract_sponsoring_rate_limit(1541 origin,1542 contract_address: T::AccountId,1543 rate_limit: T::BlockNumber1544 ) -> DispatchResult {1545 let sender = ensure_signed(origin)?;15461547 #[cfg(feature = "runtime-benchmarks")]1548 <ContractOwner<T>>::insert(contract_address.clone(), sender.clone());15491550 Self::ensure_contract_owned(sender, &contract_address)?;1551 <ContractSponsoringRateLimit<T>>::insert(contract_address, rate_limit);1552 Ok(())1553 }15541555 /// Enable the white list for a contract. Only addresses added to the white list with addToContractWhiteList will be able to call this smart contract.1556 /// 1557 /// # Permissions1558 /// 1559 /// * Address that deployed smart contract.1560 /// 1561 /// # Arguments1562 /// 1563 /// -`contract_address`: Address of the contract.1564 /// 1565 /// - `enable`: . 1566 #[weight = <T as Config>::WeightInfo::toggle_contract_white_list()]1567 #[transactional]1568 pub fn toggle_contract_white_list(1569 origin,1570 contract_address: T::AccountId,1571 enable: bool1572 ) -> DispatchResult {1573 let sender = ensure_signed(origin)?;15741575 #[cfg(feature = "runtime-benchmarks")]1576 <ContractOwner<T>>::insert(contract_address.clone(), sender.clone());15771578 Self::ensure_contract_owned(sender, &contract_address)?;1579 if enable {1580 <ContractWhiteListEnabled<T>>::insert(contract_address, true);1581 } else {1582 <ContractWhiteListEnabled<T>>::remove(contract_address);1583 }1584 Ok(())1585 }1586 1587 /// Add an address to smart contract white list.1588 /// 1589 /// # Permissions1590 /// 1591 /// * Address that deployed smart contract.1592 /// 1593 /// # Arguments1594 /// 1595 /// -`contract_address`: Address of the contract.1596 ///1597 /// -`account_address`: Address to add.1598 #[weight = <T as Config>::WeightInfo::add_to_contract_white_list()]1599 #[transactional]1600 pub fn add_to_contract_white_list(1601 origin,1602 contract_address: T::AccountId,1603 account_address: T::AccountId1604 ) -> DispatchResult {1605 let sender = ensure_signed(origin)?;16061607 #[cfg(feature = "runtime-benchmarks")]1608 <ContractOwner<T>>::insert(contract_address.clone(), sender.clone());1609 1610 Self::ensure_contract_owned(sender, &contract_address)?; 1611 <ContractWhiteList<T>>::insert(contract_address, account_address, true);1612 Ok(())1613 }16141615 /// Remove an address from smart contract white list.1616 /// 1617 /// # Permissions1618 /// 1619 /// * Address that deployed smart contract.1620 /// 1621 /// # Arguments1622 /// 1623 /// -`contract_address`: Address of the contract.1624 ///1625 /// -`account_address`: Address to remove.1626 #[weight = <T as Config>::WeightInfo::remove_from_contract_white_list()]1627 #[transactional]1628 pub fn remove_from_contract_white_list(1629 origin,1630 contract_address: T::AccountId,1631 account_address: T::AccountId1632 ) -> DispatchResult {1633 let sender = ensure_signed(origin)?;16341635 #[cfg(feature = "runtime-benchmarks")]1636 <ContractOwner<T>>::insert(contract_address.clone(), sender.clone());16371638 Self::ensure_contract_owned(sender, &contract_address)?;1639 <ContractWhiteList<T>>::remove(contract_address, account_address);1640 Ok(())1641 }16421643 #[weight = <T as Config>::WeightInfo::set_collection_limits()]1644 #[transactional]1645 pub fn set_collection_limits(1646 origin,1647 collection_id: u32,1648 new_limits: CollectionLimits<T::BlockNumber>,1649 ) -> DispatchResult {1650 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);1651 let mut target_collection = Self::get_collection(collection_id)?;1652 Self::check_owner_permissions(&target_collection, &sender)?;1653 let old_limits = &target_collection.limits;1654 let chain_limits = ChainLimit::get();16551656 // collection bounds1657 ensure!(new_limits.sponsor_transfer_timeout <= MAX_SPONSOR_TIMEOUT &&1658 new_limits.account_token_ownership_limit <= MAX_TOKEN_OWNERSHIP && 1659 new_limits.sponsored_data_size <= chain_limits.custom_data_limit,1660 Error::<T>::CollectionLimitBoundsExceeded);16611662 // token_limit check prev1663 ensure!(old_limits.token_limit >= new_limits.token_limit, Error::<T>::CollectionTokenLimitExceeded);1664 ensure!(new_limits.token_limit > 0, Error::<T>::CollectionTokenLimitExceeded);16651666 ensure!(1667 (old_limits.owner_can_transfer || !new_limits.owner_can_transfer) &&1668 (old_limits.owner_can_destroy || !new_limits.owner_can_destroy),1669 Error::<T>::OwnerPermissionsCantBeReverted,1670 );16711672 target_collection.limits = new_limits;1673 Self::save_collection(target_collection);16741675 Ok(())1676 } 1677 }1678}16791680impl<T: Config> Module<T> {16811682 pub fn transfer_internal(sender: T::CrossAccountId, recipient: T::CrossAccountId, target_collection: &CollectionHandle<T>, item_id: TokenId, value: u128) -> DispatchResult {1683 // Limits check1684 Self::is_correct_transfer(target_collection, &recipient)?;16851686 // Transfer permissions check1687 ensure!(Self::is_item_owner(&sender, target_collection, item_id) ||1688 Self::is_owner_or_admin_permissions(target_collection, &sender),1689 Error::<T>::NoPermission);16901691 if target_collection.access == AccessMode::WhiteList {1692 Self::check_white_list(target_collection, &sender)?;1693 Self::check_white_list(target_collection, &recipient)?;1694 }16951696 match target_collection.mode1697 {1698 CollectionMode::NFT => Self::transfer_nft(target_collection, item_id, sender.clone(), recipient.clone())?,1699 CollectionMode::Fungible(_) => Self::transfer_fungible(target_collection, value, &sender, &recipient)?,1700 CollectionMode::ReFungible => Self::transfer_refungible(target_collection, item_id, value, sender.clone(), recipient.clone())?,1701 _ => ()1702 };17031704 Self::deposit_event(RawEvent::Transfer(target_collection.id, item_id, sender, recipient, value));17051706 Ok(())1707 }17081709 pub fn approve_internal(1710 sender: T::CrossAccountId,1711 spender: T::CrossAccountId,1712 collection: &CollectionHandle<T>,1713 item_id: TokenId,1714 amount: u1281715 ) -> DispatchResult {1716 Self::token_exists(&collection, item_id)?;17171718 // Transfer permissions check1719 let bypasses_limits = collection.limits.owner_can_transfer &&1720 Self::is_owner_or_admin_permissions(1721 &collection,1722 &sender,1723 );17241725 let allowance_limit = if bypasses_limits {1726 None1727 } else if let Some(amount) = Self::owned_amount(1728 &sender,1729 &collection,1730 item_id,1731 ) {1732 Some(amount)1733 } else {1734 fail!(Error::<T>::NoPermission);1735 };17361737 if collection.access == AccessMode::WhiteList {1738 Self::check_white_list(&collection, &sender)?;1739 Self::check_white_list(&collection, &spender)?;1740 }17411742 let allowance: u128 = amount1743 .checked_add(<Allowances<T>>::get(collection.id, (item_id, sender.as_sub(), spender.as_sub())))1744 .ok_or(Error::<T>::NumOverflow)?;1745 if let Some(limit) = allowance_limit {1746 ensure!(limit >= allowance, Error::<T>::TokenValueTooLow);1747 }1748 <Allowances<T>>::insert(collection.id, (item_id, sender.as_sub(), spender.as_sub()), allowance);17491750 if matches!(collection.mode, CollectionMode::NFT) {1751 // TODO: NFT: only one owner may exist for token in ERC7211752 collection.log(1753 Vec::from([1754 eth::APPROVAL_NFT_TOPIC,1755 eth::address_to_topic(sender.as_eth()),1756 eth::address_to_topic(spender.as_eth()),1757 ]),1758 abi_encode!(uint256(item_id.into())),1759 );1760 }17611762 if matches!(collection.mode, CollectionMode::Fungible(_)) {1763 // TODO: NFT: only one owner may exist for token in ERC201764 collection.log(1765 Vec::from([1766 eth::APPROVAL_FUNGIBLE_TOPIC,1767 eth::address_to_topic(sender.as_eth()),1768 eth::address_to_topic(spender.as_eth()),1769 ]),1770 abi_encode!(uint256(allowance.into())),1771 );1772 }17731774 Self::deposit_event(RawEvent::Approved(collection.id, item_id, sender, spender, allowance));1775 Ok(())1776 }17771778 pub fn transfer_from_internal(1779 sender: T::CrossAccountId,1780 from: T::CrossAccountId,1781 recipient: T::CrossAccountId,1782 collection: &CollectionHandle<T>,1783 item_id: TokenId,1784 amount: u128,1785 ) -> DispatchResult {1786 // Check approval1787 let approval: u128 = <Allowances<T>>::get(collection.id, (item_id, from.as_sub(), sender.as_sub()));17881789 // Limits check1790 Self::is_correct_transfer(&collection, &recipient)?;17911792 // Transfer permissions check1793 ensure!(1794 approval >= amount || 1795 (1796 collection.limits.owner_can_transfer &&1797 Self::is_owner_or_admin_permissions(&collection, &sender)1798 ),1799 Error::<T>::NoPermission1800 );18011802 if collection.access == AccessMode::WhiteList {1803 Self::check_white_list(&collection, &sender)?;1804 Self::check_white_list(&collection, &recipient)?;1805 }18061807 // Reduce approval by transferred amount or remove if remaining approval drops to 01808 let allowance = approval.saturating_sub(amount);1809 if allowance > 0 {1810 <Allowances<T>>::insert(collection.id, (item_id, from.as_sub(), sender.as_sub()), allowance);1811 } else {1812 <Allowances<T>>::remove(collection.id, (item_id, from.as_sub(), sender.as_sub()));1813 }18141815 match collection.mode {1816 CollectionMode::NFT => {1817 Self::transfer_nft(&collection, item_id, from.clone(), recipient.clone())?1818 }1819 CollectionMode::Fungible(_) => {1820 Self::transfer_fungible(&collection, amount, &from, &recipient)?1821 }1822 CollectionMode::ReFungible => {1823 Self::transfer_refungible(&collection, item_id, amount, from.clone(), recipient.clone())?1824 }1825 _ => ()1826 };18271828 if matches!(collection.mode, CollectionMode::Fungible(_)) {1829 collection.log(1830 Vec::from([1831 eth::APPROVAL_FUNGIBLE_TOPIC,1832 eth::address_to_topic(from.as_eth()),1833 eth::address_to_topic(sender.as_eth()),1834 ]),1835 abi_encode!(uint256(allowance.into())),1836 );1837 }18381839 Ok(())1840 }18411842 pub fn set_variable_meta_data_internal(1843 sender: T::CrossAccountId,1844 collection: &CollectionHandle<T>, 1845 item_id: TokenId,1846 data: Vec<u8>,1847 ) -> DispatchResult {1848 Self::token_exists(&collection, item_id)?;18491850 ensure!(ChainLimit::get().custom_data_limit >= data.len() as u32, Error::<T>::TokenVariableDataLimitExceeded);18511852 // Modify permissions check1853 ensure!(Self::is_item_owner(&sender, &collection, item_id) ||1854 Self::is_owner_or_admin_permissions(&collection, &sender),1855 Error::<T>::NoPermission);18561857 match collection.mode1858 {1859 CollectionMode::NFT => Self::set_nft_variable_data(&collection, item_id, data)?,1860 CollectionMode::ReFungible => Self::set_re_fungible_variable_data(&collection, item_id, data)?,1861 CollectionMode::Fungible(_) => fail!(Error::<T>::CantStoreMetadataInFungibleTokens),1862 _ => fail!(Error::<T>::UnexpectedCollectionType)1863 };18641865 Ok(())1866 }18671868 pub fn create_multiple_items_internal(1869 sender: T::CrossAccountId,1870 collection: &CollectionHandle<T>,1871 owner: T::CrossAccountId,1872 items_data: Vec<CreateItemData>,1873 ) -> DispatchResult {1874 Self::can_create_items_in_collection(&collection, &sender, &owner, items_data.len() as u32)?;18751876 for data in &items_data {1877 Self::validate_create_item_args(&collection, data)?;1878 }1879 for data in &items_data {1880 Self::create_item_no_validation(&collection, owner.clone(), data.clone())?;1881 }18821883 Ok(())1884 }18851886 pub fn burn_item_internal(1887 sender: &T::CrossAccountId,1888 collection: &CollectionHandle<T>,1889 item_id: TokenId,1890 value: u128,1891 ) -> DispatchResult {1892 ensure!(1893 Self::is_item_owner(&sender, &collection, item_id) ||1894 (1895 collection.limits.owner_can_transfer &&1896 Self::is_owner_or_admin_permissions(&collection, &sender)1897 ),1898 Error::<T>::NoPermission1899 );19001901 if collection.access == AccessMode::WhiteList {1902 Self::check_white_list(&collection, &sender)?;1903 }19041905 match collection.mode1906 {1907 CollectionMode::NFT => Self::burn_nft_item(&collection, item_id)?,1908 CollectionMode::Fungible(_) => Self::burn_fungible_item(&sender, &collection, value)?,1909 CollectionMode::ReFungible => Self::burn_refungible_item(&collection, item_id, &sender)?,1910 _ => ()1911 };19121913 Ok(())1914 }19151916 fn is_correct_transfer(collection: &CollectionHandle<T>, recipient: &T::CrossAccountId) -> DispatchResult {1917 let collection_id = collection.id;19181919 // check token limit and account token limit1920 let account_items: u32 = <AddressTokens<T>>::get(collection_id, recipient.as_sub()).len() as u32;1921 ensure!(collection.limits.account_token_ownership_limit > account_items, Error::<T>::AccountTokenLimitExceeded);1922 1923 Ok(())1924 }19251926 fn can_create_items_in_collection(collection: &CollectionHandle<T>, sender: &T::CrossAccountId, owner: &T::CrossAccountId, amount: u32) -> DispatchResult {1927 let collection_id = collection.id;19281929 // check token limit and account token limit1930 let total_items: u32 = ItemListIndex::get(collection_id)1931 .checked_add(amount)1932 .ok_or(Error::<T>::CollectionTokenLimitExceeded)?;1933 let account_items: u32 = (<AddressTokens<T>>::get(collection_id, owner.as_sub()).len() as u32)1934 .checked_add(amount)1935 .ok_or(Error::<T>::AccountTokenLimitExceeded)?;1936 ensure!(collection.limits.token_limit >= total_items, Error::<T>::CollectionTokenLimitExceeded);1937 ensure!(collection.limits.account_token_ownership_limit >= account_items, Error::<T>::AccountTokenLimitExceeded);19381939 if !Self::is_owner_or_admin_permissions(collection, &sender) {1940 ensure!(collection.mint_mode == true, Error::<T>::PublicMintingNotAllowed);1941 Self::check_white_list(collection, owner)?;1942 Self::check_white_list(collection, sender)?;1943 }19441945 Ok(())1946 }19471948 fn validate_create_item_args(target_collection: &CollectionHandle<T>, data: &CreateItemData) -> DispatchResult {1949 match target_collection.mode1950 {1951 CollectionMode::NFT => {1952 if let CreateItemData::NFT(data) = data {1953 // check sizes1954 ensure!(ChainLimit::get().custom_data_limit >= data.const_data.len() as u32, Error::<T>::TokenConstDataLimitExceeded);1955 ensure!(ChainLimit::get().custom_data_limit >= data.variable_data.len() as u32, Error::<T>::TokenVariableDataLimitExceeded);1956 } else {1957 fail!(Error::<T>::NotNftDataUsedToMintNftCollectionToken);1958 }1959 },1960 CollectionMode::Fungible(_) => {1961 if let CreateItemData::Fungible(_) = data {1962 } else {1963 fail!(Error::<T>::NotFungibleDataUsedToMintFungibleCollectionToken);1964 }1965 },1966 CollectionMode::ReFungible => {1967 if let CreateItemData::ReFungible(data) = data {19681969 // check sizes1970 ensure!(ChainLimit::get().custom_data_limit >= data.const_data.len() as u32, Error::<T>::TokenConstDataLimitExceeded);1971 ensure!(ChainLimit::get().custom_data_limit >= data.variable_data.len() as u32, Error::<T>::TokenVariableDataLimitExceeded);19721973 // Check refungibility limits1974 ensure!(data.pieces <= MAX_REFUNGIBLE_PIECES, Error::<T>::WrongRefungiblePieces);1975 ensure!(data.pieces > 0, Error::<T>::WrongRefungiblePieces);1976 } else {1977 fail!(Error::<T>::NotReFungibleDataUsedToMintReFungibleCollectionToken);1978 }1979 },1980 _ => { fail!(Error::<T>::UnexpectedCollectionType); }1981 };19821983 Ok(())1984 }19851986 fn create_item_no_validation(collection: &CollectionHandle<T>, owner: T::CrossAccountId, data: CreateItemData) -> DispatchResult {1987 match data1988 {1989 CreateItemData::NFT(data) => {1990 let item = NftItemType {1991 owner: owner.clone(),1992 const_data: data.const_data,1993 variable_data: data.variable_data1994 };19951996 Self::add_nft_item(collection, item)?;1997 },1998 CreateItemData::Fungible(data) => {1999 Self::add_fungible_item(collection, &owner, data.value)?;2000 },2001 CreateItemData::ReFungible(data) => {2002 let mut owner_list = Vec::new();2003 owner_list.push(Ownership {owner: owner.clone(), fraction: data.pieces});20042005 let item = ReFungibleItemType {2006 owner: owner_list,2007 const_data: data.const_data,2008 variable_data: data.variable_data2009 };20102011 Self::add_refungible_item(collection, item)?;2012 }2013 };20142015 Ok(())2016 }20172018 fn add_fungible_item(collection: &CollectionHandle<T>, owner: &T::CrossAccountId, value: u128) -> DispatchResult {2019 let collection_id = collection.id;20202021 // Does new owner already have an account?2022 let balance: u128 = <FungibleItemList<T>>::get(collection_id, owner.as_sub()).value;20232024 // Mint 2025 let item = FungibleItemType {2026 value: balance.checked_add(value).ok_or(Error::<T>::NumOverflow)?,2027 };2028 <FungibleItemList<T>>::insert(collection_id, owner.as_sub(), item);20292030 // Update balance2031 let new_balance = <Balance<T>>::get(collection_id, owner.as_sub())2032 .checked_add(value)2033 .ok_or(Error::<T>::NumOverflow)?;2034 <Balance<T>>::insert(collection_id, owner.as_sub(), new_balance);20352036 Self::deposit_event(RawEvent::ItemCreated(collection_id, 0, owner.clone()));2037 Ok(())2038 }20392040 fn add_refungible_item(collection: &CollectionHandle<T>, item: ReFungibleItemType<T::CrossAccountId>) -> DispatchResult {2041 let collection_id = collection.id;20422043 let current_index = <ItemListIndex>::get(collection_id)2044 .checked_add(1)2045 .ok_or(Error::<T>::NumOverflow)?;2046 let itemcopy = item.clone();20472048 ensure!(2049 item.owner.len() == 1,2050 Error::<T>::BadCreateRefungibleCall,2051 );2052 let item_owner = item.owner.first().expect("only one owner is defined");20532054 let value = item_owner.fraction;2055 let owner = item_owner.owner.clone();20562057 Self::add_token_index(collection_id, current_index, &owner)?;20582059 <ItemListIndex>::insert(collection_id, current_index);2060 <ReFungibleItemList<T>>::insert(collection_id, current_index, itemcopy);20612062 // Update balance2063 let new_balance = <Balance<T>>::get(collection_id, owner.as_sub())2064 .checked_add(value)2065 .ok_or(Error::<T>::NumOverflow)?;2066 <Balance<T>>::insert(collection_id, owner.as_sub(), new_balance);20672068 Self::deposit_event(RawEvent::ItemCreated(collection_id, current_index, owner));2069 Ok(())2070 }20712072 fn add_nft_item(collection: &CollectionHandle<T>, item: NftItemType<T::CrossAccountId>) -> DispatchResult {2073 let collection_id = collection.id;20742075 let current_index = <ItemListIndex>::get(collection_id)2076 .checked_add(1)2077 .ok_or(Error::<T>::NumOverflow)?;20782079 let item_owner = item.owner.clone();2080 Self::add_token_index(collection_id, current_index, &item.owner)?;20812082 <ItemListIndex>::insert(collection_id, current_index);2083 <NftItemList<T>>::insert(collection_id, current_index, item);20842085 // Update balance2086 let new_balance = <Balance<T>>::get(collection_id, item_owner.as_sub())2087 .checked_add(1)2088 .ok_or(Error::<T>::NumOverflow)?;2089 <Balance<T>>::insert(collection_id, item_owner.as_sub(), new_balance);20902091 collection.log(2092 Vec::from([2093 eth::TRANSFER_NFT_TOPIC,2094 eth::address_to_topic(&H160::default()),2095 eth::address_to_topic(item_owner.as_eth()),2096 ]),2097 abi_encode!(uint256(current_index.into())),2098 );2099 Self::deposit_event(RawEvent::ItemCreated(collection_id, current_index, item_owner));2100 Ok(())2101 }21022103 fn burn_refungible_item(2104 collection: &CollectionHandle<T>,2105 item_id: TokenId,2106 owner: &T::CrossAccountId,2107 ) -> DispatchResult {2108 let collection_id = collection.id;21092110 let mut token = <ReFungibleItemList<T>>::get(collection_id, item_id)2111 .ok_or(Error::<T>::TokenNotFound)?;2112 let rft_balance = token2113 .owner2114 .iter()2115 .find(|&i| i.owner == *owner)2116 .ok_or(Error::<T>::TokenNotFound)?;2117 Self::remove_token_index(collection_id, item_id, owner)?;21182119 // update balance2120 let new_balance = <Balance<T>>::get(collection_id, rft_balance.owner.as_sub())2121 .checked_sub(rft_balance.fraction)2122 .ok_or(Error::<T>::NumOverflow)?;2123 <Balance<T>>::insert(collection_id, rft_balance.owner.as_sub(), new_balance);21242125 // Re-create owners list with sender removed2126 let index = token2127 .owner2128 .iter()2129 .position(|i| i.owner == *owner)2130 .expect("owned item is exists");2131 token.owner.remove(index);2132 let owner_count = token.owner.len();21332134 // Burn the token completely if this was the last (only) owner2135 if owner_count == 0 {2136 <ReFungibleItemList<T>>::remove(collection_id, item_id);2137 <VariableMetaDataBasket<T>>::remove(collection_id, item_id);2138 }2139 else {2140 <ReFungibleItemList<T>>::insert(collection_id, item_id, token);2141 }21422143 Ok(())2144 }21452146 fn burn_nft_item(collection: &CollectionHandle<T>, item_id: TokenId) -> DispatchResult {2147 let collection_id = collection.id;21482149 let item = <NftItemList<T>>::get(collection_id, item_id)2150 .ok_or(Error::<T>::TokenNotFound)?;2151 Self::remove_token_index(collection_id, item_id, &item.owner)?;21522153 // update balance2154 let new_balance = <Balance<T>>::get(collection_id, item.owner.as_sub())2155 .checked_sub(1)2156 .ok_or(Error::<T>::NumOverflow)?;2157 <Balance<T>>::insert(collection_id, item.owner.as_sub(), new_balance);2158 <NftItemList<T>>::remove(collection_id, item_id);2159 <VariableMetaDataBasket<T>>::remove(collection_id, item_id);21602161 Self::deposit_event(RawEvent::ItemDestroyed(collection.id, item_id));2162 Ok(())2163 }21642165 fn burn_fungible_item(owner: &T::CrossAccountId, collection: &CollectionHandle<T>, value: u128) -> DispatchResult {2166 let collection_id = collection.id;21672168 let mut balance = <FungibleItemList<T>>::get(collection_id, owner.as_sub());2169 ensure!(balance.value >= value, Error::<T>::TokenValueNotEnough);21702171 // update balance2172 let new_balance = <Balance<T>>::get(collection_id, owner.as_sub())2173 .checked_sub(value)2174 .ok_or(Error::<T>::NumOverflow)?;2175 <Balance<T>>::insert(collection_id, owner.as_sub(), new_balance);21762177 if balance.value - value > 0 {2178 balance.value -= value;2179 <FungibleItemList<T>>::insert(collection_id, owner.as_sub(), balance);2180 }2181 else {2182 <FungibleItemList<T>>::remove(collection_id, owner.as_sub());2183 }21842185 collection.log(2186 Vec::from([2187 eth::TRANSFER_FUNGIBLE_TOPIC,2188 eth::address_to_topic(owner.as_eth()),2189 eth::address_to_topic(&H160::default()),2190 ]),2191 abi_encode!(uint256(value.into())),2192 );2193 Ok(())2194 }21952196 pub fn get_collection(collection_id: CollectionId) -> Result<CollectionHandle<T>, sp_runtime::DispatchError> {2197 Ok(<CollectionHandle<T>>::get(collection_id)2198 .ok_or(Error::<T>::CollectionNotFound)?)2199 }22002201 fn save_collection(collection: CollectionHandle<T>) {2202 <CollectionById<T>>::insert(collection.id, collection.into_inner());2203 }22042205 fn submit_logs(collection: CollectionHandle<T>) -> DispatchResult {2206 T::EthereumTransactionSender::submit_logs_transaction(2207 eth::generate_transaction(collection.id, T::EthereumChainId::get()),2208 collection.logs.retrieve_logs_for_contract(eth::collection_id_to_address(collection.id)),2209 )2210 }22112212 fn check_owner_permissions(target_collection: &CollectionHandle<T>, subject: &T::CrossAccountId) -> DispatchResult {2213 ensure!(2214 *subject == target_collection.owner,2215 Error::<T>::NoPermission2216 );22172218 Ok(())2219 }22202221 fn is_owner_or_admin_permissions(collection: &CollectionHandle<T>, subject: &T::CrossAccountId) -> bool {2222 *subject == collection.owner || <AdminList<T>>::get(collection.id).contains(&subject)2223 }22242225 fn check_owner_or_admin_permissions(2226 collection: &CollectionHandle<T>,2227 subject: &T::CrossAccountId,2228 ) -> DispatchResult {2229 ensure!(Self::is_owner_or_admin_permissions(collection, subject), Error::<T>::NoPermission);22302231 Ok(())2232 }22332234 fn owned_amount(2235 subject: &T::CrossAccountId,2236 target_collection: &CollectionHandle<T>,2237 item_id: TokenId,2238 ) -> Option<u128> {2239 let collection_id = target_collection.id;22402241 match target_collection.mode {2242 CollectionMode::NFT => (<NftItemList<T>>::get(collection_id, item_id)?.owner == *subject)2243 .then(|| 1),2244 CollectionMode::Fungible(_) => Some(<FungibleItemList<T>>::get(collection_id, &subject.as_sub())2245 .value),2246 CollectionMode::ReFungible => <ReFungibleItemList<T>>::get(collection_id, item_id)?2247 .owner2248 .iter()2249 .find(|i| i.owner == *subject)2250 .map(|i| i.fraction),2251 CollectionMode::Invalid => None,2252 }2253 }22542255 fn is_item_owner(subject: &T::CrossAccountId, target_collection: &CollectionHandle<T>, item_id: TokenId) -> bool {2256 match target_collection.mode {2257 CollectionMode::Fungible(_) => true,2258 _ => Self::owned_amount(&subject, target_collection, item_id).is_some(),2259 }2260 }22612262 fn check_white_list(collection: &CollectionHandle<T>, address: &T::CrossAccountId) -> DispatchResult {2263 let collection_id = collection.id;22642265 let mes = Error::<T>::AddresNotInWhiteList;2266 ensure!(<WhiteList<T>>::contains_key(collection_id, address.as_sub()), mes);22672268 Ok(())2269 }22702271 /// Check if token exists. In case of Fungible, check if there is an entry for 2272 /// the owner in fungible balances double map2273 fn token_exists(2274 target_collection: &CollectionHandle<T>,2275 item_id: TokenId,2276 ) -> DispatchResult {2277 let collection_id = target_collection.id;2278 let exists = match target_collection.mode2279 {2280 CollectionMode::NFT => <NftItemList<T>>::contains_key(collection_id, item_id),2281 CollectionMode::Fungible(_) => true,2282 CollectionMode::ReFungible => <ReFungibleItemList<T>>::contains_key(collection_id, item_id),2283 _ => false2284 };22852286 ensure!(exists == true, Error::<T>::TokenNotFound);2287 Ok(())2288 }22892290 fn transfer_fungible(2291 collection: &CollectionHandle<T>,2292 value: u128,2293 owner: &T::CrossAccountId,2294 recipient: &T::CrossAccountId,2295 ) -> DispatchResult {2296 let collection_id = collection.id;22972298 let mut balance = <FungibleItemList<T>>::get(collection_id, owner.as_sub());2299 ensure!(balance.value >= value, Error::<T>::TokenValueTooLow);23002301 // Send balance to recipient (updates balanceOf of recipient)2302 Self::add_fungible_item(collection, recipient, value)?;23032304 // update balanceOf of sender2305 <Balance<T>>::insert(collection_id, owner.as_sub(), balance.value - value);23062307 // Reduce or remove sender2308 if balance.value == value {2309 <FungibleItemList<T>>::remove(collection_id, owner.as_sub());2310 }2311 else {2312 balance.value -= value;2313 <FungibleItemList<T>>::insert(collection_id, owner.as_sub(), balance);2314 }23152316 collection.log(2317 Vec::from([2318 eth::TRANSFER_FUNGIBLE_TOPIC,2319 eth::address_to_topic(owner.as_eth()),2320 eth::address_to_topic(recipient.as_eth()),2321 ]),2322 abi_encode!(uint256(value.into())),2323 );2324 Self::deposit_event(RawEvent::Transfer(collection.id, 1, owner.clone(), recipient.clone(), value));23252326 Ok(())2327 }23282329 fn transfer_refungible(2330 collection: &CollectionHandle<T>,2331 item_id: TokenId,2332 value: u128,2333 owner: T::CrossAccountId,2334 new_owner: T::CrossAccountId,2335 ) -> DispatchResult {2336 let collection_id = collection.id;2337 let full_item = <ReFungibleItemList<T>>::get(collection_id, item_id)2338 .ok_or(Error::<T>::TokenNotFound)?;23392340 let item = full_item2341 .owner2342 .iter()2343 .filter(|i| i.owner == owner)2344 .next()2345 .ok_or(Error::<T>::TokenNotFound)?;2346 let amount = item.fraction;23472348 ensure!(amount >= value, Error::<T>::TokenValueTooLow);23492350 // update balance2351 let balance_old_owner = <Balance<T>>::get(collection_id, item.owner.as_sub())2352 .checked_sub(value)2353 .ok_or(Error::<T>::NumOverflow)?;2354 <Balance<T>>::insert(collection_id, item.owner.as_sub(), balance_old_owner);23552356 let balance_new_owner = <Balance<T>>::get(collection_id, new_owner.as_sub())2357 .checked_add(value)2358 .ok_or(Error::<T>::NumOverflow)?;2359 <Balance<T>>::insert(collection_id, new_owner.as_sub(), balance_new_owner);23602361 let old_owner = item.owner.clone();2362 let new_owner_has_account = full_item.owner.iter().any(|i| i.owner == new_owner);23632364 // transfer2365 if amount == value && !new_owner_has_account {2366 // change owner2367 // new owner do not have account2368 let mut new_full_item = full_item.clone();2369 new_full_item2370 .owner2371 .iter_mut()2372 .find(|i| i.owner == owner)2373 .expect("old owner does present in refungible")2374 .owner = new_owner.clone();2375 <ReFungibleItemList<T>>::insert(collection_id, item_id, new_full_item);23762377 // update index collection2378 Self::move_token_index(collection_id, item_id, &old_owner, &new_owner)?;2379 } else {2380 let mut new_full_item = full_item.clone();2381 new_full_item2382 .owner2383 .iter_mut()2384 .find(|i| i.owner == owner)2385 .expect("old owner does present in refungible")2386 .fraction -= value;23872388 // separate amount2389 if new_owner_has_account {2390 // new owner has account2391 new_full_item2392 .owner2393 .iter_mut()2394 .find(|i| i.owner == new_owner)2395 .expect("new owner has account")2396 .fraction += value;2397 } else {2398 // new owner do not have account2399 new_full_item.owner.push(Ownership {2400 owner: new_owner.clone(),2401 fraction: value,2402 });2403 Self::add_token_index(collection_id, item_id, &new_owner)?;2404 }24052406 <ReFungibleItemList<T>>::insert(collection_id, item_id, new_full_item);2407 }24082409 Self::deposit_event(RawEvent::Transfer(collection.id, item_id, owner, new_owner, amount));24102411 Ok(())2412 }24132414 fn transfer_nft(2415 collection: &CollectionHandle<T>,2416 item_id: TokenId,2417 sender: T::CrossAccountId,2418 new_owner: T::CrossAccountId,2419 ) -> DispatchResult {2420 let collection_id = collection.id;2421 let mut item = <NftItemList<T>>::get(collection_id, item_id)2422 .ok_or(Error::<T>::TokenNotFound)?;24232424 ensure!(2425 sender == item.owner,2426 Error::<T>::MustBeTokenOwner2427 );24282429 // update balance2430 let balance_old_owner = <Balance<T>>::get(collection_id, item.owner.as_sub())2431 .checked_sub(1)2432 .ok_or(Error::<T>::NumOverflow)?;2433 <Balance<T>>::insert(collection_id, item.owner.as_sub(), balance_old_owner);24342435 let balance_new_owner = <Balance<T>>::get(collection_id, new_owner.as_sub())2436 .checked_add(1)2437 .ok_or(Error::<T>::NumOverflow)?;2438 <Balance<T>>::insert(collection_id, new_owner.as_sub(), balance_new_owner);24392440 // change owner2441 let old_owner = item.owner.clone();2442 item.owner = new_owner.clone();2443 <NftItemList<T>>::insert(collection_id, item_id, item);24442445 // update index collection2446 Self::move_token_index(collection_id, item_id, &old_owner, &new_owner)?;24472448 collection.log(2449 Vec::from([2450 eth::TRANSFER_NFT_TOPIC,2451 eth::address_to_topic(sender.as_eth()),2452 eth::address_to_topic(new_owner.as_eth()),2453 ]),2454 abi_encode!(uint256(item_id.into())),2455 );2456 Self::deposit_event(RawEvent::Transfer(collection.id, item_id, sender, new_owner, 1));24572458 Ok(())2459 }2460 2461 fn set_re_fungible_variable_data(2462 collection: &CollectionHandle<T>,2463 item_id: TokenId,2464 data: Vec<u8>2465 ) -> DispatchResult {2466 let collection_id = collection.id;2467 let mut item = <ReFungibleItemList<T>>::get(collection_id, item_id)2468 .ok_or(Error::<T>::TokenNotFound)?;24692470 item.variable_data = data;24712472 <ReFungibleItemList<T>>::insert(collection_id, item_id, item);24732474 Ok(())2475 }24762477 fn set_nft_variable_data(2478 collection: &CollectionHandle<T>,2479 item_id: TokenId,2480 data: Vec<u8>2481 ) -> DispatchResult {2482 let collection_id = collection.id;2483 let mut item = <NftItemList<T>>::get(collection_id, item_id)2484 .ok_or(Error::<T>::TokenNotFound)?;2485 2486 item.variable_data = data;24872488 <NftItemList<T>>::insert(collection_id, item_id, item);2489 2490 Ok(())2491 }24922493 fn init_collection(item: &Collection<T>) {2494 // check params2495 assert!(2496 item.decimal_points <= MAX_DECIMAL_POINTS,2497 "decimal_points parameter must be lower than MAX_DECIMAL_POINTS"2498 );2499 assert!(2500 item.name.len() <= 64,2501 "Collection name can not be longer than 63 char"2502 );2503 assert!(2504 item.name.len() <= 256,2505 "Collection description can not be longer than 255 char"2506 );2507 assert!(2508 item.token_prefix.len() <= 16,2509 "Token prefix can not be longer than 15 char"2510 );25112512 // Generate next collection ID2513 let next_id = CreatedCollectionCount::get()2514 .checked_add(1)2515 .unwrap();25162517 CreatedCollectionCount::put(next_id);2518 }25192520 fn init_nft_token(collection_id: CollectionId, item: &NftItemType<T::CrossAccountId>) {2521 let current_index = <ItemListIndex>::get(collection_id)2522 .checked_add(1)2523 .unwrap();25242525 Self::add_token_index(collection_id, current_index, &item.owner).unwrap();25262527 <ItemListIndex>::insert(collection_id, current_index);25282529 // Update balance2530 let new_balance = <Balance<T>>::get(collection_id, item.owner.as_sub())2531 .checked_add(1)2532 .unwrap();2533 <Balance<T>>::insert(collection_id, item.owner.as_sub(), new_balance);2534 }25352536 fn init_fungible_token(collection_id: CollectionId, owner: &T::CrossAccountId, item: &FungibleItemType) {2537 let current_index = <ItemListIndex>::get(collection_id)2538 .checked_add(1)2539 .unwrap();25402541 Self::add_token_index(collection_id, current_index, owner).unwrap();25422543 <ItemListIndex>::insert(collection_id, current_index);25442545 // Update balance2546 let new_balance = <Balance<T>>::get(collection_id, owner.as_sub())2547 .checked_add(item.value)2548 .unwrap();2549 <Balance<T>>::insert(collection_id, owner.as_sub(), new_balance);2550 }25512552 fn init_refungible_token(collection_id: CollectionId, item: &ReFungibleItemType<T::CrossAccountId>) {2553 let current_index = <ItemListIndex>::get(collection_id)2554 .checked_add(1)2555 .unwrap();25562557 let value = item.owner.first().unwrap().fraction;2558 let owner = item.owner.first().unwrap().owner.clone();25592560 Self::add_token_index(collection_id, current_index, &owner).unwrap();25612562 <ItemListIndex>::insert(collection_id, current_index);25632564 // Update balance2565 let new_balance = <Balance<T>>::get(collection_id, &owner.as_sub())2566 .checked_add(value)2567 .unwrap();2568 <Balance<T>>::insert(collection_id, owner.as_sub(), new_balance);2569 }25702571 fn add_token_index(collection_id: CollectionId, item_index: TokenId, owner: &T::CrossAccountId) -> DispatchResult {2572 // add to account limit2573 if <AccountItemCount<T>>::contains_key(owner.as_sub()) {25742575 // bound Owned tokens by a single address2576 let count = <AccountItemCount<T>>::get(owner.as_sub());2577 ensure!(count < ChainLimit::get().account_token_ownership_limit, Error::<T>::AddressOwnershipLimitExceeded);25782579 <AccountItemCount<T>>::insert(owner.as_sub(), count2580 .checked_add(1)2581 .ok_or(Error::<T>::NumOverflow)?);2582 }2583 else {2584 <AccountItemCount<T>>::insert(owner.as_sub(), 1);2585 }25862587 let list_exists = <AddressTokens<T>>::contains_key(collection_id, owner.as_sub());2588 if list_exists {2589 let mut list = <AddressTokens<T>>::get(collection_id, owner.as_sub());2590 let item_contains = list.contains(&item_index.clone());25912592 if !item_contains {2593 list.push(item_index.clone());2594 }25952596 <AddressTokens<T>>::insert(collection_id, owner.as_sub(), list);2597 } else {2598 let mut itm = Vec::new();2599 itm.push(item_index.clone());2600 <AddressTokens<T>>::insert(collection_id, owner.as_sub(), itm);2601 }26022603 Ok(())2604 }26052606 fn remove_token_index(2607 collection_id: CollectionId,2608 item_index: TokenId,2609 owner: &T::CrossAccountId,2610 ) -> DispatchResult {26112612 // update counter2613 <AccountItemCount<T>>::insert(owner.as_sub(), 2614 <AccountItemCount<T>>::get(owner.as_sub())2615 .checked_sub(1)2616 .ok_or(Error::<T>::NumOverflow)?);261726182619 let list_exists = <AddressTokens<T>>::contains_key(collection_id, owner.as_sub());2620 if list_exists {2621 let mut list = <AddressTokens<T>>::get(collection_id, owner.as_sub());2622 let item_contains = list.contains(&item_index.clone());26232624 if item_contains {2625 list.retain(|&item| item != item_index);2626 <AddressTokens<T>>::insert(collection_id, owner.as_sub(), list);2627 }2628 }26292630 Ok(())2631 }26322633 fn move_token_index(2634 collection_id: CollectionId,2635 item_index: TokenId,2636 old_owner: &T::CrossAccountId,2637 new_owner: &T::CrossAccountId,2638 ) -> DispatchResult {2639 Self::remove_token_index(collection_id, item_index, old_owner)?;2640 Self::add_token_index(collection_id, item_index, new_owner)?;26412642 Ok(())2643 }2644 2645 fn ensure_contract_owned(account: T::AccountId, contract: &T::AccountId) -> DispatchResult {2646 ensure!(<ContractOwner<T>>::get(contract) == Some(account), Error::<T>::NoPermission);26472648 Ok(())2649 }2650}26512652////////////////////////////////////////////////////////////////////////////////////////////////////2653// Economic models2654// #region26552656/// Fee multiplier.2657pub type Multiplier = FixedU128;26582659type BalanceOf<T> = <<T as pallet_transaction_payment::Config>::OnChargeTransaction as pallet_transaction_payment::OnChargeTransaction<T>>::Balance;26602661/// Require the transactor pay for themselves and maybe include a tip to gain additional priority2662/// in the queue.2663#[derive(Encode, Decode, Clone, Eq, PartialEq)]2664pub struct ChargeTransactionPayment<T: Config>(#[codec(compact)] BalanceOf<T>);26652666impl<T: Config + Send + Sync> sp_std::fmt::Debug 2667 for ChargeTransactionPayment<T>2668{2669 #[cfg(feature = "std")]2670 fn fmt(&self, f: &mut sp_std::fmt::Formatter) -> sp_std::fmt::Result {2671 write!(f, "ChargeTransactionPayment<{:?}>", self.0)2672 }2673 #[cfg(not(feature = "std"))]2674 fn fmt(&self, _: &mut sp_std::fmt::Formatter) -> sp_std::fmt::Result {2675 Ok(())2676 }2677}26782679impl<T: Config> ChargeTransactionPayment<T>2680where2681 T::Call: Dispatchable<Info=DispatchInfo, PostInfo=PostDispatchInfo> + IsSubType<Call<T>> + IsSubType<pallet_contracts::Call<T>>,2682 BalanceOf<T>: Send + Sync + From<u64> + FixedPointOperand,2683 T::AccountId: AsRef<[u8]>,2684 T::AccountId: UncheckedFrom<T::Hash>,2685{2686 fn traditional_fee(2687 len: usize,2688 info: &DispatchInfoOf<T::Call>,2689 tip: BalanceOf<T>,2690 ) -> BalanceOf<T>2691 where2692 T::Call: Dispatchable<Info = DispatchInfo>,2693 {2694 <pallet_transaction_payment::Module<T>>::compute_fee(len as u32, info, tip)2695 }26962697 fn get_priority(len: usize, info: &DispatchInfoOf<T::Call>, final_fee: BalanceOf<T>) -> TransactionPriority {2698 let weight_saturation = T::BlockWeights::get().max_block / info.weight.max(1);2699 let max_block_length = *T::BlockLength::get().max.get(DispatchClass::Normal);2700 let len_saturation = max_block_length as u64 / (len as u64).max(1);2701 let coefficient: BalanceOf<T> = weight_saturation2702 .min(len_saturation)2703 .saturated_into::<BalanceOf<T>>();2704 final_fee2705 .saturating_mul(coefficient)2706 .saturated_into::<TransactionPriority>()2707 }27082709 fn withdraw_fee(2710 &self,2711 who: &T::AccountId,2712 call: &T::Call,2713 info: &DispatchInfoOf<T::Call>,2714 len: usize,2715 ) -> Result<2716 (2717 BalanceOf<T>,2718 <<T as pallet_transaction_payment::Config>::OnChargeTransaction as pallet_transaction_payment::OnChargeTransaction<T>>::LiquidityInfo,2719 ),2720 TransactionValidityError,2721 > {2722 let tip = self.0;27232724 let fee = Self::traditional_fee(len, info, tip);27252726 // Only mess with balances if fee is not zero.2727 if fee.is_zero() {2728 return <<T as pallet_transaction_payment::Config>::OnChargeTransaction as pallet_transaction_payment::OnChargeTransaction<T>>::withdraw_fee(who, call, info, fee, tip)2729 .map(|i| (fee, i));2730 }27312732 // Determine who is paying transaction fee based on ecnomic model2733 // Parse call to extract collection ID and access collection sponsor2734 let mut sponsor: Option<T::AccountId> = (|| match IsSubType::<Call<T>>::is_sub_type(call) {2735 Some(Call::create_item(collection_id, _owner, _properties)) => {2736 let collection = <CollectionById<T>>::get(collection_id)?;27372738 // sponsor timeout2739 let block_number = <system::Module<T>>::block_number() as T::BlockNumber;27402741 let limit = collection.limits.sponsor_transfer_timeout;2742 if <CreateItemBasket<T>>::contains_key((collection_id, &who)) {2743 let last_tx_block = <CreateItemBasket<T>>::get((collection_id, &who));2744 let limit_time = last_tx_block + limit.into();2745 if block_number <= limit_time {2746 return None;2747 }2748 }2749 <CreateItemBasket<T>>::insert((collection_id, who.clone()), block_number);27502751 // check free create limit2752 if collection.limits.sponsored_data_size >= (_properties.len() as u32) {2753 collection.sponsorship.sponsor()2754 .cloned()2755 } else {2756 None2757 }2758 }2759 Some(Call::transfer(_new_owner, collection_id, item_id, _value)) => {2760 let collection = <CollectionById<T>>::get(collection_id)?;2761 2762 let mut sponsor_transfer = false;2763 if collection.sponsorship.confirmed() {27642765 let collection_limits = collection.limits;2766 let collection_mode = collection.mode;2767 2768 // sponsor timeout2769 let block_number = <system::Module<T>>::block_number() as T::BlockNumber;2770 sponsor_transfer = match collection_mode {2771 CollectionMode::NFT => {2772 2773 // get correct limit2774 let limit: u32 = if collection_limits.sponsor_transfer_timeout > 0 {2775 collection_limits.sponsor_transfer_timeout2776 } else {2777 ChainLimit::get().nft_sponsor_transfer_timeout2778 };2779 2780 let mut sponsored = true;2781 if <NftTransferBasket<T>>::contains_key(collection_id, item_id) {2782 let last_tx_block = <NftTransferBasket<T>>::get(collection_id, item_id);2783 let limit_time = last_tx_block + limit.into();2784 if block_number <= limit_time {2785 sponsored = false;2786 }2787 }2788 if sponsored {2789 <NftTransferBasket<T>>::insert(collection_id, item_id, block_number);2790 }27912792 sponsored2793 }2794 CollectionMode::Fungible(_) => {2795 2796 // get correct limit2797 let limit: u32 = if collection_limits.sponsor_transfer_timeout > 0 {2798 collection_limits.sponsor_transfer_timeout2799 } else {2800 ChainLimit::get().fungible_sponsor_transfer_timeout2801 };2802 2803 let block_number = <system::Module<T>>::block_number() as T::BlockNumber;2804 let mut sponsored = true;2805 if <FungibleTransferBasket<T>>::contains_key(collection_id, who) {2806 let last_tx_block = <FungibleTransferBasket<T>>::get(collection_id, who);2807 let limit_time = last_tx_block + limit.into();2808 if block_number <= limit_time {2809 sponsored = false;2810 }2811 }2812 if sponsored {2813 <FungibleTransferBasket<T>>::insert(collection_id, who, block_number);2814 }28152816 sponsored2817 }2818 CollectionMode::ReFungible => {2819 2820 // get correct limit2821 let limit: u32 = if collection_limits.sponsor_transfer_timeout > 0 {2822 collection_limits.sponsor_transfer_timeout2823 } else {2824 ChainLimit::get().refungible_sponsor_transfer_timeout2825 };2826 2827 let mut sponsored = true;2828 if <ReFungibleTransferBasket<T>>::contains_key(collection_id, item_id) {2829 let last_tx_block = <ReFungibleTransferBasket<T>>::get(collection_id, item_id);2830 let limit_time = last_tx_block + limit.into();2831 if block_number <= limit_time {2832 sponsored = false;2833 }2834 }2835 if sponsored {2836 <ReFungibleTransferBasket<T>>::insert(collection_id, item_id, block_number);2837 }28382839 sponsored2840 }2841 _ => {2842 false2843 },2844 };2845 }28462847 if !sponsor_transfer {2848 None2849 } else {2850 collection.sponsorship.sponsor()2851 .cloned()2852 }2853 }28542855 Some(Call::set_variable_meta_data(collection_id, item_id, data)) => {2856 let mut sponsor_metadata_changes = false;28572858 let collection = <CollectionById<T>>::get(collection_id)?;28592860 if2861 collection.sponsorship.confirmed() &&2862 // Can't sponsor fungible collection, this tx will be rejected2863 // as invalid2864 !matches!(collection.mode, CollectionMode::Fungible(_)) &&2865 data.len() <= collection.limits.sponsored_data_size as usize2866 {2867 if let Some(rate_limit) = collection.limits.sponsored_data_rate_limit {2868 let block_number = <system::Module<T>>::block_number() as T::BlockNumber;28692870 if <VariableMetaDataBasket<T>>::get(collection_id, item_id)2871 .map(|last_block| block_number - last_block > rate_limit)2872 .unwrap_or(true) 2873 {2874 sponsor_metadata_changes = true;2875 <VariableMetaDataBasket<T>>::insert(collection_id, item_id, block_number);2876 }2877 }2878 }28792880 if !sponsor_metadata_changes {2881 None2882 } else {2883 collection.sponsorship.sponsor().cloned()2884 }2885 }28862887 _ => None,2888 })();28892890 match IsSubType::<pallet_contracts::Call<T>>::is_sub_type(call) {2891 Some(pallet_contracts::Call::call(dest, _value, _gas_limit, _data)) => {28922893 let called_contract: T::AccountId = T::Lookup::lookup((*dest).clone()).unwrap_or(T::AccountId::default());28942895 let owned_contract = <ContractOwner<T>>::get(called_contract.clone()).as_ref() == Some(who);2896 let white_list_enabled = <ContractWhiteListEnabled<T>>::contains_key(called_contract.clone());2897 2898 if !owned_contract && white_list_enabled {2899 if !<ContractWhiteList<T>>::contains_key(called_contract.clone(), who) {2900 return Err(InvalidTransaction::Call.into());2901 }2902 }2903 },2904 _ => {},2905 }29062907 // Sponsor smart contracts2908 sponsor = sponsor.or_else(|| match IsSubType::<pallet_contracts::Call<T>>::is_sub_type(call) {29092910 // On instantiation: set the contract owner2911 Some(pallet_contracts::Call::instantiate(_endowment, _gas_limit, code_hash, _data, salt)) => {29122913 let new_contract_address = <pallet_contracts::Module<T>>::contract_address(2914 &who,2915 code_hash,2916 salt,2917 );2918 <ContractOwner<T>>::insert(new_contract_address.clone(), who.clone());29192920 None2921 },29222923 // On instantiation with code: set the contract owner2924 Some(pallet_contracts::Call::instantiate_with_code(_endowment, _gas_limit, _code, _data, _salt)) => {29252926 let new_contract_address = <pallet_contracts::Module<T>>::contract_address(2927 &who,2928 &T::Hashing::hash(&_code),2929 _salt,2930 );29312932 <ContractOwner<T>>::insert(new_contract_address.clone(), who.clone());29332934 None2935 }29362937 // When the contract is called, check if the sponsoring is enabled and pay fees from contract endowment if it is2938 Some(pallet_contracts::Call::call(dest, _value, _gas_limit, _data)) => {29392940 let called_contract: T::AccountId = T::Lookup::lookup((*dest).clone()).unwrap_or(T::AccountId::default());29412942 let mut sponsor_transfer = false;2943 if <ContractSponsoringRateLimit<T>>::contains_key(called_contract.clone()) {2944 let last_tx_block = <ContractSponsorBasket<T>>::get((&called_contract, &who));2945 let block_number = <system::Module<T>>::block_number() as T::BlockNumber;2946 let rate_limit = <ContractSponsoringRateLimit<T>>::get(&called_contract);2947 let limit_time = last_tx_block + rate_limit;29482949 if block_number >= limit_time {2950 <ContractSponsorBasket<T>>::insert((called_contract.clone(), who.clone()), block_number);2951 sponsor_transfer = true;2952 }2953 } else {2954 sponsor_transfer = false;2955 }2956 2957 if sponsor_transfer {2958 if <ContractSelfSponsoring<T>>::contains_key(called_contract.clone()) {2959 if <ContractSelfSponsoring<T>>::get(called_contract.clone()) {2960 return Some(called_contract);2961 }2962 }2963 }29642965 None2966 },29672968 _ => None,2969 });29702971 let who_pays_fee = sponsor.unwrap_or_else(|| who.clone());29722973 <<T as pallet_transaction_payment::Config>::OnChargeTransaction as pallet_transaction_payment::OnChargeTransaction<T>>::withdraw_fee(&who_pays_fee, call, info, fee, tip)2974 .map(|i| (fee, i))2975 }2976}297729782979impl<T: Config + Send + Sync> SignedExtension2980 for ChargeTransactionPayment<T>2981where2982 BalanceOf<T>: Send + Sync + From<u64> + FixedPointOperand,2983 T::Call: Dispatchable<Info = DispatchInfo, PostInfo = PostDispatchInfo> + IsSubType<Call<T>> + IsSubType<pallet_contracts::Call<T>>,2984 T::AccountId: AsRef<[u8]>,2985 T::AccountId: UncheckedFrom<T::Hash>,2986{2987 const IDENTIFIER: &'static str = "ChargeTransactionPayment";2988 type AccountId = T::AccountId;2989 type Call = T::Call;2990 type AdditionalSigned = ();2991 type Pre = (2992 // tip2993 BalanceOf<T>,2994 // who pays fee2995 Self::AccountId,2996 // imbalance resulting from withdrawing the fee2997 <<T as pallet_transaction_payment::Config>::OnChargeTransaction as pallet_transaction_payment::OnChargeTransaction<T>>::LiquidityInfo,2998 );2999 fn additional_signed(&self) -> sp_std::result::Result<(), TransactionValidityError> {3000 Ok(())3001 }30023003 fn validate(3004 &self,3005 who: &Self::AccountId,3006 call: &Self::Call,3007 info: &DispatchInfoOf<Self::Call>,3008 len: usize,3009 ) -> TransactionValidity {3010 let (fee, _) = self.withdraw_fee(who, call, info, len)?;3011 Ok(ValidTransaction {3012 priority: Self::get_priority(len, info, fee),3013 ..Default::default()3014 })3015 }30163017 fn pre_dispatch(3018 self,3019 who: &Self::AccountId,3020 call: &Self::Call,3021 info: &DispatchInfoOf<Self::Call>,3022 len: usize,3023 ) -> Result<Self::Pre, TransactionValidityError> {3024 let (_fee, imbalance) = self.withdraw_fee(who, call, info, len)?;3025 Ok((self.0, who.clone(), imbalance))3026 }30273028 fn post_dispatch(3029 pre: Self::Pre,3030 info: &DispatchInfoOf<Self::Call>,3031 post_info: &PostDispatchInfoOf<Self::Call>,3032 len: usize,3033 _result: &DispatchResult,3034 ) -> Result<(), TransactionValidityError> {3035 let (tip, who, imbalance) = pre;3036 let actual_fee = pallet_transaction_payment::Module::<T>::compute_actual_fee(3037 len as u32,3038 info,3039 post_info,3040 tip,3041 );3042 <T as pallet_transaction_payment::Config>::OnChargeTransaction::correct_and_deposit_fee(&who, info, post_info, actual_fee, tip, imbalance)?;3043 Ok(())3044 }3045}30463047// #endregion30483049sp_api::decl_runtime_apis! {3050 pub trait NftApi {3051 /// Used for ethereum integration3052 fn eth_contract_code(account: H160) -> Option<Vec<u8>>;3053 }3054}1//2// This file is subject to the terms and conditions defined in3// file 'LICENSE', which is part of this source code package.4//56#![recursion_limit = "1024"]78#![cfg_attr(not(feature = "std"), no_std)]910#[cfg(feature = "std")]11pub use serde::*;1213use core::ops::{Deref, DerefMut};14use codec::{Decode, Encode};15pub use frame_support::{16 construct_runtime, decl_event, decl_module, decl_storage, decl_error,17 dispatch::DispatchResult,18 ensure, fail, parameter_types,19 traits::{20 Currency, ExistenceRequirement, Get, Imbalance, KeyOwnerProofSystem, OnUnbalanced,21 Randomness, IsSubType, WithdrawReasons,22 },23 weights::{24 constants::{BlockExecutionWeight, ExtrinsicBaseWeight, RocksDbWeight, WEIGHT_PER_SECOND},25 DispatchInfo, GetDispatchInfo, IdentityFee, Pays, PostDispatchInfo, Weight,26 WeightToFeePolynomial, DispatchClass,27 },28 StorageValue,29 transactional,30};3132use frame_system::{self as system, ensure_signed, ensure_root};33use sp_core::{H160, H256};34use sp_runtime::sp_std::prelude::Vec;35use sp_runtime::{36 traits::{37 Hash, DispatchInfoOf, Dispatchable, PostDispatchInfoOf, Saturating, SaturatedConversion, SignedExtension, Zero,38 },39 transaction_validity::{40 TransactionPriority, InvalidTransaction, TransactionValidity, TransactionValidityError, ValidTransaction,41 },42 FixedPointOperand, FixedU128,43};44use sp_runtime::traits::StaticLookup;45use pallet_contracts::chain_extension::UncheckedFrom;46use pallet_ethereum::EthereumTransactionSender;47use pallet_transaction_payment::OnChargeTransaction;4849#[cfg(test)]50mod mock;5152#[cfg(test)]53mod tests;5455mod default_weights;56mod eth;5758pub use eth::NftErcSupport;59pub use eth::account::*;6061pub const MAX_DECIMAL_POINTS: DecimalPoints = 30;62pub const MAX_REFUNGIBLE_PIECES: u128 = 1_000_000_000_000_000_000_000;63pub const MAX_SPONSOR_TIMEOUT: u32 = 10_368_000;64pub const MAX_TOKEN_OWNERSHIP: u32 = 10_000_000;6566// Structs67// #region6869pub type CollectionId = u32;70pub type TokenId = u32;71pub type DecimalPoints = u8;7273#[derive(Encode, Decode, Eq, Debug, Clone, PartialEq)]74#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]75pub enum CollectionMode {76 Invalid,77 NFT,78 // decimal points79 Fungible(DecimalPoints),80 ReFungible,81}8283impl Default for CollectionMode {84 fn default() -> Self {85 Self::Invalid86 }87}8889impl Into<u8> for CollectionMode {90 fn into(self) -> u8 {91 match self {92 CollectionMode::Invalid => 0,93 CollectionMode::NFT => 1,94 CollectionMode::Fungible(_) => 2,95 CollectionMode::ReFungible => 3,96 }97 }98}99100#[derive(Encode, Decode, Eq, Debug, Clone, PartialEq)]101#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]102pub enum AccessMode {103 Normal,104 WhiteList,105}106impl Default for AccessMode {107 fn default() -> Self {108 Self::Normal109 }110}111112#[derive(Encode, Decode, Eq, Debug, Clone, PartialEq)]113#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]114pub enum SchemaVersion {115 ImageURL,116 Unique,117}118impl Default for SchemaVersion {119 fn default() -> Self {120 Self::ImageURL121 }122}123124#[derive(Encode, Decode, Default, Debug, Clone, PartialEq)]125#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]126pub struct Ownership<AccountId> {127 pub owner: AccountId,128 pub fraction: u128,129}130131#[derive(Encode, Decode, Debug, Clone, PartialEq)]132#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]133pub enum SponsorshipState<AccountId> {134 /// The fees are applied to the transaction sender135 Disabled,136 Unconfirmed(AccountId),137 /// Transactions are sponsored by specified account138 Confirmed(AccountId),139}140141impl<AccountId> SponsorshipState<AccountId> {142 fn sponsor(&self) -> Option<&AccountId> {143 match self {144 Self::Confirmed(sponsor) => Some(sponsor),145 _ => None,146 }147 }148149 fn pending_sponsor(&self) -> Option<&AccountId> {150 match self {151 Self::Unconfirmed(sponsor) | Self::Confirmed(sponsor) => Some(sponsor),152 _ => None,153 }154 }155156 fn confirmed(&self) -> bool {157 matches!(self, Self::Confirmed(_))158 }159}160161impl<T> Default for SponsorshipState<T> {162 fn default() -> Self {163 Self::Disabled164 }165}166167#[derive(Encode, Decode, Clone, PartialEq)]168#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]169pub struct Collection<T: Config> {170 pub owner: T::CrossAccountId,171 pub mode: CollectionMode,172 pub access: AccessMode,173 pub decimal_points: DecimalPoints,174 pub name: Vec<u16>, // 64 include null escape char175 pub description: Vec<u16>, // 256 include null escape char176 pub token_prefix: Vec<u8>, // 16 include null escape char177 pub mint_mode: bool,178 pub offchain_schema: Vec<u8>,179 pub schema_version: SchemaVersion,180 pub sponsorship: SponsorshipState<T::AccountId>,181 pub limits: CollectionLimits<T::BlockNumber>, // Collection private restrictions 182 pub variable_on_chain_schema: Vec<u8>, //183 pub const_on_chain_schema: Vec<u8>, //184}185186pub struct CollectionHandle<T: Config> {187 pub id: CollectionId,188 collection: Collection<T>,189 logs: eth::log::LogRecorder,190}191impl<T: Config> CollectionHandle<T> {192 pub fn get(id: CollectionId) -> Option<Self> {193 <CollectionById<T>>::get(id)194 .map(|collection| Self {195 id,196 collection,197 logs: eth::log::LogRecorder::default(),198 })199 }200 pub fn log(&self, topics: Vec<H256>, data: eth::abi::AbiWriter) {201 self.logs.log(topics, data)202 }203 pub fn into_inner(self) -> Collection<T> {204 self.collection.clone()205 }206}207208impl<T: Config> Deref for CollectionHandle<T> {209 type Target = Collection<T>;210211 fn deref(&self) -> &Self::Target {212 &self.collection213 }214}215216impl<T: Config> DerefMut for CollectionHandle<T> {217 fn deref_mut(&mut self) -> &mut Self::Target {218 &mut self.collection219 }220}221222#[derive(Encode, Decode, Debug, Clone, PartialEq)]223#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]224pub struct NftItemType<AccountId> {225 pub owner: AccountId,226 pub const_data: Vec<u8>,227 pub variable_data: Vec<u8>,228}229230#[derive(Encode, Decode, Default, Debug, Clone, PartialEq)]231#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]232pub struct FungibleItemType {233 pub value: u128,234}235236#[derive(Encode, Decode, Debug, Clone, PartialEq)]237#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]238pub struct ReFungibleItemType<AccountId> {239 pub owner: Vec<Ownership<AccountId>>,240 pub const_data: Vec<u8>,241 pub variable_data: Vec<u8>,242}243244// #[derive(Encode, Decode, Default, Debug, Clone, PartialEq)]245// #[cfg_attr(feature = "std", derive(Serialize, Deserialize))]246// pub struct VestingItem<AccountId, Moment> {247// pub sender: AccountId,248// pub recipient: AccountId,249// pub collection_id: CollectionId,250// pub item_id: TokenId,251// pub amount: u64,252// pub vesting_date: Moment,253// }254255#[derive(Encode, Decode, Debug, Clone, PartialEq)]256#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]257pub struct CollectionLimits<BlockNumber: Encode + Decode> {258 pub account_token_ownership_limit: u32,259 pub sponsored_data_size: u32,260 /// None - setVariableMetadata is not sponsored261 /// Some(v) - setVariableMetadata is sponsored 262 /// if there is v block between txs263 pub sponsored_data_rate_limit: Option<BlockNumber>,264 pub token_limit: u32,265266 // Timeouts for item types in passed blocks267 pub sponsor_transfer_timeout: u32,268 pub owner_can_transfer: bool,269 pub owner_can_destroy: bool,270}271272impl<BlockNumber: Encode + Decode> Default for CollectionLimits<BlockNumber> {273 fn default() -> Self {274 Self { 275 account_token_ownership_limit: 10_000_000, 276 token_limit: u32::max_value(),277 sponsored_data_size: u32::MAX, 278 sponsored_data_rate_limit: None,279 sponsor_transfer_timeout: 14400,280 owner_can_transfer: true,281 owner_can_destroy: true282 }283 }284}285286#[derive(Encode, Decode, Default, Debug, Clone, PartialEq)]287#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]288pub struct ChainLimits {289 pub collection_numbers_limit: u32,290 pub account_token_ownership_limit: u32,291 pub collections_admins_limit: u64,292 pub custom_data_limit: u32,293294 // Timeouts for item types in passed blocks295 pub nft_sponsor_transfer_timeout: u32,296 pub fungible_sponsor_transfer_timeout: u32,297 pub refungible_sponsor_transfer_timeout: u32,298299 // Schema limits300 pub offchain_schema_limit: u32,301 pub variable_on_chain_schema_limit: u32,302 pub const_on_chain_schema_limit: u32,303}304305pub trait WeightInfo {306 fn create_collection() -> Weight;307 fn destroy_collection() -> Weight;308 fn add_to_white_list() -> Weight;309 fn remove_from_white_list() -> Weight;310 fn set_public_access_mode() -> Weight;311 fn set_mint_permission() -> Weight;312 fn change_collection_owner() -> Weight;313 fn add_collection_admin() -> Weight;314 fn remove_collection_admin() -> Weight;315 fn set_collection_sponsor() -> Weight;316 fn confirm_sponsorship() -> Weight;317 fn remove_collection_sponsor() -> Weight;318 fn create_item(s: usize) -> Weight;319 fn burn_item() -> Weight;320 fn transfer() -> Weight;321 fn approve() -> Weight;322 fn transfer_from() -> Weight;323 fn set_offchain_schema() -> Weight;324 fn set_const_on_chain_schema() -> Weight;325 fn set_variable_on_chain_schema() -> Weight;326 fn set_variable_meta_data() -> Weight;327 fn enable_contract_sponsoring() -> Weight;328 fn set_schema_version() -> Weight;329 fn set_chain_limits() -> Weight;330 fn set_contract_sponsoring_rate_limit() -> Weight;331 fn set_variable_meta_data_sponsoring_rate_limit() -> Weight;332 fn toggle_contract_white_list() -> Weight;333 fn add_to_contract_white_list() -> Weight;334 fn remove_from_contract_white_list() -> Weight;335 fn set_collection_limits() -> Weight;336}337338#[derive(Encode, Decode, Default, Debug, Clone, PartialEq)]339#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]340pub struct CreateNftData {341 pub const_data: Vec<u8>,342 pub variable_data: Vec<u8>,343}344345#[derive(Encode, Decode, Default, Debug, Clone, PartialEq)]346#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]347pub struct CreateFungibleData {348 pub value: u128,349}350351#[derive(Encode, Decode, Default, Debug, Clone, PartialEq)]352#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]353pub struct CreateReFungibleData {354 pub const_data: Vec<u8>,355 pub variable_data: Vec<u8>,356 pub pieces: u128,357}358359#[derive(Encode, Decode, Debug, Clone, PartialEq)]360#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]361pub enum CreateItemData {362 NFT(CreateNftData),363 Fungible(CreateFungibleData),364 ReFungible(CreateReFungibleData),365}366367impl CreateItemData {368 pub fn len(&self) -> usize {369 let len = match self {370 CreateItemData::NFT(data) => data.variable_data.len() + data.const_data.len(),371 CreateItemData::ReFungible(data) => data.variable_data.len() + data.const_data.len(),372 _ => 0373 };374 375 return len;376 }377}378379impl From<CreateNftData> for CreateItemData {380 fn from(item: CreateNftData) -> Self {381 CreateItemData::NFT(item)382 }383}384385impl From<CreateReFungibleData> for CreateItemData {386 fn from(item: CreateReFungibleData) -> Self {387 CreateItemData::ReFungible(item)388 }389}390391impl From<CreateFungibleData> for CreateItemData {392 fn from(item: CreateFungibleData) -> Self {393 CreateItemData::Fungible(item)394 }395}396397398decl_error! {399 /// Error for non-fungible-token module.400 pub enum Error for Module<T: Config> {401 /// Total collections bound exceeded.402 TotalCollectionsLimitExceeded,403 /// Decimal_points parameter must be lower than MAX_DECIMAL_POINTS constant, currently it is 30.404 CollectionDecimalPointLimitExceeded, 405 /// Collection name can not be longer than 63 char.406 CollectionNameLimitExceeded, 407 /// Collection description can not be longer than 255 char.408 CollectionDescriptionLimitExceeded, 409 /// Token prefix can not be longer than 15 char.410 CollectionTokenPrefixLimitExceeded,411 /// This collection does not exist.412 CollectionNotFound,413 /// Item not exists.414 TokenNotFound,415 /// Admin not found416 AdminNotFound,417 /// Arithmetic calculation overflow.418 NumOverflow, 419 /// Account already has admin role.420 AlreadyAdmin, 421 /// You do not own this collection.422 NoPermission,423 /// This address is not set as sponsor, use setCollectionSponsor first.424 ConfirmUnsetSponsorFail,425 /// Collection is not in mint mode.426 PublicMintingNotAllowed,427 /// Sender parameter and item owner must be equal.428 MustBeTokenOwner,429 /// Item balance not enough.430 TokenValueTooLow,431 /// Size of item is too large.432 NftSizeLimitExceeded,433 /// No approve found434 ApproveNotFound,435 /// Requested value more than approved.436 TokenValueNotEnough,437 /// Only approved addresses can call this method.438 ApproveRequired,439 /// Address is not in white list.440 AddresNotInWhiteList,441 /// Number of collection admins bound exceeded.442 CollectionAdminsLimitExceeded,443 /// Owned tokens by a single address bound exceeded.444 AddressOwnershipLimitExceeded,445 /// Length of items properties must be greater than 0.446 EmptyArgument,447 /// const_data exceeded data limit.448 TokenConstDataLimitExceeded,449 /// variable_data exceeded data limit.450 TokenVariableDataLimitExceeded,451 /// Not NFT item data used to mint in NFT collection.452 NotNftDataUsedToMintNftCollectionToken,453 /// Not Fungible item data used to mint in Fungible collection.454 NotFungibleDataUsedToMintFungibleCollectionToken,455 /// Not Re Fungible item data used to mint in Re Fungible collection.456 NotReFungibleDataUsedToMintReFungibleCollectionToken,457 /// Unexpected collection type.458 UnexpectedCollectionType,459 /// Can't store metadata in fungible tokens.460 CantStoreMetadataInFungibleTokens,461 /// Collection token limit exceeded462 CollectionTokenLimitExceeded,463 /// Account token limit exceeded per collection464 AccountTokenLimitExceeded,465 /// Collection limit bounds per collection exceeded466 CollectionLimitBoundsExceeded,467 /// Tried to enable permissions which are only permitted to be disabled468 OwnerPermissionsCantBeReverted,469 /// Schema data size limit bound exceeded470 SchemaDataLimitExceeded,471 /// Maximum refungibility exceeded472 WrongRefungiblePieces,473 /// createRefungible should be called with one owner474 BadCreateRefungibleCall,475 }476}477478pub trait Config: system::Config + Sized + pallet_transaction_payment::Config + pallet_contracts::Config {479 type Event: From<Event<Self>> + Into<<Self as system::Config>::Event>;480481 /// Weight information for extrinsics in this pallet.482 type WeightInfo: WeightInfo;483484 type EvmAddressMapping: pallet_evm::AddressMapping<Self::AccountId>;485 type EvmBackwardsAddressMapping: EvmBackwardsAddressMapping<Self::AccountId>;486 type EvmWithdrawOrigin: pallet_evm::EnsureAddressOrigin<Self::Origin, Success = Self::AccountId>;487488 type CrossAccountId: CrossAccountId<Self::AccountId>;489 type Currency: Currency<Self::AccountId>;490 type CollectionCreationPrice: Get<<<Self as Config>::Currency as Currency<Self::AccountId>>::Balance>;491 type TreasuryAccountId: Get<Self::AccountId>;492493 type EthereumChainId: Get<u64>;494 type EthereumTransactionSender: pallet_ethereum::EthereumTransactionSender;495}496497#[cfg(feature = "runtime-benchmarks")]498mod benchmarking;499500// #endregion501502// # Used definitions503//504// ## User control levels505//506// chain-controlled - key is uncontrolled by user507// i.e autoincrementing index508// can use non-cryptographic hash509// real - key is controlled by user510// but it is hard to generate enough colliding values, i.e owner of signed txs511// can use non-cryptographic hash512// controlled - key is completly controlled by users513// i.e maps with mutable keys514// should use cryptographic hash515//516// ## User control level downgrade reasons517//518// ?1 - chain-controlled -> controlled519// collections/tokens can be destroyed, resulting in massive holes520// ?2 - chain-controlled -> controlled521// same as ?1, but can be only added, resulting in easier exploitation522// ?3 - real -> controlled523// no confirmation required, so addresses can be easily generated524decl_storage! {525 trait Store for Module<T: Config> as Nft {526527 //#region Private members528 /// Id of next collection529 CreatedCollectionCount: u32;530 /// Used for migrations531 ChainVersion: u64;532 /// Id of last collection token533 /// Collection id (controlled?1)534 ItemListIndex: map hasher(blake2_128_concat) CollectionId => TokenId;535 //#endregion536537 //#region Chain limits struct538 pub ChainLimit get(fn chain_limit) config(): ChainLimits;539 //#endregion540541 //#region Bound counters542 /// Amount of collections destroyed, used for total amount tracking with543 /// CreatedCollectionCount544 DestroyedCollectionCount: u32;545 /// Total amount of account owned tokens (NFTs + RFTs + unique fungibles)546 /// Account id (real)547 pub AccountItemCount get(fn account_item_count): map hasher(twox_64_concat) T::AccountId => u32;548 //#endregion549550 //#region Basic collections551 /// Collection info552 /// Collection id (controlled?1)553 pub CollectionById get(fn collection_id) config(): map hasher(blake2_128_concat) CollectionId => Option<Collection<T>> = None;554 /// List of collection admins555 /// Collection id (controlled?2)556 pub AdminList get(fn admin_list_collection): map hasher(blake2_128_concat) CollectionId => Vec<T::CrossAccountId>;557 /// Whitelisted collection users558 /// Collection id (controlled?2), user id (controlled?3)559 pub WhiteList get(fn white_list): double_map hasher(blake2_128_concat) CollectionId, hasher(blake2_128_concat) T::AccountId => bool;560 //#endregion561562 /// How many of collection items user have563 /// Collection id (controlled?2), account id (real)564 pub Balance get(fn balance_count): double_map hasher(blake2_128_concat) CollectionId, hasher(twox_64_concat) T::AccountId => u128;565566 /// Amount of items which spender can transfer out of owners account (via transferFrom)567 /// Collection id (controlled?2), (token id (controlled ?2) + owner account id (real) + spender account id (controlled?3))568 /// TODO: Off chain worker should remove from this map when token gets removed569 pub Allowances get(fn approved): double_map hasher(blake2_128_concat) CollectionId, hasher(blake2_128_concat) (TokenId, T::AccountId, T::AccountId) => u128;570571 //#region Item collections572 /// Collection id (controlled?2), token id (controlled?1)573 pub NftItemList get(fn nft_item_id) config(): double_map hasher(blake2_128_concat) CollectionId, hasher(blake2_128_concat) TokenId => Option<NftItemType<T::CrossAccountId>>;574 /// Collection id (controlled?2), owner (controlled?2)575 pub FungibleItemList get(fn fungible_item_id) config(): double_map hasher(blake2_128_concat) CollectionId, hasher(blake2_128_concat) T::AccountId => FungibleItemType;576 /// Collection id (controlled?2), token id (controlled?1)577 pub ReFungibleItemList get(fn refungible_item_id) config(): double_map hasher(blake2_128_concat) CollectionId, hasher(blake2_128_concat) TokenId => Option<ReFungibleItemType<T::CrossAccountId>>;578 //#endregion579580 //#region Index list581 /// Collection id (controlled?2), tokens owner (controlled?2)582 pub AddressTokens get(fn address_tokens): double_map hasher(blake2_128_concat) CollectionId, hasher(blake2_128_concat) T::AccountId => Vec<TokenId>;583 //#endregion584585 //#region Tokens transfer rate limit baskets586 /// (Collection id (controlled?2), who created (real))587 /// TODO: Off chain worker should remove from this map when collection gets removed588 pub CreateItemBasket get(fn create_item_basket): map hasher(blake2_128_concat) (CollectionId, T::AccountId) => T::BlockNumber;589 /// Collection id (controlled?2), token id (controlled?2)590 pub NftTransferBasket get(fn nft_transfer_basket): double_map hasher(blake2_128_concat) CollectionId, hasher(blake2_128_concat) TokenId => T::BlockNumber;591 /// Collection id (controlled?2), owning user (real)592 pub FungibleTransferBasket get(fn fungible_transfer_basket): double_map hasher(blake2_128_concat) CollectionId, hasher(twox_64_concat) T::AccountId => T::BlockNumber;593 /// Collection id (controlled?2), token id (controlled?2)594 pub ReFungibleTransferBasket get(fn refungible_transfer_basket): double_map hasher(blake2_128_concat) CollectionId, hasher(blake2_128_concat) TokenId => T::BlockNumber;595 //#endregion596597 /// Variable metadata sponsoring598 /// Collection id (controlled?2), token id (controlled?2)599 pub VariableMetaDataBasket get(fn variable_meta_data_basket): double_map hasher(blake2_128_concat) CollectionId, hasher(blake2_128_concat) TokenId => Option<T::BlockNumber> = None;600 601 //#region Contract Sponsorship and Ownership602 /// Contract address (real)603 pub ContractOwner get(fn contract_owner): map hasher(twox_64_concat) T::AccountId => Option<T::AccountId>;604 /// Contract address (real)605 pub ContractSelfSponsoring get(fn contract_self_sponsoring): map hasher(twox_64_concat) T::AccountId => bool;606 /// (Contract address(real), caller (real))607 pub ContractSponsorBasket get(fn contract_sponsor_basket): map hasher(twox_64_concat) (T::AccountId, T::AccountId) => T::BlockNumber;608 /// Contract address (real)609 pub ContractSponsoringRateLimit get(fn contract_sponsoring_rate_limit): map hasher(twox_64_concat) T::AccountId => T::BlockNumber;610 /// Contract address (real)611 pub ContractWhiteListEnabled get(fn contract_white_list_enabled): map hasher(twox_64_concat) T::AccountId => bool; 612 /// Contract address (real) => Whitelisted user (controlled?3)613 pub ContractWhiteList get(fn contract_white_list): double_map hasher(twox_64_concat) T::AccountId, hasher(blake2_128_concat) T::AccountId => bool; 614 //#endregion615 }616 add_extra_genesis {617 build(|config: &GenesisConfig<T>| {618 // Modification of storage619 for (_num, _c) in &config.collection_id {620 <Module<T>>::init_collection(_c);621 }622623 for (_num, _c, _i) in &config.nft_item_id {624 <Module<T>>::init_nft_token(*_c, _i);625 }626627 for (collection_id, account_id, fungible_item) in &config.fungible_item_id {628 <Module<T>>::init_fungible_token(*collection_id, &T::CrossAccountId::from_sub(account_id.clone()), fungible_item);629 }630631 for (_num, _c, _i) in &config.refungible_item_id {632 <Module<T>>::init_refungible_token(*_c, _i);633 }634 })635 }636}637638decl_event!(639 pub enum Event<T>640 where641 CrossAccountId = <T as Config>::CrossAccountId,642 {643 /// New collection was created644 /// 645 /// # Arguments646 /// 647 /// * collection_id: Globally unique identifier of newly created collection.648 /// 649 /// * mode: [CollectionMode] converted into u8.650 /// 651 /// * account_id: Collection owner.652 CollectionCreated(CollectionId, u8, CrossAccountId),653654 /// New item was created.655 /// 656 /// # Arguments657 /// 658 /// * collection_id: Id of the collection where item was created.659 /// 660 /// * item_id: Id of an item. Unique within the collection.661 ///662 /// * recipient: Owner of newly created item 663 ItemCreated(CollectionId, TokenId, CrossAccountId),664665 /// Collection item was burned.666 /// 667 /// # Arguments668 /// 669 /// collection_id.670 /// 671 /// item_id: Identifier of burned NFT.672 ItemDestroyed(CollectionId, TokenId),673674 /// Item was transferred675 ///676 /// * collection_id: Id of collection to which item is belong677 ///678 /// * item_id: Id of an item679 ///680 /// * sender: Original owner of item681 ///682 /// * recipient: New owner of item683 ///684 /// * amount: Always 1 for NFT685 Transfer(CollectionId, TokenId, CrossAccountId, CrossAccountId, u128),686687 /// * collection_id688 ///689 /// * item_id690 ///691 /// * sender692 ///693 /// * spender694 ///695 /// * amount696 Approved(CollectionId, TokenId, CrossAccountId, CrossAccountId, u128),697 }698);699700decl_module! {701 pub struct Module<T: Config> for enum Call 702 where 703 origin: T::Origin704 {705 fn deposit_event() = default;706 type Error = Error<T>;707708 fn on_initialize(now: T::BlockNumber) -> Weight {709 0710 }711712 /// This method creates a Collection of NFTs. Each Token may have multiple properties encoded as an array of bytes of certain length. The initial owner and admin of the collection are set to the address that signed the transaction. Both addresses can be changed later.713 /// 714 /// # Permissions715 /// 716 /// * Anyone.717 /// 718 /// # Arguments719 /// 720 /// * collection_name: UTF-16 string with collection name (limit 64 characters), will be stored as zero-terminated.721 /// 722 /// * collection_description: UTF-16 string with collection description (limit 256 characters), will be stored as zero-terminated.723 /// 724 /// * token_prefix: UTF-8 string with token prefix.725 /// 726 /// * mode: [CollectionMode] collection type and type dependent data.727 // returns collection ID728 #[weight = <T as Config>::WeightInfo::create_collection()]729 #[transactional]730 pub fn create_collection(origin,731 collection_name: Vec<u16>,732 collection_description: Vec<u16>,733 token_prefix: Vec<u8>,734 mode: CollectionMode) -> DispatchResult {735736 // Anyone can create a collection737 let who = T::CrossAccountId::from_sub(ensure_signed(origin)?);738739 // Take a (non-refundable) deposit of collection creation740 let mut imbalance = <<<T as Config>::Currency as Currency<T::AccountId>>::PositiveImbalance>::zero();741 imbalance.subsume(<<T as Config>::Currency as Currency<T::AccountId>>::deposit_creating(742 &T::TreasuryAccountId::get(),743 T::CollectionCreationPrice::get(),744 ));745 <T as Config>::Currency::settle(746 who.as_sub(),747 imbalance,748 WithdrawReasons::TRANSFER,749 ExistenceRequirement::KeepAlive,750 ).map_err(|_| Error::<T>::NoPermission)?;751752 let decimal_points = match mode {753 CollectionMode::Fungible(points) => points,754 _ => 0755 };756757 let chain_limit = ChainLimit::get();758759 let created_count = CreatedCollectionCount::get();760 let destroyed_count = DestroyedCollectionCount::get();761762 // bound Total number of collections763 ensure!(created_count - destroyed_count < chain_limit.collection_numbers_limit, Error::<T>::TotalCollectionsLimitExceeded);764765 // check params766 ensure!(decimal_points <= MAX_DECIMAL_POINTS, Error::<T>::CollectionDecimalPointLimitExceeded);767 ensure!(collection_name.len() <= 64, Error::<T>::CollectionNameLimitExceeded);768 ensure!(collection_description.len() <= 256, Error::<T>::CollectionDescriptionLimitExceeded);769 ensure!(token_prefix.len() <= 16, Error::<T>::CollectionTokenPrefixLimitExceeded);770771 // Generate next collection ID772 let next_id = created_count773 .checked_add(1)774 .ok_or(Error::<T>::NumOverflow)?;775776 CreatedCollectionCount::put(next_id);777778 let limits = CollectionLimits {779 sponsored_data_size: chain_limit.custom_data_limit,780 ..Default::default()781 };782783 // Create new collection784 let new_collection = Collection {785 owner: who.clone(),786 name: collection_name,787 mode: mode.clone(),788 mint_mode: false,789 access: AccessMode::Normal,790 description: collection_description,791 decimal_points: decimal_points,792 token_prefix: token_prefix,793 offchain_schema: Vec::new(),794 schema_version: SchemaVersion::ImageURL,795 sponsorship: SponsorshipState::Disabled,796 variable_on_chain_schema: Vec::new(),797 const_on_chain_schema: Vec::new(),798 limits,799 };800801 // Add new collection to map802 <CollectionById<T>>::insert(next_id, new_collection);803804 // call event805 Self::deposit_event(RawEvent::CollectionCreated(next_id, mode.into(), who));806807 Ok(())808 }809810 /// **DANGEROUS**: Destroys collection and all NFTs within this collection. Users irrecoverably lose their assets and may lose real money.811 /// 812 /// # Permissions813 /// 814 /// * Collection Owner.815 /// 816 /// # Arguments817 /// 818 /// * collection_id: collection to destroy.819 #[weight = <T as Config>::WeightInfo::destroy_collection()]820 #[transactional]821 pub fn destroy_collection(origin, collection_id: CollectionId) -> DispatchResult {822823 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);824 let collection = Self::get_collection(collection_id)?;825 Self::check_owner_permissions(&collection, &sender)?;826 if !collection.limits.owner_can_destroy {827 fail!(Error::<T>::NoPermission);828 }829830 <AddressTokens<T>>::remove_prefix(collection_id);831 <Allowances<T>>::remove_prefix(collection_id);832 <Balance<T>>::remove_prefix(collection_id);833 <ItemListIndex>::remove(collection_id);834 <AdminList<T>>::remove(collection_id);835 <CollectionById<T>>::remove(collection_id);836 <WhiteList<T>>::remove_prefix(collection_id);837838 <NftItemList<T>>::remove_prefix(collection_id);839 <FungibleItemList<T>>::remove_prefix(collection_id);840 <ReFungibleItemList<T>>::remove_prefix(collection_id);841842 <NftTransferBasket<T>>::remove_prefix(collection_id);843 <FungibleTransferBasket<T>>::remove_prefix(collection_id);844 <ReFungibleTransferBasket<T>>::remove_prefix(collection_id);845846 <VariableMetaDataBasket<T>>::remove_prefix(collection_id);847848 DestroyedCollectionCount::put(DestroyedCollectionCount::get()849 .checked_add(1)850 .ok_or(Error::<T>::NumOverflow)?);851852 Ok(())853 }854855 /// Add an address to white list.856 /// 857 /// # Permissions858 /// 859 /// * Collection Owner860 /// * Collection Admin861 /// 862 /// # Arguments863 /// 864 /// * collection_id.865 /// 866 /// * address.867 #[weight = <T as Config>::WeightInfo::add_to_white_list()]868 #[transactional]869 pub fn add_to_white_list(origin, collection_id: CollectionId, address: T::CrossAccountId) -> DispatchResult{870871 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);872 let collection = Self::get_collection(collection_id)?;873 Self::check_owner_or_admin_permissions(&collection, &sender)?;874875 <WhiteList<T>>::insert(collection_id, address.as_sub(), true);876 877 Ok(())878 }879880 /// Remove an address from white list.881 /// 882 /// # Permissions883 /// 884 /// * Collection Owner885 /// * Collection Admin886 /// 887 /// # Arguments888 /// 889 /// * collection_id.890 /// 891 /// * address.892 #[weight = <T as Config>::WeightInfo::remove_from_white_list()]893 #[transactional]894 pub fn remove_from_white_list(origin, collection_id: CollectionId, address: T::CrossAccountId) -> DispatchResult{895896 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);897 let collection = Self::get_collection(collection_id)?;898 Self::check_owner_or_admin_permissions(&collection, &sender)?;899900 <WhiteList<T>>::remove(collection_id, address.as_sub());901902 Ok(())903 }904905 /// Toggle between normal and white list access for the methods with access for `Anyone`.906 /// 907 /// # Permissions908 /// 909 /// * Collection Owner.910 /// 911 /// # Arguments912 /// 913 /// * collection_id.914 /// 915 /// * mode: [AccessMode]916 #[weight = <T as Config>::WeightInfo::set_public_access_mode()]917 #[transactional]918 pub fn set_public_access_mode(origin, collection_id: CollectionId, mode: AccessMode) -> DispatchResult919 {920 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);921922 let mut target_collection = Self::get_collection(collection_id)?;923 Self::check_owner_permissions(&target_collection, &sender)?;924 target_collection.access = mode;925 Self::save_collection(target_collection);926927 Ok(())928 }929930 /// Allows Anyone to create tokens if:931 /// * White List is enabled, and932 /// * Address is added to white list, and933 /// * This method was called with True parameter934 /// 935 /// # Permissions936 /// * Collection Owner937 ///938 /// # Arguments939 /// 940 /// * collection_id.941 /// 942 /// * mint_permission: Boolean parameter. If True, allows minting to Anyone with conditions above.943 #[weight = <T as Config>::WeightInfo::set_mint_permission()]944 #[transactional]945 pub fn set_mint_permission(origin, collection_id: CollectionId, mint_permission: bool) -> DispatchResult946 {947 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);948949 let mut target_collection = Self::get_collection(collection_id)?;950 Self::check_owner_permissions(&target_collection, &sender)?;951 target_collection.mint_mode = mint_permission;952 Self::save_collection(target_collection);953954 Ok(())955 }956957 /// Change the owner of the collection.958 /// 959 /// # Permissions960 /// 961 /// * Collection Owner.962 /// 963 /// # Arguments964 /// 965 /// * collection_id.966 /// 967 /// * new_owner.968 #[weight = <T as Config>::WeightInfo::change_collection_owner()]969 #[transactional]970 pub fn change_collection_owner(origin, collection_id: CollectionId, new_owner: T::CrossAccountId) -> DispatchResult {971972 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);973 let mut target_collection = Self::get_collection(collection_id)?;974 Self::check_owner_permissions(&target_collection, &sender)?;975 target_collection.owner = new_owner;976 Self::save_collection(target_collection);977978 Ok(())979 }980981 /// Adds an admin of the Collection.982 /// NFT Collection can be controlled by multiple admin addresses (some which can also be servers, for example). Admins can issue and burn NFTs, as well as add and remove other admins, but cannot change NFT or Collection ownership. 983 /// 984 /// # Permissions985 /// 986 /// * Collection Owner.987 /// * Collection Admin.988 /// 989 /// # Arguments990 /// 991 /// * collection_id: ID of the Collection to add admin for.992 /// 993 /// * new_admin_id: Address of new admin to add.994 #[weight = <T as Config>::WeightInfo::add_collection_admin()]995 #[transactional]996 pub fn add_collection_admin(origin, collection_id: CollectionId, new_admin_id: T::CrossAccountId) -> DispatchResult {997 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);998 let collection = Self::get_collection(collection_id)?;999 Self::check_owner_or_admin_permissions(&collection, &sender)?;1000 let mut admin_arr = <AdminList<T>>::get(collection_id);10011002 match admin_arr.binary_search(&new_admin_id) {1003 Ok(_) => {},1004 Err(idx) => {1005 let limits = ChainLimit::get();1006 ensure!(admin_arr.len() < limits.collections_admins_limit as usize, Error::<T>::CollectionAdminsLimitExceeded);1007 admin_arr.insert(idx, new_admin_id);1008 <AdminList<T>>::insert(collection_id, admin_arr);1009 }1010 }1011 Ok(())1012 }10131014 /// Remove admin address of the Collection. An admin address can remove itself. List of admins may become empty, in which case only Collection Owner will be able to add an Admin.1015 ///1016 /// # Permissions1017 /// 1018 /// * Collection Owner.1019 /// * Collection Admin.1020 /// 1021 /// # Arguments1022 /// 1023 /// * collection_id: ID of the Collection to remove admin for.1024 /// 1025 /// * account_id: Address of admin to remove.1026 #[weight = <T as Config>::WeightInfo::remove_collection_admin()]1027 #[transactional]1028 pub fn remove_collection_admin(origin, collection_id: CollectionId, account_id: T::CrossAccountId) -> DispatchResult {1029 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);1030 let collection = Self::get_collection(collection_id)?;1031 Self::check_owner_or_admin_permissions(&collection, &sender)?;1032 let mut admin_arr = <AdminList<T>>::get(collection_id);10331034 match admin_arr.binary_search(&account_id) {1035 Ok(idx) => {1036 admin_arr.remove(idx);1037 <AdminList<T>>::insert(collection_id, admin_arr);1038 },1039 Err(_) => {}1040 }1041 Ok(())1042 }10431044 /// # Permissions1045 /// 1046 /// * Collection Owner1047 /// 1048 /// # Arguments1049 /// 1050 /// * collection_id.1051 /// 1052 /// * new_sponsor.1053 #[weight = <T as Config>::WeightInfo::set_collection_sponsor()]1054 #[transactional]1055 pub fn set_collection_sponsor(origin, collection_id: CollectionId, new_sponsor: T::AccountId) -> DispatchResult {1056 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);1057 let mut target_collection = Self::get_collection(collection_id)?;1058 Self::check_owner_permissions(&target_collection, &sender)?;10591060 target_collection.sponsorship = SponsorshipState::Unconfirmed(new_sponsor);1061 Self::save_collection(target_collection);10621063 Ok(())1064 }10651066 /// # Permissions1067 /// 1068 /// * Sponsor.1069 /// 1070 /// # Arguments1071 /// 1072 /// * collection_id.1073 #[weight = <T as Config>::WeightInfo::confirm_sponsorship()]1074 #[transactional]1075 pub fn confirm_sponsorship(origin, collection_id: CollectionId) -> DispatchResult {1076 let sender = ensure_signed(origin)?;10771078 let mut target_collection = Self::get_collection(collection_id)?;1079 ensure!(1080 target_collection.sponsorship.pending_sponsor() == Some(&sender),1081 Error::<T>::ConfirmUnsetSponsorFail1082 );10831084 target_collection.sponsorship = SponsorshipState::Confirmed(sender);1085 Self::save_collection(target_collection);10861087 Ok(())1088 }10891090 /// Switch back to pay-per-own-transaction model.1091 ///1092 /// # Permissions1093 ///1094 /// * Collection owner.1095 /// 1096 /// # Arguments1097 /// 1098 /// * collection_id.1099 #[weight = <T as Config>::WeightInfo::remove_collection_sponsor()]1100 #[transactional]1101 pub fn remove_collection_sponsor(origin, collection_id: CollectionId) -> DispatchResult {1102 let sender = ensure_signed(origin)?;11031104 let mut target_collection = Self::get_collection(collection_id)?;1105 Self::check_owner_permissions(&target_collection, &T::CrossAccountId::from_sub(sender))?;11061107 target_collection.sponsorship = SponsorshipState::Disabled;1108 Self::save_collection(target_collection);11091110 Ok(())1111 }11121113 /// This method creates a concrete instance of NFT Collection created with CreateCollection method.1114 /// 1115 /// # Permissions1116 /// 1117 /// * Collection Owner.1118 /// * Collection Admin.1119 /// * Anyone if1120 /// * White List is enabled, and1121 /// * Address is added to white list, and1122 /// * MintPermission is enabled (see SetMintPermission method)1123 /// 1124 /// # Arguments1125 /// 1126 /// * collection_id: ID of the collection.1127 /// 1128 /// * owner: Address, initial owner of the NFT.1129 ///1130 /// * data: Token data to store on chain.1131 // #[weight =1132 // (130_000_000 as Weight)1133 // .saturating_add((2135 as Weight).saturating_mul((properties.len() as u64) as Weight))1134 // .saturating_add(RocksDbWeight::get().reads(10 as Weight))1135 // .saturating_add(RocksDbWeight::get().writes(8 as Weight))]11361137 #[weight = <T as Config>::WeightInfo::create_item(data.len())]1138 #[transactional]1139 pub fn create_item(origin, collection_id: CollectionId, owner: T::CrossAccountId, data: CreateItemData) -> DispatchResult {11401141 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);11421143 let target_collection = Self::get_collection(collection_id)?;11441145 Self::can_create_items_in_collection(&target_collection, &sender, &owner, 1)?;1146 Self::validate_create_item_args(&target_collection, &data)?;1147 Self::create_item_no_validation(&target_collection, owner, data)?;11481149 Self::submit_logs(target_collection)?;1150 Ok(())1151 }11521153 /// This method creates multiple items in a collection created with CreateCollection method.1154 /// 1155 /// # Permissions1156 /// 1157 /// * Collection Owner.1158 /// * Collection Admin.1159 /// * Anyone if1160 /// * White List is enabled, and1161 /// * Address is added to white list, and1162 /// * MintPermission is enabled (see SetMintPermission method)1163 /// 1164 /// # Arguments1165 /// 1166 /// * collection_id: ID of the collection.1167 /// 1168 /// * itemsData: Array items properties. Each property is an array of bytes itself, see [create_item].1169 /// 1170 /// * owner: Address, initial owner of the NFT.1171 #[weight = <T as Config>::WeightInfo::create_item(items_data.into_iter()1172 .map(|data| { data.len() })1173 .sum())]1174 #[transactional]1175 pub fn create_multiple_items(origin, collection_id: CollectionId, owner: T::CrossAccountId, items_data: Vec<CreateItemData>) -> DispatchResult {11761177 ensure!(items_data.len() > 0, Error::<T>::EmptyArgument);1178 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);1179 let collection = Self::get_collection(collection_id)?;11801181 Self::create_multiple_items_internal(sender, &collection, owner, items_data)?;11821183 Self::submit_logs(collection)?;1184 Ok(())1185 }11861187 /// Destroys a concrete instance of NFT.1188 /// 1189 /// # Permissions1190 /// 1191 /// * Collection Owner.1192 /// * Collection Admin.1193 /// * Current NFT Owner.1194 /// 1195 /// # Arguments1196 /// 1197 /// * collection_id: ID of the collection.1198 /// 1199 /// * item_id: ID of NFT to burn.1200 #[weight = <T as Config>::WeightInfo::burn_item()]1201 #[transactional]1202 pub fn burn_item(origin, collection_id: CollectionId, item_id: TokenId, value: u128) -> DispatchResult {12031204 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);1205 let target_collection = Self::get_collection(collection_id)?;12061207 Self::burn_item_internal(&sender, &target_collection, item_id, value)?;12081209 Self::submit_logs(target_collection)?;1210 Ok(())1211 }12121213 /// Change ownership of the token.1214 /// 1215 /// # Permissions1216 /// 1217 /// * Collection Owner1218 /// * Collection Admin1219 /// * Current NFT owner1220 ///1221 /// # Arguments1222 /// 1223 /// * recipient: Address of token recipient.1224 /// 1225 /// * collection_id.1226 /// 1227 /// * item_id: ID of the item1228 /// * Non-Fungible Mode: Required.1229 /// * Fungible Mode: Ignored.1230 /// * Re-Fungible Mode: Required.1231 /// 1232 /// * value: Amount to transfer.1233 /// * Non-Fungible Mode: Ignored1234 /// * Fungible Mode: Must specify transferred amount1235 /// * Re-Fungible Mode: Must specify transferred portion (between 0 and 1)1236 #[weight = <T as Config>::WeightInfo::transfer()]1237 #[transactional]1238 pub fn transfer(origin, recipient: T::CrossAccountId, collection_id: CollectionId, item_id: TokenId, value: u128) -> DispatchResult {1239 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);1240 let collection = Self::get_collection(collection_id)?;12411242 Self::transfer_internal(sender, recipient, &collection, item_id, value)?;12431244 Self::submit_logs(collection)?;1245 Ok(())1246 }12471248 /// Set, change, or remove approved address to transfer the ownership of the NFT.1249 /// 1250 /// # Permissions1251 /// 1252 /// * Collection Owner1253 /// * Collection Admin1254 /// * Current NFT owner1255 /// 1256 /// # Arguments1257 /// 1258 /// * approved: Address that is approved to transfer this NFT or zero (if needed to remove approval).1259 /// 1260 /// * collection_id.1261 /// 1262 /// * item_id: ID of the item.1263 #[weight = <T as Config>::WeightInfo::approve()]1264 #[transactional]1265 pub fn approve(origin, spender: T::CrossAccountId, collection_id: CollectionId, item_id: TokenId, amount: u128) -> DispatchResult {12661267 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);1268 let collection = Self::get_collection(collection_id)?;12691270 Self::approve_internal(sender, spender, &collection, item_id, amount)?;12711272 Self::submit_logs(collection)?;1273 Ok(())1274 }1275 1276 /// Change ownership of a NFT on behalf of the owner. See Approve method for additional information. After this method executes, the approval is removed so that the approved address will not be able to transfer this NFT again from this owner.1277 /// 1278 /// # Permissions1279 /// * Collection Owner1280 /// * Collection Admin1281 /// * Current NFT owner1282 /// * Address approved by current NFT owner1283 /// 1284 /// # Arguments1285 /// 1286 /// * from: Address that owns token.1287 /// 1288 /// * recipient: Address of token recipient.1289 /// 1290 /// * collection_id.1291 /// 1292 /// * item_id: ID of the item.1293 /// 1294 /// * value: Amount to transfer.1295 #[weight = <T as Config>::WeightInfo::transfer_from()]1296 #[transactional]1297 pub fn transfer_from(origin, from: T::CrossAccountId, recipient: T::CrossAccountId, collection_id: CollectionId, item_id: TokenId, value: u128 ) -> DispatchResult {12981299 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);1300 let collection = Self::get_collection(collection_id)?;13011302 Self::transfer_from_internal(sender, from, recipient, &collection, item_id, value)?;13031304 Self::submit_logs(collection)?;1305 Ok(())1306 }13071308 // #[weight = 0]1309 // pub fn safe_transfer_from(origin, collection_id: CollectionId, item_id: TokenId, new_owner: T::AccountId) -> DispatchResult {13101311 // // let no_perm_mes = "You do not have permissions to modify this collection";1312 // // ensure!(<ApprovedList<T>>::contains_key((collection_id, item_id)), no_perm_mes);1313 // // let list_itm = <ApprovedList<T>>::get((collection_id, item_id));1314 // // ensure!(list_itm.contains(&new_owner.clone()), no_perm_mes);13151316 // // // on_nft_received call13171318 // // Self::transfer(origin, collection_id, item_id, new_owner)?;13191320 // Ok(())1321 // }13221323 /// Set off-chain data schema.1324 /// 1325 /// # Permissions1326 /// 1327 /// * Collection Owner1328 /// * Collection Admin1329 /// 1330 /// # Arguments1331 /// 1332 /// * collection_id.1333 /// 1334 /// * schema: String representing the offchain data schema.1335 #[weight = <T as Config>::WeightInfo::set_variable_meta_data()]1336 #[transactional]1337 pub fn set_variable_meta_data (1338 origin,1339 collection_id: CollectionId,1340 item_id: TokenId,1341 data: Vec<u8>1342 ) -> DispatchResult {1343 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);1344 1345 let target_collection = Self::get_collection(collection_id)?;1346 Self::set_variable_meta_data_internal(sender, &target_collection, item_id, data)?;13471348 Ok(())1349 }1350 1351 /// Set schema standard1352 /// ImageURL1353 /// Unique1354 /// 1355 /// # Permissions1356 /// 1357 /// * Collection Owner1358 /// * Collection Admin1359 /// 1360 /// # Arguments1361 /// 1362 /// * collection_id.1363 /// 1364 /// * schema: SchemaVersion: enum1365 #[weight = <T as Config>::WeightInfo::set_schema_version()]1366 #[transactional]1367 pub fn set_schema_version(1368 origin,1369 collection_id: CollectionId,1370 version: SchemaVersion1371 ) -> DispatchResult {1372 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);1373 let mut target_collection = Self::get_collection(collection_id)?;1374 Self::check_owner_or_admin_permissions(&target_collection, &sender)?;1375 target_collection.schema_version = version;1376 Self::save_collection(target_collection);13771378 Ok(())1379 }13801381 /// Set off-chain data schema.1382 /// 1383 /// # Permissions1384 /// 1385 /// * Collection Owner1386 /// * Collection Admin1387 /// 1388 /// # Arguments1389 /// 1390 /// * collection_id.1391 /// 1392 /// * schema: String representing the offchain data schema.1393 #[weight = <T as Config>::WeightInfo::set_offchain_schema()]1394 #[transactional]1395 pub fn set_offchain_schema(1396 origin,1397 collection_id: CollectionId,1398 schema: Vec<u8>1399 ) -> DispatchResult {1400 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);1401 let mut target_collection = Self::get_collection(collection_id)?;1402 Self::check_owner_or_admin_permissions(&target_collection, &sender)?;14031404 // check schema limit1405 ensure!(schema.len() as u32 <= ChainLimit::get().offchain_schema_limit, "");14061407 target_collection.offchain_schema = schema;1408 Self::save_collection(target_collection);14091410 Ok(())1411 }14121413 /// Set const on-chain data schema.1414 /// 1415 /// # Permissions1416 /// 1417 /// * Collection Owner1418 /// * Collection Admin1419 /// 1420 /// # Arguments1421 /// 1422 /// * collection_id.1423 /// 1424 /// * schema: String representing the const on-chain data schema.1425 #[weight = <T as Config>::WeightInfo::set_const_on_chain_schema()]1426 #[transactional]1427 pub fn set_const_on_chain_schema (1428 origin,1429 collection_id: CollectionId,1430 schema: Vec<u8>1431 ) -> DispatchResult {1432 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);1433 let mut target_collection = Self::get_collection(collection_id)?;1434 Self::check_owner_or_admin_permissions(&target_collection, &sender)?;14351436 // check schema limit1437 ensure!(schema.len() as u32 <= ChainLimit::get().const_on_chain_schema_limit, "");14381439 target_collection.const_on_chain_schema = schema;1440 Self::save_collection(target_collection);14411442 Ok(())1443 }14441445 /// Set variable on-chain data schema.1446 /// 1447 /// # Permissions1448 /// 1449 /// * Collection Owner1450 /// * Collection Admin1451 /// 1452 /// # Arguments1453 /// 1454 /// * collection_id.1455 /// 1456 /// * schema: String representing the variable on-chain data schema.1457 #[weight = <T as Config>::WeightInfo::set_const_on_chain_schema()]1458 #[transactional]1459 pub fn set_variable_on_chain_schema (1460 origin,1461 collection_id: CollectionId,1462 schema: Vec<u8>1463 ) -> DispatchResult {1464 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);1465 let mut target_collection = Self::get_collection(collection_id)?;1466 Self::check_owner_or_admin_permissions(&target_collection, &sender)?;14671468 // check schema limit1469 ensure!(schema.len() as u32 <= ChainLimit::get().variable_on_chain_schema_limit, "");14701471 target_collection.variable_on_chain_schema = schema;1472 Self::save_collection(target_collection);14731474 Ok(())1475 }14761477 // Sudo permissions function1478 #[weight = <T as Config>::WeightInfo::set_chain_limits()]1479 #[transactional]1480 pub fn set_chain_limits(1481 origin,1482 limits: ChainLimits1483 ) -> DispatchResult {14841485 #[cfg(not(feature = "runtime-benchmarks"))]1486 ensure_root(origin)?;14871488 <ChainLimit>::put(limits);1489 Ok(())1490 }14911492 /// Enable smart contract self-sponsoring.1493 /// 1494 /// # Permissions1495 /// 1496 /// * Contract Owner1497 /// 1498 /// # Arguments1499 /// 1500 /// * contract address1501 /// * enable flag1502 /// 1503 #[weight = <T as Config>::WeightInfo::enable_contract_sponsoring()]1504 #[transactional]1505 pub fn enable_contract_sponsoring(1506 origin,1507 contract_address: T::AccountId,1508 enable: bool1509 ) -> DispatchResult {15101511 let sender = ensure_signed(origin)?;15121513 #[cfg(feature = "runtime-benchmarks")]1514 <ContractOwner<T>>::insert(contract_address.clone(), sender.clone());15151516 Self::ensure_contract_owned(sender, &contract_address)?;15171518 <ContractSelfSponsoring<T>>::insert(contract_address, enable);1519 Ok(())1520 }15211522 /// Set the rate limit for contract sponsoring to specified number of blocks.1523 /// 1524 /// If not set (has the default value of 0 blocks), the sponsoring will be disabled. 1525 /// If set to the number B (for blocks), the transactions will be sponsored with a rate 1526 /// limit of B, i.e. fees for every transaction sent to this smart contract will be paid 1527 /// from contract endowment if there are at least B blocks between such transactions. 1528 /// Nonetheless, if transactions are sent more frequently, the fees are paid by the sender.1529 /// 1530 /// # Permissions1531 /// 1532 /// * Contract Owner1533 /// 1534 /// # Arguments1535 /// 1536 /// -`contract_address`: Address of the contract to sponsor1537 /// -`rate_limit`: Number of blocks to wait until the next sponsored transaction is allowed1538 /// 1539 #[weight = <T as Config>::WeightInfo::set_contract_sponsoring_rate_limit()]1540 #[transactional]1541 pub fn set_contract_sponsoring_rate_limit(1542 origin,1543 contract_address: T::AccountId,1544 rate_limit: T::BlockNumber1545 ) -> DispatchResult {1546 let sender = ensure_signed(origin)?;15471548 #[cfg(feature = "runtime-benchmarks")]1549 <ContractOwner<T>>::insert(contract_address.clone(), sender.clone());15501551 Self::ensure_contract_owned(sender, &contract_address)?;1552 <ContractSponsoringRateLimit<T>>::insert(contract_address, rate_limit);1553 Ok(())1554 }15551556 /// Enable the white list for a contract. Only addresses added to the white list with addToContractWhiteList will be able to call this smart contract.1557 /// 1558 /// # Permissions1559 /// 1560 /// * Address that deployed smart contract.1561 /// 1562 /// # Arguments1563 /// 1564 /// -`contract_address`: Address of the contract.1565 /// 1566 /// - `enable`: . 1567 #[weight = <T as Config>::WeightInfo::toggle_contract_white_list()]1568 #[transactional]1569 pub fn toggle_contract_white_list(1570 origin,1571 contract_address: T::AccountId,1572 enable: bool1573 ) -> DispatchResult {1574 let sender = ensure_signed(origin)?;15751576 #[cfg(feature = "runtime-benchmarks")]1577 <ContractOwner<T>>::insert(contract_address.clone(), sender.clone());15781579 Self::ensure_contract_owned(sender, &contract_address)?;1580 if enable {1581 <ContractWhiteListEnabled<T>>::insert(contract_address, true);1582 } else {1583 <ContractWhiteListEnabled<T>>::remove(contract_address);1584 }1585 Ok(())1586 }1587 1588 /// Add an address to smart contract white list.1589 /// 1590 /// # Permissions1591 /// 1592 /// * Address that deployed smart contract.1593 /// 1594 /// # Arguments1595 /// 1596 /// -`contract_address`: Address of the contract.1597 ///1598 /// -`account_address`: Address to add.1599 #[weight = <T as Config>::WeightInfo::add_to_contract_white_list()]1600 #[transactional]1601 pub fn add_to_contract_white_list(1602 origin,1603 contract_address: T::AccountId,1604 account_address: T::AccountId1605 ) -> DispatchResult {1606 let sender = ensure_signed(origin)?;16071608 #[cfg(feature = "runtime-benchmarks")]1609 <ContractOwner<T>>::insert(contract_address.clone(), sender.clone());1610 1611 Self::ensure_contract_owned(sender, &contract_address)?; 1612 <ContractWhiteList<T>>::insert(contract_address, account_address, true);1613 Ok(())1614 }16151616 /// Remove an address from smart contract white list.1617 /// 1618 /// # Permissions1619 /// 1620 /// * Address that deployed smart contract.1621 /// 1622 /// # Arguments1623 /// 1624 /// -`contract_address`: Address of the contract.1625 ///1626 /// -`account_address`: Address to remove.1627 #[weight = <T as Config>::WeightInfo::remove_from_contract_white_list()]1628 #[transactional]1629 pub fn remove_from_contract_white_list(1630 origin,1631 contract_address: T::AccountId,1632 account_address: T::AccountId1633 ) -> DispatchResult {1634 let sender = ensure_signed(origin)?;16351636 #[cfg(feature = "runtime-benchmarks")]1637 <ContractOwner<T>>::insert(contract_address.clone(), sender.clone());16381639 Self::ensure_contract_owned(sender, &contract_address)?;1640 <ContractWhiteList<T>>::remove(contract_address, account_address);1641 Ok(())1642 }16431644 #[weight = <T as Config>::WeightInfo::set_collection_limits()]1645 #[transactional]1646 pub fn set_collection_limits(1647 origin,1648 collection_id: u32,1649 new_limits: CollectionLimits<T::BlockNumber>,1650 ) -> DispatchResult {1651 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);1652 let mut target_collection = Self::get_collection(collection_id)?;1653 Self::check_owner_permissions(&target_collection, &sender)?;1654 let old_limits = &target_collection.limits;1655 let chain_limits = ChainLimit::get();16561657 // collection bounds1658 ensure!(new_limits.sponsor_transfer_timeout <= MAX_SPONSOR_TIMEOUT &&1659 new_limits.account_token_ownership_limit <= MAX_TOKEN_OWNERSHIP && 1660 new_limits.sponsored_data_size <= chain_limits.custom_data_limit,1661 Error::<T>::CollectionLimitBoundsExceeded);16621663 // token_limit check prev1664 ensure!(old_limits.token_limit >= new_limits.token_limit, Error::<T>::CollectionTokenLimitExceeded);1665 ensure!(new_limits.token_limit > 0, Error::<T>::CollectionTokenLimitExceeded);16661667 ensure!(1668 (old_limits.owner_can_transfer || !new_limits.owner_can_transfer) &&1669 (old_limits.owner_can_destroy || !new_limits.owner_can_destroy),1670 Error::<T>::OwnerPermissionsCantBeReverted,1671 );16721673 target_collection.limits = new_limits;1674 Self::save_collection(target_collection);16751676 Ok(())1677 } 1678 }1679}16801681impl<T: Config> Module<T> {16821683 pub fn transfer_internal(sender: T::CrossAccountId, recipient: T::CrossAccountId, target_collection: &CollectionHandle<T>, item_id: TokenId, value: u128) -> DispatchResult {1684 // Limits check1685 Self::is_correct_transfer(target_collection, &recipient)?;16861687 // Transfer permissions check1688 ensure!(Self::is_item_owner(&sender, target_collection, item_id) ||1689 Self::is_owner_or_admin_permissions(target_collection, &sender),1690 Error::<T>::NoPermission);16911692 if target_collection.access == AccessMode::WhiteList {1693 Self::check_white_list(target_collection, &sender)?;1694 Self::check_white_list(target_collection, &recipient)?;1695 }16961697 match target_collection.mode1698 {1699 CollectionMode::NFT => Self::transfer_nft(target_collection, item_id, sender.clone(), recipient.clone())?,1700 CollectionMode::Fungible(_) => Self::transfer_fungible(target_collection, value, &sender, &recipient)?,1701 CollectionMode::ReFungible => Self::transfer_refungible(target_collection, item_id, value, sender.clone(), recipient.clone())?,1702 _ => ()1703 };17041705 Self::deposit_event(RawEvent::Transfer(target_collection.id, item_id, sender, recipient, value));17061707 Ok(())1708 }17091710 pub fn approve_internal(1711 sender: T::CrossAccountId,1712 spender: T::CrossAccountId,1713 collection: &CollectionHandle<T>,1714 item_id: TokenId,1715 amount: u1281716 ) -> DispatchResult {1717 Self::token_exists(&collection, item_id)?;17181719 // Transfer permissions check1720 let bypasses_limits = collection.limits.owner_can_transfer &&1721 Self::is_owner_or_admin_permissions(1722 &collection,1723 &sender,1724 );17251726 let allowance_limit = if bypasses_limits {1727 None1728 } else if let Some(amount) = Self::owned_amount(1729 &sender,1730 &collection,1731 item_id,1732 ) {1733 Some(amount)1734 } else {1735 fail!(Error::<T>::NoPermission);1736 };17371738 if collection.access == AccessMode::WhiteList {1739 Self::check_white_list(&collection, &sender)?;1740 Self::check_white_list(&collection, &spender)?;1741 }17421743 let allowance: u128 = amount1744 .checked_add(<Allowances<T>>::get(collection.id, (item_id, sender.as_sub(), spender.as_sub())))1745 .ok_or(Error::<T>::NumOverflow)?;1746 if let Some(limit) = allowance_limit {1747 ensure!(limit >= allowance, Error::<T>::TokenValueTooLow);1748 }1749 <Allowances<T>>::insert(collection.id, (item_id, sender.as_sub(), spender.as_sub()), allowance);17501751 if matches!(collection.mode, CollectionMode::NFT) {1752 // TODO: NFT: only one owner may exist for token in ERC7211753 collection.log(1754 Vec::from([1755 eth::APPROVAL_NFT_TOPIC,1756 eth::address_to_topic(sender.as_eth()),1757 eth::address_to_topic(spender.as_eth()),1758 ]),1759 abi_encode!(uint256(item_id.into())),1760 );1761 }17621763 if matches!(collection.mode, CollectionMode::Fungible(_)) {1764 // TODO: NFT: only one owner may exist for token in ERC201765 collection.log(1766 Vec::from([1767 eth::APPROVAL_FUNGIBLE_TOPIC,1768 eth::address_to_topic(sender.as_eth()),1769 eth::address_to_topic(spender.as_eth()),1770 ]),1771 abi_encode!(uint256(allowance.into())),1772 );1773 }17741775 Self::deposit_event(RawEvent::Approved(collection.id, item_id, sender, spender, allowance));1776 Ok(())1777 }17781779 pub fn transfer_from_internal(1780 sender: T::CrossAccountId,1781 from: T::CrossAccountId,1782 recipient: T::CrossAccountId,1783 collection: &CollectionHandle<T>,1784 item_id: TokenId,1785 amount: u128,1786 ) -> DispatchResult {1787 // Check approval1788 let approval: u128 = <Allowances<T>>::get(collection.id, (item_id, from.as_sub(), sender.as_sub()));17891790 // Limits check1791 Self::is_correct_transfer(&collection, &recipient)?;17921793 // Transfer permissions check1794 ensure!(1795 approval >= amount || 1796 (1797 collection.limits.owner_can_transfer &&1798 Self::is_owner_or_admin_permissions(&collection, &sender)1799 ),1800 Error::<T>::NoPermission1801 );18021803 if collection.access == AccessMode::WhiteList {1804 Self::check_white_list(&collection, &sender)?;1805 Self::check_white_list(&collection, &recipient)?;1806 }18071808 // Reduce approval by transferred amount or remove if remaining approval drops to 01809 let allowance = approval.saturating_sub(amount);1810 if allowance > 0 {1811 <Allowances<T>>::insert(collection.id, (item_id, from.as_sub(), sender.as_sub()), allowance);1812 } else {1813 <Allowances<T>>::remove(collection.id, (item_id, from.as_sub(), sender.as_sub()));1814 }18151816 match collection.mode {1817 CollectionMode::NFT => {1818 Self::transfer_nft(&collection, item_id, from.clone(), recipient.clone())?1819 }1820 CollectionMode::Fungible(_) => {1821 Self::transfer_fungible(&collection, amount, &from, &recipient)?1822 }1823 CollectionMode::ReFungible => {1824 Self::transfer_refungible(&collection, item_id, amount, from.clone(), recipient.clone())?1825 }1826 _ => ()1827 };18281829 if matches!(collection.mode, CollectionMode::Fungible(_)) {1830 collection.log(1831 Vec::from([1832 eth::APPROVAL_FUNGIBLE_TOPIC,1833 eth::address_to_topic(from.as_eth()),1834 eth::address_to_topic(sender.as_eth()),1835 ]),1836 abi_encode!(uint256(allowance.into())),1837 );1838 }18391840 Ok(())1841 }18421843 pub fn set_variable_meta_data_internal(1844 sender: T::CrossAccountId,1845 collection: &CollectionHandle<T>, 1846 item_id: TokenId,1847 data: Vec<u8>,1848 ) -> DispatchResult {1849 Self::token_exists(&collection, item_id)?;18501851 ensure!(ChainLimit::get().custom_data_limit >= data.len() as u32, Error::<T>::TokenVariableDataLimitExceeded);18521853 // Modify permissions check1854 ensure!(Self::is_item_owner(&sender, &collection, item_id) ||1855 Self::is_owner_or_admin_permissions(&collection, &sender),1856 Error::<T>::NoPermission);18571858 match collection.mode1859 {1860 CollectionMode::NFT => Self::set_nft_variable_data(&collection, item_id, data)?,1861 CollectionMode::ReFungible => Self::set_re_fungible_variable_data(&collection, item_id, data)?,1862 CollectionMode::Fungible(_) => fail!(Error::<T>::CantStoreMetadataInFungibleTokens),1863 _ => fail!(Error::<T>::UnexpectedCollectionType)1864 };18651866 Ok(())1867 }18681869 pub fn create_multiple_items_internal(1870 sender: T::CrossAccountId,1871 collection: &CollectionHandle<T>,1872 owner: T::CrossAccountId,1873 items_data: Vec<CreateItemData>,1874 ) -> DispatchResult {1875 Self::can_create_items_in_collection(&collection, &sender, &owner, items_data.len() as u32)?;18761877 for data in &items_data {1878 Self::validate_create_item_args(&collection, data)?;1879 }1880 for data in &items_data {1881 Self::create_item_no_validation(&collection, owner.clone(), data.clone())?;1882 }18831884 Ok(())1885 }18861887 pub fn burn_item_internal(1888 sender: &T::CrossAccountId,1889 collection: &CollectionHandle<T>,1890 item_id: TokenId,1891 value: u128,1892 ) -> DispatchResult {1893 ensure!(1894 Self::is_item_owner(&sender, &collection, item_id) ||1895 (1896 collection.limits.owner_can_transfer &&1897 Self::is_owner_or_admin_permissions(&collection, &sender)1898 ),1899 Error::<T>::NoPermission1900 );19011902 if collection.access == AccessMode::WhiteList {1903 Self::check_white_list(&collection, &sender)?;1904 }19051906 match collection.mode1907 {1908 CollectionMode::NFT => Self::burn_nft_item(&collection, item_id)?,1909 CollectionMode::Fungible(_) => Self::burn_fungible_item(&sender, &collection, value)?,1910 CollectionMode::ReFungible => Self::burn_refungible_item(&collection, item_id, &sender)?,1911 _ => ()1912 };19131914 Ok(())1915 }19161917 fn is_correct_transfer(collection: &CollectionHandle<T>, recipient: &T::CrossAccountId) -> DispatchResult {1918 let collection_id = collection.id;19191920 // check token limit and account token limit1921 let account_items: u32 = <AddressTokens<T>>::get(collection_id, recipient.as_sub()).len() as u32;1922 ensure!(collection.limits.account_token_ownership_limit > account_items, Error::<T>::AccountTokenLimitExceeded);1923 1924 Ok(())1925 }19261927 fn can_create_items_in_collection(collection: &CollectionHandle<T>, sender: &T::CrossAccountId, owner: &T::CrossAccountId, amount: u32) -> DispatchResult {1928 let collection_id = collection.id;19291930 // check token limit and account token limit1931 let total_items: u32 = ItemListIndex::get(collection_id)1932 .checked_add(amount)1933 .ok_or(Error::<T>::CollectionTokenLimitExceeded)?;1934 let account_items: u32 = (<AddressTokens<T>>::get(collection_id, owner.as_sub()).len() as u32)1935 .checked_add(amount)1936 .ok_or(Error::<T>::AccountTokenLimitExceeded)?;1937 ensure!(collection.limits.token_limit >= total_items, Error::<T>::CollectionTokenLimitExceeded);1938 ensure!(collection.limits.account_token_ownership_limit >= account_items, Error::<T>::AccountTokenLimitExceeded);19391940 if !Self::is_owner_or_admin_permissions(collection, &sender) {1941 ensure!(collection.mint_mode == true, Error::<T>::PublicMintingNotAllowed);1942 Self::check_white_list(collection, owner)?;1943 Self::check_white_list(collection, sender)?;1944 }19451946 Ok(())1947 }19481949 fn validate_create_item_args(target_collection: &CollectionHandle<T>, data: &CreateItemData) -> DispatchResult {1950 match target_collection.mode1951 {1952 CollectionMode::NFT => {1953 if let CreateItemData::NFT(data) = data {1954 // check sizes1955 ensure!(ChainLimit::get().custom_data_limit >= data.const_data.len() as u32, Error::<T>::TokenConstDataLimitExceeded);1956 ensure!(ChainLimit::get().custom_data_limit >= data.variable_data.len() as u32, Error::<T>::TokenVariableDataLimitExceeded);1957 } else {1958 fail!(Error::<T>::NotNftDataUsedToMintNftCollectionToken);1959 }1960 },1961 CollectionMode::Fungible(_) => {1962 if let CreateItemData::Fungible(_) = data {1963 } else {1964 fail!(Error::<T>::NotFungibleDataUsedToMintFungibleCollectionToken);1965 }1966 },1967 CollectionMode::ReFungible => {1968 if let CreateItemData::ReFungible(data) = data {19691970 // check sizes1971 ensure!(ChainLimit::get().custom_data_limit >= data.const_data.len() as u32, Error::<T>::TokenConstDataLimitExceeded);1972 ensure!(ChainLimit::get().custom_data_limit >= data.variable_data.len() as u32, Error::<T>::TokenVariableDataLimitExceeded);19731974 // Check refungibility limits1975 ensure!(data.pieces <= MAX_REFUNGIBLE_PIECES, Error::<T>::WrongRefungiblePieces);1976 ensure!(data.pieces > 0, Error::<T>::WrongRefungiblePieces);1977 } else {1978 fail!(Error::<T>::NotReFungibleDataUsedToMintReFungibleCollectionToken);1979 }1980 },1981 _ => { fail!(Error::<T>::UnexpectedCollectionType); }1982 };19831984 Ok(())1985 }19861987 fn create_item_no_validation(collection: &CollectionHandle<T>, owner: T::CrossAccountId, data: CreateItemData) -> DispatchResult {1988 match data1989 {1990 CreateItemData::NFT(data) => {1991 let item = NftItemType {1992 owner: owner.clone(),1993 const_data: data.const_data,1994 variable_data: data.variable_data1995 };19961997 Self::add_nft_item(collection, item)?;1998 },1999 CreateItemData::Fungible(data) => {2000 Self::add_fungible_item(collection, &owner, data.value)?;2001 },2002 CreateItemData::ReFungible(data) => {2003 let mut owner_list = Vec::new();2004 owner_list.push(Ownership {owner: owner.clone(), fraction: data.pieces});20052006 let item = ReFungibleItemType {2007 owner: owner_list,2008 const_data: data.const_data,2009 variable_data: data.variable_data2010 };20112012 Self::add_refungible_item(collection, item)?;2013 }2014 };20152016 Ok(())2017 }20182019 fn add_fungible_item(collection: &CollectionHandle<T>, owner: &T::CrossAccountId, value: u128) -> DispatchResult {2020 let collection_id = collection.id;20212022 // Does new owner already have an account?2023 let balance: u128 = <FungibleItemList<T>>::get(collection_id, owner.as_sub()).value;20242025 // Mint 2026 let item = FungibleItemType {2027 value: balance.checked_add(value).ok_or(Error::<T>::NumOverflow)?,2028 };2029 <FungibleItemList<T>>::insert(collection_id, owner.as_sub(), item);20302031 // Update balance2032 let new_balance = <Balance<T>>::get(collection_id, owner.as_sub())2033 .checked_add(value)2034 .ok_or(Error::<T>::NumOverflow)?;2035 <Balance<T>>::insert(collection_id, owner.as_sub(), new_balance);20362037 Self::deposit_event(RawEvent::ItemCreated(collection_id, 0, owner.clone()));2038 Ok(())2039 }20402041 fn add_refungible_item(collection: &CollectionHandle<T>, item: ReFungibleItemType<T::CrossAccountId>) -> DispatchResult {2042 let collection_id = collection.id;20432044 let current_index = <ItemListIndex>::get(collection_id)2045 .checked_add(1)2046 .ok_or(Error::<T>::NumOverflow)?;2047 let itemcopy = item.clone();20482049 ensure!(2050 item.owner.len() == 1,2051 Error::<T>::BadCreateRefungibleCall,2052 );2053 let item_owner = item.owner.first().expect("only one owner is defined");20542055 let value = item_owner.fraction;2056 let owner = item_owner.owner.clone();20572058 Self::add_token_index(collection_id, current_index, &owner)?;20592060 <ItemListIndex>::insert(collection_id, current_index);2061 <ReFungibleItemList<T>>::insert(collection_id, current_index, itemcopy);20622063 // Update balance2064 let new_balance = <Balance<T>>::get(collection_id, owner.as_sub())2065 .checked_add(value)2066 .ok_or(Error::<T>::NumOverflow)?;2067 <Balance<T>>::insert(collection_id, owner.as_sub(), new_balance);20682069 Self::deposit_event(RawEvent::ItemCreated(collection_id, current_index, owner));2070 Ok(())2071 }20722073 fn add_nft_item(collection: &CollectionHandle<T>, item: NftItemType<T::CrossAccountId>) -> DispatchResult {2074 let collection_id = collection.id;20752076 let current_index = <ItemListIndex>::get(collection_id)2077 .checked_add(1)2078 .ok_or(Error::<T>::NumOverflow)?;20792080 let item_owner = item.owner.clone();2081 Self::add_token_index(collection_id, current_index, &item.owner)?;20822083 <ItemListIndex>::insert(collection_id, current_index);2084 <NftItemList<T>>::insert(collection_id, current_index, item);20852086 // Update balance2087 let new_balance = <Balance<T>>::get(collection_id, item_owner.as_sub())2088 .checked_add(1)2089 .ok_or(Error::<T>::NumOverflow)?;2090 <Balance<T>>::insert(collection_id, item_owner.as_sub(), new_balance);20912092 collection.log(2093 Vec::from([2094 eth::TRANSFER_NFT_TOPIC,2095 eth::address_to_topic(&H160::default()),2096 eth::address_to_topic(item_owner.as_eth()),2097 ]),2098 abi_encode!(uint256(current_index.into())),2099 );2100 Self::deposit_event(RawEvent::ItemCreated(collection_id, current_index, item_owner));2101 Ok(())2102 }21032104 fn burn_refungible_item(2105 collection: &CollectionHandle<T>,2106 item_id: TokenId,2107 owner: &T::CrossAccountId,2108 ) -> DispatchResult {2109 let collection_id = collection.id;21102111 let mut token = <ReFungibleItemList<T>>::get(collection_id, item_id)2112 .ok_or(Error::<T>::TokenNotFound)?;2113 let rft_balance = token2114 .owner2115 .iter()2116 .find(|&i| i.owner == *owner)2117 .ok_or(Error::<T>::TokenNotFound)?;2118 Self::remove_token_index(collection_id, item_id, owner)?;21192120 // update balance2121 let new_balance = <Balance<T>>::get(collection_id, rft_balance.owner.as_sub())2122 .checked_sub(rft_balance.fraction)2123 .ok_or(Error::<T>::NumOverflow)?;2124 <Balance<T>>::insert(collection_id, rft_balance.owner.as_sub(), new_balance);21252126 // Re-create owners list with sender removed2127 let index = token2128 .owner2129 .iter()2130 .position(|i| i.owner == *owner)2131 .expect("owned item is exists");2132 token.owner.remove(index);2133 let owner_count = token.owner.len();21342135 // Burn the token completely if this was the last (only) owner2136 if owner_count == 0 {2137 <ReFungibleItemList<T>>::remove(collection_id, item_id);2138 <VariableMetaDataBasket<T>>::remove(collection_id, item_id);2139 }2140 else {2141 <ReFungibleItemList<T>>::insert(collection_id, item_id, token);2142 }21432144 Ok(())2145 }21462147 fn burn_nft_item(collection: &CollectionHandle<T>, item_id: TokenId) -> DispatchResult {2148 let collection_id = collection.id;21492150 let item = <NftItemList<T>>::get(collection_id, item_id)2151 .ok_or(Error::<T>::TokenNotFound)?;2152 Self::remove_token_index(collection_id, item_id, &item.owner)?;21532154 // update balance2155 let new_balance = <Balance<T>>::get(collection_id, item.owner.as_sub())2156 .checked_sub(1)2157 .ok_or(Error::<T>::NumOverflow)?;2158 <Balance<T>>::insert(collection_id, item.owner.as_sub(), new_balance);2159 <NftItemList<T>>::remove(collection_id, item_id);2160 <VariableMetaDataBasket<T>>::remove(collection_id, item_id);21612162 Self::deposit_event(RawEvent::ItemDestroyed(collection.id, item_id));2163 Ok(())2164 }21652166 fn burn_fungible_item(owner: &T::CrossAccountId, collection: &CollectionHandle<T>, value: u128) -> DispatchResult {2167 let collection_id = collection.id;21682169 let mut balance = <FungibleItemList<T>>::get(collection_id, owner.as_sub());2170 ensure!(balance.value >= value, Error::<T>::TokenValueNotEnough);21712172 // update balance2173 let new_balance = <Balance<T>>::get(collection_id, owner.as_sub())2174 .checked_sub(value)2175 .ok_or(Error::<T>::NumOverflow)?;2176 <Balance<T>>::insert(collection_id, owner.as_sub(), new_balance);21772178 if balance.value - value > 0 {2179 balance.value -= value;2180 <FungibleItemList<T>>::insert(collection_id, owner.as_sub(), balance);2181 }2182 else {2183 <FungibleItemList<T>>::remove(collection_id, owner.as_sub());2184 }21852186 collection.log(2187 Vec::from([2188 eth::TRANSFER_FUNGIBLE_TOPIC,2189 eth::address_to_topic(owner.as_eth()),2190 eth::address_to_topic(&H160::default()),2191 ]),2192 abi_encode!(uint256(value.into())),2193 );2194 Ok(())2195 }21962197 pub fn get_collection(collection_id: CollectionId) -> Result<CollectionHandle<T>, sp_runtime::DispatchError> {2198 Ok(<CollectionHandle<T>>::get(collection_id)2199 .ok_or(Error::<T>::CollectionNotFound)?)2200 }22012202 fn save_collection(collection: CollectionHandle<T>) {2203 <CollectionById<T>>::insert(collection.id, collection.into_inner());2204 }22052206 fn submit_logs(collection: CollectionHandle<T>) -> DispatchResult {2207 T::EthereumTransactionSender::submit_logs_transaction(2208 eth::generate_transaction(collection.id, T::EthereumChainId::get()),2209 collection.logs.retrieve_logs_for_contract(eth::collection_id_to_address(collection.id)),2210 )2211 }22122213 fn check_owner_permissions(target_collection: &CollectionHandle<T>, subject: &T::CrossAccountId) -> DispatchResult {2214 ensure!(2215 *subject == target_collection.owner,2216 Error::<T>::NoPermission2217 );22182219 Ok(())2220 }22212222 fn is_owner_or_admin_permissions(collection: &CollectionHandle<T>, subject: &T::CrossAccountId) -> bool {2223 *subject == collection.owner || <AdminList<T>>::get(collection.id).contains(&subject)2224 }22252226 fn check_owner_or_admin_permissions(2227 collection: &CollectionHandle<T>,2228 subject: &T::CrossAccountId,2229 ) -> DispatchResult {2230 ensure!(Self::is_owner_or_admin_permissions(collection, subject), Error::<T>::NoPermission);22312232 Ok(())2233 }22342235 fn owned_amount(2236 subject: &T::CrossAccountId,2237 target_collection: &CollectionHandle<T>,2238 item_id: TokenId,2239 ) -> Option<u128> {2240 let collection_id = target_collection.id;22412242 match target_collection.mode {2243 CollectionMode::NFT => (<NftItemList<T>>::get(collection_id, item_id)?.owner == *subject)2244 .then(|| 1),2245 CollectionMode::Fungible(_) => Some(<FungibleItemList<T>>::get(collection_id, &subject.as_sub())2246 .value),2247 CollectionMode::ReFungible => <ReFungibleItemList<T>>::get(collection_id, item_id)?2248 .owner2249 .iter()2250 .find(|i| i.owner == *subject)2251 .map(|i| i.fraction),2252 CollectionMode::Invalid => None,2253 }2254 }22552256 fn is_item_owner(subject: &T::CrossAccountId, target_collection: &CollectionHandle<T>, item_id: TokenId) -> bool {2257 match target_collection.mode {2258 CollectionMode::Fungible(_) => true,2259 _ => Self::owned_amount(&subject, target_collection, item_id).is_some(),2260 }2261 }22622263 fn check_white_list(collection: &CollectionHandle<T>, address: &T::CrossAccountId) -> DispatchResult {2264 let collection_id = collection.id;22652266 let mes = Error::<T>::AddresNotInWhiteList;2267 ensure!(<WhiteList<T>>::contains_key(collection_id, address.as_sub()), mes);22682269 Ok(())2270 }22712272 /// Check if token exists. In case of Fungible, check if there is an entry for 2273 /// the owner in fungible balances double map2274 fn token_exists(2275 target_collection: &CollectionHandle<T>,2276 item_id: TokenId,2277 ) -> DispatchResult {2278 let collection_id = target_collection.id;2279 let exists = match target_collection.mode2280 {2281 CollectionMode::NFT => <NftItemList<T>>::contains_key(collection_id, item_id),2282 CollectionMode::Fungible(_) => true,2283 CollectionMode::ReFungible => <ReFungibleItemList<T>>::contains_key(collection_id, item_id),2284 _ => false2285 };22862287 ensure!(exists == true, Error::<T>::TokenNotFound);2288 Ok(())2289 }22902291 fn transfer_fungible(2292 collection: &CollectionHandle<T>,2293 value: u128,2294 owner: &T::CrossAccountId,2295 recipient: &T::CrossAccountId,2296 ) -> DispatchResult {2297 let collection_id = collection.id;22982299 let mut balance = <FungibleItemList<T>>::get(collection_id, owner.as_sub());2300 ensure!(balance.value >= value, Error::<T>::TokenValueTooLow);23012302 // Send balance to recipient (updates balanceOf of recipient)2303 Self::add_fungible_item(collection, recipient, value)?;23042305 // update balanceOf of sender2306 <Balance<T>>::insert(collection_id, owner.as_sub(), balance.value - value);23072308 // Reduce or remove sender2309 if balance.value == value {2310 <FungibleItemList<T>>::remove(collection_id, owner.as_sub());2311 }2312 else {2313 balance.value -= value;2314 <FungibleItemList<T>>::insert(collection_id, owner.as_sub(), balance);2315 }23162317 collection.log(2318 Vec::from([2319 eth::TRANSFER_FUNGIBLE_TOPIC,2320 eth::address_to_topic(owner.as_eth()),2321 eth::address_to_topic(recipient.as_eth()),2322 ]),2323 abi_encode!(uint256(value.into())),2324 );2325 Self::deposit_event(RawEvent::Transfer(collection.id, 1, owner.clone(), recipient.clone(), value));23262327 Ok(())2328 }23292330 fn transfer_refungible(2331 collection: &CollectionHandle<T>,2332 item_id: TokenId,2333 value: u128,2334 owner: T::CrossAccountId,2335 new_owner: T::CrossAccountId,2336 ) -> DispatchResult {2337 let collection_id = collection.id;2338 let full_item = <ReFungibleItemList<T>>::get(collection_id, item_id)2339 .ok_or(Error::<T>::TokenNotFound)?;23402341 let item = full_item2342 .owner2343 .iter()2344 .filter(|i| i.owner == owner)2345 .next()2346 .ok_or(Error::<T>::TokenNotFound)?;2347 let amount = item.fraction;23482349 ensure!(amount >= value, Error::<T>::TokenValueTooLow);23502351 // update balance2352 let balance_old_owner = <Balance<T>>::get(collection_id, item.owner.as_sub())2353 .checked_sub(value)2354 .ok_or(Error::<T>::NumOverflow)?;2355 <Balance<T>>::insert(collection_id, item.owner.as_sub(), balance_old_owner);23562357 let balance_new_owner = <Balance<T>>::get(collection_id, new_owner.as_sub())2358 .checked_add(value)2359 .ok_or(Error::<T>::NumOverflow)?;2360 <Balance<T>>::insert(collection_id, new_owner.as_sub(), balance_new_owner);23612362 let old_owner = item.owner.clone();2363 let new_owner_has_account = full_item.owner.iter().any(|i| i.owner == new_owner);23642365 // transfer2366 if amount == value && !new_owner_has_account {2367 // change owner2368 // new owner do not have account2369 let mut new_full_item = full_item.clone();2370 new_full_item2371 .owner2372 .iter_mut()2373 .find(|i| i.owner == owner)2374 .expect("old owner does present in refungible")2375 .owner = new_owner.clone();2376 <ReFungibleItemList<T>>::insert(collection_id, item_id, new_full_item);23772378 // update index collection2379 Self::move_token_index(collection_id, item_id, &old_owner, &new_owner)?;2380 } else {2381 let mut new_full_item = full_item.clone();2382 new_full_item2383 .owner2384 .iter_mut()2385 .find(|i| i.owner == owner)2386 .expect("old owner does present in refungible")2387 .fraction -= value;23882389 // separate amount2390 if new_owner_has_account {2391 // new owner has account2392 new_full_item2393 .owner2394 .iter_mut()2395 .find(|i| i.owner == new_owner)2396 .expect("new owner has account")2397 .fraction += value;2398 } else {2399 // new owner do not have account2400 new_full_item.owner.push(Ownership {2401 owner: new_owner.clone(),2402 fraction: value,2403 });2404 Self::add_token_index(collection_id, item_id, &new_owner)?;2405 }24062407 <ReFungibleItemList<T>>::insert(collection_id, item_id, new_full_item);2408 }24092410 Self::deposit_event(RawEvent::Transfer(collection.id, item_id, owner, new_owner, amount));24112412 Ok(())2413 }24142415 fn transfer_nft(2416 collection: &CollectionHandle<T>,2417 item_id: TokenId,2418 sender: T::CrossAccountId,2419 new_owner: T::CrossAccountId,2420 ) -> DispatchResult {2421 let collection_id = collection.id;2422 let mut item = <NftItemList<T>>::get(collection_id, item_id)2423 .ok_or(Error::<T>::TokenNotFound)?;24242425 ensure!(2426 sender == item.owner,2427 Error::<T>::MustBeTokenOwner2428 );24292430 // update balance2431 let balance_old_owner = <Balance<T>>::get(collection_id, item.owner.as_sub())2432 .checked_sub(1)2433 .ok_or(Error::<T>::NumOverflow)?;2434 <Balance<T>>::insert(collection_id, item.owner.as_sub(), balance_old_owner);24352436 let balance_new_owner = <Balance<T>>::get(collection_id, new_owner.as_sub())2437 .checked_add(1)2438 .ok_or(Error::<T>::NumOverflow)?;2439 <Balance<T>>::insert(collection_id, new_owner.as_sub(), balance_new_owner);24402441 // change owner2442 let old_owner = item.owner.clone();2443 item.owner = new_owner.clone();2444 <NftItemList<T>>::insert(collection_id, item_id, item);24452446 // update index collection2447 Self::move_token_index(collection_id, item_id, &old_owner, &new_owner)?;24482449 collection.log(2450 Vec::from([2451 eth::TRANSFER_NFT_TOPIC,2452 eth::address_to_topic(sender.as_eth()),2453 eth::address_to_topic(new_owner.as_eth()),2454 ]),2455 abi_encode!(uint256(item_id.into())),2456 );2457 Self::deposit_event(RawEvent::Transfer(collection.id, item_id, sender, new_owner, 1));24582459 Ok(())2460 }2461 2462 fn set_re_fungible_variable_data(2463 collection: &CollectionHandle<T>,2464 item_id: TokenId,2465 data: Vec<u8>2466 ) -> DispatchResult {2467 let collection_id = collection.id;2468 let mut item = <ReFungibleItemList<T>>::get(collection_id, item_id)2469 .ok_or(Error::<T>::TokenNotFound)?;24702471 item.variable_data = data;24722473 <ReFungibleItemList<T>>::insert(collection_id, item_id, item);24742475 Ok(())2476 }24772478 fn set_nft_variable_data(2479 collection: &CollectionHandle<T>,2480 item_id: TokenId,2481 data: Vec<u8>2482 ) -> DispatchResult {2483 let collection_id = collection.id;2484 let mut item = <NftItemList<T>>::get(collection_id, item_id)2485 .ok_or(Error::<T>::TokenNotFound)?;2486 2487 item.variable_data = data;24882489 <NftItemList<T>>::insert(collection_id, item_id, item);2490 2491 Ok(())2492 }24932494 fn init_collection(item: &Collection<T>) {2495 // check params2496 assert!(2497 item.decimal_points <= MAX_DECIMAL_POINTS,2498 "decimal_points parameter must be lower than MAX_DECIMAL_POINTS"2499 );2500 assert!(2501 item.name.len() <= 64,2502 "Collection name can not be longer than 63 char"2503 );2504 assert!(2505 item.name.len() <= 256,2506 "Collection description can not be longer than 255 char"2507 );2508 assert!(2509 item.token_prefix.len() <= 16,2510 "Token prefix can not be longer than 15 char"2511 );25122513 // Generate next collection ID2514 let next_id = CreatedCollectionCount::get()2515 .checked_add(1)2516 .unwrap();25172518 CreatedCollectionCount::put(next_id);2519 }25202521 fn init_nft_token(collection_id: CollectionId, item: &NftItemType<T::CrossAccountId>) {2522 let current_index = <ItemListIndex>::get(collection_id)2523 .checked_add(1)2524 .unwrap();25252526 Self::add_token_index(collection_id, current_index, &item.owner).unwrap();25272528 <ItemListIndex>::insert(collection_id, current_index);25292530 // Update balance2531 let new_balance = <Balance<T>>::get(collection_id, item.owner.as_sub())2532 .checked_add(1)2533 .unwrap();2534 <Balance<T>>::insert(collection_id, item.owner.as_sub(), new_balance);2535 }25362537 fn init_fungible_token(collection_id: CollectionId, owner: &T::CrossAccountId, item: &FungibleItemType) {2538 let current_index = <ItemListIndex>::get(collection_id)2539 .checked_add(1)2540 .unwrap();25412542 Self::add_token_index(collection_id, current_index, owner).unwrap();25432544 <ItemListIndex>::insert(collection_id, current_index);25452546 // Update balance2547 let new_balance = <Balance<T>>::get(collection_id, owner.as_sub())2548 .checked_add(item.value)2549 .unwrap();2550 <Balance<T>>::insert(collection_id, owner.as_sub(), new_balance);2551 }25522553 fn init_refungible_token(collection_id: CollectionId, item: &ReFungibleItemType<T::CrossAccountId>) {2554 let current_index = <ItemListIndex>::get(collection_id)2555 .checked_add(1)2556 .unwrap();25572558 let value = item.owner.first().unwrap().fraction;2559 let owner = item.owner.first().unwrap().owner.clone();25602561 Self::add_token_index(collection_id, current_index, &owner).unwrap();25622563 <ItemListIndex>::insert(collection_id, current_index);25642565 // Update balance2566 let new_balance = <Balance<T>>::get(collection_id, &owner.as_sub())2567 .checked_add(value)2568 .unwrap();2569 <Balance<T>>::insert(collection_id, owner.as_sub(), new_balance);2570 }25712572 fn add_token_index(collection_id: CollectionId, item_index: TokenId, owner: &T::CrossAccountId) -> DispatchResult {2573 // add to account limit2574 if <AccountItemCount<T>>::contains_key(owner.as_sub()) {25752576 // bound Owned tokens by a single address2577 let count = <AccountItemCount<T>>::get(owner.as_sub());2578 ensure!(count < ChainLimit::get().account_token_ownership_limit, Error::<T>::AddressOwnershipLimitExceeded);25792580 <AccountItemCount<T>>::insert(owner.as_sub(), count2581 .checked_add(1)2582 .ok_or(Error::<T>::NumOverflow)?);2583 }2584 else {2585 <AccountItemCount<T>>::insert(owner.as_sub(), 1);2586 }25872588 let list_exists = <AddressTokens<T>>::contains_key(collection_id, owner.as_sub());2589 if list_exists {2590 let mut list = <AddressTokens<T>>::get(collection_id, owner.as_sub());2591 let item_contains = list.contains(&item_index.clone());25922593 if !item_contains {2594 list.push(item_index.clone());2595 }25962597 <AddressTokens<T>>::insert(collection_id, owner.as_sub(), list);2598 } else {2599 let mut itm = Vec::new();2600 itm.push(item_index.clone());2601 <AddressTokens<T>>::insert(collection_id, owner.as_sub(), itm);2602 }26032604 Ok(())2605 }26062607 fn remove_token_index(2608 collection_id: CollectionId,2609 item_index: TokenId,2610 owner: &T::CrossAccountId,2611 ) -> DispatchResult {26122613 // update counter2614 <AccountItemCount<T>>::insert(owner.as_sub(), 2615 <AccountItemCount<T>>::get(owner.as_sub())2616 .checked_sub(1)2617 .ok_or(Error::<T>::NumOverflow)?);261826192620 let list_exists = <AddressTokens<T>>::contains_key(collection_id, owner.as_sub());2621 if list_exists {2622 let mut list = <AddressTokens<T>>::get(collection_id, owner.as_sub());2623 let item_contains = list.contains(&item_index.clone());26242625 if item_contains {2626 list.retain(|&item| item != item_index);2627 <AddressTokens<T>>::insert(collection_id, owner.as_sub(), list);2628 }2629 }26302631 Ok(())2632 }26332634 fn move_token_index(2635 collection_id: CollectionId,2636 item_index: TokenId,2637 old_owner: &T::CrossAccountId,2638 new_owner: &T::CrossAccountId,2639 ) -> DispatchResult {2640 Self::remove_token_index(collection_id, item_index, old_owner)?;2641 Self::add_token_index(collection_id, item_index, new_owner)?;26422643 Ok(())2644 }2645 2646 fn ensure_contract_owned(account: T::AccountId, contract: &T::AccountId) -> DispatchResult {2647 ensure!(<ContractOwner<T>>::get(contract) == Some(account), Error::<T>::NoPermission);26482649 Ok(())2650 }2651}26522653////////////////////////////////////////////////////////////////////////////////////////////////////2654// Economic models2655// #region26562657/// Fee multiplier.2658pub type Multiplier = FixedU128;26592660type BalanceOf<T> = <<T as pallet_transaction_payment::Config>::OnChargeTransaction as pallet_transaction_payment::OnChargeTransaction<T>>::Balance;26612662/// Require the transactor pay for themselves and maybe include a tip to gain additional priority2663/// in the queue.2664#[derive(Encode, Decode, Clone, Eq, PartialEq)]2665pub struct ChargeTransactionPayment<T: Config>(#[codec(compact)] BalanceOf<T>);26662667impl<T: Config + Send + Sync> sp_std::fmt::Debug 2668 for ChargeTransactionPayment<T>2669{2670 #[cfg(feature = "std")]2671 fn fmt(&self, f: &mut sp_std::fmt::Formatter) -> sp_std::fmt::Result {2672 write!(f, "ChargeTransactionPayment<{:?}>", self.0)2673 }2674 #[cfg(not(feature = "std"))]2675 fn fmt(&self, _: &mut sp_std::fmt::Formatter) -> sp_std::fmt::Result {2676 Ok(())2677 }2678}26792680impl<T: Config> ChargeTransactionPayment<T>2681where2682 T::Call: Dispatchable<Info=DispatchInfo, PostInfo=PostDispatchInfo> + IsSubType<Call<T>> + IsSubType<pallet_contracts::Call<T>>,2683 BalanceOf<T>: Send + Sync + From<u64> + FixedPointOperand,2684 T::AccountId: AsRef<[u8]>,2685 T::AccountId: UncheckedFrom<T::Hash>,2686{2687 fn traditional_fee(2688 len: usize,2689 info: &DispatchInfoOf<T::Call>,2690 tip: BalanceOf<T>,2691 ) -> BalanceOf<T>2692 where2693 T::Call: Dispatchable<Info = DispatchInfo>,2694 {2695 <pallet_transaction_payment::Module<T>>::compute_fee(len as u32, info, tip)2696 }26972698 fn get_priority(len: usize, info: &DispatchInfoOf<T::Call>, final_fee: BalanceOf<T>) -> TransactionPriority {2699 let weight_saturation = T::BlockWeights::get().max_block / info.weight.max(1);2700 let max_block_length = *T::BlockLength::get().max.get(DispatchClass::Normal);2701 let len_saturation = max_block_length as u64 / (len as u64).max(1);2702 let coefficient: BalanceOf<T> = weight_saturation2703 .min(len_saturation)2704 .saturated_into::<BalanceOf<T>>();2705 final_fee2706 .saturating_mul(coefficient)2707 .saturated_into::<TransactionPriority>()2708 }27092710 fn withdraw_fee(2711 &self,2712 who: &T::AccountId,2713 call: &T::Call,2714 info: &DispatchInfoOf<T::Call>,2715 len: usize,2716 ) -> Result<2717 (2718 BalanceOf<T>,2719 <<T as pallet_transaction_payment::Config>::OnChargeTransaction as pallet_transaction_payment::OnChargeTransaction<T>>::LiquidityInfo,2720 ),2721 TransactionValidityError,2722 > {2723 let tip = self.0;27242725 let fee = Self::traditional_fee(len, info, tip);27262727 // Only mess with balances if fee is not zero.2728 if fee.is_zero() {2729 return <<T as pallet_transaction_payment::Config>::OnChargeTransaction as pallet_transaction_payment::OnChargeTransaction<T>>::withdraw_fee(who, call, info, fee, tip)2730 .map(|i| (fee, i));2731 }27322733 // Determine who is paying transaction fee based on ecnomic model2734 // Parse call to extract collection ID and access collection sponsor2735 let mut sponsor: Option<T::AccountId> = (|| match IsSubType::<Call<T>>::is_sub_type(call) {2736 Some(Call::create_item(collection_id, _owner, _properties)) => {2737 let collection = <CollectionById<T>>::get(collection_id)?;27382739 // sponsor timeout2740 let block_number = <system::Module<T>>::block_number() as T::BlockNumber;27412742 let limit = collection.limits.sponsor_transfer_timeout;2743 if <CreateItemBasket<T>>::contains_key((collection_id, &who)) {2744 let last_tx_block = <CreateItemBasket<T>>::get((collection_id, &who));2745 let limit_time = last_tx_block + limit.into();2746 if block_number <= limit_time {2747 return None;2748 }2749 }2750 <CreateItemBasket<T>>::insert((collection_id, who.clone()), block_number);27512752 // check free create limit2753 if collection.limits.sponsored_data_size >= (_properties.len() as u32) {2754 collection.sponsorship.sponsor()2755 .cloned()2756 } else {2757 None2758 }2759 }2760 Some(Call::transfer(_new_owner, collection_id, item_id, _value)) => {2761 let collection = <CollectionById<T>>::get(collection_id)?;2762 2763 let mut sponsor_transfer = false;2764 if collection.sponsorship.confirmed() {27652766 let collection_limits = collection.limits;2767 let collection_mode = collection.mode;2768 2769 // sponsor timeout2770 let block_number = <system::Module<T>>::block_number() as T::BlockNumber;2771 sponsor_transfer = match collection_mode {2772 CollectionMode::NFT => {2773 2774 // get correct limit2775 let limit: u32 = if collection_limits.sponsor_transfer_timeout > 0 {2776 collection_limits.sponsor_transfer_timeout2777 } else {2778 ChainLimit::get().nft_sponsor_transfer_timeout2779 };2780 2781 let mut sponsored = true;2782 if <NftTransferBasket<T>>::contains_key(collection_id, item_id) {2783 let last_tx_block = <NftTransferBasket<T>>::get(collection_id, item_id);2784 let limit_time = last_tx_block + limit.into();2785 if block_number <= limit_time {2786 sponsored = false;2787 }2788 }2789 if sponsored {2790 <NftTransferBasket<T>>::insert(collection_id, item_id, block_number);2791 }27922793 sponsored2794 }2795 CollectionMode::Fungible(_) => {2796 2797 // get correct limit2798 let limit: u32 = if collection_limits.sponsor_transfer_timeout > 0 {2799 collection_limits.sponsor_transfer_timeout2800 } else {2801 ChainLimit::get().fungible_sponsor_transfer_timeout2802 };2803 2804 let block_number = <system::Module<T>>::block_number() as T::BlockNumber;2805 let mut sponsored = true;2806 if <FungibleTransferBasket<T>>::contains_key(collection_id, who) {2807 let last_tx_block = <FungibleTransferBasket<T>>::get(collection_id, who);2808 let limit_time = last_tx_block + limit.into();2809 if block_number <= limit_time {2810 sponsored = false;2811 }2812 }2813 if sponsored {2814 <FungibleTransferBasket<T>>::insert(collection_id, who, block_number);2815 }28162817 sponsored2818 }2819 CollectionMode::ReFungible => {2820 2821 // get correct limit2822 let limit: u32 = if collection_limits.sponsor_transfer_timeout > 0 {2823 collection_limits.sponsor_transfer_timeout2824 } else {2825 ChainLimit::get().refungible_sponsor_transfer_timeout2826 };2827 2828 let mut sponsored = true;2829 if <ReFungibleTransferBasket<T>>::contains_key(collection_id, item_id) {2830 let last_tx_block = <ReFungibleTransferBasket<T>>::get(collection_id, item_id);2831 let limit_time = last_tx_block + limit.into();2832 if block_number <= limit_time {2833 sponsored = false;2834 }2835 }2836 if sponsored {2837 <ReFungibleTransferBasket<T>>::insert(collection_id, item_id, block_number);2838 }28392840 sponsored2841 }2842 _ => {2843 false2844 },2845 };2846 }28472848 if !sponsor_transfer {2849 None2850 } else {2851 collection.sponsorship.sponsor()2852 .cloned()2853 }2854 }28552856 Some(Call::set_variable_meta_data(collection_id, item_id, data)) => {2857 let mut sponsor_metadata_changes = false;28582859 let collection = <CollectionById<T>>::get(collection_id)?;28602861 if2862 collection.sponsorship.confirmed() &&2863 // Can't sponsor fungible collection, this tx will be rejected2864 // as invalid2865 !matches!(collection.mode, CollectionMode::Fungible(_)) &&2866 data.len() <= collection.limits.sponsored_data_size as usize2867 {2868 if let Some(rate_limit) = collection.limits.sponsored_data_rate_limit {2869 let block_number = <system::Module<T>>::block_number() as T::BlockNumber;28702871 if <VariableMetaDataBasket<T>>::get(collection_id, item_id)2872 .map(|last_block| block_number - last_block > rate_limit)2873 .unwrap_or(true) 2874 {2875 sponsor_metadata_changes = true;2876 <VariableMetaDataBasket<T>>::insert(collection_id, item_id, block_number);2877 }2878 }2879 }28802881 if !sponsor_metadata_changes {2882 None2883 } else {2884 collection.sponsorship.sponsor().cloned()2885 }2886 }28872888 _ => None,2889 })();28902891 match IsSubType::<pallet_contracts::Call<T>>::is_sub_type(call) {2892 Some(pallet_contracts::Call::call(dest, _value, _gas_limit, _data)) => {28932894 let called_contract: T::AccountId = T::Lookup::lookup((*dest).clone()).unwrap_or(T::AccountId::default());28952896 let owned_contract = <ContractOwner<T>>::get(called_contract.clone()).as_ref() == Some(who);2897 let white_list_enabled = <ContractWhiteListEnabled<T>>::contains_key(called_contract.clone());2898 2899 if !owned_contract && white_list_enabled {2900 if !<ContractWhiteList<T>>::contains_key(called_contract.clone(), who) {2901 return Err(InvalidTransaction::Call.into());2902 }2903 }2904 },2905 _ => {},2906 }29072908 // Sponsor smart contracts2909 sponsor = sponsor.or_else(|| match IsSubType::<pallet_contracts::Call<T>>::is_sub_type(call) {29102911 // On instantiation: set the contract owner2912 Some(pallet_contracts::Call::instantiate(_endowment, _gas_limit, code_hash, _data, salt)) => {29132914 let new_contract_address = <pallet_contracts::Module<T>>::contract_address(2915 &who,2916 code_hash,2917 salt,2918 );2919 <ContractOwner<T>>::insert(new_contract_address.clone(), who.clone());29202921 None2922 },29232924 // On instantiation with code: set the contract owner2925 Some(pallet_contracts::Call::instantiate_with_code(_endowment, _gas_limit, _code, _data, _salt)) => {29262927 let new_contract_address = <pallet_contracts::Module<T>>::contract_address(2928 &who,2929 &T::Hashing::hash(&_code),2930 _salt,2931 );29322933 <ContractOwner<T>>::insert(new_contract_address.clone(), who.clone());29342935 None2936 }29372938 // When the contract is called, check if the sponsoring is enabled and pay fees from contract endowment if it is2939 Some(pallet_contracts::Call::call(dest, _value, _gas_limit, _data)) => {29402941 let called_contract: T::AccountId = T::Lookup::lookup((*dest).clone()).unwrap_or(T::AccountId::default());29422943 let mut sponsor_transfer = false;2944 if <ContractSponsoringRateLimit<T>>::contains_key(called_contract.clone()) {2945 let last_tx_block = <ContractSponsorBasket<T>>::get((&called_contract, &who));2946 let block_number = <system::Module<T>>::block_number() as T::BlockNumber;2947 let rate_limit = <ContractSponsoringRateLimit<T>>::get(&called_contract);2948 let limit_time = last_tx_block + rate_limit;29492950 if block_number >= limit_time {2951 <ContractSponsorBasket<T>>::insert((called_contract.clone(), who.clone()), block_number);2952 sponsor_transfer = true;2953 }2954 } else {2955 sponsor_transfer = false;2956 }2957 2958 if sponsor_transfer {2959 if <ContractSelfSponsoring<T>>::contains_key(called_contract.clone()) {2960 if <ContractSelfSponsoring<T>>::get(called_contract.clone()) {2961 return Some(called_contract);2962 }2963 }2964 }29652966 None2967 },29682969 _ => None,2970 });29712972 let who_pays_fee = sponsor.unwrap_or_else(|| who.clone());29732974 <<T as pallet_transaction_payment::Config>::OnChargeTransaction as pallet_transaction_payment::OnChargeTransaction<T>>::withdraw_fee(&who_pays_fee, call, info, fee, tip)2975 .map(|i| (fee, i))2976 }2977}297829792980impl<T: Config + Send + Sync> SignedExtension2981 for ChargeTransactionPayment<T>2982where2983 BalanceOf<T>: Send + Sync + From<u64> + FixedPointOperand,2984 T::Call: Dispatchable<Info = DispatchInfo, PostInfo = PostDispatchInfo> + IsSubType<Call<T>> + IsSubType<pallet_contracts::Call<T>>,2985 T::AccountId: AsRef<[u8]>,2986 T::AccountId: UncheckedFrom<T::Hash>,2987{2988 const IDENTIFIER: &'static str = "ChargeTransactionPayment";2989 type AccountId = T::AccountId;2990 type Call = T::Call;2991 type AdditionalSigned = ();2992 type Pre = (2993 // tip2994 BalanceOf<T>,2995 // who pays fee2996 Self::AccountId,2997 // imbalance resulting from withdrawing the fee2998 <<T as pallet_transaction_payment::Config>::OnChargeTransaction as pallet_transaction_payment::OnChargeTransaction<T>>::LiquidityInfo,2999 );3000 fn additional_signed(&self) -> sp_std::result::Result<(), TransactionValidityError> {3001 Ok(())3002 }30033004 fn validate(3005 &self,3006 who: &Self::AccountId,3007 call: &Self::Call,3008 info: &DispatchInfoOf<Self::Call>,3009 len: usize,3010 ) -> TransactionValidity {3011 let (fee, _) = self.withdraw_fee(who, call, info, len)?;3012 Ok(ValidTransaction {3013 priority: Self::get_priority(len, info, fee),3014 ..Default::default()3015 })3016 }30173018 fn pre_dispatch(3019 self,3020 who: &Self::AccountId,3021 call: &Self::Call,3022 info: &DispatchInfoOf<Self::Call>,3023 len: usize,3024 ) -> Result<Self::Pre, TransactionValidityError> {3025 let (_fee, imbalance) = self.withdraw_fee(who, call, info, len)?;3026 Ok((self.0, who.clone(), imbalance))3027 }30283029 fn post_dispatch(3030 pre: Self::Pre,3031 info: &DispatchInfoOf<Self::Call>,3032 post_info: &PostDispatchInfoOf<Self::Call>,3033 len: usize,3034 _result: &DispatchResult,3035 ) -> Result<(), TransactionValidityError> {3036 let (tip, who, imbalance) = pre;3037 let actual_fee = pallet_transaction_payment::Module::<T>::compute_actual_fee(3038 len as u32,3039 info,3040 post_info,3041 tip,3042 );3043 <T as pallet_transaction_payment::Config>::OnChargeTransaction::correct_and_deposit_fee(&who, info, post_info, actual_fee, tip, imbalance)?;3044 Ok(())3045 }3046}30473048// #endregion30493050sp_api::decl_runtime_apis! {3051 pub trait NftApi {3052 /// Used for ethereum integration3053 fn eth_contract_code(account: H160) -> Option<Vec<u8>>;3054 }3055}