diff --git a/src/Neo/Wallets/AssetDescriptor.cs b/src/Neo/Wallets/AssetDescriptor.cs
index 7b9be7b16b..7fdfadc90e 100644
--- a/src/Neo/Wallets/AssetDescriptor.cs
+++ b/src/Neo/Wallets/AssetDescriptor.cs
@@ -50,22 +50,29 @@ public class AssetDescriptor
/// The id of the asset.
public AssetDescriptor(DataCache snapshot, ProtocolSettings settings, UInt160 asset_id)
{
- var contract = NativeContract.ContractManagement.GetContract(snapshot, asset_id);
- if (contract is null) throw new ArgumentException(null, nameof(asset_id));
+ try
+ {
+ var contract = NativeContract.ContractManagement.GetContract(snapshot, asset_id);
+ if (contract is null) throw new WalletException(WalletErrorType.ContractNotFound, nameof(asset_id));
- byte[] script;
- using (ScriptBuilder sb = new())
+ byte[] script;
+ using (ScriptBuilder sb = new())
+ {
+ sb.EmitDynamicCall(asset_id, "decimals", CallFlags.ReadOnly);
+ sb.EmitDynamicCall(asset_id, "symbol", CallFlags.ReadOnly);
+ script = sb.ToArray();
+ }
+ using ApplicationEngine engine = ApplicationEngine.Run(script, snapshot, settings: settings, gas: 0_30000000L);
+ if (engine.State != VMState.HALT) throw new WalletException(WalletErrorType.ExecutionFault, nameof(asset_id));
+ AssetId = asset_id;
+ AssetName = contract.Manifest.Name;
+ Symbol = engine.ResultStack.Pop().GetString();
+ Decimals = (byte)engine.ResultStack.Pop().GetInteger();
+ }
+ catch (Exception ex) when (ex is not WalletException)
{
- sb.EmitDynamicCall(asset_id, "decimals", CallFlags.ReadOnly);
- sb.EmitDynamicCall(asset_id, "symbol", CallFlags.ReadOnly);
- script = sb.ToArray();
+ throw WalletException.FromException(ex);
}
- using ApplicationEngine engine = ApplicationEngine.Run(script, snapshot, settings: settings, gas: 0_30000000L);
- if (engine.State != VMState.HALT) throw new ArgumentException(null, nameof(asset_id));
- AssetId = asset_id;
- AssetName = contract.Manifest.Name;
- Symbol = engine.ResultStack.Pop().GetString();
- Decimals = (byte)engine.ResultStack.Pop().GetInteger();
}
public override string ToString()
diff --git a/src/Neo/Wallets/Helper.cs b/src/Neo/Wallets/Helper.cs
index b8427d078d..46e455d7a9 100644
--- a/src/Neo/Wallets/Helper.cs
+++ b/src/Neo/Wallets/Helper.cs
@@ -36,7 +36,14 @@ public static class Helper
/// The signature for the .
public static byte[] Sign(this IVerifiable verifiable, KeyPair key, uint network)
{
- return Crypto.Sign(verifiable.GetSignData(network), key.PrivateKey);
+ try
+ {
+ return Crypto.Sign(verifiable.GetSignData(network), key.PrivateKey);
+ }
+ catch (Exception ex)
+ {
+ throw WalletException.FromException(ex);
+ }
}
///
@@ -61,17 +68,24 @@ public static string ToAddress(this UInt160 scriptHash, byte version)
/// The converted script hash.
public static UInt160 ToScriptHash(this string address, byte version)
{
- byte[] data = address.Base58CheckDecode();
- if (data.Length != 21)
- throw new FormatException();
- if (data[0] != version)
- throw new FormatException();
- return new UInt160(data.AsSpan(1));
+ try
+ {
+ byte[] data = address.Base58CheckDecode();
+ if (data.Length != 21)
+ throw new WalletException(WalletErrorType.FormatError, "Invalid address format: incorrect length.");
+ if (data[0] != version)
+ throw new WalletException(WalletErrorType.FormatError, "Invalid address version.");
+ return new UInt160(data.AsSpan(1));
+ }
+ catch (Exception e) when (e is not WalletException)
+ {
+ throw new WalletException(WalletErrorType.FormatError, "Invalid address format.");
+ }
}
internal static byte[] XOR(byte[] x, byte[] y)
{
- if (x.Length != y.Length) throw new ArgumentException();
+ if (x.Length != y.Length) throw new WalletException(WalletErrorType.InvalidOperation, "Arrays must have the same length.");
byte[] r = new byte[x.Length];
for (int i = 0; i < r.Length; i++)
r[i] = (byte)(x[i] ^ y[i]);
@@ -90,78 +104,90 @@ internal static byte[] XOR(byte[] x, byte[] y)
/// The network fee of the transaction.
public static long CalculateNetworkFee(this Transaction tx, DataCache snapshot, ProtocolSettings settings, Func accountScript, long maxExecutionCost = ApplicationEngine.TestModeGas)
{
- UInt160[] hashes = tx.GetScriptHashesForVerifying(snapshot);
-
- // base size for transaction: includes const_header + signers + attributes + script + hashes
- int size = Transaction.HeaderSize + tx.Signers.GetVarSize() + tx.Attributes.GetVarSize() + tx.Script.GetVarSize() + IO.Helper.GetVarSize(hashes.Length), index = -1;
- uint exec_fee_factor = NativeContract.Policy.GetExecFeeFactor(snapshot);
- long networkFee = 0;
- foreach (UInt160 hash in hashes)
+ try
{
- index++;
- byte[] witnessScript = accountScript(hash);
- byte[] invocationScript = null;
+ UInt160[] hashes = tx.GetScriptHashesForVerifying(snapshot);
- if (tx.Witnesses != null && witnessScript is null)
+ // base size for transaction: includes const_header + signers + attributes + script + hashes
+ int size = Transaction.HeaderSize + tx.Signers.GetVarSize() + tx.Attributes.GetVarSize() + tx.Script.GetVarSize() + IO.Helper.GetVarSize(hashes.Length), index = -1;
+ uint exec_fee_factor = NativeContract.Policy.GetExecFeeFactor(snapshot);
+ long networkFee = 0;
+ foreach (UInt160 hash in hashes)
{
- // Try to find the script in the witnesses
- Witness witness = tx.Witnesses[index];
- witnessScript = witness?.VerificationScript.ToArray();
+ index++;
+ byte[] witnessScript = accountScript(hash);
+ byte[] invocationScript = null;
- if (witnessScript is null || witnessScript.Length == 0)
+ if (tx.Witnesses != null && witnessScript is null)
{
- // Then it's a contract-based witness, so try to get the corresponding invocation script for it
- invocationScript = witness?.InvocationScript.ToArray();
- }
- }
+ // Try to find the script in the witnesses
+ Witness witness = tx.Witnesses[index];
+ witnessScript = witness?.VerificationScript.ToArray();
- if (witnessScript is null || witnessScript.Length == 0)
- {
- var contract = NativeContract.ContractManagement.GetContract(snapshot, hash);
- if (contract is null)
- throw new ArgumentException($"The smart contract or address {hash} is not found");
- var md = contract.Manifest.Abi.GetMethod(ContractBasicMethod.Verify, ContractBasicMethod.VerifyPCount);
- if (md is null)
- throw new ArgumentException($"The smart contract {contract.Hash} haven't got verify method");
- if (md.ReturnType != ContractParameterType.Boolean)
- throw new ArgumentException("The verify method doesn't return boolean value.");
- if (md.Parameters.Length > 0 && invocationScript is null)
- throw new ArgumentException("The verify method requires parameters that need to be passed via the witness' invocation script.");
+ if (witnessScript is null || witnessScript.Length == 0)
+ {
+ // Then it's a contract-based witness, so try to get the corresponding invocation script for it
+ invocationScript = witness?.InvocationScript.ToArray();
+ }
+ }
+ if (witnessScript is null || witnessScript.Length == 0)
+ {
+ var contract = NativeContract.ContractManagement.GetContract(snapshot, hash);
+ if (contract is null)
+ throw new WalletException(WalletErrorType.ContractNotFound, $"The smart contract or address {hash} is not found.");
+ var md = contract.Manifest.Abi.GetMethod(ContractBasicMethod.Verify, ContractBasicMethod.VerifyPCount);
+ if (md is null)
+ throw new WalletException(WalletErrorType.ContractError, $"The smart contract {contract.Hash} hasn't got a verify method.");
+ if (md.ReturnType != ContractParameterType.Boolean)
+ throw new WalletException(WalletErrorType.ContractError, "The verify method doesn't return boolean value.");
+ if (md.Parameters.Length > 0 && invocationScript is null)
+ throw new WalletException(WalletErrorType.ContractError, "The verify method requires parameters that need to be passed via the witness' invocation script.");
- // Empty verification and non-empty invocation scripts
- var invSize = invocationScript?.GetVarSize() ?? Array.Empty().GetVarSize();
- size += Array.Empty().GetVarSize() + invSize;
+ // Empty verification and non-empty invocation scripts
+ var invSize = invocationScript?.GetVarSize() ?? Array.Empty().GetVarSize();
+ size += Array.Empty().GetVarSize() + invSize;
- // Check verify cost
- using ApplicationEngine engine = ApplicationEngine.Create(TriggerType.Verification, tx, snapshot.CloneCache(), settings: settings, gas: maxExecutionCost);
- engine.LoadContract(contract, md, CallFlags.ReadOnly);
- if (invocationScript != null) engine.LoadScript(invocationScript, configureState: p => p.CallFlags = CallFlags.None);
- if (engine.Execute() == VMState.FAULT) throw new ArgumentException($"Smart contract {contract.Hash} verification fault.");
- if (!engine.ResultStack.Pop().GetBoolean()) throw new ArgumentException($"Smart contract {contract.Hash} returns false.");
+ // Check verify cost
+ using ApplicationEngine engine = ApplicationEngine.Create(TriggerType.Verification, tx, snapshot.CloneCache(), settings: settings, gas: maxExecutionCost);
+ engine.LoadContract(contract, md, CallFlags.ReadOnly);
+ if (invocationScript != null) engine.LoadScript(invocationScript, configureState: p => p.CallFlags = CallFlags.None);
+ if (engine.Execute() == VMState.FAULT) throw new WalletException(WalletErrorType.ExecutionFault, $"Smart contract {contract.Hash} verification fault.");
+ if (!engine.ResultStack.Pop().GetBoolean()) throw new WalletException(WalletErrorType.VerificationFailed, $"Smart contract {contract.Hash} returns false.");
- maxExecutionCost -= engine.FeeConsumed;
- if (maxExecutionCost <= 0) throw new InvalidOperationException("Insufficient GAS.");
- networkFee += engine.FeeConsumed;
- }
- else if (IsSignatureContract(witnessScript))
- {
- size += 67 + witnessScript.GetVarSize();
- networkFee += exec_fee_factor * SignatureContractCost();
+ maxExecutionCost -= engine.FeeConsumed;
+ if (maxExecutionCost <= 0) throw new WalletException(WalletErrorType.InsufficientFunds, "Insufficient GAS.");
+ networkFee += engine.FeeConsumed;
+ }
+ else if (IsSignatureContract(witnessScript))
+ {
+ size += 67 + witnessScript.GetVarSize();
+ networkFee += exec_fee_factor * SignatureContractCost();
+ }
+ else if (IsMultiSigContract(witnessScript, out int m, out int n))
+ {
+ int size_inv = 66 * m;
+ size += IO.Helper.GetVarSize(size_inv) + size_inv + witnessScript.GetVarSize();
+ networkFee += exec_fee_factor * MultiSignatureContractCost(m, n);
+ }
+ // We can support more contract types in the future.
}
- else if (IsMultiSigContract(witnessScript, out int m, out int n))
+ networkFee += size * NativeContract.Policy.GetFeePerByte(snapshot);
+ foreach (TransactionAttribute attr in tx.Attributes)
{
- int size_inv = 66 * m;
- size += IO.Helper.GetVarSize(size_inv) + size_inv + witnessScript.GetVarSize();
- networkFee += exec_fee_factor * MultiSignatureContractCost(m, n);
+ networkFee += attr.CalculateNetworkFee(snapshot, tx);
}
- // We can support more contract types in the future.
+ return networkFee;
}
- networkFee += size * NativeContract.Policy.GetFeePerByte(snapshot);
- foreach (TransactionAttribute attr in tx.Attributes)
+ catch (Exception ex) when (ex is not WalletException)
{
- networkFee += attr.CalculateNetworkFee(snapshot, tx);
+ throw new WalletException(WalletErrorType.UnknownError, ex.Message, ex);
}
- return networkFee;
+ }
+
+ internal static void ThrowIfNull(object argument, string paramName)
+ {
+ if (argument == null)
+ throw new WalletException(WalletErrorType.ArgumentNull, $"Argument {paramName} cannot be null.");
}
}
}
diff --git a/src/Neo/Wallets/KeyPair.cs b/src/Neo/Wallets/KeyPair.cs
index 7b15c0bbd0..cfc7546f93 100644
--- a/src/Neo/Wallets/KeyPair.cs
+++ b/src/Neo/Wallets/KeyPair.cs
@@ -47,7 +47,7 @@ public class KeyPair : IEquatable
public KeyPair(byte[] privateKey)
{
if (privateKey.Length != 32 && privateKey.Length != 96 && privateKey.Length != 104)
- throw new ArgumentException(null, nameof(privateKey));
+ throw new WalletException(WalletErrorType.InvalidPrivateKey, nameof(privateKey));
PrivateKey = privateKey[^32..];
if (privateKey.Length == 32)
{
@@ -55,7 +55,15 @@ public KeyPair(byte[] privateKey)
}
else
{
- PublicKey = Cryptography.ECC.ECPoint.FromBytes(privateKey, Cryptography.ECC.ECCurve.Secp256r1);
+ try
+ {
+ PublicKey = Cryptography.ECC.ECPoint.FromBytes(privateKey, Cryptography.ECC.ECCurve.Secp256r1);
+ }
+ catch
+ {
+ throw new WalletException(WalletErrorType.InvalidPrivateKey, $"{nameof(privateKey)}.");
+ }
+
}
}
@@ -119,30 +127,44 @@ public string Export(string passphrase, byte version, int N = 16384, int r = 8,
/// The private key in NEP-2 format.
public string Export(byte[] passphrase, byte version, int N = 16384, int r = 8, int p = 8)
{
- UInt160 script_hash = Contract.CreateSignatureRedeemScript(PublicKey).ToScriptHash();
- string address = script_hash.ToAddress(version);
- byte[] addresshash = Encoding.ASCII.GetBytes(address).Sha256().Sha256()[..4];
- byte[] derivedkey = SCrypt.Generate(passphrase, addresshash, N, r, p, 64);
- byte[] derivedhalf1 = derivedkey[..32];
- byte[] derivedhalf2 = derivedkey[32..];
- byte[] encryptedkey = Encrypt(XOR(PrivateKey, derivedhalf1), derivedhalf2);
- Span buffer = stackalloc byte[39];
- buffer[0] = 0x01;
- buffer[1] = 0x42;
- buffer[2] = 0xe0;
- addresshash.CopyTo(buffer[3..]);
- encryptedkey.CopyTo(buffer[7..]);
- return Base58.Base58CheckEncode(buffer);
+ try
+ {
+ UInt160 script_hash = Contract.CreateSignatureRedeemScript(PublicKey).ToScriptHash();
+ string address = script_hash.ToAddress(version);
+ byte[] addresshash = Encoding.ASCII.GetBytes(address).Sha256().Sha256()[..4];
+ byte[] derivedkey = SCrypt.Generate(passphrase, addresshash, N, r, p, 64);
+ byte[] derivedhalf1 = derivedkey[..32];
+ byte[] derivedhalf2 = derivedkey[32..];
+ byte[] encryptedkey = Encrypt(XOR(PrivateKey, derivedhalf1), derivedhalf2);
+ Span buffer = stackalloc byte[39];
+ buffer[0] = 0x01;
+ buffer[1] = 0x42;
+ buffer[2] = 0xe0;
+ addresshash.CopyTo(buffer[3..]);
+ encryptedkey.CopyTo(buffer[7..]);
+ return Base58.Base58CheckEncode(buffer);
+ }
+ catch (Exception e)
+ {
+ throw WalletException.FromException(e);
+ }
}
private static byte[] Encrypt(byte[] data, byte[] key)
{
- using Aes aes = Aes.Create();
- aes.Key = key;
- aes.Mode = CipherMode.ECB;
- aes.Padding = PaddingMode.None;
- using ICryptoTransform encryptor = aes.CreateEncryptor();
- return encryptor.TransformFinalBlock(data, 0, data.Length);
+ try
+ {
+ using Aes aes = Aes.Create();
+ aes.Key = key;
+ aes.Mode = CipherMode.ECB;
+ aes.Padding = PaddingMode.None;
+ using ICryptoTransform encryptor = aes.CreateEncryptor();
+ return encryptor.TransformFinalBlock(data, 0, data.Length);
+ }
+ catch (Exception e)
+ {
+ throw WalletException.FromException(e);
+ }
}
public override int GetHashCode()
diff --git a/src/Neo/Wallets/NEP6/NEP6Account.cs b/src/Neo/Wallets/NEP6/NEP6Account.cs
index 7998b9ea29..477ea8464d 100644
--- a/src/Neo/Wallets/NEP6/NEP6Account.cs
+++ b/src/Neo/Wallets/NEP6/NEP6Account.cs
@@ -41,14 +41,21 @@ public NEP6Account(NEP6Wallet wallet, UInt160 scriptHash, KeyPair key, string pa
public static NEP6Account FromJson(JObject json, NEP6Wallet wallet)
{
- return new NEP6Account(wallet, json["address"].GetString().ToScriptHash(wallet.ProtocolSettings.AddressVersion), json["key"]?.GetString())
+ try
{
- Label = json["label"]?.GetString(),
- IsDefault = json["isDefault"].GetBoolean(),
- Lock = json["lock"].GetBoolean(),
- Contract = NEP6Contract.FromJson((JObject)json["contract"]),
- Extra = json["extra"]
- };
+ return new NEP6Account(wallet, json["address"].GetString().ToScriptHash(wallet.ProtocolSettings.AddressVersion), json["key"]?.GetString())
+ {
+ Label = json["label"]?.GetString(),
+ IsDefault = json["isDefault"].GetBoolean(),
+ Lock = json["lock"].GetBoolean(),
+ Contract = NEP6Contract.FromJson((JObject)json["contract"]),
+ Extra = json["extra"]
+ };
+ }
+ catch (Exception e) when (e is not WalletException)
+ {
+ throw WalletException.FromException(e);
+ }
}
public override KeyPair GetKey()
@@ -66,7 +73,14 @@ public KeyPair GetKey(string password)
if (nep2key == null) return null;
if (key == null)
{
- key = new KeyPair(Wallet.GetPrivateKeyFromNEP2(nep2key, password, ProtocolSettings.AddressVersion, wallet.Scrypt.N, wallet.Scrypt.R, wallet.Scrypt.P));
+ try
+ {
+ key = new KeyPair(Wallet.GetPrivateKeyFromNEP2(nep2key, password, ProtocolSettings.AddressVersion, wallet.Scrypt.N, wallet.Scrypt.R, wallet.Scrypt.P));
+ }
+ catch (Exception e) when (e is not WalletException)
+ {
+ throw new WalletException(WalletErrorType.PasswordIncorrect);
+ }
}
return key;
}
@@ -91,7 +105,7 @@ public bool VerifyPassword(string password)
Wallet.GetPrivateKeyFromNEP2(nep2key, password, ProtocolSettings.AddressVersion, wallet.Scrypt.N, wallet.Scrypt.R, wallet.Scrypt.P);
return true;
}
- catch (FormatException)
+ catch
{
return false;
}
diff --git a/src/Neo/Wallets/NEP6/NEP6Contract.cs b/src/Neo/Wallets/NEP6/NEP6Contract.cs
index cecc80be7e..5df6bfab5c 100644
--- a/src/Neo/Wallets/NEP6/NEP6Contract.cs
+++ b/src/Neo/Wallets/NEP6/NEP6Contract.cs
@@ -24,28 +24,42 @@ internal class NEP6Contract : Contract
public static NEP6Contract FromJson(JObject json)
{
if (json == null) return null;
- return new NEP6Contract
+ try
{
- Script = Convert.FromBase64String(json["script"].AsString()),
- ParameterList = ((JArray)json["parameters"]).Select(p => p["type"].GetEnum()).ToArray(),
- ParameterNames = ((JArray)json["parameters"]).Select(p => p["name"].AsString()).ToArray(),
- Deployed = json["deployed"].AsBoolean()
- };
+ return new NEP6Contract
+ {
+ Script = Convert.FromBase64String(json["script"].AsString()),
+ ParameterList = ((JArray)json["parameters"]).Select(p => p["type"].GetEnum()).ToArray(),
+ ParameterNames = ((JArray)json["parameters"]).Select(p => p["name"].AsString()).ToArray(),
+ Deployed = json["deployed"].AsBoolean()
+ };
+ }
+ catch (Exception e)
+ {
+ throw WalletException.FromException(e);
+ }
}
public JObject ToJson()
{
- JObject contract = new();
- contract["script"] = Convert.ToBase64String(Script);
- contract["parameters"] = new JArray(ParameterList.Zip(ParameterNames, (type, name) =>
+ try
+ {
+ JObject contract = new()
+ {
+ ["script"] = Convert.ToBase64String(Script),
+ ["parameters"] = new JArray(ParameterList.Zip(ParameterNames, (type, name) =>
+ {
+ JObject parameter = new() { ["name"] = name, ["type"] = type };
+ return parameter;
+ })),
+ ["deployed"] = Deployed
+ };
+ return contract;
+ }
+ catch (Exception e)
{
- JObject parameter = new();
- parameter["name"] = name;
- parameter["type"] = type;
- return parameter;
- }));
- contract["deployed"] = Deployed;
- return contract;
+ throw WalletException.FromException(e);
+ }
}
}
}
diff --git a/src/Neo/Wallets/NEP6/NEP6Wallet.cs b/src/Neo/Wallets/NEP6/NEP6Wallet.cs
index 17a59fc830..c60b382177 100644
--- a/src/Neo/Wallets/NEP6/NEP6Wallet.cs
+++ b/src/Neo/Wallets/NEP6/NEP6Wallet.cs
@@ -58,8 +58,15 @@ public NEP6Wallet(string path, string password, ProtocolSettings settings, strin
this.password = password;
if (File.Exists(path))
{
- JObject wallet = (JObject)JToken.Parse(File.ReadAllBytes(path));
- LoadFromJson(wallet, out Scrypt, out accounts, out extra);
+ try
+ {
+ JObject wallet = (JObject)JToken.Parse(File.ReadAllBytes(path));
+ LoadFromJson(wallet, out Scrypt, out accounts, out extra);
+ }
+ catch (Exception ex)
+ {
+ throw new WalletException(WalletErrorType.LoadWalletError, "Failed to load wallet file.", ex);
+ }
}
else
{
@@ -86,13 +93,20 @@ public NEP6Wallet(string path, string password, ProtocolSettings settings, JObje
private void LoadFromJson(JObject wallet, out ScryptParameters scrypt, out Dictionary accounts, out JToken extra)
{
- version = Version.Parse(wallet["version"].AsString());
- name = wallet["name"]?.AsString();
- scrypt = ScryptParameters.FromJson((JObject)wallet["scrypt"]);
- accounts = ((JArray)wallet["accounts"]).Select(p => NEP6Account.FromJson((JObject)p, this)).ToDictionary(p => p.ScriptHash);
- extra = wallet["extra"];
- if (!VerifyPasswordInternal(password))
- throw new InvalidOperationException("Wrong password.");
+ try
+ {
+ version = Version.Parse(wallet["version"].AsString());
+ name = wallet["name"]?.AsString();
+ scrypt = ScryptParameters.FromJson((JObject)wallet["scrypt"]);
+ accounts = ((JArray)wallet["accounts"]).Select(p => NEP6Account.FromJson((JObject)p, this)).ToDictionary(p => p.ScriptHash);
+ extra = wallet["extra"];
+ if (!VerifyPasswordInternal(password))
+ throw new WalletException(WalletErrorType.PasswordIncorrect, "Wrong password.");
+ }
+ catch (Exception ex) when (ex is not WalletException)
+ {
+ throw new WalletException(WalletErrorType.LoadWalletError, "Failed to parse wallet JSON.", ex);
+ }
}
private void AddAccount(NEP6Account account)
@@ -134,22 +148,29 @@ public override bool Contains(UInt160 scriptHash)
public override WalletAccount CreateAccount(byte[] privateKey)
{
- if (privateKey is null) throw new ArgumentNullException(nameof(privateKey));
- KeyPair key = new(privateKey);
- if (key.PublicKey.IsInfinity) throw new ArgumentException(null, nameof(privateKey));
- NEP6Contract contract = new()
+ Helper.ThrowIfNull(privateKey, nameof(privateKey));
+ try
{
- Script = Contract.CreateSignatureRedeemScript(key.PublicKey),
- ParameterList = new[] { ContractParameterType.Signature },
- ParameterNames = new[] { "signature" },
- Deployed = false
- };
- NEP6Account account = new(this, contract.ScriptHash, key, password)
+ KeyPair key = new(privateKey);
+ if (key.PublicKey.IsInfinity) throw new WalletException(WalletErrorType.InvalidPrivateKey, "Invalid private key.");
+ NEP6Contract contract = new()
+ {
+ Script = Contract.CreateSignatureRedeemScript(key.PublicKey),
+ ParameterList = new[] { ContractParameterType.Signature },
+ ParameterNames = new[] { "signature" },
+ Deployed = false
+ };
+ NEP6Account account = new(this, contract.ScriptHash, key, password)
+ {
+ Contract = contract
+ };
+ AddAccount(account);
+ return account;
+ }
+ catch (Exception ex) when (!(ex is WalletException))
{
- Contract = contract
- };
- AddAccount(account);
- return account;
+ throw new WalletException(WalletErrorType.CreateAccountError, "Failed to create account.", ex);
+ }
}
public override WalletAccount CreateAccount(Contract contract, KeyPair key = null)
@@ -226,7 +247,7 @@ public override WalletAccount Import(X509Certificate2 cert)
{
if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX))
{
- throw new PlatformNotSupportedException("Importing certificates is not supported on macOS.");
+ throw new WalletException(WalletErrorType.UnsupportedOperation, "Importing certificates is not supported on macOS.");
}
KeyPair key;
using (ECDsa ecdsa = cert.GetECDsaPrivateKey())
diff --git a/src/Neo/Wallets/NEP6/NEP6WalletFactory.cs b/src/Neo/Wallets/NEP6/NEP6WalletFactory.cs
index 759ab98de6..2b64b7a6d2 100644
--- a/src/Neo/Wallets/NEP6/NEP6WalletFactory.cs
+++ b/src/Neo/Wallets/NEP6/NEP6WalletFactory.cs
@@ -26,7 +26,7 @@ public bool Handle(string path)
public Wallet CreateWallet(string name, string path, string password, ProtocolSettings settings)
{
if (File.Exists(path))
- throw new InvalidOperationException("The wallet file already exists.");
+ throw new WalletException(WalletErrorType.FileAlreadyExists, "The wallet file already exists.");
NEP6Wallet wallet = new NEP6Wallet(path, password, settings, name);
wallet.Save();
return wallet;
diff --git a/src/Neo/Wallets/NEP6/ScryptParameters.cs b/src/Neo/Wallets/NEP6/ScryptParameters.cs
index d8e076272a..7f45973baf 100644
--- a/src/Neo/Wallets/NEP6/ScryptParameters.cs
+++ b/src/Neo/Wallets/NEP6/ScryptParameters.cs
@@ -10,6 +10,7 @@
// modifications are permitted.
using Neo.Json;
+using System;
namespace Neo.Wallets.NEP6
{
@@ -58,7 +59,14 @@ public ScryptParameters(int n, int r, int p)
/// The converted parameters.
public static ScryptParameters FromJson(JObject json)
{
- return new ScryptParameters((int)json["n"].AsNumber(), (int)json["r"].AsNumber(), (int)json["p"].AsNumber());
+ try
+ {
+ return new ScryptParameters((int)json["n"].AsNumber(), (int)json["r"].AsNumber(), (int)json["p"].AsNumber());
+ }
+ catch (Exception e)
+ {
+ throw WalletException.FromException(e);
+ }
}
///
@@ -67,10 +75,7 @@ public static ScryptParameters FromJson(JObject json)
/// The parameters represented by a JSON object.
public JObject ToJson()
{
- JObject json = new();
- json["n"] = N;
- json["r"] = R;
- json["p"] = P;
+ JObject json = new() { ["n"] = N, ["r"] = R, ["p"] = P };
return json;
}
}
diff --git a/src/Neo/Wallets/Wallet.cs b/src/Neo/Wallets/Wallet.cs
index fc71a9135c..c8f3575164 100644
--- a/src/Neo/Wallets/Wallet.cs
+++ b/src/Neo/Wallets/Wallet.cs
@@ -141,6 +141,7 @@ public WalletAccount CreateAccount()
{
var privateKey = new byte[32];
using var rng = RandomNumberGenerator.Create();
+ var maxTry = 30;
do
{
@@ -149,16 +150,18 @@ public WalletAccount CreateAccount()
rng.GetBytes(privateKey);
return CreateAccount(privateKey);
}
- catch (ArgumentException)
+ catch
{
// Try again
+ maxTry--;
}
finally
{
Array.Clear(privateKey, 0, privateKey.Length);
}
}
- while (true);
+ while (maxTry > 0);
+ throw new WalletException(WalletErrorType.UnknownError, "Failed to create account.");
}
///
@@ -280,34 +283,48 @@ public BigDecimal GetAvailable(DataCache snapshot, UInt160 asset_id)
/// The balance for the specified asset.
public BigDecimal GetBalance(DataCache snapshot, UInt160 asset_id, params UInt160[] accounts)
{
- byte[] script;
- using (ScriptBuilder sb = new())
+ try
{
- sb.EmitPush(0);
- foreach (UInt160 account in accounts)
+ byte[] script;
+ using (ScriptBuilder sb = new())
{
- sb.EmitDynamicCall(asset_id, "balanceOf", CallFlags.ReadOnly, account);
- sb.Emit(OpCode.ADD);
+ sb.EmitPush(0);
+ foreach (UInt160 account in accounts)
+ {
+ sb.EmitDynamicCall(asset_id, "balanceOf", CallFlags.ReadOnly, account);
+ sb.Emit(OpCode.ADD);
+ }
+ sb.EmitDynamicCall(asset_id, "decimals", CallFlags.ReadOnly);
+ script = sb.ToArray();
}
- sb.EmitDynamicCall(asset_id, "decimals", CallFlags.ReadOnly);
- script = sb.ToArray();
+ using ApplicationEngine engine = ApplicationEngine.Run(script, snapshot, settings: ProtocolSettings, gas: 0_60000000L * accounts.Length);
+ if (engine.State == VMState.FAULT)
+ return new BigDecimal(BigInteger.Zero, 0);
+ byte decimals = (byte)engine.ResultStack.Pop().GetInteger();
+ BigInteger amount = engine.ResultStack.Pop().GetInteger();
+ return new BigDecimal(amount, decimals);
+ }
+ catch (Exception e) when (e is not WalletException)
+ {
+ throw WalletException.FromException(e);
}
- using ApplicationEngine engine = ApplicationEngine.Run(script, snapshot, settings: ProtocolSettings, gas: 0_60000000L * accounts.Length);
- if (engine.State == VMState.FAULT)
- return new BigDecimal(BigInteger.Zero, 0);
- byte decimals = (byte)engine.ResultStack.Pop().GetInteger();
- BigInteger amount = engine.ResultStack.Pop().GetInteger();
- return new BigDecimal(amount, decimals);
}
private static byte[] Decrypt(byte[] data, byte[] key)
{
- using Aes aes = Aes.Create();
- aes.Key = key;
- aes.Mode = CipherMode.ECB;
- aes.Padding = PaddingMode.None;
- using ICryptoTransform decryptor = aes.CreateDecryptor();
- return decryptor.TransformFinalBlock(data, 0, data.Length);
+ try
+ {
+ using Aes aes = Aes.Create();
+ aes.Key = key;
+ aes.Mode = CipherMode.ECB;
+ aes.Padding = PaddingMode.None;
+ using ICryptoTransform decryptor = aes.CreateDecryptor();
+ return decryptor.TransformFinalBlock(data, 0, data.Length);
+ }
+ catch (Exception e) when (e is not WalletException)
+ {
+ throw WalletException.FromException(e);
+ }
}
///
@@ -322,11 +339,16 @@ private static byte[] Decrypt(byte[] data, byte[] key)
/// The decoded private key.
public static byte[] GetPrivateKeyFromNEP2(string nep2, string passphrase, byte version, int N = 16384, int r = 8, int p = 8)
{
+ ThrowIfNull(passphrase, nameof(passphrase));
byte[] passphrasedata = Encoding.UTF8.GetBytes(passphrase);
try
{
return GetPrivateKeyFromNEP2(nep2, passphrasedata, version, N, r, p);
}
+ catch (Exception e) when (e is not WalletException)
+ {
+ throw WalletException.FromException(e);
+ }
finally
{
passphrasedata.AsSpan().Clear();
@@ -345,29 +367,37 @@ public static byte[] GetPrivateKeyFromNEP2(string nep2, string passphrase, byte
/// The decoded private key.
public static byte[] GetPrivateKeyFromNEP2(string nep2, byte[] passphrase, byte version, int N = 16384, int r = 8, int p = 8)
{
- if (nep2 == null) throw new ArgumentNullException(nameof(nep2));
- if (passphrase == null) throw new ArgumentNullException(nameof(passphrase));
- byte[] data = nep2.Base58CheckDecode();
- if (data.Length != 39 || data[0] != 0x01 || data[1] != 0x42 || data[2] != 0xe0)
- throw new FormatException();
- byte[] addresshash = new byte[4];
- Buffer.BlockCopy(data, 3, addresshash, 0, 4);
- byte[] derivedkey = SCrypt.Generate(passphrase, addresshash, N, r, p, 64);
- byte[] derivedhalf1 = derivedkey[..32];
- byte[] derivedhalf2 = derivedkey[32..];
- Array.Clear(derivedkey, 0, derivedkey.Length);
- byte[] encryptedkey = new byte[32];
- Buffer.BlockCopy(data, 7, encryptedkey, 0, 32);
- Array.Clear(data, 0, data.Length);
- byte[] prikey = XOR(Decrypt(encryptedkey, derivedhalf2), derivedhalf1);
- Array.Clear(derivedhalf1, 0, derivedhalf1.Length);
- Array.Clear(derivedhalf2, 0, derivedhalf2.Length);
- ECPoint pubkey = Cryptography.ECC.ECCurve.Secp256r1.G * prikey;
- UInt160 script_hash = Contract.CreateSignatureRedeemScript(pubkey).ToScriptHash();
- string address = script_hash.ToAddress(version);
- if (!Encoding.ASCII.GetBytes(address).Sha256().Sha256().AsSpan(0, 4).SequenceEqual(addresshash))
- throw new FormatException();
- return prikey;
+
+ ThrowIfNull(nep2, nameof(nep2));
+ ThrowIfNull(passphrase, nameof(passphrase));
+ try
+ {
+ byte[] data = nep2.Base58CheckDecode();
+ if (data.Length != 39 || data[0] != 0x01 || data[1] != 0x42 || data[2] != 0xe0)
+ throw new WalletException(WalletErrorType.FormatError, "Invalid NEP-2 format.");
+ byte[] addresshash = new byte[4];
+ Buffer.BlockCopy(data, 3, addresshash, 0, 4);
+ byte[] derivedkey = SCrypt.Generate(passphrase, addresshash, N, r, p, 64);
+ byte[] derivedhalf1 = derivedkey[..32];
+ byte[] derivedhalf2 = derivedkey[32..];
+ Array.Clear(derivedkey, 0, derivedkey.Length);
+ byte[] encryptedkey = new byte[32];
+ Buffer.BlockCopy(data, 7, encryptedkey, 0, 32);
+ Array.Clear(data, 0, data.Length);
+ byte[] prikey = XOR(Decrypt(encryptedkey, derivedhalf2), derivedhalf1);
+ Array.Clear(derivedhalf1, 0, derivedhalf1.Length);
+ Array.Clear(derivedhalf2, 0, derivedhalf2.Length);
+ ECPoint pubkey = Cryptography.ECC.ECCurve.Secp256r1.G * prikey;
+ UInt160 script_hash = Contract.CreateSignatureRedeemScript(pubkey).ToScriptHash();
+ string address = script_hash.ToAddress(version);
+ if (!Encoding.ASCII.GetBytes(address).Sha256().Sha256().AsSpan(0, 4).SequenceEqual(addresshash))
+ throw new WalletException(WalletErrorType.FormatError);
+ return prikey;
+ }
+ catch (Exception e)
+ {
+ throw WalletException.FromException(e);
+ }
}
///
@@ -377,14 +407,21 @@ public static byte[] GetPrivateKeyFromNEP2(string nep2, byte[] passphrase, byte
/// The decoded private key.
public static byte[] GetPrivateKeyFromWIF(string wif)
{
- if (wif is null) throw new ArgumentNullException(nameof(wif));
- byte[] data = wif.Base58CheckDecode();
- if (data.Length != 34 || data[0] != 0x80 || data[33] != 0x01)
- throw new FormatException();
- byte[] privateKey = new byte[32];
- Buffer.BlockCopy(data, 1, privateKey, 0, privateKey.Length);
- Array.Clear(data, 0, data.Length);
- return privateKey;
+ try
+ {
+ ThrowIfNull(wif, nameof(wif));
+ byte[] data = wif.Base58CheckDecode();
+ if (data.Length != 34 || data[0] != 0x80 || data[33] != 0x01)
+ throw new WalletException(WalletErrorType.InvalidPrivateKey, "Invalid WIF format.");
+ byte[] privateKey = new byte[32];
+ Buffer.BlockCopy(data, 1, privateKey, 0, privateKey.Length);
+ Array.Clear(data, 0, data.Length);
+ return privateKey;
+ }
+ catch (Exception e) when (e is not WalletException)
+ {
+ throw new WalletException(WalletErrorType.InvalidPrivateKey, "Invalid WIF format.", e);
+ }
}
private static Signer[] GetSigners(UInt160 sender, Signer[] cosigners)
@@ -416,16 +453,24 @@ public virtual WalletAccount Import(X509Certificate2 cert)
{
if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX))
{
- throw new PlatformNotSupportedException("Importing certificates is not supported on macOS.");
+ throw new WalletException(WalletErrorType.UnsupportedOperation, "Importing certificates is not supported on macOS.");
+ }
+
+ try
+ {
+ byte[] privateKey;
+ using (ECDsa ecdsa = cert.GetECDsaPrivateKey())
+ {
+ privateKey = ecdsa.ExportParameters(true).D;
+ }
+ WalletAccount account = CreateAccount(privateKey);
+ Array.Clear(privateKey, 0, privateKey.Length);
+ return account;
}
- byte[] privateKey;
- using (ECDsa ecdsa = cert.GetECDsaPrivateKey())
+ catch (Exception e) when (e is not WalletException)
{
- privateKey = ecdsa.ExportParameters(true).D;
+ throw WalletException.FromException(e);
}
- WalletAccount account = CreateAccount(privateKey);
- Array.Clear(privateKey, 0, privateKey.Length);
- return account;
}
///
@@ -435,10 +480,17 @@ public virtual WalletAccount Import(X509Certificate2 cert)
/// The imported account.
public virtual WalletAccount Import(string wif)
{
- byte[] privateKey = GetPrivateKeyFromWIF(wif);
- WalletAccount account = CreateAccount(privateKey);
- Array.Clear(privateKey, 0, privateKey.Length);
- return account;
+ try
+ {
+ byte[] privateKey = GetPrivateKeyFromWIF(wif);
+ WalletAccount account = CreateAccount(privateKey);
+ Array.Clear(privateKey, 0, privateKey.Length);
+ return account;
+ }
+ catch (Exception e) when (e is not WalletException)
+ {
+ throw WalletException.FromException(e);
+ }
}
///
@@ -452,10 +504,17 @@ public virtual WalletAccount Import(string wif)
/// The imported account.
public virtual WalletAccount Import(string nep2, string passphrase, int N = 16384, int r = 8, int p = 8)
{
- byte[] privateKey = GetPrivateKeyFromNEP2(nep2, passphrase, ProtocolSettings.AddressVersion, N, r, p);
- WalletAccount account = CreateAccount(privateKey);
- Array.Clear(privateKey, 0, privateKey.Length);
- return account;
+ try
+ {
+ byte[] privateKey = GetPrivateKeyFromNEP2(nep2, passphrase, ProtocolSettings.AddressVersion, N, r, p);
+ WalletAccount account = CreateAccount(privateKey);
+ Array.Clear(privateKey, 0, privateKey.Length);
+ return account;
+ }
+ catch (Exception e) when (e is not WalletException)
+ {
+ throw WalletException.FromException(e);
+ }
}
///
@@ -492,13 +551,14 @@ public Transaction MakeTransaction(DataCache snapshot, TransferOutput[] outputs,
sb2.EmitDynamicCall(assetId, "balanceOf", CallFlags.ReadOnly, account);
using ApplicationEngine engine = ApplicationEngine.Run(sb2.ToArray(), snapshot, settings: ProtocolSettings, persistingBlock: persistingBlock);
if (engine.State != VMState.HALT)
- throw new InvalidOperationException($"Execution for {assetId}.balanceOf('{account}' fault");
+ throw new WalletException(WalletErrorType.ContractError, $"Execution for {assetId}.balanceOf('{account}') failed.");
BigInteger value = engine.ResultStack.Pop().GetInteger();
if (value.Sign > 0) balances.Add((account, value));
}
BigInteger sum_balance = balances.Select(p => p.Value).Sum();
if (sum_balance < sum)
- throw new InvalidOperationException($"It does not have enough balance, expected: {sum} found: {sum_balance}");
+ throw new WalletException(WalletErrorType.InsufficientFunds, $"Insufficient balance for asset {assetId}. Required: {sum}, Available: {sum_balance}.");
+
foreach (TransferOutput output in group)
{
balances = balances.OrderBy(p => p.Value).ToList();
@@ -530,7 +590,15 @@ public Transaction MakeTransaction(DataCache snapshot, TransferOutput[] outputs,
if (balances_gas is null)
balances_gas = accounts.Select(p => (Account: p, Value: NativeContract.GAS.BalanceOf(snapshot, p))).Where(p => p.Value.Sign > 0).ToList();
- return MakeTransaction(snapshot, script, cosignerList.Values.ToArray(), Array.Empty(), balances_gas, persistingBlock: persistingBlock);
+ try
+ {
+ return MakeTransaction(snapshot, script, cosignerList.Values.ToArray(), Array.Empty(), balances_gas, persistingBlock: persistingBlock);
+ }
+ catch (Exception ex)
+ {
+ throw new WalletException(WalletErrorType.TransactionCreationError, "Failed to create transaction.", ex);
+ }
+
}
///
@@ -579,7 +647,7 @@ private Transaction MakeTransaction(DataCache snapshot, ReadOnlyMemory scr
{
if (engine.State == VMState.FAULT)
{
- throw new InvalidOperationException($"Failed execution for '{Convert.ToBase64String(script.Span)}'", engine.FaultException);
+ throw new WalletException(WalletErrorType.ExecutionFault, $"Failed execution for '{Convert.ToBase64String(script.Span)}'.", engine.FaultException);
}
tx.SystemFee = engine.FeeConsumed;
}
@@ -587,7 +655,7 @@ private Transaction MakeTransaction(DataCache snapshot, ReadOnlyMemory scr
tx.NetworkFee = tx.CalculateNetworkFee(snapshot, ProtocolSettings, (a) => GetAccount(a)?.Contract?.Script, maxGas);
if (value >= tx.SystemFee + tx.NetworkFee) return tx;
}
- throw new InvalidOperationException("Insufficient GAS");
+ throw new WalletException(WalletErrorType.InsufficientFunds, "Insufficient GAS.");
}
///
@@ -597,61 +665,68 @@ private Transaction MakeTransaction(DataCache snapshot, ReadOnlyMemory scr
/// if the signature is successfully added to the context; otherwise, .
public bool Sign(ContractParametersContext context)
{
- if (context.Network != ProtocolSettings.Network) return false;
- bool fSuccess = false;
- foreach (UInt160 scriptHash in context.ScriptHashes)
+ try
{
- WalletAccount account = GetAccount(scriptHash);
-
- if (account != null)
+ if (context.Network != ProtocolSettings.Network) return false;
+ bool fSuccess = false;
+ foreach (UInt160 scriptHash in context.ScriptHashes)
{
- // Try to sign self-contained multiSig
+ WalletAccount account = GetAccount(scriptHash);
- Contract multiSigContract = account.Contract;
-
- if (multiSigContract != null &&
- IsMultiSigContract(multiSigContract.Script, out int m, out ECPoint[] points))
+ if (account != null)
{
- foreach (var point in points)
+ // Try to sign self-contained multiSig
+
+ Contract multiSigContract = account.Contract;
+
+ if (multiSigContract != null &&
+ IsMultiSigContract(multiSigContract.Script, out int m, out ECPoint[] points))
{
- account = GetAccount(point);
- if (account?.HasKey != true) continue;
+ foreach (var point in points)
+ {
+ account = GetAccount(point);
+ if (account?.HasKey != true) continue;
+ KeyPair key = account.GetKey();
+ byte[] signature = context.Verifiable.Sign(key, context.Network);
+ fSuccess |= context.AddSignature(multiSigContract, key.PublicKey, signature);
+ if (fSuccess) m--;
+ if (context.Completed || m <= 0) break;
+ }
+ continue;
+ }
+ else if (account.HasKey)
+ {
+ // Try to sign with regular accounts
KeyPair key = account.GetKey();
byte[] signature = context.Verifiable.Sign(key, context.Network);
- fSuccess |= context.AddSignature(multiSigContract, key.PublicKey, signature);
- if (fSuccess) m--;
- if (context.Completed || m <= 0) break;
+ fSuccess |= context.AddSignature(account.Contract, key.PublicKey, signature);
+ continue;
}
- continue;
- }
- else if (account.HasKey)
- {
- // Try to sign with regular accounts
- KeyPair key = account.GetKey();
- byte[] signature = context.Verifiable.Sign(key, context.Network);
- fSuccess |= context.AddSignature(account.Contract, key.PublicKey, signature);
- continue;
}
- }
- // Try Smart contract verification
+ // Try Smart contract verification
- var contract = NativeContract.ContractManagement.GetContract(context.SnapshotCache, scriptHash);
+ var contract = NativeContract.ContractManagement.GetContract(context.SnapshotCache, scriptHash);
- if (contract != null)
- {
- var deployed = new DeployedContract(contract);
+ if (contract != null)
+ {
+ var deployed = new DeployedContract(contract);
- // Only works with verify without parameters
+ // Only works with verify without parameters
- if (deployed.ParameterList.Length == 0)
- {
- fSuccess |= context.Add(deployed);
+ if (deployed.ParameterList.Length == 0)
+ {
+ fSuccess |= context.Add(deployed);
+ }
}
}
- }
- return fSuccess;
+ return fSuccess;
+ }
+ catch (Exception e) when (e is not WalletException)
+ {
+ throw WalletException.FromException(e);
+ }
}
///
@@ -686,20 +761,51 @@ public static Wallet Open(string path, string password, ProtocolSettings setting
/// The created new wallet.
public static Wallet Migrate(string path, string oldPath, string password, ProtocolSettings settings)
{
+ ThrowIfNull(path, nameof(path));
+ ThrowIfNull(oldPath, nameof(oldPath));
+ ThrowIfNull(password, nameof(password));
+ ThrowIfNull(settings, nameof(settings));
+
IWalletFactory factoryOld = GetFactory(oldPath);
if (factoryOld is null)
- throw new InvalidOperationException("The old wallet file format is not supported.");
+ throw new WalletException(WalletErrorType.UnsupportedWalletFormat, "The old wallet file format is not supported.");
+
IWalletFactory factoryNew = GetFactory(path);
if (factoryNew is null)
- throw new InvalidOperationException("The new wallet file format is not supported.");
+ throw new WalletException(WalletErrorType.UnsupportedWalletFormat, "The new wallet file format is not supported.");
- Wallet oldWallet = factoryOld.OpenWallet(oldPath, password, settings);
- Wallet newWallet = factoryNew.CreateWallet(oldWallet.Name, path, password, settings);
+ Wallet oldWallet;
+ try
+ {
+ oldWallet = factoryOld.OpenWallet(oldPath, password, settings);
+ }
+ catch (Exception ex)
+ {
+ throw new WalletException(WalletErrorType.OpenWalletError, "Failed to open the old wallet.", ex);
+ }
+
+ Wallet newWallet;
+ try
+ {
+ newWallet = factoryNew.CreateWallet(oldWallet.Name, path, password, settings);
+ }
+ catch (Exception ex)
+ {
+ throw new WalletException(WalletErrorType.CreateWalletError, "Failed to create the new wallet.", ex);
+ }
foreach (WalletAccount account in oldWallet.GetAccounts())
{
- newWallet.CreateAccount(account.Contract, account.GetKey());
+ try
+ {
+ newWallet.CreateAccount(account.Contract, account.GetKey());
+ }
+ catch (Exception ex)
+ {
+ throw new WalletException(WalletErrorType.MigrateAccountError, $"Failed to migrate account {account.Address}.", ex);
+ }
}
+
return newWallet;
}
diff --git a/src/Neo/Wallets/WalletErrorType.cs b/src/Neo/Wallets/WalletErrorType.cs
new file mode 100644
index 0000000000..6678e78aa7
--- /dev/null
+++ b/src/Neo/Wallets/WalletErrorType.cs
@@ -0,0 +1,40 @@
+// Copyright (C) 2015-2024 The Neo Project.
+//
+// WalletErrorType.cs file belongs to the neo project and is free
+// software distributed under the MIT software license, see the
+// accompanying file LICENSE in the main directory of the
+// repository or http://www.opensource.org/licenses/mit-license.php
+// for more details.
+//
+// Redistribution and use in source and binary forms with or without
+// modifications are permitted.
+
+namespace Neo.Wallets;
+
+public enum WalletErrorType
+{
+ OpenWalletError,
+ CreateWalletError,
+ MigrateAccountError,
+ LoadWalletError,
+ CreateAccountError,
+ InvalidPrivateKey,
+ ChangePasswordError,
+ InsufficientFunds,
+ InvalidTransaction,
+ AccountLocked,
+ TransactionCreationError,
+ ExecutionFault,
+ FormatError,
+ InvalidOperation,
+ FileAlreadyExists,
+ ArgumentException,
+ ContractNotFound,
+ ContractError,
+ VerificationFailed,
+ UnsupportedOperation,
+ PasswordIncorrect,
+ UnsupportedWalletFormat,
+ ArgumentNull,
+ UnknownError
+}
diff --git a/src/Neo/Wallets/WalletException.cs b/src/Neo/Wallets/WalletException.cs
new file mode 100644
index 0000000000..5d6ca4f728
--- /dev/null
+++ b/src/Neo/Wallets/WalletException.cs
@@ -0,0 +1,91 @@
+// Copyright (C) 2015-2024 The Neo Project.
+//
+// WalletException.cs file belongs to the neo project and is free
+// software distributed under the MIT software license, see the
+// accompanying file LICENSE in the main directory of the
+// repository or http://www.opensource.org/licenses/mit-license.php
+// for more details.
+//
+// Redistribution and use in source and binary forms with or without
+// modifications are permitted.
+
+using System;
+using System.IO;
+using System.Runtime.CompilerServices;
+
+namespace Neo.Wallets;
+
+public class WalletException : Exception
+{
+ public WalletErrorType ErrorType { get; }
+ public string CustomErrorMessage { get; }
+
+ public WalletException(WalletErrorType errorType, string customErrorMessage = null)
+ : base($"{errorType}: {customErrorMessage ?? GetDefaultMessage(errorType)}")
+ {
+ ErrorType = errorType;
+ CustomErrorMessage = customErrorMessage;
+ }
+
+ public WalletException(WalletErrorType errorType, string customErrorMessage, Exception innerException)
+ : base($"{errorType}: {customErrorMessage ?? GetDefaultMessage(errorType)}", innerException)
+ {
+ ErrorType = errorType;
+ CustomErrorMessage = customErrorMessage;
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static WalletException FromException(Exception exception)
+ {
+ if (exception is WalletException walletException)
+ {
+ return walletException;
+ }
+
+ var errorType = exception switch
+ {
+ FormatException => WalletErrorType.FormatError,
+ InvalidOperationException => WalletErrorType.InvalidOperation,
+ ArgumentException => WalletErrorType.ArgumentException,
+ UnauthorizedAccessException => WalletErrorType.AccountLocked,
+ IOException => WalletErrorType.ExecutionFault,
+ _ => WalletErrorType.UnknownError
+ };
+
+ return new WalletException(errorType, exception.Message, exception);
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ private static string GetDefaultMessage(WalletErrorType errorType)
+ {
+ return errorType switch
+ {
+ WalletErrorType.OpenWalletError => "Failed to open the wallet.",
+ WalletErrorType.CreateWalletError => "Failed to create the wallet.",
+ WalletErrorType.MigrateAccountError => "Failed to migrate the account.",
+ WalletErrorType.LoadWalletError => "Failed to load the wallet.",
+ WalletErrorType.CreateAccountError => "Failed to create the account.",
+ WalletErrorType.InvalidPrivateKey => "Invalid private key provided.",
+ WalletErrorType.ChangePasswordError => "Failed to change the password.",
+ WalletErrorType.InsufficientFunds => "Insufficient funds in the wallet.",
+ WalletErrorType.InvalidTransaction => "Invalid transaction.",
+ WalletErrorType.AccountLocked => "The account is locked.",
+ WalletErrorType.TransactionCreationError => "Failed to create the transaction.",
+ WalletErrorType.ExecutionFault => "Execution fault.",
+ WalletErrorType.FormatError => "Format error occurred.",
+ WalletErrorType.InvalidOperation => "Invalid operation.",
+ WalletErrorType.FileAlreadyExists => "The wallet file already exists.",
+ WalletErrorType.ArgumentException => "Invalid argument provided.",
+ WalletErrorType.UnknownError => "An unknown error occurred.",
+ WalletErrorType.ContractNotFound => "The contract was not found.",
+ WalletErrorType.ContractError => "A contract error occurred.",
+ WalletErrorType.VerificationFailed => "Verification failed.",
+ WalletErrorType.UnsupportedOperation => "Unsupported operation.",
+ WalletErrorType.PasswordIncorrect => "Incorrect password.",
+ WalletErrorType.UnsupportedWalletFormat => "Unsupported wallet format.",
+ _ => "A wallet error occurred."
+ };
+ }
+
+ public override string Message => CustomErrorMessage ?? base.Message;
+}
diff --git a/src/Plugins/RpcServer/RpcServer.Wallet.cs b/src/Plugins/RpcServer/RpcServer.Wallet.cs
index 50f3c7a1e0..63fca7cdfd 100644
--- a/src/Plugins/RpcServer/RpcServer.Wallet.cs
+++ b/src/Plugins/RpcServer/RpcServer.Wallet.cs
@@ -229,6 +229,10 @@ protected internal virtual JToken OpenWallet(JArray _params)
{
throw new RpcException(RpcError.WalletNotSupported.WithData("Invalid password."));
}
+ catch (WalletException e)
+ {
+ throw new RpcException(RpcError.WalletNotSupported.WithData(e.Message));
+ }
return true;
}
diff --git a/tests/Neo.Plugins.RpcServer.Tests/UT_RpcServer.Wallet.cs b/tests/Neo.Plugins.RpcServer.Tests/UT_RpcServer.Wallet.cs
index 2db71ae570..efb207ad44 100644
--- a/tests/Neo.Plugins.RpcServer.Tests/UT_RpcServer.Wallet.cs
+++ b/tests/Neo.Plugins.RpcServer.Tests/UT_RpcServer.Wallet.cs
@@ -63,7 +63,7 @@ public void TestOpenInvalidWallet()
File.WriteAllText(Path, "{\"name\":null,\"version\":\"1.0\",\"scrypt\":{\"n\":16384,\"r\":8,\"p\":8},\"accounts\":[{\"address\":\"NVizn8DiExdmnpTQfjiVY3dox8uXg3Vrxv\",\"label\":null,\"isDefault\":false,\"lock\":false,\"key\":\"6PYPMrsCJ3D4AXJCFWYT2WMSBGF7dLoaNipW14t4UFAkZw3Z9vQRQV1bEU\",\"contract\":{\"script\":\"DCEDaR\\u002BFVb8lOdiMZ/wCHLiI\\u002Bzuf17YuGFReFyHQhB80yMpBVuezJw==\",\"parameters\":[{\"name\":\"signature\",\"type\":\"Signature\"}],\"deployed\":false},\"extra\":null}],\"extra\":null}");
exception = Assert.ThrowsException(() => _rpcServer.OpenWallet(paramsArray), "Should throw RpcException for unsupported wallet");
Assert.AreEqual(RpcError.WalletNotSupported.Code, exception.HResult);
- Assert.AreEqual(exception.Message, "Wallet not supported - Invalid password.");
+ Assert.AreEqual(exception.Message, "Wallet not supported - Failed to load wallet file.");
File.Delete(Path);
}
diff --git a/tests/Neo.UnitTests/Network/P2P/Payloads/UT_Transaction.cs b/tests/Neo.UnitTests/Network/P2P/Payloads/UT_Transaction.cs
index dc910c5142..1022a04a4f 100644
--- a/tests/Neo.UnitTests/Network/P2P/Payloads/UT_Transaction.cs
+++ b/tests/Neo.UnitTests/Network/P2P/Payloads/UT_Transaction.cs
@@ -580,7 +580,8 @@ public void FeeIsSignatureContract_TestScope_CurrentHash_NEO_FAULT()
// expects FAULT on execution of 'transfer' Application script
// due to lack of a valid witness validation
Transaction tx = null;
- Assert.ThrowsException(() => tx = wallet.MakeTransaction(snapshotCache, script, acc.ScriptHash, signers));
+ var exception = Assert.ThrowsException(() => tx = wallet.MakeTransaction(snapshotCache, script, acc.ScriptHash, signers));
+ Assert.AreEqual(exception.ErrorType, WalletErrorType.ExecutionFault);
Assert.IsNull(tx);
}
@@ -711,7 +712,8 @@ public void FeeIsSignatureContract_TestScope_NoScopeFAULT()
// expects FAULT on execution of 'transfer' Application script
// due to lack of a valid witness validation
Transaction tx = null;
- Assert.ThrowsException(() => tx = wallet.MakeTransaction(snapshotCache, script, acc.ScriptHash, signers, attributes));
+ var exception = Assert.ThrowsException(() => tx = wallet.MakeTransaction(snapshotCache, script, acc.ScriptHash, signers, attributes));
+ Assert.AreEqual(exception.ErrorType, WalletErrorType.ExecutionFault);
Assert.IsNull(tx);
}
@@ -761,7 +763,8 @@ public void FeeIsSignatureContract_UnexistingVerificationContractFAULT()
// expects ArgumentException on execution of 'CalculateNetworkFee' due to
// null witness_script (no account in the wallet, no corresponding witness
// and no verification contract for the signer)
- Assert.ThrowsException(() => walletWithoutAcc.MakeTransaction(snapshotCache, script, acc.ScriptHash, signers));
+ var exception = Assert.ThrowsException(() => walletWithoutAcc.MakeTransaction(snapshotCache, script, acc.ScriptHash, signers));
+ Assert.AreEqual(exception.ErrorType, WalletErrorType.ContractNotFound);
Assert.IsNull(tx);
}
@@ -1016,7 +1019,8 @@ public void FeeIsSignatureContract_TestScope_FeeOnly_Default()
Scopes = WitnessScope.None
} };
- Assert.ThrowsException(() => wallet.MakeTransaction(snapshotCache, script, acc.ScriptHash, signers));
+ var exception = Assert.ThrowsException(() => wallet.MakeTransaction(snapshotCache, script, acc.ScriptHash, signers));
+ Assert.AreEqual(exception.ErrorType, WalletErrorType.ExecutionFault);
// change to global scope
signers[0].Scopes = WitnessScope.Global;
diff --git a/tests/Neo.UnitTests/Wallets/NEP6/UT_NEP6Wallet.cs b/tests/Neo.UnitTests/Wallets/NEP6/UT_NEP6Wallet.cs
index a914b235b7..d9f623f27d 100644
--- a/tests/Neo.UnitTests/Wallets/NEP6/UT_NEP6Wallet.cs
+++ b/tests/Neo.UnitTests/Wallets/NEP6/UT_NEP6Wallet.cs
@@ -93,8 +93,10 @@ public void TestCreateAccount()
Assert.IsTrue(uut.Sign(ctx));
tx.Witnesses = ctx.GetWitnesses();
Assert.IsTrue(tx.VerifyWitnesses(TestProtocolSettings.Default, TestBlockchain.GetTestSnapshotCache(), long.MaxValue));
- Assert.ThrowsException(() => uut.CreateAccount((byte[])null));
- Assert.ThrowsException(() => uut.CreateAccount("FFFFFFFF00000000FFFFFFFFFFFFFFFFBCE6FAADA7179E84F3B9CAC2FC632551".HexToBytes()));
+ var exception = Assert.ThrowsException(() => uut.CreateAccount((byte[])null));
+ Assert.AreEqual(exception.ErrorType, WalletErrorType.ArgumentNull);
+ exception = Assert.ThrowsException(() => uut.CreateAccount("FFFFFFFF00000000FFFFFFFFFFFFFFFFBCE6FAADA7179E84F3B9CAC2FC632551".HexToBytes()));
+ Assert.AreEqual(exception.ErrorType, WalletErrorType.InvalidPrivateKey);
}
[TestMethod]
@@ -305,7 +307,8 @@ public void TestImportCert()
Assert.AreEqual(true, cert.HasPrivateKey);
if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX))
{
- Assert.ThrowsException(() => uut.Import(cert));
+ var exception = Assert.ThrowsException(() => uut.Import(cert));
+ Assert.AreEqual(exception.ErrorType, WalletErrorType.UnsupportedOperation);
return;
}
WalletAccount account = uut.Import(cert);
diff --git a/tests/Neo.UnitTests/Wallets/UT_AssetDescriptor.cs b/tests/Neo.UnitTests/Wallets/UT_AssetDescriptor.cs
index ff2386c466..da7a9cef71 100644
--- a/tests/Neo.UnitTests/Wallets/UT_AssetDescriptor.cs
+++ b/tests/Neo.UnitTests/Wallets/UT_AssetDescriptor.cs
@@ -12,6 +12,7 @@
using FluentAssertions;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using Neo.SmartContract.Native;
+using Neo.Wallets;
using System;
namespace Neo.UnitTests.Wallets
@@ -23,11 +24,11 @@ public class UT_AssetDescriptor
public void TestConstructorWithNonexistAssetId()
{
var snapshotCache = TestBlockchain.GetTestSnapshotCache();
- Action action = () =>
+ var exception = Assert.ThrowsException(() =>
{
var descriptor = new Neo.Wallets.AssetDescriptor(snapshotCache, TestProtocolSettings.Default, UInt160.Parse("01ff00ff00ff00ff00ff00ff00ff00ff00ff00a4"));
- };
- action.Should().Throw();
+ });
+ Assert.AreEqual(exception.ErrorType, WalletErrorType.ContractNotFound);
}
[TestMethod]
diff --git a/tests/Neo.UnitTests/Wallets/UT_KeyPair.cs b/tests/Neo.UnitTests/Wallets/UT_KeyPair.cs
index 7ae84960c7..77bd4a3f16 100644
--- a/tests/Neo.UnitTests/Wallets/UT_KeyPair.cs
+++ b/tests/Neo.UnitTests/Wallets/UT_KeyPair.cs
@@ -46,7 +46,7 @@ public void TestConstructor()
for (int i = 0; i < privateKey31.Length; i++)
privateKey31[i] = (byte)random.Next(256);
Action action = () => new KeyPair(privateKey31);
- action.Should().Throw();
+ action.Should().Throw();
}
[TestMethod]
diff --git a/tests/Neo.UnitTests/Wallets/UT_Wallet.cs b/tests/Neo.UnitTests/Wallets/UT_Wallet.cs
index bba7ff05a1..4d71e14440 100644
--- a/tests/Neo.UnitTests/Wallets/UT_Wallet.cs
+++ b/tests/Neo.UnitTests/Wallets/UT_Wallet.cs
@@ -252,11 +252,11 @@ public void TestGetBalance()
[TestMethod]
public void TestGetPrivateKeyFromNEP2()
{
- Action action = () => Wallet.GetPrivateKeyFromNEP2("3vQB7B6MrGQZaxCuFg4oh", "TestGetPrivateKeyFromNEP2", ProtocolSettings.Default.AddressVersion, 2, 1, 1);
- action.Should().Throw();
+ var exception = Assert.ThrowsException(() => Wallet.GetPrivateKeyFromNEP2("3vQB7B6MrGQZaxCuFg4oh", "TestGetPrivateKeyFromNEP2", ProtocolSettings.Default.AddressVersion, 2, 1, 1));
+ Assert.AreEqual(exception.ErrorType, WalletErrorType.FormatError);
- action = () => Wallet.GetPrivateKeyFromNEP2(nep2Key, "Test", ProtocolSettings.Default.AddressVersion, 2, 1, 1);
- action.Should().Throw();
+ exception = Assert.ThrowsException(() => Wallet.GetPrivateKeyFromNEP2(nep2Key, "Test", ProtocolSettings.Default.AddressVersion, 2, 1, 1));
+ Assert.AreEqual(exception.ErrorType, WalletErrorType.FormatError);
Wallet.GetPrivateKeyFromNEP2(nep2Key, "pwd", ProtocolSettings.Default.AddressVersion, 2, 1, 1).Should().BeEquivalentTo(new byte[] { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31 });
}
@@ -264,11 +264,11 @@ public void TestGetPrivateKeyFromNEP2()
[TestMethod]
public void TestGetPrivateKeyFromWIF()
{
- Action action = () => Wallet.GetPrivateKeyFromWIF(null);
- action.Should().Throw();
+ var exception = Assert.ThrowsException(() => Wallet.GetPrivateKeyFromWIF(null));
+ Assert.AreEqual(exception.ErrorType, WalletErrorType.ArgumentNull);
- action = () => Wallet.GetPrivateKeyFromWIF("3vQB7B6MrGQZaxCuFg4oh");
- action.Should().Throw();
+ exception = Assert.ThrowsException(() => Wallet.GetPrivateKeyFromWIF("3vQB7B6MrGQZaxCuFg4oh"));
+ Assert.AreEqual(exception.ErrorType, WalletErrorType.InvalidPrivateKey);
Wallet.GetPrivateKeyFromWIF("L3tgppXLgdaeqSGSFw1Go3skBiy8vQAM7YMXvTHsKQtE16PBncSU").Should().BeEquivalentTo(new byte[] { 199, 19, 77, 111, 216, 231, 61, 129, 158, 130, 117, 92, 100, 201, 55, 136, 216, 219, 9, 97, 146, 158, 2, 90, 83, 54, 60, 76, 192, 42, 105, 98 });
}
@@ -296,7 +296,7 @@ public void TestMakeTransaction1()
WalletAccount account = wallet.CreateAccount(contract, glkey.PrivateKey);
account.Lock = false;
- Action action = () => wallet.MakeTransaction(snapshotCache, new TransferOutput[]
+ var exception = Assert.ThrowsException(() => wallet.MakeTransaction(snapshotCache, new TransferOutput[]
{
new TransferOutput()
{
@@ -305,10 +305,10 @@ public void TestMakeTransaction1()
Value = new BigDecimal(BigInteger.One,8),
Data = "Dec 12th"
}
- }, UInt160.Zero);
- action.Should().Throw();
+ }, UInt160.Zero));
+ Assert.AreEqual(exception.ErrorType, WalletErrorType.InsufficientFunds);
- action = () => wallet.MakeTransaction(snapshotCache, new TransferOutput[]
+ exception = Assert.ThrowsException(() => wallet.MakeTransaction(snapshotCache, new TransferOutput[]
{
new TransferOutput()
{
@@ -317,10 +317,9 @@ public void TestMakeTransaction1()
Value = new BigDecimal(BigInteger.One,8),
Data = "Dec 12th"
}
- }, account.ScriptHash);
- action.Should().Throw();
-
- action = () => wallet.MakeTransaction(snapshotCache, new TransferOutput[]
+ }, account.ScriptHash));
+ Assert.AreEqual(exception.ErrorType, WalletErrorType.InsufficientFunds);
+ exception = Assert.ThrowsException(() => wallet.MakeTransaction(snapshotCache, new TransferOutput[]
{
new TransferOutput()
{
@@ -329,8 +328,8 @@ public void TestMakeTransaction1()
Value = new BigDecimal(BigInteger.One,8),
Data = "Dec 12th"
}
- }, account.ScriptHash);
- action.Should().Throw();
+ }, account.ScriptHash));
+ Assert.AreEqual(exception.ErrorType, WalletErrorType.ContractError);
// Fake balance
var key = NativeContract.GAS.CreateStorageKey(20, account.ScriptHash);
@@ -375,8 +374,8 @@ public void TestMakeTransaction2()
{
var snapshotCache = TestBlockchain.GetTestSnapshotCache();
MyWallet wallet = new();
- Action action = () => wallet.MakeTransaction(snapshotCache, Array.Empty(), null, null, Array.Empty());
- action.Should().Throw();
+ var exception = Assert.ThrowsException(() => wallet.MakeTransaction(snapshotCache, Array.Empty(), null, null, Array.Empty()));
+ Assert.AreEqual(exception.ErrorType, WalletErrorType.InsufficientFunds);
Contract contract = Contract.Create(new ContractParameterType[] { ContractParameterType.Boolean }, new byte[] { 1 });
WalletAccount account = wallet.CreateAccount(contract, glkey.PrivateKey);
diff --git a/tests/Neo.UnitTests/Wallets/UT_Wallets_Helper.cs b/tests/Neo.UnitTests/Wallets/UT_Wallets_Helper.cs
index 843eeae192..26196fb549 100644
--- a/tests/Neo.UnitTests/Wallets/UT_Wallets_Helper.cs
+++ b/tests/Neo.UnitTests/Wallets/UT_Wallets_Helper.cs
@@ -26,10 +26,12 @@ public void TestToScriptHash()
{
byte[] array = { 0x01 };
UInt160 scriptHash = new UInt160(Crypto.Hash160(array));
- "NdtB8RXRmJ7Nhw1FPTm7E6HoDZGnDw37nf".ToScriptHash(TestProtocolSettings.Default.AddressVersion).Should().Be(scriptHash);
+ "NdtB8RXRmJ7Nhw1FPTm7E6HoDZGnDw37nf".ToScriptHash(TestProtocolSettings.Default.AddressVersion).Should()
+ .Be(scriptHash);
- Action action = () => "3vQB7B6MrGQZaxCuFg4oh".ToScriptHash(TestProtocolSettings.Default.AddressVersion);
- action.Should().Throw();
+ var exception = Assert.ThrowsException(() =>
+ "3vQB7B6MrGQZaxCuFg4oh".ToScriptHash(TestProtocolSettings.Default.AddressVersion));
+ Assert.AreEqual(exception.ErrorType, WalletErrorType.FormatError);
var address = scriptHash.ToAddress(ProtocolSettings.Default.AddressVersion);
Span data = stackalloc byte[21];
@@ -37,8 +39,9 @@ public void TestToScriptHash()
data[0] = 0x01;
scriptHash.ToArray().CopyTo(data[1..]);
address = Base58.Base58CheckEncode(data);
- action = () => address.ToScriptHash(ProtocolSettings.Default.AddressVersion);
- action.Should().Throw();
+ exception = Assert.ThrowsException(() =>
+ address.ToScriptHash(ProtocolSettings.Default.AddressVersion));
+ Assert.AreEqual(exception.ErrorType, WalletErrorType.FormatError);
}
}
}