Exchange rate canister required to deploy CMC

Is the exchange rate canister absolutely required to deploy the CMC locally or is there a way to deploys it with some constants?

I deployed the CMC with following parameters:

const sourceArg: CyclesCanisterInitPayload = {
      exchange_rate_canister: [],
      last_purged_notification: [0n],
      governance_canister_id: [Principal.fromText(governanceCanisterId)],
      minting_account_id: [{bytes: minterAccountIdentifier.toUint8Array()}],
      ledger_canister_id: [Principal.fromText(icpLedgerCanisterId)]
    };

    const arg = IDL.encode(init({IDL}), [[sourceArg]]);

But I am facing following error at runtime in my local replica:

Panicked at ā€˜average_icp_xdr_conversion_rate is not set’, rs/nns/cmc/src/main.rs:731:14
[Governance] Error when refreshing XDR rate in run_periodic_tasks: External: Error calling ā€˜get_average_icp_xdr_conversion_rate’: code: Some(5), message: IC0503: Canister rkp4c-7iaaa-aaaaa-aaaca-cai trapped explicitly: Panicked at ā€˜average_icp_xdr_conversion_rate is not set’, rs/nns/cmc/src/main.rs:731:14

The exchange rate canister is not required for the CMC to operate. You can use ic-admin to submit a XDR ICP conversion rate proposal.

Usage:

ic-admin propose-xdr-icp-conversion-rate [OPTIONS] --xdr-permyriad-per-icp <XDR_PERMYRIAD_PER_ICP>

Do you mean that once the NNS Governance and CMC are installed for the first time locally, a proposal to submit an XDR to ICP conversion rate must be submitted and executed?

If yes, is this mandatory, or is there another way to set up those values when I spin up a local test environment? Such as an initial parameter maybe?

If no, can you point me to which type of proposal should be submitted, since I don’t use ic-admin?

In order for the average conversion rate to be populated, at least one rate must be submitted via proposal or retrieved using an exchange rate canister.

The proposal is an ExecuteNnsFunction: ic/rs/nns/governance/canister/governance.did at master Ā· dfinity/ic Ā· GitHub.

For the arguments:

The NNS function is ten: ic/rs/nns/governance/proto/ic_nns_governance/pb/v1/governance.proto at master Ā· dfinity/ic Ā· GitHub

The blob value is created by candid encoding the following: ic/rs/nns/common/src/types.rs at master Ā· dfinity/ic Ā· GitHub

Those NNS and CMC canisters are so depressing…

Thanks for the useful links and information; I’ll start exploring from there.

What are the exact values that need to be encoded to define the XDR ICP conversion rate? This seems to be documented nowhere.

pub struct UpdateIcpXdrConversionRatePayload {
    pub data_source: String,
    pub timestamp_seconds: u64,
    pub xdr_permyriad_per_icp: u64,
    pub reason: Option<UpdateIcpXdrConversionRatePayloadReason>,
}

I’m trying this:

const arg = IDL.encode(
      [
        IDL.Record({
          data_source: IDL.Text,
          timestamp_seconds: IDL.Int,
          xdr_permyriad_per_icp: IDL.Int,
        })
      ],
      [{
        data_source: '{"icp":["Binance"],"sdr":"xe.com"}',
        timestamp_seconds: 1683500400,
        xdr_permyriad_per_icp: 41388
      }]
  );

but I get an error:

error_message: 'The payload could not be decoded into a UpdateIcpXdrConversionRatePayload: Fail to decode argument 0 from table0 to record {\n' +

Unless I did not find the information, there seem to be an absolute lack of documentation here.

const arg = IDL.encode(
    [
      IDL.Record({
        data_source: IDL.Text,
        timestamp_seconds: IDL.Int64,
        xdr_permyriad_per_icp: IDL.Int64,
        reason: IDL.Opt(
          IDL.Variant({
            OldRate: IDL.Null,
            DivergedRate: IDL.Null,
            EnableAutomaticExchangeRateUpdates: IDL.Null
          })
        )
      })
    ],
    [
      {
        data_source: '{"icp":["Binance"],"sdr":"xe.com"}',
        timestamp_seconds: 1683500400,
        xdr_permyriad_per_icp: 41388,
        reason: []
      }
    ]
  );

Still no luck…

error_message: ā€˜The payload could not be decoded into a UpdateIcpXdrConversionRatePayload: Fail to decode argument 0 from table2 to record {\n’ +
’ data_source : text;\n’ +
’ xdr_permyriad_per_icp : nat64;\n’ +
’ timestamp_seconds : nat64;\n’ +
’ reason : opt variant {\n’ +
’ EnableAutomaticExchangeRateUpdates;\n’ +
’ DivergedRate;\n’ +
’ OldRate;\n’ +
’ };\n’ +
ā€˜}’,
error_type: 16

It expects a Candid encoding right, not some protobufsh**?

The rust doc says that the payload is a payload…

    /// The payload of the NNS function.
    #[prost(bytes = "vec", tag = "2")]
    #[serde(with = "serde_bytes")]
    pub payload: ::prost::alloc::vec::Vec<u8>,

Ok IDL.Nat64 instead of IDL.Int64 solves the encoding issue.

That said, now the proposal is executed but I still get the same issue as the original issue of this post.

Here results of listProposals after makeProposal.

proposals: [
{
  id: 1n,
  ballots: [Array],
  rejectCost: 100000000n,
  proposalTimestampSeconds: 1715517584n,
  rewardEventRound: 0n,
  failedTimestampSeconds: 0n,
  deadlineTimestampSeconds: 1715560784n,
  decidedTimestampSeconds: 1715517584n,
  proposal: [Object],
  proposer: 666n,
  latestTally: [Object],
  executedTimestampSeconds: 1715517584n,
  topic: 2,
  status: 4,
  rewardStatus: 1
  }
   ]
  }

status: 4, ------> Proposal is executed.

However I still get

2024-05-12 12:39:46.062604543 UTC: [Canister rkp4c-7iaaa-aaaaa-aaaca-cai] Panicked at ā€˜average_icp_xdr_conversion_rate is not set’, rs/nns/cmc/src/main.rs:731:14
| 2024-05-12 12:39:46.062604543 UTC: [Canister rrkah-fqaaa-aaaaa-aaaaq-cai] [Governance] Error when refreshing XDR rate in run_periodic_tasks: External: Error calling ā€˜get_average_icp_xdr_conversion_rate’: code: Some(5), message: IC0503: Canister rkp4c-7iaaa-aaaaa-aaaca-cai trapped explicitly: Panicked at ā€˜average_icp_xdr_conversion_rate is not set’, rs/nns/cmc/src/main.rs:731:14

Also

| 2024-05-12 12:39:44.156991084 UTC: [Canister rrkah-fqaaa-aaaaa-aaaaq-cai] [Governance] Proposal 1 decided, thanks to majority. Tally at decision time: Tally { timestamp_seconds: 1715517584, yes: 249914442162901, no: 0, total: 249914442162901 }
| 2024-05-12 12:39:44.156991084 UTC: [Canister rkp4c-7iaaa-aaaaa-aaaca-cai] [cycles] conversion rate update: IcpXdrConversionRate { timestamp_seconds: 1683500400, xdr_permyriad_per_icp: 41388 }
| 2024-05-12 12:39:44.156991084 UTC: [Canister rrkah-fqaaa-aaaaa-aaaaq-cai] [Governance] Execution of proposal: 1 succeeded. (Proposal title: Some(ā€œICP/XDR Conversion Rateā€))

The timestamp_seconds value is too old (older than 30 days). Try something more recent like 1715472000. There is a 30 day filter in the computation of the average rate. That should work. The rest looks good to me.

1 Like

Thanks! It seems to have worked out, and I no longer face the error.

If anyone is ever interested in the solution:

  • Once you have spun up a CMC and an NNS governance canister, you must make a proposal.
  • When you spin up the Governance canister, you must provide it with a neuron.
  • This neuron is then used to make the proposal.
  • The proposal should include a timestamp for the XDR rate that is more recent than 30 days old.
  • Diverged should be used in such a way that the CMC stops querying the exchange rate.

Here is the code for the make proposal part:

import {IDL} from '@dfinity/candid';
import {GovernanceCanister, NnsFunction, type MakeProposalRequest} from '@dfinity/nns';
import {createAgent} from '@dfinity/utils';
import {MAIN_IDENTITY_KEY} from '../../constants/constants';
import type {ModuleInstallParams} from '../../types/module';
import {NEURON_ID} from './governance.constants';

export const makeIcpXdrProposal = async ({identities}: Pick<ModuleInstallParams, 'identities'>) => {
  const {[MAIN_IDENTITY_KEY]: identity} = identities;

  const agent = await createAgent({
    identity,
    host: 'http://127.0.0.1:5987',
    fetchRootKey: true
  });

  const {makeProposal} = GovernanceCanister.create({
    agent
  });

  const arg = IDL.encode(
    [
      IDL.Record({
        data_source: IDL.Text,
        timestamp_seconds: IDL.Nat64,
        xdr_permyriad_per_icp: IDL.Nat64,
        reason: IDL.Opt(
          IDL.Variant({
            OldRate: IDL.Null,
            DivergedRate: IDL.Null,
            EnableAutomaticExchangeRateUpdates: IDL.Null
          })
        )
      })
    ],
    [
      {
        data_source: '{"icp":["Binance"],"sdr":"xe.com"}',   // Example of payload data found it some proposal
        timestamp_seconds: BigInt(Math.floor(Date.now() / 1000)), // Timestamp should not be < than 30 days from now
        xdr_permyriad_per_icp: BigInt(41388),
        reason: [{DivergedRate: null}]
      }
    ]
  );

  const request: MakeProposalRequest = {
    neuronId: BigInt(NEURON_ID),
    url: 'https://forum.dfinity.org',
    title: 'ICP/XDR Conversion Rate',
    summary: `Set ICP/XDR conversion rate to ${41_388}`,
    action: {
      ExecuteNnsFunction: {
        nnsFunctionId: NnsFunction.IcpXdrConversionRate,
        payloadBytes: arg
      }
    }
  };

  await makeProposal(request);
};

You do not need to use a diverged rate reason. If you do not initialize the CMC with an exchange rate canister, it will not attempt to make a request.

1 Like

Gotcha. Thanks again for the few tips!

1 Like

Hi @peterparker, if I understand correctly, the error you are seeing from the replica log comes from the NNS Governance heartbeat. Other than seeing the error message (which can prevent one from seeing useful logs), did you run into other issues? I’m trying to understand if it will help if we simply remove the error message.

Not sure what you mean. I saw errors because I had to undergo all the mumbo jumbo I described above. Once done, issue was solved.

I’m seeing that the error you are initially trying to solve is this:

Panicked at ā€˜average_icp_xdr_conversion_rate is not set’, rs/nns/cmc/src/main.rs:731:14
[Governance] Error when refreshing XDR rate in run_periodic_tasks: External: Error calling ā€˜get_average_icp_xdr_conversion_rate’: code: Some(5), message: IC0503: Canister rkp4c-7iaaa-aaaaa-aaaca-cai trapped explicitly: Panicked at ā€˜average_icp_xdr_conversion_rate is not set’, rs/nns/cmc/src/main.rs:731:14

which indicates NNS Governance is trying to fetch the conversion rate and it failed. However, an accurate conversion rate is only needed for maturity modulation (and probably some neurons’ fund stuff), which I doubt most people will need locally.

I’m wondering do you see any other issues (e.g. NNS Governance behaving in a bad way) than simply seeing this error message (in a large volume) on the replica logs?

Zooming out a little bit: we no longer use the exchange rate proposal since a year ago, and I don’t suppose anyone want to send such a proposal every time they set up a local environment. I’m trying to figure out whether there is a way to set up a NNS locally without having to make an exchange rate proposal.

Gotcha, thanks for the explanation!

Indeed, I can confirm that I absolutely do not need maturity modulation. A fixed rate would be perfectly suitable for local development.

I did not see any other issues so far, but I have to say that once everything was deployed correctly, I stopped the effort and jumped to another task. I’ve got various tasks in Juno which will require using the CMC, notably deploying canisters in other subnets (e.g., feature #452), but I have other priorities right now. Happy to update this thread if I encounter any errors when I implement those features.

Do you by any chance have what these Keys and Constants are? I’m trying to follow the SNS test rep at GitHub - dfinity/sns-testing: Testing SNS in local testing environment and keep getting these messages in my console. It would be nice if this step was added to the script.