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:

impl<T: Config> Pallet<T> {
  pub fn my_extrinsic(origin: OriginFor<T>) -> DispatchResult {

    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) {
    } else {


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


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?

  • 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
    Oct 4, 2023 at 12:34

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

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