From d18ed7dc533565a3c0182aae04c765ad8254f64d Mon Sep 17 00:00:00 2001 From: Ruud van Asseldonk Date: Thu, 12 Aug 2021 16:45:59 +0200 Subject: [PATCH] Fix parsing of multisig errors This is broken in Anchor, but we can do it by hand then ... --- cli/src/error.rs | 71 +++++++++++++++++++++++++++++++++++++----- tests/test_multisig.py | 18 ++--------- 2 files changed, 67 insertions(+), 22 deletions(-) diff --git a/cli/src/error.rs b/cli/src/error.rs index ae4974a9a..e881ee205 100644 --- a/cli/src/error.rs +++ b/cli/src/error.rs @@ -264,6 +264,63 @@ impl AsPrettyError for ClientError { } } +/// Parse an error code back to a multisig error. +/// +/// We need to write this manually, because `multisig::Error::from` +/// unfortunately doesn’t convert back from error codes, it appears +/// to be broken. See also . +pub fn multisig_error_from_u32(error_code: u32) -> Option { + use multisig::ErrorCode; + + let all_errors = [ + ErrorCode::InvalidOwner, + ErrorCode::NotEnoughSigners, + ErrorCode::TransactionAlreadySigned, + ErrorCode::Overflow, + ErrorCode::UnableToDelete, + ErrorCode::AlreadyExecuted, + ErrorCode::InvalidThreshold, + ]; + + // The purpose of the match statement below is to trigger a compile error if + // the `ErrorCode` enum changes in a future version of the multisig program. + // If you ended up here to fix that, please also add the new variant to the + // list above! + match all_errors[0] { + ErrorCode::InvalidOwner => { /* See comment above! */ } + ErrorCode::NotEnoughSigners => { /* See comment above! */ } + ErrorCode::TransactionAlreadySigned => { /* See comment above! */ } + ErrorCode::Overflow => { /* See comment above! */ } + ErrorCode::UnableToDelete => { /* See comment above! */ } + ErrorCode::AlreadyExecuted => { /* See comment above! */ } + ErrorCode::InvalidThreshold => { /* See comment above! */ } + } + + for &error in &all_errors { + match ProgramError::from(error) { + ProgramError::Custom(x) if x == error_code => return Some(error), + _ => continue, + } + } + + None +} + +#[cfg(test)] +mod test { + use crate::error::multisig_error_from_u32; + + #[test] + fn test_multisig_error_from_u32() { + // We use `assert!matches!` because `ErrorCode` does not implement `Eq`. + assert!(matches!( + multisig_error_from_u32(0x65), + Some(multisig::ErrorCode::NotEnoughSigners), + )); + assert!(matches!(multisig_error_from_u32(u32::MAX), None)); + } +} + pub fn print_pretty_error_code(error_code: u32) { print_key("Error code interpretations:"); println!("\n"); @@ -271,14 +328,14 @@ pub fn print_pretty_error_code(error_code: u32) { Some(err) => println!(" Solido error {} is {:?}", error_code, err), None => println!(" Error {} is not a known Solido error.", error_code), } - match multisig::Error::from(ProgramError::Custom(error_code)) { - // Anchor calls it an "ErrorCode", but it's really an enum - // with user-defined errors (as opposed to the Solana ProgramError). - multisig::Error::ErrorCode(custom_error) => { - println!(" Multisig error {} is {:?}", error_code, custom_error); - println!(" {}", custom_error); + match multisig_error_from_u32(error_code) { + Some(multisig_error) => { + println!( + " Multisig error {} is {:?}: {}", + error_code, multisig_error, multisig_error + ); } - _ => { + None => { println!(" Error {} is not a known Multisig error.", error_code); } } diff --git a/tests/test_multisig.py b/tests/test_multisig.py index 6f604a13e..c8d824aef 100755 --- a/tests/test_multisig.py +++ b/tests/test_multisig.py @@ -201,11 +201,7 @@ ) except subprocess.CalledProcessError as err: assert err.returncode != 0 - # assert 'Not enough owners signed this transaction' in err.stderr - # TODO(#177) Previously the error included a human-readable message, why does it - # only include the error code now? Something to do with different Anchor - # versions? - assert 'custom program error: 0x65' in err.stdout + assert 'Not enough owners signed this transaction' in err.stdout new_info = solana_program_show(program_id) assert new_info == upload_info, 'Program should not have changed.' print('> Execution failed as expected.') @@ -286,11 +282,7 @@ ) except subprocess.CalledProcessError as err: assert err.returncode != 0 - # assert 'The given transaction has already been executed.' in err.stderr - # TODO(#177) Previously the error included a human-readable message, why does it - # only include the error code now? Something to do with different Anchor - # versions? - assert 'custom program error: 0x69' in err.stdout + assert 'The given transaction has already been executed.' in err.stdout new_info = solana_program_show(program_id) assert new_info == upgrade_info, 'Program should not have changed.' print('> Execution failed as expected.') @@ -455,11 +447,7 @@ ) except subprocess.CalledProcessError as err: assert err.returncode != 0 - # assert 'The given owner is not part of this multisig.' in err.stderr - # TODO(#177) Previously the error included a human-readable message, why does it - # only include the error code now? Something to do with different Anchor - # versions? - assert 'custom program error: 0x64' in err.stdout + assert 'The given owner is not part of this multisig.' in err.stdout result = multisig( 'show-transaction', '--multisig-program-id',