Skip to content
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 5 additions & 1 deletion packages/rs-dpp/src/errors/consensus/basic/basic_error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,8 @@ use crate::consensus::basic::identity::{
IdentityAssetLockStateTransitionReplayError, IdentityAssetLockTransactionIsNotFoundError,
IdentityAssetLockTransactionOutPointAlreadyConsumedError,
IdentityAssetLockTransactionOutPointNotEnoughBalanceError,
IdentityAssetLockTransactionOutputNotFoundError, IdentityCreditTransferToSelfError,
IdentityAssetLockTransactionOutputNotFoundError,
IdentityAssetLockTransactionTooManyInputsError, IdentityCreditTransferToSelfError,
InvalidAssetLockProofCoreChainHeightError, InvalidAssetLockProofTransactionHeightError,
InvalidAssetLockTransactionOutputReturnSizeError,
InvalidCreditWithdrawalTransitionCoreFeeError,
Expand Down Expand Up @@ -677,6 +678,9 @@ pub enum BasicError {

#[error(transparent)]
ShieldedEncryptedNoteSizeMismatchError(ShieldedEncryptedNoteSizeMismatchError),

#[error(transparent)]
IdentityAssetLockTransactionTooManyInputsError(IdentityAssetLockTransactionTooManyInputsError),
}

impl From<BasicError> for ConsensusError {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
use crate::consensus::basic::BasicError;
use crate::consensus::ConsensusError;
use crate::errors::ProtocolError;
Comment thread
lklimek marked this conversation as resolved.
use platform_serialization_derive::{PlatformDeserialize, PlatformSerialize};
use thiserror::Error;

use bincode::{Decode, Encode};

#[derive(
Error, Debug, Clone, Encode, Decode, PlatformSerialize, PlatformDeserialize, PartialEq,
)]
#[error("Asset lock transaction has too many inputs: {actual_inputs} (max {max_inputs}). Consolidate UTXOs before creating the asset lock.")]
#[platform_serialize(unversioned)]
pub struct IdentityAssetLockTransactionTooManyInputsError {
/*

DO NOT CHANGE ORDER OF FIELDS WITHOUT INTRODUCING OF NEW VERSION

*/
max_inputs: u16,
actual_inputs: u16,
}

impl IdentityAssetLockTransactionTooManyInputsError {
pub fn new(actual_inputs: u16, max_inputs: u16) -> Self {
Self {
max_inputs,
actual_inputs,
}
}

pub fn max_inputs(&self) -> u16 {
self.max_inputs
}

pub fn actual_inputs(&self) -> u16 {
self.actual_inputs
}
}

impl From<IdentityAssetLockTransactionTooManyInputsError> for ConsensusError {
fn from(err: IdentityAssetLockTransactionTooManyInputsError) -> Self {
Self::BasicError(BasicError::IdentityAssetLockTransactionTooManyInputsError(
err,
))
}
}
2 changes: 2 additions & 0 deletions packages/rs-dpp/src/errors/consensus/basic/identity/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ pub use identity_asset_lock_transaction_is_not_found_error::*;
pub use identity_asset_lock_transaction_out_point_already_consumed_error::*;
pub use identity_asset_lock_transaction_out_point_not_enough_balance_error::*;
pub use identity_asset_lock_transaction_output_not_found_error::*;
pub use identity_asset_lock_transaction_too_many_inputs_error::*;
pub use identity_credit_transfer_to_self_error::*;
pub use invalid_asset_lock_proof_core_chain_height_error::*;
pub use invalid_asset_lock_proof_transaction_height_error::*;
Expand Down Expand Up @@ -39,6 +40,7 @@ mod duplicated_identity_public_key_id_basic_error;
mod identity_asset_lock_proof_locked_transaction_mismatch_error;
mod identity_asset_lock_transaction_is_not_found_error;
mod identity_asset_lock_transaction_out_point_already_consumed_error;
mod identity_asset_lock_transaction_too_many_inputs_error;

mod identity_asset_lock_state_transition_replay_error;
mod identity_asset_lock_transaction_out_point_not_enough_balance_error;
Expand Down
1 change: 1 addition & 0 deletions packages/rs-dpp/src/errors/consensus/codes.rs
Original file line number Diff line number Diff line change
Expand Up @@ -202,6 +202,7 @@ impl ErrorWithCode for BasicError {
Self::IdentityAssetLockStateTransitionReplayError(_) => 10531,
Self::WithdrawalOutputScriptNotAllowedWhenSigningWithOwnerKeyError(_) => 10532,
Self::InvalidKeyPurposeForContractBoundsError(_) => 10533,
Self::IdentityAssetLockTransactionTooManyInputsError(_) => 10534,

// State Transition Errors: 10600-10699
Self::InvalidStateTransitionTypeError { .. } => 10600,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,12 @@ pub fn validate_asset_lock_transaction_structure(
0 => Ok(v0::validate_asset_lock_transaction_structure_v0(
transaction,
output_index,
platform_version
.dpp
.state_transitions
.identities
.asset_locks
.max_asset_lock_transaction_inputs,
)),
version => Err(ProtocolError::UnknownVersionMismatch {
method: "validate_asset_lock_transaction_structure".to_string(),
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
use crate::consensus::basic::identity::{
IdentityAssetLockTransactionOutputNotFoundError, InvalidIdentityAssetLockTransactionError,
IdentityAssetLockTransactionOutputNotFoundError,
IdentityAssetLockTransactionTooManyInputsError, InvalidIdentityAssetLockTransactionError,
InvalidIdentityAssetLockTransactionOutputError,
};
use crate::validation::ConsensusValidationResult;
Expand All @@ -11,7 +12,19 @@ use dashcore::{Transaction, TxOut};
pub(super) fn validate_asset_lock_transaction_structure_v0(
transaction: &Transaction,
output_index: u32,
max_inputs: u16,
) -> ConsensusValidationResult<TxOut> {
let input_count = transaction.input.len();

@lklimek lklimek Apr 20, 2026

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@QuantumExplorer please check if we don't need new version of validate_asset_lock_transaction_structure/v0/mod.rs

if input_count > max_inputs as usize {
Comment on lines +15 to +18

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Suggestion: input_count as u16 can wrap and report the wrong actual input count

transaction.input.len() is a usize, but the new error payload stores actual_inputs as u16 via an unchecked as cast. For pathological transactions with at least 65536 inputs, validation will still fail, but the reported actual_inputs value wraps modulo 2^16 and can contradict reality when surfaced through the Rust and WASM error accessors. Saturating or checked conversion keeps the diagnostic accurate without changing the validation outcome.

💡 Suggested change
Suggested change
max_inputs: u16,
) -> ConsensusValidationResult<TxOut> {
let input_count = transaction.input.len();
if input_count > max_inputs as usize {
let input_count = transaction.input.len();
if input_count > max_inputs as usize {
let actual_inputs = u16::try_from(input_count).unwrap_or(u16::MAX);
return ConsensusValidationResult::new_with_error(
IdentityAssetLockTransactionTooManyInputsError::new(actual_inputs, max_inputs)
.into(),
);
}

source: ['claude']

🤖 Fix this with AI agents
These findings are from an automated code review. Verify each finding against the current code and only fix it if needed.

In `packages/rs-dpp/src/identity/state_transition/asset_lock_proof/validate_asset_lock_transaction_structure/v0/mod.rs`:
- [SUGGESTION] lines 15-18: `input_count as u16` can wrap and report the wrong actual input count
  `transaction.input.len()` is a `usize`, but the new error payload stores `actual_inputs` as `u16` via an unchecked `as` cast. For pathological transactions with at least 65536 inputs, validation will still fail, but the reported `actual_inputs` value wraps modulo 2^16 and can contradict reality when surfaced through the Rust and WASM error accessors. Saturating or checked conversion keeps the diagnostic accurate without changing the validation outcome.

return ConsensusValidationResult::new_with_error(
IdentityAssetLockTransactionTooManyInputsError::new(
u16::try_from(input_count).unwrap_or(u16::MAX),
max_inputs,
)
.into(),
);
Comment on lines +15 to +22

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔴 Blocking: The new input-limit check still runs only after the asset lock transaction has already been broadcast

This guard rejects oversized asset-lock transactions during validate_asset_lock_transaction_structure_v0, but that validator is only reached from asset-lock proof / state-transition validation. In the actual JS client flow, the asset-lock transaction is still broadcast first (packages/js-dash-sdk/src/SDK/Client/Platform/methods/identities/register.ts:27-32 and .../topUp.ts:25-30), and only afterwards does createAssetLockProof() wait for metadata / InstantLock and invoke DPP validation (packages/js-dash-sdk/src/SDK/Client/Platform/methods/identities/internal/createAssetLockProof.ts:24-95). So on this SHA, a 101+ input asset-lock can still be sent to Core before this error ever fires, which means the stuck-funds scenario described in the PR is still present in the main registration/top-up path. The limit needs to be enforced before broadcastTransaction, in the asset-lock transaction creation path, not only during proof validation.

source: ['codex']

🤖 Fix this with AI agents
These findings are from an automated code review. Verify each finding against the current code and only fix it if needed.

In `packages/rs-dpp/src/identity/state_transition/asset_lock_proof/validate_asset_lock_transaction_structure/v0/mod.rs`:
- [BLOCKING] lines 15-22: The new input-limit check still runs only after the asset lock transaction has already been broadcast
  This guard rejects oversized asset-lock transactions during `validate_asset_lock_transaction_structure_v0`, but that validator is only reached from asset-lock proof / state-transition validation. In the actual JS client flow, the asset-lock transaction is still broadcast first (`packages/js-dash-sdk/src/SDK/Client/Platform/methods/identities/register.ts:27-32` and `.../topUp.ts:25-30`), and only afterwards does `createAssetLockProof()` wait for metadata / InstantLock and invoke DPP validation (`packages/js-dash-sdk/src/SDK/Client/Platform/methods/identities/internal/createAssetLockProof.ts:24-95`). So on this SHA, a 101+ input asset-lock can still be sent to Core before this error ever fires, which means the stuck-funds scenario described in the PR is still present in the main registration/top-up path. The limit needs to be enforced before `broadcastTransaction`, in the asset-lock transaction creation path, not only during proof validation.

}

// It must be an Asset Lock Special Transaction
let Some(TransactionPayload::AssetLockPayloadType(ref payload)) =
transaction.special_transaction_payload
Expand Down Expand Up @@ -41,3 +54,129 @@ pub(super) fn validate_asset_lock_transaction_structure_v0(
ConsensusValidationResult::new_with_data(output.clone())
}
}

#[cfg(test)]
mod tests {
use super::*;
use dashcore::secp256k1::rand::thread_rng;
use dashcore::secp256k1::Secp256k1;
use dashcore::transaction::special_transaction::asset_lock::AssetLockPayload;
use dashcore::{Network, OutPoint, PrivateKey, ScriptBuf, TxIn, Txid};
use std::str::FromStr;

fn make_asset_lock_transaction(num_inputs: usize) -> Transaction {
let secp = Secp256k1::new();
let mut rng = thread_rng();

let private_key_hex = "cSBnVM4xvxarwGQuAfQFwqDg9k5tErHUHzgWsEfD4zdwUasvqRVY";
let private_key = PrivateKey::from_str(private_key_hex).unwrap();
let public_key = private_key.public_key(&secp);
let public_key_hash = public_key.pubkey_hash();

let secret_key = dashcore::secp256k1::SecretKey::new(&mut rng);
let one_time_private_key = PrivateKey::new(secret_key, Network::Testnet);
let one_time_public_key = one_time_private_key.public_key(&secp);
let one_time_key_hash = one_time_public_key.pubkey_hash();
Comment thread
lklimek marked this conversation as resolved.

let base_txid =
Txid::from_str("a477af6b2667c29670467e4e0728b685ee07b240235771862318e29ddbe58458")
.unwrap();

let inputs: Vec<TxIn> = (0..num_inputs)
.map(|i| TxIn {
previous_output: OutPoint::new(base_txid, i as u32),
script_sig: ScriptBuf::new_p2pkh(&public_key_hash),
sequence: 0,
witness: Default::default(),
})
.collect();

let funding_output = TxOut {
value: 100_000_000,
script_pubkey: ScriptBuf::new_p2pkh(&one_time_key_hash),
};

let burn_output = TxOut {
value: 100_000_000,
script_pubkey: ScriptBuf::new_op_return(&[]),
};

let payload = TransactionPayload::AssetLockPayloadType(AssetLockPayload {
version: 0,
credit_outputs: vec![funding_output],
});

Transaction {
version: 0,
lock_time: 0,
input: inputs,
output: vec![burn_output],
special_transaction_payload: Some(payload),
}
}

#[test]
fn should_reject_transaction_with_too_many_inputs() {
let tx = make_asset_lock_transaction(101);
let result = validate_asset_lock_transaction_structure_v0(&tx, 0, 100);

assert!(result.errors.len() == 1);
let error = &result.errors[0];
match error {
crate::consensus::ConsensusError::BasicError(
crate::consensus::basic::BasicError::IdentityAssetLockTransactionTooManyInputsError(
e,
),
) => {
assert_eq!(e.actual_inputs(), 101);
assert_eq!(e.max_inputs(), 100);
}
other => {
panic!("Expected IdentityAssetLockTransactionTooManyInputsError, got: {other:?}")
}
}
}

#[test]
fn should_accept_transaction_at_max_inputs() {
let tx = make_asset_lock_transaction(100);
let result = validate_asset_lock_transaction_structure_v0(&tx, 0, 100);

let has_too_many_inputs_error = result.errors.iter().any(|e| {
Comment thread
lklimek marked this conversation as resolved.
matches!(
e,
crate::consensus::ConsensusError::BasicError(
crate::consensus::basic::BasicError::IdentityAssetLockTransactionTooManyInputsError(_)
)
)
});
assert!(
!has_too_many_inputs_error,
"Should not reject exactly 100 inputs"
);
assert!(
result.is_valid(),
"Transaction at max inputs should be valid"
);
}

#[test]
fn should_accept_transaction_with_one_input() {
let tx = make_asset_lock_transaction(1);
let result = validate_asset_lock_transaction_structure_v0(&tx, 0, 100);

let has_too_many_inputs_error = result.errors.iter().any(|e| {
matches!(
e,
crate::consensus::ConsensusError::BasicError(
crate::consensus::basic::BasicError::IdentityAssetLockTransactionTooManyInputsError(_)
)
)
});
assert!(!has_too_many_inputs_error, "Should not reject single input");
assert!(
result.is_valid(),
"Single input transaction should be valid"
);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@ pub struct IdentityTransitionAssetLockVersions {
pub required_asset_lock_duff_balance_for_processing_start_for_address_funding: u64,
pub validate_asset_lock_transaction_structure: FeatureVersion,
pub validate_instant_asset_lock_proof_structure: FeatureVersion,
pub max_asset_lock_transaction_inputs: u16,
}

#[derive(Clone, Debug, Default)]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ pub const STATE_TRANSITION_VERSIONS_V1: DPPStateTransitionVersions = DPPStateTra
required_asset_lock_duff_balance_for_processing_start_for_address_funding: 50000,
validate_asset_lock_transaction_structure: 0,
validate_instant_asset_lock_proof_structure: 0,
max_asset_lock_transaction_inputs: 100,
Comment thread
lklimek marked this conversation as resolved.
Outdated
Comment on lines +22 to +25

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔴 Blocking: The 100-input cap is retrofitted into existing protocol snapshots instead of behind a new version gate

validate_asset_lock_transaction_structure() still dispatches only on version 0, so the only thing changing consensus behavior here is the new max_asset_lock_transaction_inputs value. This SHA writes that value directly into the existing STATE_TRANSITION_VERSIONS_V1, and the same pattern is repeated in .../v2.rs and .../v3.rs, which means old and new binaries running the same protocol version can now disagree about whether a high-input asset-lock transaction is valid. That is exactly the kind of historical-rule mutation the versioned dispatch system is supposed to avoid. If this limit is meant to be a new consensus rule, it needs a new validation version (or a new protocol snapshot that references one) rather than editing existing v1/v2/v3 constants in place.

source: ['claude', 'codex']

🤖 Fix this with AI agents
These findings are from an automated code review. Verify each finding against the current code and only fix it if needed.

In `packages/rs-platform-version/src/version/dpp_versions/dpp_state_transition_versions/v1.rs`:
- [BLOCKING] lines 21-25: The 100-input cap is retrofitted into existing protocol snapshots instead of behind a new version gate
  `validate_asset_lock_transaction_structure()` still dispatches only on version `0`, so the only thing changing consensus behavior here is the new `max_asset_lock_transaction_inputs` value. This SHA writes that value directly into the existing `STATE_TRANSITION_VERSIONS_V1`, and the same pattern is repeated in `.../v2.rs` and `.../v3.rs`, which means old and new binaries running the same protocol version can now disagree about whether a high-input asset-lock transaction is valid. That is exactly the kind of historical-rule mutation the versioned dispatch system is supposed to avoid. If this limit is meant to be a new consensus rule, it needs a new validation version (or a new protocol snapshot that references one) rather than editing existing v1/v2/v3 constants in place.

},
credit_withdrawal: IdentityCreditWithdrawalTransitionVersions {
default_constructor: 0,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ pub const STATE_TRANSITION_VERSIONS_V2: DPPStateTransitionVersions = DPPStateTra
required_asset_lock_duff_balance_for_processing_start_for_address_funding: 50000,
validate_asset_lock_transaction_structure: 0,
validate_instant_asset_lock_proof_structure: 0,
max_asset_lock_transaction_inputs: 100,
Comment thread
lklimek marked this conversation as resolved.
Outdated
},
credit_withdrawal: IdentityCreditWithdrawalTransitionVersions {
default_constructor: 1,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ pub const STATE_TRANSITION_VERSIONS_V3: DPPStateTransitionVersions = DPPStateTra
required_asset_lock_duff_balance_for_processing_start_for_address_funding: 50000,
validate_asset_lock_transaction_structure: 0,
validate_instant_asset_lock_proof_structure: 0,
max_asset_lock_transaction_inputs: 100,
},
credit_withdrawal: IdentityCreditWithdrawalTransitionVersions {
default_constructor: 1,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
use dpp::consensus::basic::identity::IdentityAssetLockTransactionTooManyInputsError;
use dpp::consensus::codes::ErrorWithCode;
use dpp::consensus::ConsensusError;

use wasm_bindgen::prelude::*;

#[wasm_bindgen(js_name=IdentityAssetLockTransactionTooManyInputsError)]
pub struct IdentityAssetLockTransactionTooManyInputsErrorWasm {
inner: IdentityAssetLockTransactionTooManyInputsError,
}

impl From<&IdentityAssetLockTransactionTooManyInputsError>
for IdentityAssetLockTransactionTooManyInputsErrorWasm
{
fn from(e: &IdentityAssetLockTransactionTooManyInputsError) -> Self {
Self { inner: e.clone() }
}
}

#[wasm_bindgen(js_class=IdentityAssetLockTransactionTooManyInputsError)]
impl IdentityAssetLockTransactionTooManyInputsErrorWasm {
#[wasm_bindgen(js_name=getMaxInputs)]
pub fn get_max_inputs(&self) -> u16 {
self.inner.max_inputs()
}

#[wasm_bindgen(js_name=getActualInputs)]
pub fn get_actual_inputs(&self) -> u16 {
self.inner.actual_inputs()
}

#[wasm_bindgen(js_name=getCode)]
pub fn get_code(&self) -> u32 {
ConsensusError::from(self.inner.clone()).code()
}

#[wasm_bindgen(getter)]
pub fn message(&self) -> String {
self.inner.to_string()
}
}
2 changes: 2 additions & 0 deletions packages/wasm-dpp/src/errors/consensus/basic/identity/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ mod identity_asset_lock_state_transition_replay_error;
mod identity_asset_lock_transaction_is_not_found_error;
mod identity_asset_lock_transaction_out_point_already_exists_error;
mod identity_asset_lock_transaction_output_not_found_error;
mod identity_asset_lock_transaction_too_many_inputs_error;
mod identity_credit_transfer_to_self_error;
mod identity_insufficient_balance_error;
mod invalid_asset_lock_proof_core_chain_height_error;
Expand Down Expand Up @@ -34,6 +35,7 @@ pub use identity_asset_lock_state_transition_replay_error::*;
pub use identity_asset_lock_transaction_is_not_found_error::*;
pub use identity_asset_lock_transaction_out_point_already_exists_error::*;
pub use identity_asset_lock_transaction_output_not_found_error::*;
pub use identity_asset_lock_transaction_too_many_inputs_error::*;
pub use identity_credit_transfer_to_self_error::*;
pub use identity_insufficient_balance_error::*;
pub use invalid_asset_lock_proof_core_chain_height_error::*;
Expand Down
9 changes: 7 additions & 2 deletions packages/wasm-dpp/src/errors/consensus/consensus_error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,8 @@ use crate::errors::consensus::basic::identity::{
IdentityAssetLockTransactionIsNotFoundErrorWasm,
IdentityAssetLockTransactionOutPointAlreadyExistsErrorWasm,
IdentityAssetLockTransactionOutPointNotEnoughBalanceErrorWasm,
IdentityAssetLockTransactionOutputNotFoundErrorWasm, IdentityCreditTransferToSelfErrorWasm,
IdentityAssetLockTransactionOutputNotFoundErrorWasm,
IdentityAssetLockTransactionTooManyInputsErrorWasm, IdentityCreditTransferToSelfErrorWasm,
IdentityInsufficientBalanceErrorWasm, InvalidAssetLockProofCoreChainHeightErrorWasm,
InvalidAssetLockProofTransactionHeightErrorWasm,
InvalidAssetLockTransactionOutputReturnSizeErrorWasm,
Expand All @@ -38,7 +39,8 @@ use dpp::consensus::basic::BasicError::{
IdentityAssetLockStateTransitionReplayError, IdentityAssetLockTransactionIsNotFoundError,
IdentityAssetLockTransactionOutPointAlreadyConsumedError,
IdentityAssetLockTransactionOutPointNotEnoughBalanceError,
IdentityAssetLockTransactionOutputNotFoundError, IncompatibleProtocolVersionError,
IdentityAssetLockTransactionOutputNotFoundError,
IdentityAssetLockTransactionTooManyInputsError, IncompatibleProtocolVersionError,
IncompatibleRe2PatternError, InvalidAssetLockProofCoreChainHeightError,
InvalidAssetLockProofTransactionHeightError, InvalidAssetLockTransactionOutputReturnSizeError,
InvalidCreditWithdrawalTransitionCoreFeeError,
Expand Down Expand Up @@ -614,6 +616,9 @@ fn from_basic_error(basic_error: &BasicError) -> JsValue {
InvalidIdentityAssetLockTransactionError(e) => {
InvalidIdentityAssetLockTransactionErrorWasm::from(e).into()
}
IdentityAssetLockTransactionTooManyInputsError(e) => {
IdentityAssetLockTransactionTooManyInputsErrorWasm::from(e).into()
}
InvalidInstantAssetLockProofError(e) => {
InvalidInstantAssetLockProofErrorWasm::from(e).into()
}
Expand Down
Loading