2

What is the best way to disable ink! smart contracts code upgradability for a class of contracts? Below I describe what I need it for, just in case. Thank you!

For my use case, I need my pallet to accept a user-defined algorithm for getting a value used in an extrinsic call handler. As I see, the best way for that case is ink! smart contract. The user provides the contract address and a getter selector to my pallet and then my extrinsic handler can get the value invoking some user-defined code. It may look like this:

#[pallet::call]
impl<T: Config> Pallet<T> {
  #[pallet::call_index(0)]
  #[pallet::weight(T::WeightInfo::my_extrinsic())]
  pub fn my_extrinsic(origin: OriginFor<T>) -> DispatchResult {
    ensure_signed(origin)?;

    let user_defined = pallet_contracts::Pallet::<T>::bare_call(<contract address and a value getter selector provided by user earlier>).result?;

    if Self::check(user_defined) {
      Self::do_foo();
    } else {
      Self::do_bar();
    }

    Ok(())
  }
}

The problem here is that by default the user may replace the value getter using the set_code_hash, but I want the getter to be non-upgradable and the algorithm never changes.

I can create a fork of the pallet-contracts to remove set_code_hash from it but is there a better approach?

1 Answer 1

2

I believe the recommended approach in your case because you only want to disable a call is to customize the BaseCalleFilter (In pallet_contract Config) to filter out the calls you want to execute.

More on this here: How to set up BaseCallFilter to only allow a limited set of calls of one pallet to be executed?, with an interesting example in the responses and here Whitelist a call subset of a pallet.

If you wanted to add logic on top of the pallet instead of just disable a call, you approach is could work, fork pallet_contract and make changes directly to the code but that means that any future updates and improvement to the pallet_contract you would have to manually fix it in your pallet.

However in that scenario the recommended approach is to create a new pallet that acts as a "wrapper pallet" for the pallet_contract where you can add additional logic on top, in this case you would have to disable all calls from pallet_contracts and in your pallet have all calls forwarding to pallet_contracts except one. I wrote more about this approach here: How to customize pallet_contracts?

1
  • Thank you for the answer! As far as I see right now, there are only three ways to change the user_defined getter: 1 - proxy forwarding, 2 - set_code_hash(), and 3 - a cross-contract call to an upgradable contract. Hacking around the CallFilter looks promising to restrict the first and the second. But a non-upgradable contract still can do cross-contract call an upgradable one. Do you think a custom ChainExtension may help with it?
    – khassanov
    Commented Oct 4, 2023 at 12:34

Not the answer you're looking for? Browse other questions tagged or ask your own question.