NNS Maturity Disbursment - manage_neuron returns None instead of Ok

,

After writing tests for NNS maturity disbursement me and my colleague noticed that the manage_neuron function returns None instead of Ok even though the results of the test show that the maturity was successfully transferred/disbursed to the account that is supposed to receive it.

Is this the expected behavior/logic for this scenario because to me it seems strange that if the transfer succeeded we would receive None?

If that is the expected behavior we will change our code to not treat None as an error in that case.

Below is our current handling of the response:

        match nns_governance_canister_c2c_client::manage_neuron(
            nns_governance_canister_id,
            manage_neuron,
        )
        .await
        {
            Ok(manage_neuron_response) => match manage_neuron_response.command {
                Some(
                    nns_governance_canister::types::manage_neuron_response::Command::DisburseMaturity(
                        response,
                    ),
                ) => {
                    info!("Successfully disbursed maturity for neuron {:?}", response);
                    Ok(())
                }
                Some(response) => {
                    let error_msg = format!("Unexpected response from manage_neuron: {:?}", response);
                    error!("{}", error_msg);
    
                    return Err(error_msg);
                }
                None => {
                    let error_msg = "manage_neuron response contained no command.".to_string();
                    error!("{}", &error_msg);
                    return Err(error_msg);
                }
            },
            Err(e) => {
                let error_msg = format!(
                    "Failed to disburse maturity for neuron {:?}: {:?}",
                    neuron_id, e
                );
                error!("{}", &error_msg);
                return Err(error_msg);
            }
        }

Would love to hear from someone who encountered something similar or can help explain the logic behind this.

1 Like

Hi,

Could you also post all the type definitions involved here (request+response), especially for response type (assuming the disbursement is indeed successful)?

One issue I can think of is that the type that’s supposed to be ManageNeuronResponse does not match the candid definition used by the NNS Governance canister, which cause it to decode successfully but to null (i.e. None in Rust).

I can confirm that if the types are all correct, then the expected behavior for your example is something like

Successfully disbursed maturity for neuron {amount_disbursed_e8s : opt [0-9]+ }

2 Likes

Hi,

Yeah I think you might be right with the interface for response, we don’t have the DisburseMaturity command so maybe that could be the issue?

type Command = variant {
  Spawn : Spawn;
  Split : Split;
  Follow : Follow;
  ClaimOrRefresh : ClaimOrRefresh;
  Configure : Configure;
  RegisterVote : RegisterVote;
  Merge : Merge;
  DisburseToNeuron : DisburseToNeuron;
  MakeProposal : Proposal;
  StakeMaturity : StakeMaturity;
  MergeMaturity : MergeMaturity;
  Disburse : Disburse;
};
type Command_1 = variant {
  Error : GovernanceError;
  Spawn : SpawnResponse;
  Split : SpawnResponse;
  Follow : record {};
  ClaimOrRefresh : ClaimOrRefreshResponse;
  Configure : record {};
  RegisterVote : record {};
  Merge : MergeResponse;
  DisburseToNeuron : SpawnResponse;
  MakeProposal : MakeProposalResponse;
  StakeMaturity : StakeMaturityResponse;
  MergeMaturity : MergeMaturityResponse;
  Disburse : DisburseResponse;
};

type ManageNeuron = record {
  id : opt NeuronId;
  command : opt Command;
  neuron_id_or_subaccount : opt NeuronIdOrSubaccount;
};
type ManageNeuronResponse = record { command : opt Command_1 };

Right. If your types are generated from the governance.did file, then you probably need to generate it again from the latest version.

I’m curious though, if your request also does not have DisburseMaturity, how were you able to send such a request: “the results of the test show that the maturity was successfully transferred/disbursed to the account that is supposed to receive it”?

So the thing is that we didn’t have the candid definitions (now we do) but we did have the actual types in the Rust code implemented.

Here is the request:

    /// Disburse the maturity of a neuron to any ledger account. If an account
    /// is not specified, the caller's account will be used. The caller can choose
    /// a percentage of the current maturity to disburse to the ledger account. The
    /// resulting amount to disburse must be greater than or equal to the
    /// transaction fee.
    use icrc_ledger_types::icrc1::account::Account;
    #[derive(CandidType, Serialize, Deserialize, Clone, Debug)]
    pub struct DisburseMaturity {
        /// The percentage to disburse, from 1 to 100
        pub percentage_to_disburse: u32,
        /// The (optional) principal to which to transfer the stake.
        pub to_account: Option<Account>,
    }
    
        #[derive(CandidType, Serialize, Deserialize, Clone, Debug)]
    pub enum Command {
        Configure(Configure),
        Disburse(Disburse),
        Spawn(Spawn),
        Follow(Follow),
        RegisterVote(RegisterVote),
        Split(Split),
        DisburseMaturity(DisburseMaturity),
        DisburseToNeuron(DisburseToNeuron),
        ClaimOrRefresh(ClaimOrRefresh),
        MergeMaturity(MergeMaturity),
        Merge(Merge),
        StakeMaturity(StakeMaturity),
        RefreshVotingPower(RefreshVotingPower),
        MakeProposal(Proposal),
    }

And here is the response:

pub mod manage_neuron_response {
    use super::*;

    #[derive(CandidType, Serialize, Deserialize, Clone, Debug)]
    pub struct ClaimOrRefreshResponse {
        pub refreshed_neuron_id: Option<NeuronId>,
    }

    #[derive(CandidType, Serialize, Deserialize, Clone, Debug)]
    pub struct DisburseMaturityResponse {
        /// The amount disbursed in e8s of the governance token.
        pub amount_disbursed_e8s: u64,
    }

    #[derive(CandidType, Serialize, Deserialize, Clone, Debug)]
    pub enum Command {
        Error(GovernanceError),
        Configure(Empty),
        Disburse(Empty),
        Spawn(Empty),
        Follow(Empty),
        MakeProposal(Empty),
        RegisterVote(Empty),
        Split(Empty),
        DisburseToNeuron(Empty),
        ClaimOrRefresh(ClaimOrRefreshResponse),
        DisburseMaturity(DisburseMaturityResponse),
        MergeMaturity(Empty),
        Merge(Empty),
        StakeMaturity(Empty),
    }
}
1 Like

So do you still experience the problem (response.command is None) with these type definitions?

Yes we do, since the Rust type definitions were there from the beginning I would assume that wouldn’t be the source of the problem, even after adding the candid definitions the issue persists so I’m really not sure what the cause is.

I see. Then perhaps you could share more code, and hopefully a complete reproducible example? Mainly, it’s not clear what nns_governance_canister_c2c_client does exactly. Does it wrap over ic_cdk::api::call or is there some custom implementation?