nft-registry
所属分类:NFT
开发工具:Rust
文件大小:25KB
下载次数:0
上传日期:2020-01-06 09:30:17
上 传 者:
sh-1993
说明: nft注册中心,,
(nft-registry,,)
文件列表:
Cargo.toml (3099, 2020-01-06)
LICENSE (1210, 2020-01-06)
build.rs (246, 2020-01-06)
runtime (0, 2020-01-06)
runtime\Cargo.toml (5416, 2020-01-06)
runtime\src (0, 2020-01-06)
runtime\src\lib.rs (13281, 2020-01-06)
runtime\src\nftregistry (0, 2020-01-06)
runtime\src\nftregistry\mod.rs (4294, 2020-01-06)
runtime\src\nftregistry\tests.rs (14530, 2020-01-06)
scripts (0, 2020-01-06)
scripts\init.sh (225, 2020-01-06)
src (0, 2020-01-06)
src\chain_spec.rs (4995, 2020-01-06)
src\cli.rs (4008, 2020-01-06)
src\main.rs (557, 2020-01-06)
src\service.rs (8543, 2020-01-06)
### Build
```bash
./scripts/init.sh
cargo build --release
```
### Test
Run tests from the ```runtime``` package, not the root
```bash
cd ./runtime
cargo test
```
You can see the substrate runtime event logs with
```bash
cargo test -- --nocapture
```
### Runtime Design
The nftregistry substrate module allows new NFT registries to be ..registered.
Custom validation logic for minting new NFTs of a given registry is provided in
a WebAssembly contract that will be invoked by the runtime before minting.
#### User flow to create a new registry
1. User uploads and instantiates an instance of a WebAssembly contract that contains a validation method
(Details in [])
// 2. User encodes method selector to validation function with the Substrate SCALE
// encoding (details [])
2. User calls "new_registry" method of nftregistry module, providing the
address of the instantiated validation contract and encoded method selector
#### User flow to mint an NFT with an existing registry
1. User calls "mint" method of nftregistry module, providing registry uid to
register and encoded parameters for validation contract
Nomenclature:
Registry - A set of NFTs of a single class and a custom mint validation function
NFT - A specific token instance in a registry
The Centrifuge chain has a custom Nft Registry module that defines the
following dispatchable functions (meaning they can be called by users of the
blockchain):
- fn new_registry(validation_fn_addr: AccountId);
- fn mint(uid: Hash, parameters: Vec, value: BalanceOf, gas_limit: Gas);
- fn finish_mint(uid: &RegistryUid);
### New Registry
A registry is a mapping of a unique id to an address. The address should be a
smart contract that represents an NFT and has a validation function for minting
new tokens. Further measures can be taken to make guarantees that the addres
indeed points to a contract representing a type of NFT.
The new_registry function assigns a unique id to a validation contract address
and stores the key-value pair in a mapping, and finally emits an event.
### Mint
This function will invoke a call to the specified registry contract's
validation function, passing along any provided call data.
The function assumes the contract will in turn call `finish_mint` on a successful
validation.
### Finish Mint
Is intended to be called by the validation contract and **not directly by a
user**. Therefore it checks that the sender is the validator contract address of the
registry that was specified as an argument.
Once ensured, a unique id is generated for the new NFT and it is stored as a
mapping to a byte array that represents the NFT storage structure.
### Runtime vs Contract storage
In general there are two places to store data for a given NFT
1. Mappings in the runtime storage
2. The validation contract associated with a registry
Currently there are no unique data types for NFTs in the runtime - just a byte
array for each NFT. Data that you
want to control the behaviour of should go in the runtime. For instance, if you
know that an NFT will always have a single account owner, then the owner and
transfer logic should become part of the module storage and dispatch methods.
Otherwise, you could leave such details to be tracked in the validator contract
storage according to its own defined behaviours.
### Issues Run Into
I've run into some issues in the Substrate code. This is to be expected,
Substrate is a work in progress. But it just means the process is very involved
right now and you may end up needing to contribute fixes/enhancements. Also,
the generated Rust docs hosted on substrate.dev are for v1 of Substrate. The
best resource is to read the code straight from Github. The [Riot
channels](https://riot.im/app/#/room/#substrate-technical:matrix.org) are also
decent. There is one for Substrate and one for Ink!.
Currently, if you try to build the ink contract you will get an error that
`invoke_runtime` is not a function of `EnvAccessMut`. This is ready to be
fixed, see this [PR](https://github.com/paritytech/ink/pull/302). In the
meantime if you want to build the contract, you can clone the ink repository
and make the changes locally [].
Other related issues
https://github.com/paritytech/substrate/issues/4506
https://github.com/paritytech/ink/issues/301
https://github.com/paritytech/ink/issues/297
https://github.com/paritytech/ink-types-node-runtime/pull/7
### Failed attempts at custom validation
The method I converged on is to use the Wasm runtime interface function,
`dispatch_call`. Also called `invoke_runtime` through the ink env wrapper. The
contracts module exposes a set of these functions as a sort of system API for
ink contracts to control the more crtical parts of the runtime. For instance,
contracts can read and write runtime state through these methods. The complete
list is specified in the code at
[contracts/src/wasm/runtime.rs](https://github.com/paritytech/substrate/blob/40a16efefc070faf5a25442bc3ae1d0ea2478eee/frame/contracts/src/wasm/runtime.rs#L296)
There is also a document discussing the API design in v2
[here](https://hackmd.io/@robbepop/rkPdZHrrS#Current-State).
There were a few other ways to I tried to get communication between the
substrate runtime and Wasm contracts.
#### Events
Events are stored in a global list and therefore seemed like a good avenue to
extract data from a contract. Essentially the runtime calls the contract, the
contract emits an event specifying whether the validation was a success, and
the runtime then pulls the event off the stack and reads its contents.
In Substrate the Event enum for each module is rolled up into one aggregate
enum via a macro at compile time. When a contract emits an event it will be of
variant `Contract(..)` as specified
[here](https://substrate.dev/rustdocs/v1.0/srml_contract/enum.RawEvent.html).
But when it is stored in the global event log it is converted into a
system::Trait Event type, because that is the module which stores the event
log. Unfortunately the substrate primitive types do not support converting back
from a system trait and so the global variants cannot be matched on from a
different module (like nftregistry). A conversation with the devs on Riot
confirmed that this has never been considered and so I abandoned this option.
#### Return values from a dispatchable function
It seems that it would make sense to retrieve return data of a contract call
from the `contracts` `call` dispatch function
[srml_contract/lib.rs.html#335](https://substrate.dev/rustdocs/v1.0/src/srml_contract/lib.rs.html#335).
However, dispatch methods are apparently not intended to ever return useful
results in this way. I think the devs have future plans to make functions
asynchronous and maybe this would not play well together. Either way, if they
aren't willing to support the change then its a dead end unless we want to fork
Substrate.
### Encoding calls for Wasm
A core principle of Substrate is interoperability with other implementations.
Therefore all useful runtime types are serializable using [Scale
encoding](https://substrate.dev/docs/en/overview/low-level-data-format).
Encoding a Wasm contract call is similar to Ethereum contracts. To specify the
function of a contract for invoking (called the selector), the function name (aka "validate") is hashed
with the system runtime hash function, and the first four bytes are used. Any
parameters to the method are encoded with Scale and concatenated to the
selector.
This is how the cross-contract [call
builder](https://github.com/paritytech/ink/blob/master/core/src/env2/call/create.rs#L58) works in ink2.
Internally it uses the CallData struct to build the encoding as described. As
seen in [/env2/call/utils.rs](https://github.com/paritytech/ink/blob/master/core/src/env2/call/utils.rs#L83), CallData takes a selector arguments via the push_arg method. CallData is itself scale::Encodable as well.
This all being said, I have not yet managed to successfully invoke the method
of an ink contract. By testing the flow with a more simple wasm contract (fn
mint_nft_from_basic_contract), it seems likely that the problem is a faulty
encoding to the ink compiled contract.
The Wasm contract code also needs an encoding of the runtime dispatch method it
will call on successful validation. Luckily, encoding dispatch methods is much
easier because the outer dispatch enum generated by Substrate, `Call`, implements
`scale::Encode`. Therefore the following code will generate the encoding.
```rust
codec::Encode::encode(&Call::NftRegistry(nftregistry::Call::finish_mint(registry_id))
```
There is a similar interface for typed calls to ink contracts through a
compilation feature, `ink-as-dependency`. An example use is in the [delegator
contract on github](https://github.com/paritytech/ink/blob/master/examples/lang2/delegator/lib.rs).
However, this is meant for use by other contracts, and has not really been
considered to be used within the substrate runtime. I say this because the
interface uses an `Env` type to construct contract types in the [`create_using`](https://github.com/paritytech/ink/blob/master/core/src/env2/call/create.rs#L214) method.
Though `Env` could certainly be implemented by the substrate Runtime struct
with some work, there is also a way to bypass the Env via the
[`FromAccountId`](https://github.com/paritytech/ink/blob/master/core/src/env2/call/create.rs#L49) trait which should be able to construct a Contract from just the address.
Simply implement the ink trait, `EnvTypes`, for the test runtime struct and
implement `FromAccountId for Testcontract`.
```rust
Testcontract::from_account_id( contract_addr )
```
This is done in the code but currently I'm getting an error of incompatible ink_core versions of the
`AccountId` in the Testcontract constructor. This stems from the fact that the
test environment is using u*** for AccountIds and ink! has a hardcoded `struct
AccountId([u8;32])`, which may be the reason for confusion in encoding.
It's an unfinished avenue to explore but its certainly the case
that this code was not made to be used in this way and so there may be more issues to
solve if this path is continued.
### Tests
There are two complete tests in
[nftregistry/tests.rs](https://github.com/jaybutera/nft-registry/tree/master/runtime/src/nftregistry/tests.rs#L331).
Both upload a WebAssembly contract, create a registry, and mint
an NFT.
```mint_nft_from_basic_contract``` - Compiles a simple contract written
directly in WebAssembly to dispatch an encoded call to
`nftregistry::finish_mint`.
```mint_nft_from_ink_contract``` - Reads a compiled .wasm file of an ink
contract and attempts to invoke a method `validate` within it.
There is also a lot of boilerplate code in the same file. The Dummy structures are used for
behaviours needed in the contracts module to use u*** AccountIds instead of H256 like the
production runtime does. This is all just to make testing more simple and can
be ignored now that its defined.
近期下载者:
相关文件:
收藏者: