difftreelog
Merge pull request #871 from UniqueNetwork/feature/move_TokenChanged_event
in: master
21 files changed
Cargo.lockdiffbeforeafterboth--- a/Cargo.lock
+++ b/Cargo.lock
@@ -2445,8 +2445,8 @@
[[package]]
name = "evm-coder"
-version = "0.3.0"
-source = "git+https://github.com/uniquenetwork/evm-coder?tag=v0.3.0#c4bfdf39d84fbe74c6178417177f2a7f81d92bdd"
+version = "0.3.1"
+source = "git+https://github.com/uniquenetwork/evm-coder?tag=v0.3.1#21e61c627f71337d6b8a03899fe8de14bfbf67aa"
dependencies = [
"ethereum",
"evm-coder-procedural",
@@ -2457,8 +2457,8 @@
[[package]]
name = "evm-coder-procedural"
-version = "0.3.0"
-source = "git+https://github.com/uniquenetwork/evm-coder?tag=v0.3.0#c4bfdf39d84fbe74c6178417177f2a7f81d92bdd"
+version = "0.3.1"
+source = "git+https://github.com/uniquenetwork/evm-coder?tag=v0.3.1#21e61c627f71337d6b8a03899fe8de14bfbf67aa"
dependencies = [
"Inflector",
"hex",
Cargo.tomldiffbeforeafterboth--- a/Cargo.toml
+++ b/Cargo.toml
@@ -19,16 +19,9 @@
[workspace.dependencies]
# Unique
-evm-coder = { git = "https://github.com/uniquenetwork/evm-coder", tag = "v0.3.0", default-features = false }
-struct-versioning = { path = "crates/struct-versioning" }
-precompile-utils-macro = { path = "runtime/common/ethereum/precompiles/utils/macro" }
-up-common = { path = "primitives/common", default-features = false }
-up-data-structs = { path = "primitives/data-structs", default-features = false }
-up-pov-estimate-rpc = { path = "primitives/pov-estimate-rpc", default-features = false }
-up-rpc = { path = "primitives/rpc", default-features = false }
-up-sponsorship = { default-features = false, git = "https://github.com/uniquenetwork/pallet-sponsoring", branch = "polkadot-v0.9.37" }
-uc-rpc = { path = "client/rpc" }
-unique-rpc = { path = "node/rpc" }
+app-promotion-rpc = { path = "primitives/app_promotion_rpc", default-features = false }
+evm-coder = { git = "https://github.com/uniquenetwork/evm-coder", tag = "v0.3.1", default-features = false }
+pallet-app-promotion = { path = "pallets/app-promotion", default-features = false }
pallet-charge-transaction = { package = "pallet-template-transaction-payment", default-features = false, git = "https://github.com/uniquenetwork/pallet-sponsoring", branch = "polkadot-v0.9.37" }
pallet-collator-selection = { default-features = false, path = "pallets/collator-selection" }
pallet-common = { default-features = false, path = "pallets/common" }
@@ -45,56 +38,59 @@
pallet-nonfungible = { default-features = false, path = "pallets/nonfungible" }
pallet-refungible = { default-features = false, path = "pallets/refungible" }
pallet-structure = { default-features = false, path = "pallets/structure" }
+pallet-test-utils = { default-features = false, path = "test-pallets/utils" }
pallet-unique = { path = "pallets/unique", default-features = false }
pallet-unique-scheduler-v2 = { path = "pallets/scheduler-v2", default-features = false }
-pallet-test-utils = { default-features = false, path = "test-pallets/utils" }
-pallet-app-promotion = { path = "pallets/app-promotion", default-features = false }
-app-promotion-rpc = { path = "primitives/app_promotion_rpc", default-features = false }
+precompile-utils-macro = { path = "runtime/common/ethereum/precompiles/utils/macro" }
+struct-versioning = { path = "crates/struct-versioning" }
+uc-rpc = { path = "client/rpc" }
+unique-rpc = { path = "node/rpc" }
+up-common = { path = "primitives/common", default-features = false }
+up-data-structs = { path = "primitives/data-structs", default-features = false }
+up-pov-estimate-rpc = { path = "primitives/pov-estimate-rpc", default-features = false }
+up-rpc = { path = "primitives/rpc", default-features = false }
+up-sponsorship = { default-features = false, git = "https://github.com/uniquenetwork/pallet-sponsoring", branch = "polkadot-v0.9.37" }
# Unique: Runtimes
+opal-runtime = { path = "runtime/opal" }
+quartz-runtime = { path = "runtime/quartz" }
unique-runtime = { path = "runtime/unique" }
-quartz-runtime = { path = "runtime/quartz" }
-opal-runtime = { path = "runtime/opal" }
# Frontier (Unique patches over the Parity version)
+fc-consensus = { git = "https://github.com/uniquenetwork/unique-frontier", branch = "unique-polkadot-v0.9.37" }
+fc-db = { default-features = false, git = "https://github.com/uniquenetwork/unique-frontier", branch = "unique-polkadot-v0.9.37" }
+fc-mapping-sync = { git = "https://github.com/uniquenetwork/unique-frontier", branch = "unique-polkadot-v0.9.37" }
+fc-rpc = { default-features = false, git = "https://github.com/uniquenetwork/unique-frontier", branch = "unique-polkadot-v0.9.37" }
+fc-rpc-core = { default-features = false, git = "https://github.com/uniquenetwork/unique-frontier", branch = "unique-polkadot-v0.9.37" }
fp-evm = { default-features = false, git = "https://github.com/uniquenetwork/unique-frontier", branch = "unique-polkadot-v0.9.37" }
fp-evm-mapping = { default-features = false, git = "https://github.com/uniquenetwork/unique-frontier", branch = "unique-polkadot-v0.9.37" }
fp-rpc = { default-features = false, git = "https://github.com/uniquenetwork/unique-frontier", branch = "unique-polkadot-v0.9.37" }
fp-self-contained = { default-features = false, git = "https://github.com/uniquenetwork/unique-frontier", branch = "unique-polkadot-v0.9.37" }
-fc-db = { default-features = false, git = "https://github.com/uniquenetwork/unique-frontier", branch = "unique-polkadot-v0.9.37" }
-fc-rpc = { default-features = false, git = "https://github.com/uniquenetwork/unique-frontier", branch = "unique-polkadot-v0.9.37" }
-fc-rpc-core = { default-features = false, git = "https://github.com/uniquenetwork/unique-frontier", branch = "unique-polkadot-v0.9.37" }
fp-storage = { default-features = false, git = "https://github.com/uniquenetwork/unique-frontier", branch = "unique-polkadot-v0.9.37" }
-fc-consensus = { git = "https://github.com/uniquenetwork/unique-frontier", branch = "unique-polkadot-v0.9.37" }
-fc-mapping-sync = { git = "https://github.com/uniquenetwork/unique-frontier", branch = "unique-polkadot-v0.9.37" }
+pallet-base-fee = { default-features = false, git = "https://github.com/uniquenetwork/unique-frontier", branch = "unique-polkadot-v0.9.37" }
pallet-ethereum = { default-features = false, git = "https://github.com/uniquenetwork/unique-frontier", branch = "unique-polkadot-v0.9.37" }
pallet-evm = { default-features = false, git = "https://github.com/uniquenetwork/unique-frontier", branch = "unique-polkadot-v0.9.37" }
-pallet-base-fee = { default-features = false, git = "https://github.com/uniquenetwork/unique-frontier", branch = "unique-polkadot-v0.9.37" }
pallet-evm-precompile-simple = { default-features = false, git = "https://github.com/uniquenetwork/unique-frontier", branch = "unique-polkadot-v0.9.37" }
# Parity
codec = { default-features = false, features = ['derive'], package = 'parity-scale-codec', version = "3.2.2" }
-scale-info = { version = "2.1.1", default-features = false, features = ["derive"] }
-polkadot-cli = { git = "https://github.com/paritytech/polkadot", branch = "release-v0.9.37" }
-polkadot-primitives = { git = "https://github.com/paritytech/polkadot", branch = "release-v0.9.37" }
-polkadot-service = { git = "https://github.com/paritytech/polkadot", branch = "release-v0.9.37" }
cumulus-client-cli = { git = "https://github.com/paritytech/cumulus", branch = "polkadot-v0.9.37" }
cumulus-client-consensus-aura = { git = "https://github.com/paritytech/cumulus", branch = "polkadot-v0.9.37" }
cumulus-client-consensus-common = { git = "https://github.com/paritytech/cumulus", branch = "polkadot-v0.9.37" }
cumulus-client-network = { git = "https://github.com/paritytech/cumulus", branch = "polkadot-v0.9.37" }
cumulus-client-service = { git = "https://github.com/paritytech/cumulus", branch = "polkadot-v0.9.37" }
-cumulus-primitives-parachain-inherent = { git = "https://github.com/paritytech/cumulus", branch = "polkadot-v0.9.37" }
-cumulus-relay-chain-inprocess-interface = { git = "https://github.com/paritytech/cumulus", branch = "polkadot-v0.9.37" }
-cumulus-relay-chain-interface = { git = "https://github.com/paritytech/cumulus", branch = "polkadot-v0.9.37" }
-cumulus-relay-chain-minimal-node = { git = "https://github.com/paritytech/cumulus", branch = "polkadot-v0.9.37" }
cumulus-pallet-aura-ext = { git = "https://github.com/paritytech/cumulus", branch = "polkadot-v0.9.37", default-features = false }
cumulus-pallet-dmp-queue = { git = "https://github.com/paritytech/cumulus", branch = "polkadot-v0.9.37", default-features = false }
cumulus-pallet-parachain-system = { git = "https://github.com/paritytech/cumulus", branch = "polkadot-v0.9.37", default-features = false }
cumulus-pallet-xcm = { git = "https://github.com/paritytech/cumulus", branch = "polkadot-v0.9.37", default-features = false }
cumulus-pallet-xcmp-queue = { git = "https://github.com/paritytech/cumulus", branch = "polkadot-v0.9.37", default-features = false }
cumulus-primitives-core = { git = "https://github.com/paritytech/cumulus", branch = "polkadot-v0.9.37", default-features = false }
+cumulus-primitives-parachain-inherent = { git = "https://github.com/paritytech/cumulus", branch = "polkadot-v0.9.37" }
cumulus-primitives-timestamp = { git = "https://github.com/paritytech/cumulus", branch = "polkadot-v0.9.37", default-features = false }
cumulus-primitives-utility = { git = "https://github.com/paritytech/cumulus", branch = "polkadot-v0.9.37", default-features = false }
+cumulus-relay-chain-inprocess-interface = { git = "https://github.com/paritytech/cumulus", branch = "polkadot-v0.9.37" }
+cumulus-relay-chain-interface = { git = "https://github.com/paritytech/cumulus", branch = "polkadot-v0.9.37" }
+cumulus-relay-chain-minimal-node = { git = "https://github.com/paritytech/cumulus", branch = "polkadot-v0.9.37" }
frame-executive = { default-features = false, git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.37" }
frame-support = { default-features = false, git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.37" }
frame-system = { default-features = false, git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.37" }
@@ -107,67 +103,71 @@
pallet-sudo = { default-features = false, git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.37" }
pallet-timestamp = { default-features = false, git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.37" }
pallet-transaction-payment = { default-features = false, git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.37" }
+pallet-transaction-payment-rpc = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.37" }
pallet-transaction-payment-rpc-runtime-api = { default-features = false, git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.37" }
pallet-treasury = { default-features = false, git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.37" }
pallet-xcm = { git = "https://github.com/paritytech/polkadot", branch = "release-v0.9.37", default-features = false }
-pallet-transaction-payment-rpc = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.37" }
parachain-info = { default-features = false, git = "https://github.com/paritytech/cumulus", branch = "polkadot-v0.9.37" }
+polkadot-cli = { git = "https://github.com/paritytech/polkadot", branch = "release-v0.9.37" }
polkadot-parachain = { git = "https://github.com/paritytech/polkadot", branch = "release-v0.9.37", default-features = false }
+polkadot-primitives = { git = "https://github.com/paritytech/polkadot", branch = "release-v0.9.37" }
+polkadot-service = { git = "https://github.com/paritytech/polkadot", branch = "release-v0.9.37" }
+sc-basic-authorship = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.37" }
+sc-chain-spec = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.37" }
+sc-cli = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.37" }
+sc-client-api = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.37" }
+sc-consensus = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.37" }
+sc-consensus-manual-seal = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.37" }
+sc-executor = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.37" }
+sc-finality-grandpa = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.37" }
+sc-network = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.37" }
+sc-rpc = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.37" }
+sc-rpc-api = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.37" }
+sc-service = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.37" }
+sc-sysinfo = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.37" }
+sc-telemetry = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.37" }
+sc-tracing = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.37" }
+sc-transaction-pool = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.37" }
+scale-info = { version = "2.1.1", default-features = false, features = ["derive"] }
sp-api = { default-features = false, git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.37" }
sp-arithmetic = { default-features = false, git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.37" }
sp-block-builder = { default-features = false, git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.37" }
+sp-blockchain = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.37" }
sp-consensus-aura = { default-features = false, git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.37" }
sp-core = { default-features = false, git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.37" }
+sp-externalities = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.37" }
sp-inherents = { default-features = false, git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.37" }
sp-io = { default-features = false, git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.37" }
+sp-keystore = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.37" }
sp-offchain = { default-features = false, git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.37" }
sp-runtime = { default-features = false, git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.37" }
sp-session = { default-features = false, git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.37" }
-sp-std = { default-features = false, git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.37" }
-sp-transaction-pool = { default-features = false, git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.37" }
-sp-version = { default-features = false, git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.37" }
sp-staking = { default-features = false, git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.37" }
+sp-state-machine = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.37" }
+sp-std = { default-features = false, git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.37" }
+sp-timestamp = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.37" }
sp-tracing = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.37" }
-sp-blockchain = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.37" }
-sp-keystore = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.37" }
-sp-timestamp = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.37" }
-sp-externalities = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.37" }
-sp-state-machine = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.37" }
+sp-transaction-pool = { default-features = false, git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.37" }
sp-trie = { default-features = false, git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.37" }
-substrate-prometheus-endpoint = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.37" }
-sc-client-api = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.37" }
-sc-finality-grandpa = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.37" }
-sc-network = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.37" }
-sc-rpc = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.37" }
-sc-rpc-api = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.37" }
-sc-service = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.37" }
-sc-transaction-pool = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.37" }
-sc-basic-authorship = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.37" }
-sc-chain-spec = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.37" }
-sc-cli = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.37" }
-sc-consensus = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.37" }
-sc-consensus-manual-seal = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.37" }
-sc-executor = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.37" }
-sc-sysinfo = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.37" }
-sc-telemetry = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.37" }
-sc-tracing = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.37" }
+sp-version = { default-features = false, git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.37" }
substrate-frame-rpc-system = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.37" }
+substrate-prometheus-endpoint = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.37" }
xcm = { git = "https://github.com/paritytech/polkadot", branch = "release-v0.9.37", default-features = false }
xcm-builder = { git = "https://github.com/paritytech/polkadot", branch = "release-v0.9.37", default-features = false }
xcm-executor = { git = "https://github.com/paritytech/polkadot", branch = "release-v0.9.37", default-features = false }
# Parity: Build utils
+substrate-build-script-utils = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.37" }
substrate-wasm-builder = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.37" }
-substrate-build-script-utils = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.37" }
# Parity: Benchmarking
+frame-benchmarking = { default-features = false, git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.37" }
frame-benchmarking-cli = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.37" }
frame-system-benchmarking = { default-features = false, git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.37" }
-frame-benchmarking = { default-features = false, git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.37" }
# Parity: Try Runtime
+frame-try-runtime = { default-features = false, git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.37" }
try-runtime-cli = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.37" }
-frame-try-runtime = { default-features = false, git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.37" }
# ORML
orml-tokens = { git = "https://github.com/open-web3-stack/open-runtime-module-library", branch = "polkadot-v0.9.37", default-features = false }
@@ -176,15 +176,15 @@
orml-xtokens = { git = "https://github.com/open-web3-stack/open-runtime-module-library", branch = "polkadot-v0.9.37", default-features = false }
# Other
-smallvec = "1.6.1"
derivative = { version = "2.2.0", features = ["use_core"] }
-serde = { default-features = false, features = ['derive'], version = "1.0.136" }
-log = { version = "0.4.16", default-features = false }
-num_enum = { version = "0.5.3", default-features = false }
+ethereum = { version = "0.14.0", default-features = false }
+evm-core = { default-features = false, version = "0.37.0" }
hex-literal = "0.3.4"
impl-trait-for-tuples = "0.2.2"
+jsonrpsee = { version = "0.16.2", features = ["macros", "server"] }
+log = { version = "0.4.16", default-features = false }
logtest = "2.0.0"
-ethereum = { version = "0.14.0", default-features = false }
-evm-core = { default-features = false, version = "0.37.0" }
+num_enum = { version = "0.5.3", default-features = false }
primitive-types = { version = "0.12.1", default-features = false }
-jsonrpsee = { version = "0.16.2", features = ["macros", "server"] }
+serde = { default-features = false, features = ['derive'], version = "1.0.136" }
+smallvec = "1.6.1"
pallets/common/src/erc.rsdiffbeforeafterboth--- a/pallets/common/src/erc.rs
+++ b/pallets/common/src/erc.rs
@@ -26,7 +26,6 @@
};
use pallet_evm_coder_substrate::dispatch_to_evm;
use sp_std::{vec, vec::Vec};
-use sp_core::U256;
use up_data_structs::{
CollectionMode, CollectionPermissions, OwnerRestrictedSet, Property, SponsoringRateLimit,
SponsorshipState,
@@ -62,18 +61,9 @@
},
/// The collection has been changed.
CollectionChanged {
- /// Collection ID.
- #[indexed]
- collection_id: Address,
- },
-
- /// The token has been changed.
- TokenChanged {
/// Collection ID.
#[indexed]
collection_id: Address,
- /// Token ID.
- token_id: U256,
},
}
pallets/common/src/lib.rsdiffbeforeafterboth--- a/pallets/common/src/lib.rs
+++ b/pallets/common/src/lib.rs
@@ -86,7 +86,6 @@
use sp_core::H160;
use sp_runtime::{ArithmeticError, DispatchError, DispatchResult};
-use crate::erc::CollectionHelpersEvents;
#[cfg(feature = "runtime-benchmarks")]
pub mod benchmarking;
pub mod dispatch;
@@ -1250,6 +1249,7 @@
mut stored_properties: Properties,
is_token_owner: impl Fn() -> Result<bool, DispatchError>,
set_token_properties: impl FnOnce(Properties),
+ log: evm_coder::ethereum::Log,
) -> DispatchResult {
let is_collection_admin = collection.is_owner_or_admin(sender);
let permissions = Self::property_permissions(collection.id);
@@ -1304,13 +1304,7 @@
}
}
- <PalletEvm<T>>::deposit_log(
- CollectionHelpersEvents::TokenChanged {
- collection_id: eth::collection_id_to_address(collection.id),
- token_id: token_id.into(),
- }
- .to_log(T::ContractAddress::get()),
- );
+ <PalletEvm<T>>::deposit_log(log.clone());
}
set_token_properties(stored_properties);
pallets/nonfungible/src/erc.rsdiffbeforeafterboth--- a/pallets/nonfungible/src/erc.rs
+++ b/pallets/nonfungible/src/erc.rs
@@ -50,13 +50,24 @@
TokenProperties, SelfWeightOf, weights::WeightInfo,
};
+/// Nft events.
+#[derive(ToLog)]
+pub enum ERC721TokenEvent {
+ /// The token has been changed.
+ TokenChanged {
+ /// Token ID.
+ #[indexed]
+ token_id: U256,
+ },
+}
+
frontier_contract! {
macro_rules! NonfungibleHandle_result {...}
impl<T: Config> Contract for NonfungibleHandle<T> {...}
}
/// @title A contract that allows to set and delete token properties and change token property permissions.
-#[solidity_interface(name = TokenProperties, enum(derive(PreDispatch)), enum_attr(weight))]
+#[solidity_interface(name = TokenProperties, events(ERC721TokenEvent), enum(derive(PreDispatch)), enum_attr(weight))]
impl<T: Config> NonfungibleHandle<T> {
/// @notice Set permissions for token property.
/// @dev Throws error if `msg.sender` is not admin or owner of the collection.
pallets/nonfungible/src/lib.rsdiffbeforeafterboth--- a/pallets/nonfungible/src/lib.rs
+++ b/pallets/nonfungible/src/lib.rs
@@ -112,7 +112,7 @@
};
use pallet_structure::{Pallet as PalletStructure, Error as StructureError};
use pallet_evm_coder_substrate::{SubstrateRecorder, WithRecorder};
-use sp_core::H160;
+use sp_core::{Get, H160};
use sp_runtime::{ArithmeticError, DispatchError, DispatchResult, TransactionOutcome};
use sp_std::{vec::Vec, vec, collections::btree_map::BTreeMap};
use core::ops::Deref;
@@ -622,6 +622,10 @@
stored_properties,
is_token_owner,
|properties| <TokenProperties<T>>::set((collection.id, token_id), properties),
+ erc::ERC721TokenEvent::TokenChanged {
+ token_id: token_id.into(),
+ }
+ .to_log(T::ContractAddress::get()),
)
}
pallets/nonfungible/src/stubs/UniqueNFT.rawdiffbeforeafterbothbinary blob — no preview
pallets/nonfungible/src/stubs/UniqueNFT.soldiffbeforeafterboth--- a/pallets/nonfungible/src/stubs/UniqueNFT.sol
+++ b/pallets/nonfungible/src/stubs/UniqueNFT.sol
@@ -17,9 +17,14 @@
}
}
+/// @dev inlined interface
+contract ERC721TokenEvent {
+ event TokenChanged(uint256 indexed tokenId);
+}
+
/// @title A contract that allows to set and delete token properties and change token property permissions.
/// @dev the ERC-165 identifier for this interface is 0xde0695c2
-contract TokenProperties is Dummy, ERC165 {
+contract TokenProperties is Dummy, ERC165, ERC721TokenEvent {
// /// @notice Set permissions for token property.
// /// @dev Throws error if `msg.sender` is not admin or owner of the collection.
// /// @param key Property key.
pallets/refungible/src/erc.rsdiffbeforeafterboth1// Copyright 2019-2022 Unique Network (Gibraltar) Ltd.2// This file is part of Unique Network.34// Unique Network is free software: you can redistribute it and/or modify5// it under the terms of the GNU General Public License as published by6// the Free Software Foundation, either version 3 of the License, or7// (at your option) any later version.89// Unique Network is distributed in the hope that it will be useful,10// but WITHOUT ANY WARRANTY; without even the implied warranty of11// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the12// GNU General Public License for more details.1314// You should have received a copy of the GNU General Public License15// along with Unique Network. If not, see <http://www.gnu.org/licenses/>.1617//! # Refungible Pallet EVM API for tokens18//!19//! Provides ERC-721 standart support implementation and EVM API for unique extensions for Refungible Pallet.20//! Method implementations are mostly doing parameter conversion and calling Refungible Pallet methods.2122extern crate alloc;2324use core::{25 char::{REPLACEMENT_CHARACTER, decode_utf16},26 convert::TryInto,27};28use evm_coder::{abi::AbiType, ToLog, generate_stubgen, solidity_interface, types::*};29use frame_support::{BoundedBTreeMap, BoundedVec};30use pallet_common::{31 CollectionHandle, CollectionPropertyPermissions, CommonCollectionOperations,32 Error as CommonError,33 erc::{CommonEvmHandler, CollectionCall, static_property::key},34 eth::{self, TokenUri},35};36use pallet_evm::{account::CrossAccountId, PrecompileHandle};37use pallet_evm_coder_substrate::{38 call, dispatch_to_evm,39 execution::{PreDispatch, Result, Error},40 frontier_contract,41};42use pallet_structure::{SelfWeightOf as StructureWeight, weights::WeightInfo as _};43use sp_core::{H160, U256, Get};44use sp_std::{collections::btree_map::BTreeMap, vec::Vec, vec};45use up_data_structs::{46 CollectionId, CollectionPropertiesVec, mapping::TokenAddressMapping, Property, PropertyKey,47 PropertyKeyPermission, PropertyPermission, TokenId, TokenOwnerError,48};4950use crate::{51 AccountBalance, Balance, Config, CreateItemData, Pallet, RefungibleHandle, TokenProperties,52 TokensMinted, TotalSupply, SelfWeightOf, weights::WeightInfo,53};5455frontier_contract! {56 macro_rules! RefungibleHandle_result {...}57 impl<T: Config> Contract for RefungibleHandle<T> {...}58}5960pub const ADDRESS_FOR_PARTIALLY_OWNED_TOKENS: H160 = H160::repeat_byte(0xff);6162/// @title A contract that allows to set and delete token properties and change token property permissions.63#[solidity_interface(name = TokenProperties, enum(derive(PreDispatch)), enum_attr(weight))]64impl<T: Config> RefungibleHandle<T> {65 /// @notice Set permissions for token property.66 /// @dev Throws error if `msg.sender` is not admin or owner of the collection.67 /// @param key Property key.68 /// @param isMutable Permission to mutate property.69 /// @param collectionAdmin Permission to mutate property by collection admin if property is mutable.70 /// @param tokenOwner Permission to mutate property by token owner if property is mutable.71 #[solidity(hide)]72 #[weight(<SelfWeightOf<T>>::set_token_property_permissions(1))]73 fn set_token_property_permission(74 &mut self,75 caller: Caller,76 key: String,77 is_mutable: bool,78 collection_admin: bool,79 token_owner: bool,80 ) -> Result<()> {81 let caller = T::CrossAccountId::from_eth(caller);82 <Pallet<T>>::set_token_property_permissions(83 self,84 &caller,85 vec![PropertyKeyPermission {86 key: <Vec<u8>>::from(key)87 .try_into()88 .map_err(|_| "too long key")?,89 permission: PropertyPermission {90 mutable: is_mutable,91 collection_admin,92 token_owner,93 },94 }],95 )96 .map_err(dispatch_to_evm::<T>)97 }9899 /// @notice Set permissions for token property.100 /// @dev Throws error if `msg.sender` is not admin or owner of the collection.101 /// @param permissions Permissions for keys.102 #[weight(<SelfWeightOf<T>>::set_token_property_permissions(permissions.len() as u32))]103 fn set_token_property_permissions(104 &mut self,105 caller: Caller,106 permissions: Vec<eth::TokenPropertyPermission>,107 ) -> Result<()> {108 let caller = T::CrossAccountId::from_eth(caller);109 let perms = eth::TokenPropertyPermission::into_property_key_permissions(permissions)?;110111 <Pallet<T>>::set_token_property_permissions(self, &caller, perms)112 .map_err(dispatch_to_evm::<T>)113 }114115 /// @notice Get permissions for token properties.116 fn token_property_permissions(&self) -> Result<Vec<eth::TokenPropertyPermission>> {117 let perms = <Pallet<T>>::token_property_permission(self.id);118 Ok(perms119 .into_iter()120 .map(eth::TokenPropertyPermission::from)121 .collect())122 }123124 /// @notice Set token property value.125 /// @dev Throws error if `msg.sender` has no permission to edit the property.126 /// @param tokenId ID of the token.127 /// @param key Property key.128 /// @param value Property value.129 #[solidity(hide)]130 #[weight(<SelfWeightOf<T>>::set_token_properties(1))]131 fn set_property(132 &mut self,133 caller: Caller,134 token_id: U256,135 key: String,136 value: Bytes,137 ) -> Result<()> {138 let caller = T::CrossAccountId::from_eth(caller);139 let token_id: u32 = token_id.try_into().map_err(|_| "token id overflow")?;140 let key = <Vec<u8>>::from(key)141 .try_into()142 .map_err(|_| "key too long")?;143 let value = value.0.try_into().map_err(|_| "value too long")?;144145 let nesting_budget = self146 .recorder147 .weight_calls_budget(<StructureWeight<T>>::find_parent());148149 <Pallet<T>>::set_token_property(150 self,151 &caller,152 TokenId(token_id),153 Property { key, value },154 &nesting_budget,155 )156 .map_err(dispatch_to_evm::<T>)157 }158159 /// @notice Set token properties value.160 /// @dev Throws error if `msg.sender` has no permission to edit the property.161 /// @param tokenId ID of the token.162 /// @param properties settable properties163 #[weight(<SelfWeightOf<T>>::set_token_properties(properties.len() as u32))]164 fn set_properties(165 &mut self,166 caller: Caller,167 token_id: U256,168 properties: Vec<eth::Property>,169 ) -> Result<()> {170 let caller = T::CrossAccountId::from_eth(caller);171 let token_id: u32 = token_id.try_into().map_err(|_| "token id overflow")?;172173 let nesting_budget = self174 .recorder175 .weight_calls_budget(<StructureWeight<T>>::find_parent());176177 let properties = properties178 .into_iter()179 .map(eth::Property::try_into)180 .collect::<Result<Vec<_>>>()?;181182 <Pallet<T>>::set_token_properties(183 self,184 &caller,185 TokenId(token_id),186 properties.into_iter(),187 false,188 &nesting_budget,189 )190 .map_err(dispatch_to_evm::<T>)191 }192193 /// @notice Delete token property value.194 /// @dev Throws error if `msg.sender` has no permission to edit the property.195 /// @param tokenId ID of the token.196 /// @param key Property key.197 #[solidity(hide)]198 #[weight(<SelfWeightOf<T>>::delete_token_properties(1))]199 fn delete_property(&mut self, token_id: U256, caller: Caller, key: String) -> Result<()> {200 let caller = T::CrossAccountId::from_eth(caller);201 let token_id: u32 = token_id.try_into().map_err(|_| "token id overflow")?;202 let key = <Vec<u8>>::from(key)203 .try_into()204 .map_err(|_| "key too long")?;205206 let nesting_budget = self207 .recorder208 .weight_calls_budget(<StructureWeight<T>>::find_parent());209210 <Pallet<T>>::delete_token_property(self, &caller, TokenId(token_id), key, &nesting_budget)211 .map_err(dispatch_to_evm::<T>)212 }213214 /// @notice Delete token properties value.215 /// @dev Throws error if `msg.sender` has no permission to edit the property.216 /// @param tokenId ID of the token.217 /// @param keys Properties key.218 #[weight(<SelfWeightOf<T>>::delete_token_properties(keys.len() as u32))]219 fn delete_properties(220 &mut self,221 token_id: U256,222 caller: Caller,223 keys: Vec<String>,224 ) -> Result<()> {225 let caller = T::CrossAccountId::from_eth(caller);226 let token_id: u32 = token_id.try_into().map_err(|_| "token id overflow")?;227 let keys = keys228 .into_iter()229 .map(|k| Ok(<Vec<u8>>::from(k).try_into().map_err(|_| "key too long")?))230 .collect::<Result<Vec<_>>>()?;231232 let nesting_budget = self233 .recorder234 .weight_calls_budget(<StructureWeight<T>>::find_parent());235236 <Pallet<T>>::delete_token_properties(237 self,238 &caller,239 TokenId(token_id),240 keys.into_iter(),241 &nesting_budget,242 )243 .map_err(dispatch_to_evm::<T>)244 }245246 /// @notice Get token property value.247 /// @dev Throws error if key not found248 /// @param tokenId ID of the token.249 /// @param key Property key.250 /// @return Property value bytes251 fn property(&self, token_id: U256, key: String) -> Result<Bytes> {252 let token_id: u32 = token_id.try_into().map_err(|_| "token id overflow")?;253 let key = <Vec<u8>>::from(key)254 .try_into()255 .map_err(|_| "key too long")?;256257 let props = <TokenProperties<T>>::get((self.id, token_id));258 let prop = props.get(&key).ok_or("key not found")?;259260 Ok(prop.to_vec().into())261 }262}263264#[derive(ToLog)]265pub enum ERC721Events {266 /// @dev This event emits when NFTs are created (`from` == 0) and destroyed267 /// (`to` == 0). Exception: during contract creation, any number of RFTs268 /// may be created and assigned without emitting Transfer.269 Transfer {270 #[indexed]271 from: Address,272 #[indexed]273 to: Address,274 #[indexed]275 token_id: U256,276 },277 /// @dev Not supported278 Approval {279 #[indexed]280 owner: Address,281 #[indexed]282 approved: Address,283 #[indexed]284 token_id: U256,285 },286 /// @dev Not supported287 #[allow(dead_code)]288 ApprovalForAll {289 #[indexed]290 owner: Address,291 #[indexed]292 operator: Address,293 approved: bool,294 },295}296297/// @title ERC-721 Non-Fungible Token Standard, optional metadata extension298/// @dev See https://eips.ethereum.org/EIPS/eip-721299#[solidity_interface(name = ERC721Metadata, enum(derive(PreDispatch)), expect_selector = 0x5b5e139f)]300impl<T: Config> RefungibleHandle<T>301where302 T::AccountId: From<[u8; 32]> + AsRef<[u8; 32]>,303{304 /// @notice A descriptive name for a collection of NFTs in this contract305 /// @dev real implementation of this function lies in `ERC721UniqueExtensions`306 #[solidity(hide, rename_selector = "name")]307 fn name_proxy(&self) -> Result<String> {308 self.name()309 }310311 /// @notice An abbreviated name for NFTs in this contract312 /// @dev real implementation of this function lies in `ERC721UniqueExtensions`313 #[solidity(hide, rename_selector = "symbol")]314 fn symbol_proxy(&self) -> Result<String> {315 self.symbol()316 }317318 /// @notice A distinct Uniform Resource Identifier (URI) for a given asset.319 ///320 /// @dev If the token has a `url` property and it is not empty, it is returned.321 /// Else If the collection does not have a property with key `schemaName` or its value is not equal to `ERC721Metadata`, it return an error `tokenURI not set`.322 /// If the collection property `baseURI` is empty or absent, return "" (empty string)323 /// otherwise, if token property `suffix` present and is non-empty, return concatenation of baseURI and suffix324 /// otherwise, return concatenation of `baseURI` and stringified token id (decimal stringifying, without paddings).325 ///326 /// @return token's const_metadata327 #[solidity(rename_selector = "tokenURI")]328 fn token_uri(&self, token_id: U256) -> Result<String> {329 let token_id_u32: u32 = token_id.try_into().map_err(|_| "token id overflow")?;330331 match get_token_property(self, token_id_u32, &key::url()).as_deref() {332 Err(_) | Ok("") => (),333 Ok(url) => {334 return Ok(url.into());335 }336 };337338 let base_uri =339 pallet_common::Pallet::<T>::get_collection_property(self.id, &key::base_uri())340 .map(BoundedVec::into_inner)341 .map(String::from_utf8)342 .transpose()343 .map_err(|e| {344 Error::Revert(alloc::format!(345 "Can not convert value \"baseURI\" to string with error \"{}\"",346 e347 ))348 })?;349350 let base_uri = match base_uri.as_deref() {351 None | Some("") => {352 return Ok("".into());353 }354 Some(base_uri) => base_uri.into(),355 };356357 Ok(358 match get_token_property(self, token_id_u32, &key::suffix()).as_deref() {359 Err(_) | Ok("") => base_uri,360 Ok(suffix) => base_uri + suffix,361 },362 )363 }364}365366/// @title ERC-721 Non-Fungible Token Standard, optional enumeration extension367/// @dev See https://eips.ethereum.org/EIPS/eip-721368#[solidity_interface(name = ERC721Enumerable, enum(derive(PreDispatch)), expect_selector = 0x780e9d63)]369impl<T: Config> RefungibleHandle<T> {370 /// @notice Enumerate valid RFTs371 /// @param index A counter less than `totalSupply()`372 /// @return The token identifier for the `index`th NFT,373 /// (sort order not specified)374 fn token_by_index(&self, index: U256) -> U256 {375 index376 }377378 /// Not implemented379 fn token_of_owner_by_index(&self, _owner: Address, _index: U256) -> Result<U256> {380 // TODO: Not implemetable381 Err("not implemented".into())382 }383384 /// @notice Count RFTs tracked by this contract385 /// @return A count of valid RFTs tracked by this contract, where each one of386 /// them has an assigned and queryable owner not equal to the zero address387 fn total_supply(&self) -> Result<U256> {388 self.consume_store_reads(1)?;389 Ok(<Pallet<T>>::total_supply(self).into())390 }391}392393/// @title ERC-721 Non-Fungible Token Standard394/// @dev See https://github.com/ethereum/EIPs/blob/master/EIPS/eip-721.md395#[solidity_interface(name = ERC721, events(ERC721Events), enum(derive(PreDispatch)), enum_attr(weight), expect_selector = 0x80ac58cd)]396impl<T: Config> RefungibleHandle<T> {397 /// @notice Count all RFTs assigned to an owner398 /// @dev RFTs assigned to the zero address are considered invalid, and this399 /// function throws for queries about the zero address.400 /// @param owner An address for whom to query the balance401 /// @return The number of RFTs owned by `owner`, possibly zero402 fn balance_of(&self, owner: Address) -> Result<U256> {403 self.consume_store_reads(1)?;404 let owner = T::CrossAccountId::from_eth(owner);405 let balance = <AccountBalance<T>>::get((self.id, owner));406 Ok(balance.into())407 }408409 /// @notice Find the owner of an RFT410 /// @dev RFTs assigned to zero address are considered invalid, and queries411 /// about them do throw.412 /// Returns special 0xffffffffffffffffffffffffffffffffffffffff address for413 /// the tokens that are partially owned.414 /// @param tokenId The identifier for an RFT415 /// @return The address of the owner of the RFT416 fn owner_of(&self, token_id: U256) -> Result<Address> {417 self.consume_store_reads(2)?;418 let token = token_id.try_into()?;419 let owner = <Pallet<T>>::token_owner(self.id, token);420 owner421 .map(|address| *address.as_eth())422 .or_else(|err| match err {423 TokenOwnerError::NotFound => Err(Error::Revert("token not found".into())),424 TokenOwnerError::MultipleOwners => Ok(ADDRESS_FOR_PARTIALLY_OWNED_TOKENS),425 })426 }427428 /// @dev Not implemented429 #[solidity(rename_selector = "safeTransferFrom")]430 fn safe_transfer_from_with_data(431 &mut self,432 _from: Address,433 _to: Address,434 _token_id: U256,435 _data: Bytes,436 ) -> Result<()> {437 // TODO: Not implemetable438 Err("not implemented".into())439 }440441 /// @dev Not implemented442 #[solidity(rename_selector = "safeTransferFrom")]443 fn safe_transfer_from(&mut self, _from: Address, _to: Address, _token_id: U256) -> Result<()> {444 // TODO: Not implemetable445 Err("not implemented".into())446 }447448 /// @notice Transfer ownership of an RFT -- THE CALLER IS RESPONSIBLE449 /// TO CONFIRM THAT `to` IS CAPABLE OF RECEIVING NFTS OR ELSE450 /// THEY MAY BE PERMANENTLY LOST451 /// @dev Throws unless `msg.sender` is the current owner or an authorized452 /// operator for this RFT. Throws if `from` is not the current owner. Throws453 /// if `to` is the zero address. Throws if `tokenId` is not a valid RFT.454 /// Throws if RFT pieces have multiple owners.455 /// @param from The current owner of the NFT456 /// @param to The new owner457 /// @param tokenId The NFT to transfer458 #[weight(<SelfWeightOf<T>>::transfer_from_creating_removing())]459 fn transfer_from(460 &mut self,461 caller: Caller,462 from: Address,463 to: Address,464 token_id: U256,465 ) -> Result<()> {466 let caller = T::CrossAccountId::from_eth(caller);467 let from = T::CrossAccountId::from_eth(from);468 let to = T::CrossAccountId::from_eth(to);469 let token = token_id.try_into()?;470 let budget = self471 .recorder472 .weight_calls_budget(<StructureWeight<T>>::find_parent());473474 let balance = balance(&self, token, &from)?;475 ensure_single_owner(&self, token, balance)?;476477 <Pallet<T>>::transfer_from(self, &caller, &from, &to, token, balance, &budget)478 .map_err(dispatch_to_evm::<T>)?;479480 Ok(())481 }482483 /// @dev Not implemented484 fn approve(&mut self, _caller: Caller, _approved: Address, _token_id: U256) -> Result<()> {485 Err("not implemented".into())486 }487488 /// @notice Sets or unsets the approval of a given operator.489 /// The `operator` is allowed to transfer all token pieces of the `caller` on their behalf.490 /// @param operator Operator491 /// @param approved Should operator status be granted or revoked?492 #[weight(<SelfWeightOf<T>>::set_allowance_for_all())]493 fn set_approval_for_all(494 &mut self,495 caller: Caller,496 operator: Address,497 approved: bool,498 ) -> Result<()> {499 let caller = T::CrossAccountId::from_eth(caller);500 let operator = T::CrossAccountId::from_eth(operator);501502 <Pallet<T>>::set_allowance_for_all(self, &caller, &operator, approved)503 .map_err(dispatch_to_evm::<T>)?;504 Ok(())505 }506507 /// @dev Not implemented508 fn get_approved(&self, _token_id: U256) -> Result<Address> {509 // TODO: Not implemetable510 Err("not implemented".into())511 }512513 /// @notice Tells whether the given `owner` approves the `operator`.514 #[weight(<SelfWeightOf<T>>::allowance_for_all())]515 fn is_approved_for_all(&self, owner: Address, operator: Address) -> Result<bool> {516 let owner = T::CrossAccountId::from_eth(owner);517 let operator = T::CrossAccountId::from_eth(operator);518519 Ok(<Pallet<T>>::allowance_for_all(self, &owner, &operator))520 }521}522523/// Returns amount of pieces of `token` that `owner` have524pub fn balance<T: Config>(525 collection: &RefungibleHandle<T>,526 token: TokenId,527 owner: &T::CrossAccountId,528) -> Result<u128> {529 collection.consume_store_reads(1)?;530 let balance = <Balance<T>>::get((collection.id, token, &owner));531 Ok(balance)532}533534/// Throws if `owner_balance` is lower than total amount of `token` pieces535pub fn ensure_single_owner<T: Config>(536 collection: &RefungibleHandle<T>,537 token: TokenId,538 owner_balance: u128,539) -> Result<()> {540 collection.consume_store_reads(1)?;541 let total_supply = <TotalSupply<T>>::get((collection.id, token));542543 if owner_balance == 0 {544 return Err(dispatch_to_evm::<T>(545 <CommonError<T>>::MustBeTokenOwner.into(),546 ));547 }548549 if total_supply != owner_balance {550 return Err("token has multiple owners".into());551 }552 Ok(())553}554555/// @title ERC721 Token that can be irreversibly burned (destroyed).556#[solidity_interface(name = ERC721Burnable, enum(derive(PreDispatch)), enum_attr(weight))]557impl<T: Config> RefungibleHandle<T> {558 /// @notice Burns a specific ERC721 token.559 /// @dev Throws unless `msg.sender` is the current RFT owner, or an authorized560 /// operator of the current owner.561 /// @param tokenId The RFT to approve562 #[weight(<SelfWeightOf<T>>::burn_item_fully())]563 fn burn(&mut self, caller: Caller, token_id: U256) -> Result<()> {564 let caller = T::CrossAccountId::from_eth(caller);565 let token = token_id.try_into()?;566567 let balance = balance(&self, token, &caller)?;568 ensure_single_owner(&self, token, balance)?;569570 <Pallet<T>>::burn(self, &caller, token, balance).map_err(dispatch_to_evm::<T>)?;571 Ok(())572 }573}574575/// @title ERC721 minting logic.576#[solidity_interface(name = ERC721UniqueMintable, enum(derive(PreDispatch)), enum_attr(weight))]577impl<T: Config> RefungibleHandle<T> {578 /// @notice Function to mint a token.579 /// @param to The new owner580 /// @return uint256 The id of the newly minted token581 #[weight(<SelfWeightOf<T>>::create_item())]582 fn mint(&mut self, caller: Caller, to: Address) -> Result<U256> {583 let token_id: U256 = <TokensMinted<T>>::get(self.id)584 .checked_add(1)585 .ok_or("item id overflow")?586 .into();587 self.mint_check_id(caller, to, token_id)?;588 Ok(token_id)589 }590591 /// @notice Function to mint a token.592 /// @dev `tokenId` should be obtained with `nextTokenId` method,593 /// unlike standard, you can't specify it manually594 /// @param to The new owner595 /// @param tokenId ID of the minted RFT596 #[solidity(hide, rename_selector = "mint")]597 #[weight(<SelfWeightOf<T>>::create_item())]598 fn mint_check_id(&mut self, caller: Caller, to: Address, token_id: U256) -> Result<bool> {599 let caller = T::CrossAccountId::from_eth(caller);600 let to = T::CrossAccountId::from_eth(to);601 let token_id: u32 = token_id.try_into()?;602 let budget = self603 .recorder604 .weight_calls_budget(<StructureWeight<T>>::find_parent());605606 if <TokensMinted<T>>::get(self.id)607 .checked_add(1)608 .ok_or("item id overflow")?609 != token_id610 {611 return Err("item id should be next".into());612 }613614 let users = [(to.clone(), 1)]615 .into_iter()616 .collect::<BTreeMap<_, _>>()617 .try_into()618 .unwrap();619 <Pallet<T>>::create_item(620 self,621 &caller,622 CreateItemData::<T> {623 users,624 properties: CollectionPropertiesVec::default(),625 },626 &budget,627 )628 .map_err(dispatch_to_evm::<T>)?;629630 Ok(true)631 }632633 /// @notice Function to mint token with the given tokenUri.634 /// @param to The new owner635 /// @param tokenUri Token URI that would be stored in the NFT properties636 /// @return uint256 The id of the newly minted token637 #[solidity(rename_selector = "mintWithTokenURI")]638 #[weight(<SelfWeightOf<T>>::create_item() + <SelfWeightOf<T>>::set_token_properties(1))]639 fn mint_with_token_uri(640 &mut self,641 caller: Caller,642 to: Address,643 token_uri: String,644 ) -> Result<U256> {645 let token_id: U256 = <TokensMinted<T>>::get(self.id)646 .checked_add(1)647 .ok_or("item id overflow")?648 .into();649 self.mint_with_token_uri_check_id(caller, to, token_id, token_uri)?;650 Ok(token_id)651 }652653 /// @notice Function to mint token with the given tokenUri.654 /// @dev `tokenId` should be obtained with `nextTokenId` method,655 /// unlike standard, you can't specify it manually656 /// @param to The new owner657 /// @param tokenId ID of the minted RFT658 /// @param tokenUri Token URI that would be stored in the RFT properties659 #[solidity(hide, rename_selector = "mintWithTokenURI")]660 #[weight(<SelfWeightOf<T>>::create_item() + <SelfWeightOf<T>>::set_token_properties(1))]661 fn mint_with_token_uri_check_id(662 &mut self,663 caller: Caller,664 to: Address,665 token_id: U256,666 token_uri: String,667 ) -> Result<bool> {668 let key = key::url();669 let permission = get_token_permission::<T>(self.id, &key)?;670 if !permission.collection_admin {671 return Err("Operation is not allowed".into());672 }673674 let caller = T::CrossAccountId::from_eth(caller);675 let to = T::CrossAccountId::from_eth(to);676 let token_id: u32 = token_id.try_into().map_err(|_| "amount overflow")?;677 let budget = self678 .recorder679 .weight_calls_budget(<StructureWeight<T>>::find_parent());680681 if <TokensMinted<T>>::get(self.id)682 .checked_add(1)683 .ok_or("item id overflow")?684 != token_id685 {686 return Err("item id should be next".into());687 }688689 let mut properties = CollectionPropertiesVec::default();690 properties691 .try_push(Property {692 key,693 value: token_uri694 .into_bytes()695 .try_into()696 .map_err(|_| "token uri is too long")?,697 })698 .map_err(|e| Error::Revert(alloc::format!("Can't add property: {:?}", e)))?;699700 let users = [(to.clone(), 1)]701 .into_iter()702 .collect::<BTreeMap<_, _>>()703 .try_into()704 .unwrap();705 <Pallet<T>>::create_item(706 self,707 &caller,708 CreateItemData::<T> { users, properties },709 &budget,710 )711 .map_err(dispatch_to_evm::<T>)?;712 Ok(true)713 }714}715716fn get_token_property<T: Config>(717 collection: &CollectionHandle<T>,718 token_id: u32,719 key: &up_data_structs::PropertyKey,720) -> Result<String> {721 collection.consume_store_reads(1)?;722 let properties = <TokenProperties<T>>::try_get((collection.id, token_id))723 .map_err(|_| Error::Revert("Token properties not found".into()))?;724 if let Some(property) = properties.get(key) {725 return Ok(String::from_utf8_lossy(property).into());726 }727728 Err("Property tokenURI not found".into())729}730731fn get_token_permission<T: Config>(732 collection_id: CollectionId,733 key: &PropertyKey,734) -> Result<PropertyPermission> {735 let token_property_permissions = CollectionPropertyPermissions::<T>::try_get(collection_id)736 .map_err(|_| Error::Revert("No permissions for collection".into()))?;737 let a = token_property_permissions738 .get(key)739 .map(Clone::clone)740 .ok_or_else(|| {741 let key = String::from_utf8(key.clone().into_inner()).unwrap_or_default();742 Error::Revert(alloc::format!("No permission for key {}", key))743 })?;744 Ok(a)745}746747/// @title Unique extensions for ERC721.748#[solidity_interface(name = ERC721UniqueExtensions, enum(derive(PreDispatch)), enum_attr(weight))]749impl<T: Config> RefungibleHandle<T>750where751 T::AccountId: From<[u8; 32]> + AsRef<[u8; 32]>,752{753 /// @notice A descriptive name for a collection of NFTs in this contract754 fn name(&self) -> Result<String> {755 Ok(decode_utf16(self.name.iter().copied())756 .map(|r| r.unwrap_or(REPLACEMENT_CHARACTER))757 .collect::<String>())758 }759760 /// @notice An abbreviated name for NFTs in this contract761 fn symbol(&self) -> Result<String> {762 Ok(String::from_utf8_lossy(&self.token_prefix).into())763 }764765 /// @notice A description for the collection.766 fn description(&self) -> Result<String> {767 Ok(decode_utf16(self.description.iter().copied())768 .map(|r| r.unwrap_or(REPLACEMENT_CHARACTER))769 .collect::<String>())770 }771772 /// Returns the owner (in cross format) of the token.773 ///774 /// @param tokenId Id for the token.775 fn cross_owner_of(&self, token_id: U256) -> Result<eth::CrossAddress> {776 Self::token_owner(&self, token_id.try_into()?)777 .map(|o| eth::CrossAddress::from_sub_cross_account::<T>(&o))778 .or_else(|err| match err {779 TokenOwnerError::NotFound => Err(Error::Revert("token not found".into())),780 TokenOwnerError::MultipleOwners => Ok(eth::CrossAddress::from_eth(781 ADDRESS_FOR_PARTIALLY_OWNED_TOKENS,782 )),783 })784 }785786 /// Returns the token properties.787 ///788 /// @param tokenId Id for the token.789 /// @param keys Properties keys. Empty keys for all propertyes.790 /// @return Vector of properties key/value pairs.791 fn properties(&self, token_id: U256, keys: Vec<String>) -> Result<Vec<eth::Property>> {792 let keys = keys793 .into_iter()794 .map(|key| {795 <Vec<u8>>::from(key)796 .try_into()797 .map_err(|_| Error::Revert("key too large".into()))798 })799 .collect::<Result<Vec<_>>>()?;800801 <Self as CommonCollectionOperations<T>>::token_properties(802 &self,803 token_id.try_into()?,804 if keys.is_empty() { None } else { Some(keys) },805 )806 .into_iter()807 .map(eth::Property::try_from)808 .collect::<Result<Vec<_>>>()809 }810 /// @notice Transfer ownership of an RFT811 /// @dev Throws unless `msg.sender` is the current owner. Throws if `to`812 /// is the zero address. Throws if `tokenId` is not a valid RFT.813 /// Throws if RFT pieces have multiple owners.814 /// @param to The new owner815 /// @param tokenId The RFT to transfer816 #[weight(<SelfWeightOf<T>>::transfer_creating_removing())]817 fn transfer(&mut self, caller: Caller, to: Address, token_id: U256) -> Result<()> {818 let caller = T::CrossAccountId::from_eth(caller);819 let to = T::CrossAccountId::from_eth(to);820 let token = token_id.try_into()?;821 let budget = self822 .recorder823 .weight_calls_budget(<StructureWeight<T>>::find_parent());824825 let balance = balance(self, token, &caller)?;826 ensure_single_owner(self, token, balance)?;827828 <Pallet<T>>::transfer(self, &caller, &to, token, balance, &budget)829 .map_err(dispatch_to_evm::<T>)?;830 Ok(())831 }832833 /// @notice Transfer ownership of an RFT834 /// @dev Throws unless `msg.sender` is the current owner. Throws if `to`835 /// is the zero address. Throws if `tokenId` is not a valid RFT.836 /// Throws if RFT pieces have multiple owners.837 /// @param to The new owner838 /// @param tokenId The RFT to transfer839 #[weight(<SelfWeightOf<T>>::transfer_creating_removing())]840 fn transfer_cross(841 &mut self,842 caller: Caller,843 to: eth::CrossAddress,844 token_id: U256,845 ) -> Result<()> {846 let caller = T::CrossAccountId::from_eth(caller);847 let to = to.into_sub_cross_account::<T>()?;848 let token = token_id.try_into()?;849 let budget = self850 .recorder851 .weight_calls_budget(<StructureWeight<T>>::find_parent());852853 let balance = balance(self, token, &caller)?;854 ensure_single_owner(self, token, balance)?;855856 <Pallet<T>>::transfer(self, &caller, &to, token, balance, &budget)857 .map_err(dispatch_to_evm::<T>)?;858 Ok(())859 }860861 /// @notice Transfer ownership of an RFT862 /// @dev Throws unless `msg.sender` is the current owner. Throws if `to`863 /// is the zero address. Throws if `tokenId` is not a valid RFT.864 /// Throws if RFT pieces have multiple owners.865 /// @param to The new owner866 /// @param tokenId The RFT to transfer867 #[weight(<SelfWeightOf<T>>::transfer_creating_removing())]868 fn transfer_from_cross(869 &mut self,870 caller: Caller,871 from: eth::CrossAddress,872 to: eth::CrossAddress,873 token_id: U256,874 ) -> Result<()> {875 let caller = T::CrossAccountId::from_eth(caller);876 let from = from.into_sub_cross_account::<T>()?;877 let to = to.into_sub_cross_account::<T>()?;878 let token_id = token_id.try_into()?;879 let budget = self880 .recorder881 .weight_calls_budget(<StructureWeight<T>>::find_parent());882883 let balance = balance(self, token_id, &from)?;884 ensure_single_owner(self, token_id, balance)?;885886 Pallet::<T>::transfer_from(self, &caller, &from, &to, token_id, balance, &budget)887 .map_err(dispatch_to_evm::<T>)?;888 Ok(())889 }890891 /// @notice Burns a specific ERC721 token.892 /// @dev Throws unless `msg.sender` is the current owner or an authorized893 /// operator for this RFT. Throws if `from` is not the current owner. Throws894 /// if `to` is the zero address. Throws if `tokenId` is not a valid RFT.895 /// Throws if RFT pieces have multiple owners.896 /// @param from The current owner of the RFT897 /// @param tokenId The RFT to transfer898 #[solidity(hide)]899 #[weight(<SelfWeightOf<T>>::burn_from())]900 fn burn_from(&mut self, caller: Caller, from: Address, token_id: U256) -> Result<()> {901 let caller = T::CrossAccountId::from_eth(caller);902 let from = T::CrossAccountId::from_eth(from);903 let token = token_id.try_into()?;904 let budget = self905 .recorder906 .weight_calls_budget(<StructureWeight<T>>::find_parent());907908 let balance = balance(self, token, &from)?;909 ensure_single_owner(self, token, balance)?;910911 <Pallet<T>>::burn_from(self, &caller, &from, token, balance, &budget)912 .map_err(dispatch_to_evm::<T>)?;913 Ok(())914 }915916 /// @notice Burns a specific ERC721 token.917 /// @dev Throws unless `msg.sender` is the current owner or an authorized918 /// operator for this RFT. Throws if `from` is not the current owner. Throws919 /// if `to` is the zero address. Throws if `tokenId` is not a valid RFT.920 /// Throws if RFT pieces have multiple owners.921 /// @param from The current owner of the RFT922 /// @param tokenId The RFT to transfer923 #[weight(<SelfWeightOf<T>>::burn_from())]924 fn burn_from_cross(925 &mut self,926 caller: Caller,927 from: eth::CrossAddress,928 token_id: U256,929 ) -> Result<()> {930 let caller = T::CrossAccountId::from_eth(caller);931 let from = from.into_sub_cross_account::<T>()?;932 let token = token_id.try_into()?;933 let budget = self934 .recorder935 .weight_calls_budget(<StructureWeight<T>>::find_parent());936937 let balance = balance(self, token, &from)?;938 ensure_single_owner(self, token, balance)?;939940 <Pallet<T>>::burn_from(self, &caller, &from, token, balance, &budget)941 .map_err(dispatch_to_evm::<T>)?;942 Ok(())943 }944945 /// @notice Returns next free RFT ID.946 fn next_token_id(&self) -> Result<U256> {947 self.consume_store_reads(1)?;948 Ok(<TokensMinted<T>>::get(self.id)949 .checked_add(1)950 .ok_or("item id overflow")?951 .into())952 }953954 /// @notice Function to mint multiple tokens.955 /// @dev `tokenIds` should be an array of consecutive numbers and first number956 /// should be obtained with `nextTokenId` method957 /// @param to The new owner958 /// @param tokenIds IDs of the minted RFTs959 #[solidity(hide)]960 #[weight(<SelfWeightOf<T>>::create_multiple_items(token_ids.len() as u32))]961 fn mint_bulk(&mut self, caller: Caller, to: Address, token_ids: Vec<U256>) -> Result<bool> {962 let caller = T::CrossAccountId::from_eth(caller);963 let to = T::CrossAccountId::from_eth(to);964 let mut expected_index = <TokensMinted<T>>::get(self.id)965 .checked_add(1)966 .ok_or("item id overflow")?;967 let budget = self968 .recorder969 .weight_calls_budget(<StructureWeight<T>>::find_parent());970971 let total_tokens = token_ids.len();972 for id in token_ids.into_iter() {973 let id: u32 = id.try_into().map_err(|_| "token id overflow")?;974 if id != expected_index {975 return Err("item id should be next".into());976 }977 expected_index = expected_index.checked_add(1).ok_or("item id overflow")?;978 }979 let users = [(to.clone(), 1)]980 .into_iter()981 .collect::<BTreeMap<_, _>>()982 .try_into()983 .unwrap();984 let create_item_data = CreateItemData::<T> {985 users,986 properties: CollectionPropertiesVec::default(),987 };988 let data = (0..total_tokens)989 .map(|_| create_item_data.clone())990 .collect();991992 <Pallet<T>>::create_multiple_items(self, &caller, data, &budget)993 .map_err(dispatch_to_evm::<T>)?;994 Ok(true)995 }996997 /// @notice Function to mint multiple tokens with the given tokenUris.998 /// @dev `tokenIds` is array of pairs of token ID and token URI. Token IDs should be consecutive999 /// numbers and first number should be obtained with `nextTokenId` method1000 /// @param to The new owner1001 /// @param tokens array of pairs of token ID and token URI for minted tokens1002 #[solidity(hide, rename_selector = "mintBulkWithTokenURI")]1003 #[weight(<SelfWeightOf<T>>::create_multiple_items(tokens.len() as u32) + <SelfWeightOf<T>>::set_token_properties(tokens.len() as u32))]1004 fn mint_bulk_with_token_uri(1005 &mut self,1006 caller: Caller,1007 to: Address,1008 tokens: Vec<TokenUri>,1009 ) -> Result<bool> {1010 let key = key::url();1011 let caller = T::CrossAccountId::from_eth(caller);1012 let to = T::CrossAccountId::from_eth(to);1013 let mut expected_index = <TokensMinted<T>>::get(self.id)1014 .checked_add(1)1015 .ok_or("item id overflow")?;1016 let budget = self1017 .recorder1018 .weight_calls_budget(<StructureWeight<T>>::find_parent());10191020 let mut data = Vec::with_capacity(tokens.len());1021 let users: BoundedBTreeMap<_, _, _> = [(to.clone(), 1)]1022 .into_iter()1023 .collect::<BTreeMap<_, _>>()1024 .try_into()1025 .unwrap();1026 for TokenUri { id, uri } in tokens {1027 let id: u32 = id.try_into().map_err(|_| "token id overflow")?;1028 if id != expected_index {1029 return Err("item id should be next".into());1030 }1031 expected_index = expected_index.checked_add(1).ok_or("item id overflow")?;10321033 let mut properties = CollectionPropertiesVec::default();1034 properties1035 .try_push(Property {1036 key: key.clone(),1037 value: uri1038 .into_bytes()1039 .try_into()1040 .map_err(|_| "token uri is too long")?,1041 })1042 .map_err(|e| Error::Revert(alloc::format!("Can't add property: {:?}", e)))?;10431044 let create_item_data = CreateItemData::<T> {1045 users: users.clone(),1046 properties,1047 };1048 data.push(create_item_data);1049 }10501051 <Pallet<T>>::create_multiple_items(self, &caller, data, &budget)1052 .map_err(dispatch_to_evm::<T>)?;1053 Ok(true)1054 }10551056 /// @notice Function to mint a token.1057 /// @param to The new owner crossAccountId1058 /// @param properties Properties of minted token1059 /// @return uint256 The id of the newly minted token1060 #[weight(<SelfWeightOf<T>>::create_item() + <SelfWeightOf<T>>::set_token_properties(properties.len() as u32))]1061 fn mint_cross(1062 &mut self,1063 caller: Caller,1064 to: eth::CrossAddress,1065 properties: Vec<eth::Property>,1066 ) -> Result<U256> {1067 let token_id = <TokensMinted<T>>::get(self.id)1068 .checked_add(1)1069 .ok_or("item id overflow")?;10701071 let to = to.into_sub_cross_account::<T>()?;10721073 let properties = properties1074 .into_iter()1075 .map(eth::Property::try_into)1076 .collect::<Result<Vec<_>>>()?1077 .try_into()1078 .map_err(|_| Error::Revert(alloc::format!("too many properties")))?;10791080 let caller = T::CrossAccountId::from_eth(caller);10811082 let budget = self1083 .recorder1084 .weight_calls_budget(<StructureWeight<T>>::find_parent());10851086 let users = [(to, 1)]1087 .into_iter()1088 .collect::<BTreeMap<_, _>>()1089 .try_into()1090 .unwrap();1091 <Pallet<T>>::create_item(1092 self,1093 &caller,1094 CreateItemData::<T> { users, properties },1095 &budget,1096 )1097 .map_err(dispatch_to_evm::<T>)?;10981099 Ok(token_id.into())1100 }11011102 /// Returns EVM address for refungible token1103 ///1104 /// @param token ID of the token1105 fn token_contract_address(&self, token: U256) -> Result<Address> {1106 Ok(T::EvmTokenAddressMapping::token_to_address(1107 self.id,1108 token.try_into().map_err(|_| "token id overflow")?,1109 ))1110 }11111112 /// @notice Returns collection helper contract address1113 fn collection_helper_address(&self) -> Result<Address> {1114 Ok(T::ContractAddress::get())1115 }1116}11171118#[solidity_interface(1119 name = UniqueRefungible,1120 is(1121 ERC721,1122 ERC721Enumerable,1123 ERC721UniqueExtensions,1124 ERC721UniqueMintable,1125 ERC721Burnable,1126 ERC721Metadata(if(this.flags.erc721metadata)),1127 Collection(via(common_mut returns CollectionHandle<T>)),1128 TokenProperties,1129 ),1130 enum(derive(PreDispatch)),1131)]1132impl<T: Config> RefungibleHandle<T> where T::AccountId: From<[u8; 32]> + AsRef<[u8; 32]> {}11331134// Not a tests, but code generators1135generate_stubgen!(gen_impl, UniqueRefungibleCall<()>, true);1136generate_stubgen!(gen_iface, UniqueRefungibleCall<()>, false);11371138impl<T: Config> CommonEvmHandler for RefungibleHandle<T>1139where1140 T::AccountId: From<[u8; 32]> + AsRef<[u8; 32]>,1141{1142 const CODE: &'static [u8] = include_bytes!("./stubs/UniqueRefungible.raw");1143 fn call(1144 self,1145 handle: &mut impl PrecompileHandle,1146 ) -> Option<pallet_common::erc::PrecompileResult> {1147 call::<T, UniqueRefungibleCall<T>, _, _>(handle, self)1148 }1149}1// Copyright 2019-2022 Unique Network (Gibraltar) Ltd.2// This file is part of Unique Network.34// Unique Network is free software: you can redistribute it and/or modify5// it under the terms of the GNU General Public License as published by6// the Free Software Foundation, either version 3 of the License, or7// (at your option) any later version.89// Unique Network is distributed in the hope that it will be useful,10// but WITHOUT ANY WARRANTY; without even the implied warranty of11// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the12// GNU General Public License for more details.1314// You should have received a copy of the GNU General Public License15// along with Unique Network. If not, see <http://www.gnu.org/licenses/>.1617//! # Refungible Pallet EVM API for tokens18//!19//! Provides ERC-721 standart support implementation and EVM API for unique extensions for Refungible Pallet.20//! Method implementations are mostly doing parameter conversion and calling Refungible Pallet methods.2122extern crate alloc;2324use core::{25 char::{REPLACEMENT_CHARACTER, decode_utf16},26 convert::TryInto,27};28use evm_coder::{abi::AbiType, ToLog, generate_stubgen, solidity_interface, types::*};29use frame_support::{BoundedBTreeMap, BoundedVec};30use pallet_common::{31 CollectionHandle, CollectionPropertyPermissions, CommonCollectionOperations,32 Error as CommonError,33 erc::{CommonEvmHandler, CollectionCall, static_property::key},34 eth::{self, TokenUri},35};36use pallet_evm::{account::CrossAccountId, PrecompileHandle};37use pallet_evm_coder_substrate::{38 call, dispatch_to_evm,39 execution::{PreDispatch, Result, Error},40 frontier_contract,41};42use pallet_structure::{SelfWeightOf as StructureWeight, weights::WeightInfo as _};43use sp_core::{H160, U256, Get};44use sp_std::{collections::btree_map::BTreeMap, vec::Vec, vec};45use up_data_structs::{46 CollectionId, CollectionPropertiesVec, mapping::TokenAddressMapping, Property, PropertyKey,47 PropertyKeyPermission, PropertyPermission, TokenId, TokenOwnerError,48};4950use crate::{51 AccountBalance, Balance, Config, CreateItemData, Pallet, RefungibleHandle, TokenProperties,52 TokensMinted, TotalSupply, SelfWeightOf, weights::WeightInfo,53};5455frontier_contract! {56 macro_rules! RefungibleHandle_result {...}57 impl<T: Config> Contract for RefungibleHandle<T> {...}58}5960pub const ADDRESS_FOR_PARTIALLY_OWNED_TOKENS: H160 = H160::repeat_byte(0xff);6162/// Rft events.63#[derive(ToLog)]64pub enum ERC721TokenEvent {65 /// The token has been changed.66 TokenChanged {67 /// Token ID.68 #[indexed]69 token_id: U256,70 },71}7273/// @title A contract that allows to set and delete token properties and change token property permissions.74#[solidity_interface(name = TokenProperties, events(ERC721TokenEvent), enum(derive(PreDispatch)), enum_attr(weight))]75impl<T: Config> RefungibleHandle<T> {76 /// @notice Set permissions for token property.77 /// @dev Throws error if `msg.sender` is not admin or owner of the collection.78 /// @param key Property key.79 /// @param isMutable Permission to mutate property.80 /// @param collectionAdmin Permission to mutate property by collection admin if property is mutable.81 /// @param tokenOwner Permission to mutate property by token owner if property is mutable.82 #[solidity(hide)]83 #[weight(<SelfWeightOf<T>>::set_token_property_permissions(1))]84 fn set_token_property_permission(85 &mut self,86 caller: Caller,87 key: String,88 is_mutable: bool,89 collection_admin: bool,90 token_owner: bool,91 ) -> Result<()> {92 let caller = T::CrossAccountId::from_eth(caller);93 <Pallet<T>>::set_token_property_permissions(94 self,95 &caller,96 vec![PropertyKeyPermission {97 key: <Vec<u8>>::from(key)98 .try_into()99 .map_err(|_| "too long key")?,100 permission: PropertyPermission {101 mutable: is_mutable,102 collection_admin,103 token_owner,104 },105 }],106 )107 .map_err(dispatch_to_evm::<T>)108 }109110 /// @notice Set permissions for token property.111 /// @dev Throws error if `msg.sender` is not admin or owner of the collection.112 /// @param permissions Permissions for keys.113 #[weight(<SelfWeightOf<T>>::set_token_property_permissions(permissions.len() as u32))]114 fn set_token_property_permissions(115 &mut self,116 caller: Caller,117 permissions: Vec<eth::TokenPropertyPermission>,118 ) -> Result<()> {119 let caller = T::CrossAccountId::from_eth(caller);120 let perms = eth::TokenPropertyPermission::into_property_key_permissions(permissions)?;121122 <Pallet<T>>::set_token_property_permissions(self, &caller, perms)123 .map_err(dispatch_to_evm::<T>)124 }125126 /// @notice Get permissions for token properties.127 fn token_property_permissions(&self) -> Result<Vec<eth::TokenPropertyPermission>> {128 let perms = <Pallet<T>>::token_property_permission(self.id);129 Ok(perms130 .into_iter()131 .map(eth::TokenPropertyPermission::from)132 .collect())133 }134135 /// @notice Set token property value.136 /// @dev Throws error if `msg.sender` has no permission to edit the property.137 /// @param tokenId ID of the token.138 /// @param key Property key.139 /// @param value Property value.140 #[solidity(hide)]141 #[weight(<SelfWeightOf<T>>::set_token_properties(1))]142 fn set_property(143 &mut self,144 caller: Caller,145 token_id: U256,146 key: String,147 value: Bytes,148 ) -> Result<()> {149 let caller = T::CrossAccountId::from_eth(caller);150 let token_id: u32 = token_id.try_into().map_err(|_| "token id overflow")?;151 let key = <Vec<u8>>::from(key)152 .try_into()153 .map_err(|_| "key too long")?;154 let value = value.0.try_into().map_err(|_| "value too long")?;155156 let nesting_budget = self157 .recorder158 .weight_calls_budget(<StructureWeight<T>>::find_parent());159160 <Pallet<T>>::set_token_property(161 self,162 &caller,163 TokenId(token_id),164 Property { key, value },165 &nesting_budget,166 )167 .map_err(dispatch_to_evm::<T>)168 }169170 /// @notice Set token properties value.171 /// @dev Throws error if `msg.sender` has no permission to edit the property.172 /// @param tokenId ID of the token.173 /// @param properties settable properties174 #[weight(<SelfWeightOf<T>>::set_token_properties(properties.len() as u32))]175 fn set_properties(176 &mut self,177 caller: Caller,178 token_id: U256,179 properties: Vec<eth::Property>,180 ) -> Result<()> {181 let caller = T::CrossAccountId::from_eth(caller);182 let token_id: u32 = token_id.try_into().map_err(|_| "token id overflow")?;183184 let nesting_budget = self185 .recorder186 .weight_calls_budget(<StructureWeight<T>>::find_parent());187188 let properties = properties189 .into_iter()190 .map(eth::Property::try_into)191 .collect::<Result<Vec<_>>>()?;192193 <Pallet<T>>::set_token_properties(194 self,195 &caller,196 TokenId(token_id),197 properties.into_iter(),198 false,199 &nesting_budget,200 )201 .map_err(dispatch_to_evm::<T>)202 }203204 /// @notice Delete token property value.205 /// @dev Throws error if `msg.sender` has no permission to edit the property.206 /// @param tokenId ID of the token.207 /// @param key Property key.208 #[solidity(hide)]209 #[weight(<SelfWeightOf<T>>::delete_token_properties(1))]210 fn delete_property(&mut self, token_id: U256, caller: Caller, key: String) -> Result<()> {211 let caller = T::CrossAccountId::from_eth(caller);212 let token_id: u32 = token_id.try_into().map_err(|_| "token id overflow")?;213 let key = <Vec<u8>>::from(key)214 .try_into()215 .map_err(|_| "key too long")?;216217 let nesting_budget = self218 .recorder219 .weight_calls_budget(<StructureWeight<T>>::find_parent());220221 <Pallet<T>>::delete_token_property(self, &caller, TokenId(token_id), key, &nesting_budget)222 .map_err(dispatch_to_evm::<T>)223 }224225 /// @notice Delete token properties value.226 /// @dev Throws error if `msg.sender` has no permission to edit the property.227 /// @param tokenId ID of the token.228 /// @param keys Properties key.229 #[weight(<SelfWeightOf<T>>::delete_token_properties(keys.len() as u32))]230 fn delete_properties(231 &mut self,232 token_id: U256,233 caller: Caller,234 keys: Vec<String>,235 ) -> Result<()> {236 let caller = T::CrossAccountId::from_eth(caller);237 let token_id: u32 = token_id.try_into().map_err(|_| "token id overflow")?;238 let keys = keys239 .into_iter()240 .map(|k| Ok(<Vec<u8>>::from(k).try_into().map_err(|_| "key too long")?))241 .collect::<Result<Vec<_>>>()?;242243 let nesting_budget = self244 .recorder245 .weight_calls_budget(<StructureWeight<T>>::find_parent());246247 <Pallet<T>>::delete_token_properties(248 self,249 &caller,250 TokenId(token_id),251 keys.into_iter(),252 &nesting_budget,253 )254 .map_err(dispatch_to_evm::<T>)255 }256257 /// @notice Get token property value.258 /// @dev Throws error if key not found259 /// @param tokenId ID of the token.260 /// @param key Property key.261 /// @return Property value bytes262 fn property(&self, token_id: U256, key: String) -> Result<Bytes> {263 let token_id: u32 = token_id.try_into().map_err(|_| "token id overflow")?;264 let key = <Vec<u8>>::from(key)265 .try_into()266 .map_err(|_| "key too long")?;267268 let props = <TokenProperties<T>>::get((self.id, token_id));269 let prop = props.get(&key).ok_or("key not found")?;270271 Ok(prop.to_vec().into())272 }273}274275#[derive(ToLog)]276pub enum ERC721Events {277 /// @dev This event emits when NFTs are created (`from` == 0) and destroyed278 /// (`to` == 0). Exception: during contract creation, any number of RFTs279 /// may be created and assigned without emitting Transfer.280 Transfer {281 #[indexed]282 from: Address,283 #[indexed]284 to: Address,285 #[indexed]286 token_id: U256,287 },288 /// @dev Not supported289 Approval {290 #[indexed]291 owner: Address,292 #[indexed]293 approved: Address,294 #[indexed]295 token_id: U256,296 },297 /// @dev Not supported298 #[allow(dead_code)]299 ApprovalForAll {300 #[indexed]301 owner: Address,302 #[indexed]303 operator: Address,304 approved: bool,305 },306}307308/// @title ERC-721 Non-Fungible Token Standard, optional metadata extension309/// @dev See https://eips.ethereum.org/EIPS/eip-721310#[solidity_interface(name = ERC721Metadata, enum(derive(PreDispatch)), expect_selector = 0x5b5e139f)]311impl<T: Config> RefungibleHandle<T>312where313 T::AccountId: From<[u8; 32]> + AsRef<[u8; 32]>,314{315 /// @notice A descriptive name for a collection of NFTs in this contract316 /// @dev real implementation of this function lies in `ERC721UniqueExtensions`317 #[solidity(hide, rename_selector = "name")]318 fn name_proxy(&self) -> Result<String> {319 self.name()320 }321322 /// @notice An abbreviated name for NFTs in this contract323 /// @dev real implementation of this function lies in `ERC721UniqueExtensions`324 #[solidity(hide, rename_selector = "symbol")]325 fn symbol_proxy(&self) -> Result<String> {326 self.symbol()327 }328329 /// @notice A distinct Uniform Resource Identifier (URI) for a given asset.330 ///331 /// @dev If the token has a `url` property and it is not empty, it is returned.332 /// Else If the collection does not have a property with key `schemaName` or its value is not equal to `ERC721Metadata`, it return an error `tokenURI not set`.333 /// If the collection property `baseURI` is empty or absent, return "" (empty string)334 /// otherwise, if token property `suffix` present and is non-empty, return concatenation of baseURI and suffix335 /// otherwise, return concatenation of `baseURI` and stringified token id (decimal stringifying, without paddings).336 ///337 /// @return token's const_metadata338 #[solidity(rename_selector = "tokenURI")]339 fn token_uri(&self, token_id: U256) -> Result<String> {340 let token_id_u32: u32 = token_id.try_into().map_err(|_| "token id overflow")?;341342 match get_token_property(self, token_id_u32, &key::url()).as_deref() {343 Err(_) | Ok("") => (),344 Ok(url) => {345 return Ok(url.into());346 }347 };348349 let base_uri =350 pallet_common::Pallet::<T>::get_collection_property(self.id, &key::base_uri())351 .map(BoundedVec::into_inner)352 .map(String::from_utf8)353 .transpose()354 .map_err(|e| {355 Error::Revert(alloc::format!(356 "Can not convert value \"baseURI\" to string with error \"{}\"",357 e358 ))359 })?;360361 let base_uri = match base_uri.as_deref() {362 None | Some("") => {363 return Ok("".into());364 }365 Some(base_uri) => base_uri.into(),366 };367368 Ok(369 match get_token_property(self, token_id_u32, &key::suffix()).as_deref() {370 Err(_) | Ok("") => base_uri,371 Ok(suffix) => base_uri + suffix,372 },373 )374 }375}376377/// @title ERC-721 Non-Fungible Token Standard, optional enumeration extension378/// @dev See https://eips.ethereum.org/EIPS/eip-721379#[solidity_interface(name = ERC721Enumerable, enum(derive(PreDispatch)), expect_selector = 0x780e9d63)]380impl<T: Config> RefungibleHandle<T> {381 /// @notice Enumerate valid RFTs382 /// @param index A counter less than `totalSupply()`383 /// @return The token identifier for the `index`th NFT,384 /// (sort order not specified)385 fn token_by_index(&self, index: U256) -> U256 {386 index387 }388389 /// Not implemented390 fn token_of_owner_by_index(&self, _owner: Address, _index: U256) -> Result<U256> {391 // TODO: Not implemetable392 Err("not implemented".into())393 }394395 /// @notice Count RFTs tracked by this contract396 /// @return A count of valid RFTs tracked by this contract, where each one of397 /// them has an assigned and queryable owner not equal to the zero address398 fn total_supply(&self) -> Result<U256> {399 self.consume_store_reads(1)?;400 Ok(<Pallet<T>>::total_supply(self).into())401 }402}403404/// @title ERC-721 Non-Fungible Token Standard405/// @dev See https://github.com/ethereum/EIPs/blob/master/EIPS/eip-721.md406#[solidity_interface(name = ERC721, events(ERC721Events), enum(derive(PreDispatch)), enum_attr(weight), expect_selector = 0x80ac58cd)]407impl<T: Config> RefungibleHandle<T> {408 /// @notice Count all RFTs assigned to an owner409 /// @dev RFTs assigned to the zero address are considered invalid, and this410 /// function throws for queries about the zero address.411 /// @param owner An address for whom to query the balance412 /// @return The number of RFTs owned by `owner`, possibly zero413 fn balance_of(&self, owner: Address) -> Result<U256> {414 self.consume_store_reads(1)?;415 let owner = T::CrossAccountId::from_eth(owner);416 let balance = <AccountBalance<T>>::get((self.id, owner));417 Ok(balance.into())418 }419420 /// @notice Find the owner of an RFT421 /// @dev RFTs assigned to zero address are considered invalid, and queries422 /// about them do throw.423 /// Returns special 0xffffffffffffffffffffffffffffffffffffffff address for424 /// the tokens that are partially owned.425 /// @param tokenId The identifier for an RFT426 /// @return The address of the owner of the RFT427 fn owner_of(&self, token_id: U256) -> Result<Address> {428 self.consume_store_reads(2)?;429 let token = token_id.try_into()?;430 let owner = <Pallet<T>>::token_owner(self.id, token);431 owner432 .map(|address| *address.as_eth())433 .or_else(|err| match err {434 TokenOwnerError::NotFound => Err(Error::Revert("token not found".into())),435 TokenOwnerError::MultipleOwners => Ok(ADDRESS_FOR_PARTIALLY_OWNED_TOKENS),436 })437 }438439 /// @dev Not implemented440 #[solidity(rename_selector = "safeTransferFrom")]441 fn safe_transfer_from_with_data(442 &mut self,443 _from: Address,444 _to: Address,445 _token_id: U256,446 _data: Bytes,447 ) -> Result<()> {448 // TODO: Not implemetable449 Err("not implemented".into())450 }451452 /// @dev Not implemented453 #[solidity(rename_selector = "safeTransferFrom")]454 fn safe_transfer_from(&mut self, _from: Address, _to: Address, _token_id: U256) -> Result<()> {455 // TODO: Not implemetable456 Err("not implemented".into())457 }458459 /// @notice Transfer ownership of an RFT -- THE CALLER IS RESPONSIBLE460 /// TO CONFIRM THAT `to` IS CAPABLE OF RECEIVING NFTS OR ELSE461 /// THEY MAY BE PERMANENTLY LOST462 /// @dev Throws unless `msg.sender` is the current owner or an authorized463 /// operator for this RFT. Throws if `from` is not the current owner. Throws464 /// if `to` is the zero address. Throws if `tokenId` is not a valid RFT.465 /// Throws if RFT pieces have multiple owners.466 /// @param from The current owner of the NFT467 /// @param to The new owner468 /// @param tokenId The NFT to transfer469 #[weight(<SelfWeightOf<T>>::transfer_from_creating_removing())]470 fn transfer_from(471 &mut self,472 caller: Caller,473 from: Address,474 to: Address,475 token_id: U256,476 ) -> Result<()> {477 let caller = T::CrossAccountId::from_eth(caller);478 let from = T::CrossAccountId::from_eth(from);479 let to = T::CrossAccountId::from_eth(to);480 let token = token_id.try_into()?;481 let budget = self482 .recorder483 .weight_calls_budget(<StructureWeight<T>>::find_parent());484485 let balance = balance(&self, token, &from)?;486 ensure_single_owner(&self, token, balance)?;487488 <Pallet<T>>::transfer_from(self, &caller, &from, &to, token, balance, &budget)489 .map_err(dispatch_to_evm::<T>)?;490491 Ok(())492 }493494 /// @dev Not implemented495 fn approve(&mut self, _caller: Caller, _approved: Address, _token_id: U256) -> Result<()> {496 Err("not implemented".into())497 }498499 /// @notice Sets or unsets the approval of a given operator.500 /// The `operator` is allowed to transfer all token pieces of the `caller` on their behalf.501 /// @param operator Operator502 /// @param approved Should operator status be granted or revoked?503 #[weight(<SelfWeightOf<T>>::set_allowance_for_all())]504 fn set_approval_for_all(505 &mut self,506 caller: Caller,507 operator: Address,508 approved: bool,509 ) -> Result<()> {510 let caller = T::CrossAccountId::from_eth(caller);511 let operator = T::CrossAccountId::from_eth(operator);512513 <Pallet<T>>::set_allowance_for_all(self, &caller, &operator, approved)514 .map_err(dispatch_to_evm::<T>)?;515 Ok(())516 }517518 /// @dev Not implemented519 fn get_approved(&self, _token_id: U256) -> Result<Address> {520 // TODO: Not implemetable521 Err("not implemented".into())522 }523524 /// @notice Tells whether the given `owner` approves the `operator`.525 #[weight(<SelfWeightOf<T>>::allowance_for_all())]526 fn is_approved_for_all(&self, owner: Address, operator: Address) -> Result<bool> {527 let owner = T::CrossAccountId::from_eth(owner);528 let operator = T::CrossAccountId::from_eth(operator);529530 Ok(<Pallet<T>>::allowance_for_all(self, &owner, &operator))531 }532}533534/// Returns amount of pieces of `token` that `owner` have535pub fn balance<T: Config>(536 collection: &RefungibleHandle<T>,537 token: TokenId,538 owner: &T::CrossAccountId,539) -> Result<u128> {540 collection.consume_store_reads(1)?;541 let balance = <Balance<T>>::get((collection.id, token, &owner));542 Ok(balance)543}544545/// Throws if `owner_balance` is lower than total amount of `token` pieces546pub fn ensure_single_owner<T: Config>(547 collection: &RefungibleHandle<T>,548 token: TokenId,549 owner_balance: u128,550) -> Result<()> {551 collection.consume_store_reads(1)?;552 let total_supply = <TotalSupply<T>>::get((collection.id, token));553554 if owner_balance == 0 {555 return Err(dispatch_to_evm::<T>(556 <CommonError<T>>::MustBeTokenOwner.into(),557 ));558 }559560 if total_supply != owner_balance {561 return Err("token has multiple owners".into());562 }563 Ok(())564}565566/// @title ERC721 Token that can be irreversibly burned (destroyed).567#[solidity_interface(name = ERC721Burnable, enum(derive(PreDispatch)), enum_attr(weight))]568impl<T: Config> RefungibleHandle<T> {569 /// @notice Burns a specific ERC721 token.570 /// @dev Throws unless `msg.sender` is the current RFT owner, or an authorized571 /// operator of the current owner.572 /// @param tokenId The RFT to approve573 #[weight(<SelfWeightOf<T>>::burn_item_fully())]574 fn burn(&mut self, caller: Caller, token_id: U256) -> Result<()> {575 let caller = T::CrossAccountId::from_eth(caller);576 let token = token_id.try_into()?;577578 let balance = balance(&self, token, &caller)?;579 ensure_single_owner(&self, token, balance)?;580581 <Pallet<T>>::burn(self, &caller, token, balance).map_err(dispatch_to_evm::<T>)?;582 Ok(())583 }584}585586/// @title ERC721 minting logic.587#[solidity_interface(name = ERC721UniqueMintable, enum(derive(PreDispatch)), enum_attr(weight))]588impl<T: Config> RefungibleHandle<T> {589 /// @notice Function to mint a token.590 /// @param to The new owner591 /// @return uint256 The id of the newly minted token592 #[weight(<SelfWeightOf<T>>::create_item())]593 fn mint(&mut self, caller: Caller, to: Address) -> Result<U256> {594 let token_id: U256 = <TokensMinted<T>>::get(self.id)595 .checked_add(1)596 .ok_or("item id overflow")?597 .into();598 self.mint_check_id(caller, to, token_id)?;599 Ok(token_id)600 }601602 /// @notice Function to mint a token.603 /// @dev `tokenId` should be obtained with `nextTokenId` method,604 /// unlike standard, you can't specify it manually605 /// @param to The new owner606 /// @param tokenId ID of the minted RFT607 #[solidity(hide, rename_selector = "mint")]608 #[weight(<SelfWeightOf<T>>::create_item())]609 fn mint_check_id(&mut self, caller: Caller, to: Address, token_id: U256) -> Result<bool> {610 let caller = T::CrossAccountId::from_eth(caller);611 let to = T::CrossAccountId::from_eth(to);612 let token_id: u32 = token_id.try_into()?;613 let budget = self614 .recorder615 .weight_calls_budget(<StructureWeight<T>>::find_parent());616617 if <TokensMinted<T>>::get(self.id)618 .checked_add(1)619 .ok_or("item id overflow")?620 != token_id621 {622 return Err("item id should be next".into());623 }624625 let users = [(to.clone(), 1)]626 .into_iter()627 .collect::<BTreeMap<_, _>>()628 .try_into()629 .unwrap();630 <Pallet<T>>::create_item(631 self,632 &caller,633 CreateItemData::<T> {634 users,635 properties: CollectionPropertiesVec::default(),636 },637 &budget,638 )639 .map_err(dispatch_to_evm::<T>)?;640641 Ok(true)642 }643644 /// @notice Function to mint token with the given tokenUri.645 /// @param to The new owner646 /// @param tokenUri Token URI that would be stored in the NFT properties647 /// @return uint256 The id of the newly minted token648 #[solidity(rename_selector = "mintWithTokenURI")]649 #[weight(<SelfWeightOf<T>>::create_item() + <SelfWeightOf<T>>::set_token_properties(1))]650 fn mint_with_token_uri(651 &mut self,652 caller: Caller,653 to: Address,654 token_uri: String,655 ) -> Result<U256> {656 let token_id: U256 = <TokensMinted<T>>::get(self.id)657 .checked_add(1)658 .ok_or("item id overflow")?659 .into();660 self.mint_with_token_uri_check_id(caller, to, token_id, token_uri)?;661 Ok(token_id)662 }663664 /// @notice Function to mint token with the given tokenUri.665 /// @dev `tokenId` should be obtained with `nextTokenId` method,666 /// unlike standard, you can't specify it manually667 /// @param to The new owner668 /// @param tokenId ID of the minted RFT669 /// @param tokenUri Token URI that would be stored in the RFT properties670 #[solidity(hide, rename_selector = "mintWithTokenURI")]671 #[weight(<SelfWeightOf<T>>::create_item() + <SelfWeightOf<T>>::set_token_properties(1))]672 fn mint_with_token_uri_check_id(673 &mut self,674 caller: Caller,675 to: Address,676 token_id: U256,677 token_uri: String,678 ) -> Result<bool> {679 let key = key::url();680 let permission = get_token_permission::<T>(self.id, &key)?;681 if !permission.collection_admin {682 return Err("Operation is not allowed".into());683 }684685 let caller = T::CrossAccountId::from_eth(caller);686 let to = T::CrossAccountId::from_eth(to);687 let token_id: u32 = token_id.try_into().map_err(|_| "amount overflow")?;688 let budget = self689 .recorder690 .weight_calls_budget(<StructureWeight<T>>::find_parent());691692 if <TokensMinted<T>>::get(self.id)693 .checked_add(1)694 .ok_or("item id overflow")?695 != token_id696 {697 return Err("item id should be next".into());698 }699700 let mut properties = CollectionPropertiesVec::default();701 properties702 .try_push(Property {703 key,704 value: token_uri705 .into_bytes()706 .try_into()707 .map_err(|_| "token uri is too long")?,708 })709 .map_err(|e| Error::Revert(alloc::format!("Can't add property: {:?}", e)))?;710711 let users = [(to.clone(), 1)]712 .into_iter()713 .collect::<BTreeMap<_, _>>()714 .try_into()715 .unwrap();716 <Pallet<T>>::create_item(717 self,718 &caller,719 CreateItemData::<T> { users, properties },720 &budget,721 )722 .map_err(dispatch_to_evm::<T>)?;723 Ok(true)724 }725}726727fn get_token_property<T: Config>(728 collection: &CollectionHandle<T>,729 token_id: u32,730 key: &up_data_structs::PropertyKey,731) -> Result<String> {732 collection.consume_store_reads(1)?;733 let properties = <TokenProperties<T>>::try_get((collection.id, token_id))734 .map_err(|_| Error::Revert("Token properties not found".into()))?;735 if let Some(property) = properties.get(key) {736 return Ok(String::from_utf8_lossy(property).into());737 }738739 Err("Property tokenURI not found".into())740}741742fn get_token_permission<T: Config>(743 collection_id: CollectionId,744 key: &PropertyKey,745) -> Result<PropertyPermission> {746 let token_property_permissions = CollectionPropertyPermissions::<T>::try_get(collection_id)747 .map_err(|_| Error::Revert("No permissions for collection".into()))?;748 let a = token_property_permissions749 .get(key)750 .map(Clone::clone)751 .ok_or_else(|| {752 let key = String::from_utf8(key.clone().into_inner()).unwrap_or_default();753 Error::Revert(alloc::format!("No permission for key {}", key))754 })?;755 Ok(a)756}757758/// @title Unique extensions for ERC721.759#[solidity_interface(name = ERC721UniqueExtensions, enum(derive(PreDispatch)), enum_attr(weight))]760impl<T: Config> RefungibleHandle<T>761where762 T::AccountId: From<[u8; 32]> + AsRef<[u8; 32]>,763{764 /// @notice A descriptive name for a collection of NFTs in this contract765 fn name(&self) -> Result<String> {766 Ok(decode_utf16(self.name.iter().copied())767 .map(|r| r.unwrap_or(REPLACEMENT_CHARACTER))768 .collect::<String>())769 }770771 /// @notice An abbreviated name for NFTs in this contract772 fn symbol(&self) -> Result<String> {773 Ok(String::from_utf8_lossy(&self.token_prefix).into())774 }775776 /// @notice A description for the collection.777 fn description(&self) -> Result<String> {778 Ok(decode_utf16(self.description.iter().copied())779 .map(|r| r.unwrap_or(REPLACEMENT_CHARACTER))780 .collect::<String>())781 }782783 /// Returns the owner (in cross format) of the token.784 ///785 /// @param tokenId Id for the token.786 fn cross_owner_of(&self, token_id: U256) -> Result<eth::CrossAddress> {787 Self::token_owner(&self, token_id.try_into()?)788 .map(|o| eth::CrossAddress::from_sub_cross_account::<T>(&o))789 .or_else(|err| match err {790 TokenOwnerError::NotFound => Err(Error::Revert("token not found".into())),791 TokenOwnerError::MultipleOwners => Ok(eth::CrossAddress::from_eth(792 ADDRESS_FOR_PARTIALLY_OWNED_TOKENS,793 )),794 })795 }796797 /// Returns the token properties.798 ///799 /// @param tokenId Id for the token.800 /// @param keys Properties keys. Empty keys for all propertyes.801 /// @return Vector of properties key/value pairs.802 fn properties(&self, token_id: U256, keys: Vec<String>) -> Result<Vec<eth::Property>> {803 let keys = keys804 .into_iter()805 .map(|key| {806 <Vec<u8>>::from(key)807 .try_into()808 .map_err(|_| Error::Revert("key too large".into()))809 })810 .collect::<Result<Vec<_>>>()?;811812 <Self as CommonCollectionOperations<T>>::token_properties(813 &self,814 token_id.try_into()?,815 if keys.is_empty() { None } else { Some(keys) },816 )817 .into_iter()818 .map(eth::Property::try_from)819 .collect::<Result<Vec<_>>>()820 }821 /// @notice Transfer ownership of an RFT822 /// @dev Throws unless `msg.sender` is the current owner. Throws if `to`823 /// is the zero address. Throws if `tokenId` is not a valid RFT.824 /// Throws if RFT pieces have multiple owners.825 /// @param to The new owner826 /// @param tokenId The RFT to transfer827 #[weight(<SelfWeightOf<T>>::transfer_creating_removing())]828 fn transfer(&mut self, caller: Caller, to: Address, token_id: U256) -> Result<()> {829 let caller = T::CrossAccountId::from_eth(caller);830 let to = T::CrossAccountId::from_eth(to);831 let token = token_id.try_into()?;832 let budget = self833 .recorder834 .weight_calls_budget(<StructureWeight<T>>::find_parent());835836 let balance = balance(self, token, &caller)?;837 ensure_single_owner(self, token, balance)?;838839 <Pallet<T>>::transfer(self, &caller, &to, token, balance, &budget)840 .map_err(dispatch_to_evm::<T>)?;841 Ok(())842 }843844 /// @notice Transfer ownership of an RFT845 /// @dev Throws unless `msg.sender` is the current owner. Throws if `to`846 /// is the zero address. Throws if `tokenId` is not a valid RFT.847 /// Throws if RFT pieces have multiple owners.848 /// @param to The new owner849 /// @param tokenId The RFT to transfer850 #[weight(<SelfWeightOf<T>>::transfer_creating_removing())]851 fn transfer_cross(852 &mut self,853 caller: Caller,854 to: eth::CrossAddress,855 token_id: U256,856 ) -> Result<()> {857 let caller = T::CrossAccountId::from_eth(caller);858 let to = to.into_sub_cross_account::<T>()?;859 let token = token_id.try_into()?;860 let budget = self861 .recorder862 .weight_calls_budget(<StructureWeight<T>>::find_parent());863864 let balance = balance(self, token, &caller)?;865 ensure_single_owner(self, token, balance)?;866867 <Pallet<T>>::transfer(self, &caller, &to, token, balance, &budget)868 .map_err(dispatch_to_evm::<T>)?;869 Ok(())870 }871872 /// @notice Transfer ownership of an RFT873 /// @dev Throws unless `msg.sender` is the current owner. Throws if `to`874 /// is the zero address. Throws if `tokenId` is not a valid RFT.875 /// Throws if RFT pieces have multiple owners.876 /// @param to The new owner877 /// @param tokenId The RFT to transfer878 #[weight(<SelfWeightOf<T>>::transfer_creating_removing())]879 fn transfer_from_cross(880 &mut self,881 caller: Caller,882 from: eth::CrossAddress,883 to: eth::CrossAddress,884 token_id: U256,885 ) -> Result<()> {886 let caller = T::CrossAccountId::from_eth(caller);887 let from = from.into_sub_cross_account::<T>()?;888 let to = to.into_sub_cross_account::<T>()?;889 let token_id = token_id.try_into()?;890 let budget = self891 .recorder892 .weight_calls_budget(<StructureWeight<T>>::find_parent());893894 let balance = balance(self, token_id, &from)?;895 ensure_single_owner(self, token_id, balance)?;896897 Pallet::<T>::transfer_from(self, &caller, &from, &to, token_id, balance, &budget)898 .map_err(dispatch_to_evm::<T>)?;899 Ok(())900 }901902 /// @notice Burns a specific ERC721 token.903 /// @dev Throws unless `msg.sender` is the current owner or an authorized904 /// operator for this RFT. Throws if `from` is not the current owner. Throws905 /// if `to` is the zero address. Throws if `tokenId` is not a valid RFT.906 /// Throws if RFT pieces have multiple owners.907 /// @param from The current owner of the RFT908 /// @param tokenId The RFT to transfer909 #[solidity(hide)]910 #[weight(<SelfWeightOf<T>>::burn_from())]911 fn burn_from(&mut self, caller: Caller, from: Address, token_id: U256) -> Result<()> {912 let caller = T::CrossAccountId::from_eth(caller);913 let from = T::CrossAccountId::from_eth(from);914 let token = token_id.try_into()?;915 let budget = self916 .recorder917 .weight_calls_budget(<StructureWeight<T>>::find_parent());918919 let balance = balance(self, token, &from)?;920 ensure_single_owner(self, token, balance)?;921922 <Pallet<T>>::burn_from(self, &caller, &from, token, balance, &budget)923 .map_err(dispatch_to_evm::<T>)?;924 Ok(())925 }926927 /// @notice Burns a specific ERC721 token.928 /// @dev Throws unless `msg.sender` is the current owner or an authorized929 /// operator for this RFT. Throws if `from` is not the current owner. Throws930 /// if `to` is the zero address. Throws if `tokenId` is not a valid RFT.931 /// Throws if RFT pieces have multiple owners.932 /// @param from The current owner of the RFT933 /// @param tokenId The RFT to transfer934 #[weight(<SelfWeightOf<T>>::burn_from())]935 fn burn_from_cross(936 &mut self,937 caller: Caller,938 from: eth::CrossAddress,939 token_id: U256,940 ) -> Result<()> {941 let caller = T::CrossAccountId::from_eth(caller);942 let from = from.into_sub_cross_account::<T>()?;943 let token = token_id.try_into()?;944 let budget = self945 .recorder946 .weight_calls_budget(<StructureWeight<T>>::find_parent());947948 let balance = balance(self, token, &from)?;949 ensure_single_owner(self, token, balance)?;950951 <Pallet<T>>::burn_from(self, &caller, &from, token, balance, &budget)952 .map_err(dispatch_to_evm::<T>)?;953 Ok(())954 }955956 /// @notice Returns next free RFT ID.957 fn next_token_id(&self) -> Result<U256> {958 self.consume_store_reads(1)?;959 Ok(<TokensMinted<T>>::get(self.id)960 .checked_add(1)961 .ok_or("item id overflow")?962 .into())963 }964965 /// @notice Function to mint multiple tokens.966 /// @dev `tokenIds` should be an array of consecutive numbers and first number967 /// should be obtained with `nextTokenId` method968 /// @param to The new owner969 /// @param tokenIds IDs of the minted RFTs970 #[solidity(hide)]971 #[weight(<SelfWeightOf<T>>::create_multiple_items(token_ids.len() as u32))]972 fn mint_bulk(&mut self, caller: Caller, to: Address, token_ids: Vec<U256>) -> Result<bool> {973 let caller = T::CrossAccountId::from_eth(caller);974 let to = T::CrossAccountId::from_eth(to);975 let mut expected_index = <TokensMinted<T>>::get(self.id)976 .checked_add(1)977 .ok_or("item id overflow")?;978 let budget = self979 .recorder980 .weight_calls_budget(<StructureWeight<T>>::find_parent());981982 let total_tokens = token_ids.len();983 for id in token_ids.into_iter() {984 let id: u32 = id.try_into().map_err(|_| "token id overflow")?;985 if id != expected_index {986 return Err("item id should be next".into());987 }988 expected_index = expected_index.checked_add(1).ok_or("item id overflow")?;989 }990 let users = [(to.clone(), 1)]991 .into_iter()992 .collect::<BTreeMap<_, _>>()993 .try_into()994 .unwrap();995 let create_item_data = CreateItemData::<T> {996 users,997 properties: CollectionPropertiesVec::default(),998 };999 let data = (0..total_tokens)1000 .map(|_| create_item_data.clone())1001 .collect();10021003 <Pallet<T>>::create_multiple_items(self, &caller, data, &budget)1004 .map_err(dispatch_to_evm::<T>)?;1005 Ok(true)1006 }10071008 /// @notice Function to mint multiple tokens with the given tokenUris.1009 /// @dev `tokenIds` is array of pairs of token ID and token URI. Token IDs should be consecutive1010 /// numbers and first number should be obtained with `nextTokenId` method1011 /// @param to The new owner1012 /// @param tokens array of pairs of token ID and token URI for minted tokens1013 #[solidity(hide, rename_selector = "mintBulkWithTokenURI")]1014 #[weight(<SelfWeightOf<T>>::create_multiple_items(tokens.len() as u32) + <SelfWeightOf<T>>::set_token_properties(tokens.len() as u32))]1015 fn mint_bulk_with_token_uri(1016 &mut self,1017 caller: Caller,1018 to: Address,1019 tokens: Vec<TokenUri>,1020 ) -> Result<bool> {1021 let key = key::url();1022 let caller = T::CrossAccountId::from_eth(caller);1023 let to = T::CrossAccountId::from_eth(to);1024 let mut expected_index = <TokensMinted<T>>::get(self.id)1025 .checked_add(1)1026 .ok_or("item id overflow")?;1027 let budget = self1028 .recorder1029 .weight_calls_budget(<StructureWeight<T>>::find_parent());10301031 let mut data = Vec::with_capacity(tokens.len());1032 let users: BoundedBTreeMap<_, _, _> = [(to.clone(), 1)]1033 .into_iter()1034 .collect::<BTreeMap<_, _>>()1035 .try_into()1036 .unwrap();1037 for TokenUri { id, uri } in tokens {1038 let id: u32 = id.try_into().map_err(|_| "token id overflow")?;1039 if id != expected_index {1040 return Err("item id should be next".into());1041 }1042 expected_index = expected_index.checked_add(1).ok_or("item id overflow")?;10431044 let mut properties = CollectionPropertiesVec::default();1045 properties1046 .try_push(Property {1047 key: key.clone(),1048 value: uri1049 .into_bytes()1050 .try_into()1051 .map_err(|_| "token uri is too long")?,1052 })1053 .map_err(|e| Error::Revert(alloc::format!("Can't add property: {:?}", e)))?;10541055 let create_item_data = CreateItemData::<T> {1056 users: users.clone(),1057 properties,1058 };1059 data.push(create_item_data);1060 }10611062 <Pallet<T>>::create_multiple_items(self, &caller, data, &budget)1063 .map_err(dispatch_to_evm::<T>)?;1064 Ok(true)1065 }10661067 /// @notice Function to mint a token.1068 /// @param to The new owner crossAccountId1069 /// @param properties Properties of minted token1070 /// @return uint256 The id of the newly minted token1071 #[weight(<SelfWeightOf<T>>::create_item() + <SelfWeightOf<T>>::set_token_properties(properties.len() as u32))]1072 fn mint_cross(1073 &mut self,1074 caller: Caller,1075 to: eth::CrossAddress,1076 properties: Vec<eth::Property>,1077 ) -> Result<U256> {1078 let token_id = <TokensMinted<T>>::get(self.id)1079 .checked_add(1)1080 .ok_or("item id overflow")?;10811082 let to = to.into_sub_cross_account::<T>()?;10831084 let properties = properties1085 .into_iter()1086 .map(eth::Property::try_into)1087 .collect::<Result<Vec<_>>>()?1088 .try_into()1089 .map_err(|_| Error::Revert(alloc::format!("too many properties")))?;10901091 let caller = T::CrossAccountId::from_eth(caller);10921093 let budget = self1094 .recorder1095 .weight_calls_budget(<StructureWeight<T>>::find_parent());10961097 let users = [(to, 1)]1098 .into_iter()1099 .collect::<BTreeMap<_, _>>()1100 .try_into()1101 .unwrap();1102 <Pallet<T>>::create_item(1103 self,1104 &caller,1105 CreateItemData::<T> { users, properties },1106 &budget,1107 )1108 .map_err(dispatch_to_evm::<T>)?;11091110 Ok(token_id.into())1111 }11121113 /// Returns EVM address for refungible token1114 ///1115 /// @param token ID of the token1116 fn token_contract_address(&self, token: U256) -> Result<Address> {1117 Ok(T::EvmTokenAddressMapping::token_to_address(1118 self.id,1119 token.try_into().map_err(|_| "token id overflow")?,1120 ))1121 }11221123 /// @notice Returns collection helper contract address1124 fn collection_helper_address(&self) -> Result<Address> {1125 Ok(T::ContractAddress::get())1126 }1127}11281129#[solidity_interface(1130 name = UniqueRefungible,1131 is(1132 ERC721,1133 ERC721Enumerable,1134 ERC721UniqueExtensions,1135 ERC721UniqueMintable,1136 ERC721Burnable,1137 ERC721Metadata(if(this.flags.erc721metadata)),1138 Collection(via(common_mut returns CollectionHandle<T>)),1139 TokenProperties,1140 ),1141 enum(derive(PreDispatch)),1142)]1143impl<T: Config> RefungibleHandle<T> where T::AccountId: From<[u8; 32]> + AsRef<[u8; 32]> {}11441145// Not a tests, but code generators1146generate_stubgen!(gen_impl, UniqueRefungibleCall<()>, true);1147generate_stubgen!(gen_iface, UniqueRefungibleCall<()>, false);11481149impl<T: Config> CommonEvmHandler for RefungibleHandle<T>1150where1151 T::AccountId: From<[u8; 32]> + AsRef<[u8; 32]>,1152{1153 const CODE: &'static [u8] = include_bytes!("./stubs/UniqueRefungible.raw");1154 fn call(1155 self,1156 handle: &mut impl PrecompileHandle,1157 ) -> Option<pallet_common::erc::PrecompileResult> {1158 call::<T, UniqueRefungibleCall<T>, _, _>(handle, self)1159 }1160}pallets/refungible/src/lib.rsdiffbeforeafterboth--- a/pallets/refungible/src/lib.rs
+++ b/pallets/refungible/src/lib.rs
@@ -100,7 +100,7 @@
Event as CommonEvent, Pallet as PalletCommon,
};
use pallet_structure::Pallet as PalletStructure;
-use sp_core::H160;
+use sp_core::{Get, H160};
use sp_runtime::{ArithmeticError, DispatchError, DispatchResult, TransactionOutcome};
use sp_std::{vec::Vec, vec, collections::btree_map::BTreeMap};
use up_data_structs::{
@@ -571,6 +571,10 @@
stored_properties,
is_token_owner,
|properties| <TokenProperties<T>>::set((collection.id, token_id), properties),
+ erc::ERC721TokenEvent::TokenChanged {
+ token_id: token_id.into(),
+ }
+ .to_log(T::ContractAddress::get()),
)
}
pallets/refungible/src/stubs/UniqueRefungible.rawdiffbeforeafterbothbinary blob — no preview
pallets/refungible/src/stubs/UniqueRefungible.soldiffbeforeafterboth--- a/pallets/refungible/src/stubs/UniqueRefungible.sol
+++ b/pallets/refungible/src/stubs/UniqueRefungible.sol
@@ -17,9 +17,14 @@
}
}
+/// @dev inlined interface
+contract ERC721TokenEvent {
+ event TokenChanged(uint256 indexed tokenId);
+}
+
/// @title A contract that allows to set and delete token properties and change token property permissions.
/// @dev the ERC-165 identifier for this interface is 0xde0695c2
-contract TokenProperties is Dummy, ERC165 {
+contract TokenProperties is Dummy, ERC165, ERC721TokenEvent {
// /// @notice Set permissions for token property.
// /// @dev Throws error if `msg.sender` is not admin or owner of the collection.
// /// @param key Property key.
pallets/unique/src/eth/stubs/CollectionHelpers.rawdiffbeforeafterbothbinary blob — no preview
pallets/unique/src/eth/stubs/CollectionHelpers.soldiffbeforeafterboth--- a/pallets/unique/src/eth/stubs/CollectionHelpers.sol
+++ b/pallets/unique/src/eth/stubs/CollectionHelpers.sol
@@ -22,7 +22,6 @@
event CollectionCreated(address indexed owner, address indexed collectionId);
event CollectionDestroyed(address indexed collectionId);
event CollectionChanged(address indexed collectionId);
- event TokenChanged(address indexed collectionId, uint256 tokenId);
}
/// @title Contract, which allows users to operate with collections
tests/src/eth/abi/collectionHelpers.jsondiffbeforeafterboth--- a/tests/src/eth/abi/collectionHelpers.json
+++ b/tests/src/eth/abi/collectionHelpers.json
@@ -45,25 +45,6 @@
"type": "event"
},
{
- "anonymous": false,
- "inputs": [
- {
- "indexed": true,
- "internalType": "address",
- "name": "collectionId",
- "type": "address"
- },
- {
- "indexed": false,
- "internalType": "uint256",
- "name": "tokenId",
- "type": "uint256"
- }
- ],
- "name": "TokenChanged",
- "type": "event"
- },
- {
"inputs": [
{ "internalType": "uint32", "name": "collectionId", "type": "uint32" }
],
tests/src/eth/abi/nonFungible.jsondiffbeforeafterboth--- a/tests/src/eth/abi/nonFungible.json
+++ b/tests/src/eth/abi/nonFungible.json
@@ -54,6 +54,19 @@
"inputs": [
{
"indexed": true,
+ "internalType": "uint256",
+ "name": "tokenId",
+ "type": "uint256"
+ }
+ ],
+ "name": "TokenChanged",
+ "type": "event"
+ },
+ {
+ "anonymous": false,
+ "inputs": [
+ {
+ "indexed": true,
"internalType": "address",
"name": "from",
"type": "address"
tests/src/eth/abi/reFungible.jsondiffbeforeafterboth--- a/tests/src/eth/abi/reFungible.json
+++ b/tests/src/eth/abi/reFungible.json
@@ -54,6 +54,19 @@
"inputs": [
{
"indexed": true,
+ "internalType": "uint256",
+ "name": "tokenId",
+ "type": "uint256"
+ }
+ ],
+ "name": "TokenChanged",
+ "type": "event"
+ },
+ {
+ "anonymous": false,
+ "inputs": [
+ {
+ "indexed": true,
"internalType": "address",
"name": "from",
"type": "address"
tests/src/eth/api/CollectionHelpers.soldiffbeforeafterboth--- a/tests/src/eth/api/CollectionHelpers.sol
+++ b/tests/src/eth/api/CollectionHelpers.sol
@@ -17,7 +17,6 @@
event CollectionCreated(address indexed owner, address indexed collectionId);
event CollectionDestroyed(address indexed collectionId);
event CollectionChanged(address indexed collectionId);
- event TokenChanged(address indexed collectionId, uint256 tokenId);
}
/// @title Contract, which allows users to operate with collections
tests/src/eth/api/UniqueNFT.soldiffbeforeafterboth--- a/tests/src/eth/api/UniqueNFT.sol
+++ b/tests/src/eth/api/UniqueNFT.sol
@@ -12,9 +12,14 @@
function supportsInterface(bytes4 interfaceID) external view returns (bool);
}
+/// @dev inlined interface
+interface ERC721TokenEvent {
+ event TokenChanged(uint256 indexed tokenId);
+}
+
/// @title A contract that allows to set and delete token properties and change token property permissions.
/// @dev the ERC-165 identifier for this interface is 0xde0695c2
-interface TokenProperties is Dummy, ERC165 {
+interface TokenProperties is Dummy, ERC165, ERC721TokenEvent {
// /// @notice Set permissions for token property.
// /// @dev Throws error if `msg.sender` is not admin or owner of the collection.
// /// @param key Property key.
tests/src/eth/api/UniqueRefungible.soldiffbeforeafterboth--- a/tests/src/eth/api/UniqueRefungible.sol
+++ b/tests/src/eth/api/UniqueRefungible.sol
@@ -12,9 +12,14 @@
function supportsInterface(bytes4 interfaceID) external view returns (bool);
}
+/// @dev inlined interface
+interface ERC721TokenEvent {
+ event TokenChanged(uint256 indexed tokenId);
+}
+
/// @title A contract that allows to set and delete token properties and change token property permissions.
/// @dev the ERC-165 identifier for this interface is 0xde0695c2
-interface TokenProperties is Dummy, ERC165 {
+interface TokenProperties is Dummy, ERC165, ERC721TokenEvent {
// /// @notice Set permissions for token property.
// /// @dev Throws error if `msg.sender` is not admin or owner of the collection.
// /// @param key Property key.
tests/src/eth/events.test.tsdiffbeforeafterboth--- a/tests/src/eth/events.test.ts
+++ b/tests/src/eth/events.test.ts
@@ -29,8 +29,10 @@
});
});
-function clearEvents(ethEvents: NormalizedEvent[], subEvents: IEvent[]) {
- ethEvents.splice(0);
+function clearEvents(ethEvents: NormalizedEvent[] | null, subEvents: IEvent[]) {
+ if (ethEvents !== null) {
+ ethEvents.splice(0);
+ }
subEvents.splice(0);
}
@@ -374,7 +376,6 @@
const owner = await helper.eth.createAccountWithBalance(donor);
const {collectionAddress} = await helper.eth.createCollection(mode, owner, 'A', 'B', 'C');
const collection = await helper.ethNativeContract.collection(collectionAddress, mode, owner);
- const collectionHelper = await helper.ethNativeContract.collectionHelpers(owner);
const result = await collection.methods.mint(owner).send({from: owner});
const tokenId = result.events.Transfer.returnValues.tokenId;
await collection.methods.setTokenPropertyPermissions([
@@ -384,38 +385,29 @@
[TokenPermissionField.CollectionAdmin, true]],
],
]).send({from: owner});
-
- const ethEvents: any = [];
- collectionHelper.events.allEvents((_: any, event: any) => {
- ethEvents.push(event);
- });
const {unsubscribe, collectedEvents: subEvents} = await helper.subscribeEvents([{section: 'common', names: ['TokenPropertySet', 'TokenPropertyDeleted']}]);
{
- await collection.methods.setProperties(tokenId, [{key: 'A', value: [1,2,3]}]).send({from: owner});
+ const result = await collection.methods.setProperties(tokenId, [{key: 'A', value: [1,2,3]}]).send({from: owner});
await helper.wait.newBlocks(1);
- expect(ethEvents).to.containSubset([
- {
- event: 'TokenChanged',
- returnValues: {
- collectionId: collectionAddress,
- },
+ expect(result.events.TokenChanged).to.be.like({
+ event: 'TokenChanged',
+ returnValues: {
+ tokenId: tokenId,
},
- ]);
+ });
expect(subEvents).to.containSubset([{method: 'TokenPropertySet'}]);
- clearEvents(ethEvents, subEvents);
+ clearEvents(null, subEvents);
}
{
- await collection.methods.deleteProperties(tokenId, ['A']).send({from: owner});
+ const result = await collection.methods.deleteProperties(tokenId, ['A']).send({from: owner});
await helper.wait.newBlocks(1);
- expect(ethEvents).to.containSubset([
- {
- event: 'TokenChanged',
- returnValues: {
- collectionId: collectionAddress,
- },
+ expect(result.events.TokenChanged).to.be.like({
+ event: 'TokenChanged',
+ returnValues: {
+ tokenId: tokenId,
},
- ]);
+ });
expect(subEvents).to.containSubset([{method: 'TokenPropertyDeleted'}]);
}
unsubscribe();