rmrk-substrate icon indicating copy to clipboard operation
rmrk-substrate copied to clipboard

Approval mechanism - ink! environment

Open boyswan opened this issue 3 years ago • 6 comments

RMRK pallet usage from within an ink! environment via chain-extensions requires a Contract AccountId origin. This unfortunately drastically limits the capability of the chain-extension, as the contract can never act on behalf of a user due to the security issues of proxying a User AccountId origin. Original issue discussed

An ERC* style Approval mechanism could allow contracts to have complete flexibility whilst maintaining user security.

Some low-level but powerful use cases for approval based actions:

  • Escrow (Contract can send approved NFT(s) on behalf of a user)
  • Multi-collection minters (Multiple contracts can mint from a single collection)
  • Complex Item "destroy" mechanics (Contract can burn NFT(s) on behalf of a user)

I imagine an approval mechanism has already been considered by the RMRK team, so this post is intended as more of a discussion point as to whether this functionality should, shouldn't (or cannot) exist in the RMRK pallets.

boyswan avatar Aug 16 '22 08:08 boyswan

Good point. Another idea I can come up with is to allow to register "precompiled" contract in the ink environment. This way, on the ink side all the contract interactions will be much more natural. You can interact with a precompiled contract like a regular contract, and you can authenticate the both sides by simply looking up the caller address.

h4x3rotab avatar Aug 16 '22 09:08 h4x3rotab

Oops, I'd better to post the comments to the ink repo

h4x3rotab avatar Aug 16 '22 09:08 h4x3rotab

Interestingly it looks like the Gear implementation has token approvals.

boyswan avatar Aug 24 '22 22:08 boyswan

I didn't dig into the ink! chain extension code. So my understanding could be inaccurate. Please correct me if there's anything wrong.

There are two problems:

  1. How to allow someone to interact with RMRK?
  2. How to allow a 3rd party contract to control the NFTs on behalf of a user?

We need to implement an approval mechanism for Q2 anyway, but the answer to Q1 may affect the implementation. So let's start with Q1.

Without reviewing the implementation I guess it's one of the two ways:

  1. Expose the RMRK pallets functions to ink! by chain extensions, and then allow any contract to call the chain extensions
  2. Same as 1 but only allow a "designated" ink! contract to interact with RMRK, and the contract acts as a proxy contract to be used by users or other contracts in the ink! world.

Chain extensions are the code running in the Substrate runtime world. They inherently have full access to the entire runtime (e.g. call any functions, and read & write to any storage). On the other hand, ink contracts can be deployed by anyone and thus cannot be trusted. So if the chain extension acts as a bridge to the pallet world, it must verify the contract has sufficient permission.

If we go with the first approach, i.e. anyone can call the chain extension to interact with RMRK, we will face the complicated permission problem. The chain extension should only expose permissionless functions to smart contracts. For example, similar to ERC20's transferFrom() with the pre-approval from the user. This leaves a few problems:

  1. How can the user approve the contract? If only permissionless functions are exposed, end users need to approve the contract on the Substrate side, which makes the user experience less smooth. To support 3rd party contracts to approve the spending, we need an additional chain extension function to call approve() from the ink side.
  2. The chain extension can only be called by a contract. So only deployed contracts can interact with RMRK. Users must jump back to the Substrate side to transfer and manage the NFTs, which makes the user experience inconsistent and put a lot of burden on the frontend developers.
  3. The approval mechanism needs to be implemented in the RMRK pallet, but it's not used by any pallet, which is not "zero cost" for the parachains that don't have ink contracts.

The second approach is to create a special ink contract and use it to manage the permission. The chain extension should be configurable to specify the address of a "ProxyContract" that lives in the ink world. The address can be controlled by a DAO (e.g. the council or via on-chain democracy). Then the chain extension can expose the full admin access to the RMRK pallet to the ProxyContract. It can authenticate the ProxyContract by checking env.ext().address (the current running contract). With this setup, the RMRK pallet access is fully delegated to the ProxyContract. Then, inside the ProxyContract, we can implement the permission check and the approval mechanism in a very ink way.

I'd prefer the second approach because it doesn't have any of the above problems:

  1. Can implement the approve and transferFrom mechanisms purely in an ink! contract. It doesn't distinguish the user can contract, as they are treated equally. So both users and contracts can approve or transfer on behalf of the approver account.
  2. Both users can contracts can access the ProxyContract since it's just a regular contract. So the on-chain and off-chain invocation can be treated equally, saving the frontend developer a lot of time
  3. The chain extension adds minimum changes to RMRK. So it's nearly "zero-cost" for the RMRK users without the ink integration requirement.

h4x3rotab avatar Aug 26 '22 08:08 h4x3rotab

Thanks for the reply!

So a couple of considerations/thoughts that might influence the decision. Likewise there may be inaccuracies, but based on my current understanding:

  • The RMRK pallet is not only accessible to ink via chain-extensions, but can also be directly called ie. via Polkadot.js. Pushing the approval logic to the smart contract means that any other usage of the pallet loses out on this functionality.

  • There is currently an approve mechanism for accept_nft, accept_resource etc as part of the RMRK spec. Extending the spec to include approvals for approve_transfer and approve_mint is most in line with the current implementation.

  • Chain extensions do have full access, but are implemented at the parachain level. Any access to functionality/storage/etc is limited by what the extension allows - it can be as defensive as it needs. If there’s a security issue at the chain-extension level then this is a parachain problem, not an ink one. What are the risks you see coming from ink contracts?

I feel like the proxy contract route might be a bit of a rabbit hole. It means the chain-extension has to give more 'power' to ink, and then as you say you need to introduce an ink <> chain-ext permission system. It would also mean that you would need to do cross-chain calls for access to the proxy contract in order to use it - I assume this would come with extra overhead/cost for contract devs.

I think the first thing to understand is whether the approval mechanism is a smart-contract concern, a pallet concern or a RMRK spec concern.

boyswan avatar Aug 26 '22 14:08 boyswan

The RMRK pallet is not only accessible to ink via chain-extensions, but can also be directly called ie. via Polkadot.js.

Yes.

Pushing the approval logic to the smart contract means that any other usage of the pallet loses out on this functionality.

Here comes the core argument. Please let me explain why the allowance design is useful in the context of smart contracts, but not in pallets.

0x Protocol has an excellent website to explain the allowance mechanism. It's mostly is used for an user to grant the access to his assets to a 3rd party, usually smart contracts. This is a must because the smart contracts are deployable by anyone, and thus it's not trusted.

However in the context of pallets (inside a substrate runtime), things are little bit different. The runtime is deployed through the on-chain governance, and thus every pallet in the runtime has the full access to the blockchain. In other words, in the pallet world, all the modules are "trusted", and they share the same permission (i.e. root access). That's why the democracy pallet can directly lock users funds without asking any user's permission. You can hardly find any allowance mechanism in pallet code for the same reason.

Do you think there's specific case where the allowance mechanism is required outside the smart contract context?

There is currently an approve mechanism for accept_nft, accept_resource etc as part of the RMRK spec.

When talking about the approval mechanism, I always compare with ERC20/721's allowance mechanism, which is the typical case where we want a 3rd party smart contract to proactively transfer RMRK NFTs to itself. In my understanding this is different from accept_nft and accept_resource, since they allow anyone to "propose" making change to a NFT owned by someone else, and the owner can accept or reject.

Do you want the ERC721-style allowance mechanism, or RMRK's proposal-style mechanism? It's a good idea to discuss the both cases.

Chain extensions do have full access, but are implemented at the parachain level. Any access to functionality/storage/etc is limited by what the extension allows - it can be as defensive as it needs. If there’s a security issue at the chain-extension level then this is a parachain problem, not an ink one. What are the risks you see coming from ink contracts?

You are right. I don't think there's any risk from the contract side.

It means the chain-extension has to give more 'power' to ink, and then as you say you need to introduce an ink <> chain-ext permission system.

It can be done pretty easily. We just deploy a "ProxyContract" and save its address in the RMRK pallet, which can only be set by on-chain governance (the same safety level as on-chain upgrade), and in the extension we make sure it can only be called by the "ProxyContract" account id.

This way, the extension can expose the full access to the RMRK pallet to the ink contract. In the contract side, all the interaction to the contract is regular calls, which has proper caller and callee information.

It would also mean that you would need to do cross-chain calls for access to the proxy contract in order to use it - I assume this would come with extra overhead/cost for contract devs.

I'm not sure why cross-chain call is needed. The scenarios we have discussed are all within the scope of a single parachain or standalone substrate blockchain. If you referred to the cross-chain NFT transfer case, it's supposed to be handled by the pallets in the first place, and then can be also handled by ink! when the ink-XCM integration is finished in the future.

h4x3rotab avatar Aug 30 '22 12:08 h4x3rotab

Closing it for now, feel free to follow ink native implementation in https://github.com/rmrk-team/rmrk-ink

ilionic avatar Oct 27 '22 15:10 ilionic