From a0e77061e4d50f55303bb48495075d57bfceaeb1 Mon Sep 17 00:00:00 2001 From: skibitsky Date: Mon, 15 Jul 2024 12:49:59 +0300 Subject: [PATCH 1/6] =?UTF-8?q?Don=E2=80=99t=20wait=20for=201s=20before=20?= =?UTF-8?q?restarting=20relayer=20transport?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- WalletConnectSharp.Core/Controllers/Relayer.cs | 3 --- 1 file changed, 3 deletions(-) diff --git a/WalletConnectSharp.Core/Controllers/Relayer.cs b/WalletConnectSharp.Core/Controllers/Relayer.cs index 8d63840..45dc852 100644 --- a/WalletConnectSharp.Core/Controllers/Relayer.cs +++ b/WalletConnectSharp.Core/Controllers/Relayer.cs @@ -227,9 +227,6 @@ private async void OnProviderDisconnected(object sender, EventArgs e) if (this._transportExplicitlyClosed) return; - // Attempt to reconnect after one second - await Task.Delay(1000); - await RestartTransport(); } From d39843bd3aac8bdb27c938d7a37cee8cbcabc5d5 Mon Sep 17 00:00:00 2001 From: skibitsky Date: Mon, 15 Jul 2024 13:54:22 +0300 Subject: [PATCH 2/6] =?UTF-8?q?Don=E2=80=99t=20throw=20`System.Exception`?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../WalletConnectSharp.Crypto/Crypto.cs | 13 ++-- .../Encoder/BaseX.cs | 16 ++--- .../JsonRpcProvider.cs | 2 +- .../Models/RpcMethodAttribute.cs | 38 ++++++---- .../Controllers/Relayer.cs | 4 +- .../Controllers/Subscriber.cs | 4 -- .../Controllers/TypedMessageHandler.cs | 69 ++++++++++++------- .../Models/Expirer/ExpirerTarget.cs | 26 ++++--- .../Models/Verify/Verifier.cs | 2 - WalletConnectSharp.Sign/Engine.cs | 6 +- .../Models/ProposalStruct.cs | 44 ++++++------ 11 files changed, 128 insertions(+), 96 deletions(-) diff --git a/Core Modules/WalletConnectSharp.Crypto/Crypto.cs b/Core Modules/WalletConnectSharp.Crypto/Crypto.cs index 47dd5de..fc2d730 100644 --- a/Core Modules/WalletConnectSharp.Crypto/Crypto.cs +++ b/Core Modules/WalletConnectSharp.Crypto/Crypto.cs @@ -159,11 +159,16 @@ public Task GenerateKeyPair() generator.Init(options); var keypair = generator.GenerateKeyPair(); - var publicKeyData = keypair.Public as X25519PublicKeyParameters; - var privateKeyData = keypair.Private as X25519PrivateKeyParameters; - if (publicKeyData == null || privateKeyData == null) - throw new Exception("Could not generate keypair"); + if (keypair.Public is not X25519PublicKeyParameters publicKeyData) + { + throw new InvalidCastException($"Public key is not an {nameof(X25519PublicKeyParameters)}"); + } + + if (keypair.Private is not X25519PrivateKeyParameters privateKeyData) + { + throw new InvalidCastException($"Private key is not an {nameof(X25519PrivateKeyParameters)}"); + } var publicKey = publicKeyData.GetEncoded().ToHex(); var privateKey = privateKeyData.GetEncoded().ToHex(); diff --git a/Core Modules/WalletConnectSharp.Crypto/Encoder/BaseX.cs b/Core Modules/WalletConnectSharp.Crypto/Encoder/BaseX.cs index 10df155..abac881 100644 --- a/Core Modules/WalletConnectSharp.Crypto/Encoder/BaseX.cs +++ b/Core Modules/WalletConnectSharp.Crypto/Encoder/BaseX.cs @@ -1,5 +1,3 @@ -using System; - namespace WalletConnectSharp.Crypto.Encoder { internal sealed class BaseX @@ -14,9 +12,11 @@ internal sealed class BaseX public BaseX(string alphabet, string name) { - if (alphabet.Length >= 255) - throw new ArgumentException("Alphabet too long"); - + if (alphabet.Length >= 255) + { + throw new ArgumentException("Alphabet too long", nameof(alphabet)); + } + this.name = name; this.Alphabet = alphabet.ToCharArray(); @@ -70,7 +70,7 @@ public string Encode(byte[] source) } if (carry != 0) { - throw new Exception("Non-zero carry"); + throw new InvalidOperationException("Non-zero carry"); } length = i; pbegin++; @@ -126,7 +126,7 @@ public byte[] DecodeUnsafe(string source) if (carry != 0) { - throw new Exception("Non-zero carry"); + throw new InvalidOperationException("Non-zero carry"); } length = i; psz++; @@ -155,7 +155,7 @@ public byte[] Decode(string source) var buffer = DecodeUnsafe(source); if (buffer != null) return buffer; - throw new Exception($"Non-{name} character"); + throw new InvalidOperationException($"Non-{name} character"); } } } diff --git a/Core Modules/WalletConnectSharp.Network/JsonRpcProvider.cs b/Core Modules/WalletConnectSharp.Network/JsonRpcProvider.cs index 3266a59..099ed13 100644 --- a/Core Modules/WalletConnectSharp.Network/JsonRpcProvider.cs +++ b/Core Modules/WalletConnectSharp.Network/JsonRpcProvider.cs @@ -174,7 +174,7 @@ private void FinalizeConnection(IJsonRpcConnection connection) public async Task Connect() { if (_connection == null) - throw new Exception("No connection is set"); + throw new InvalidOperationException("Connection is null"); await Connect(_connection); } diff --git a/Core Modules/WalletConnectSharp.Network/Models/RpcMethodAttribute.cs b/Core Modules/WalletConnectSharp.Network/Models/RpcMethodAttribute.cs index 66099da..a00ac88 100644 --- a/Core Modules/WalletConnectSharp.Network/Models/RpcMethodAttribute.cs +++ b/Core Modules/WalletConnectSharp.Network/Models/RpcMethodAttribute.cs @@ -1,6 +1,3 @@ -using System; -using System.Linq; - namespace WalletConnectSharp.Network.Models { /// @@ -25,25 +22,36 @@ public RpcMethodAttribute(string method) { MethodName = method; } - + /// - /// Get the method name that should be used for a given class type T. This is - /// defined by the RpcMethodAttribute attached to the type T. If the type T has no - /// RpcMethodAttribute, then an Exception is thrown + /// Retrieves the method name to be used for a given class type T, as defined by the RpcMethodAttribute + /// attached to type T. This method ensures that exactly one RpcMethodAttribute is present on the type T. + /// If no RpcMethodAttribute is found, or if multiple are found, an InvalidOperationException is thrown. /// - /// The type T to get the method name for - /// The method name to use as a string - /// If the type T has no - /// RpcMethodAttribute, then an Exception is thrown + /// The type T for which to get the method name. + /// The method name to use, as a string. + /// + /// Thrown if the type T has no RpcMethodAttribute defined, + /// or if multiple RpcMethodAttribute definitions are found. + /// public static string MethodForType() { var attributes = typeof(T).GetCustomAttributes(typeof(RpcMethodAttribute), true); - if (attributes.Length != 1) - throw new Exception($"Type {typeof(T).FullName} has no WcMethod attribute!"); + switch (attributes.Length) + { + case 0: + throw new InvalidOperationException($"Type {typeof(T).FullName} has no {nameof(RpcMethodAttribute)} defined."); + case > 1: + throw new InvalidOperationException($"Type {typeof(T).FullName} has multiple {nameof(RpcMethodAttribute)} definitions. Only one is allowed."); + } - var method = attributes.Cast().First().MethodName; + var methodAttribute = attributes.Cast().SingleOrDefault(); + if (methodAttribute == null) + { + throw new InvalidOperationException($"Type {typeof(T).FullName} has multiple RpcMethodAttribute definitions. Only one is allowed."); + } - return method; + return methodAttribute.MethodName; } } } diff --git a/WalletConnectSharp.Core/Controllers/Relayer.cs b/WalletConnectSharp.Core/Controllers/Relayer.cs index 45dc852..ee1f1d4 100644 --- a/WalletConnectSharp.Core/Controllers/Relayer.cs +++ b/WalletConnectSharp.Core/Controllers/Relayer.cs @@ -414,7 +414,9 @@ public async Task TransportOpen(string relayUrl = null) void RejectTransportOpen(object sender, EventArgs @event) { - task2.TrySetException(new Exception("closeTransport called before connection was established")); + task2.TrySetException( + new IOException("The transport was closed before the connection was established.") + ); } async void Task2() diff --git a/WalletConnectSharp.Core/Controllers/Subscriber.cs b/WalletConnectSharp.Core/Controllers/Subscriber.cs index 2268fc9..600a3e0 100644 --- a/WalletConnectSharp.Core/Controllers/Subscriber.cs +++ b/WalletConnectSharp.Core/Controllers/Subscriber.cs @@ -260,7 +260,6 @@ protected virtual async void CheckPending() if (_relayer.TransportExplicitlyClosed) return; - await BatchSubscribe(pending.Values.ToArray()); } @@ -338,7 +337,6 @@ protected virtual void OnDisable() _cached = Values; _subscriptions.Clear(); _topicMap.Clear(); - _initialized = false; } protected virtual async void OnConnect() @@ -353,9 +351,7 @@ private async Task RestartToComplete() { if (!RestartInProgress) return; - _logger.Log("waiting for restart"); await restartTask.Task; - _logger.Log("restart completed"); } protected virtual void OnSubscribe(string id, PendingSubscription @params) diff --git a/WalletConnectSharp.Core/Controllers/TypedMessageHandler.cs b/WalletConnectSharp.Core/Controllers/TypedMessageHandler.cs index a5b9a7a..af3ed25 100644 --- a/WalletConnectSharp.Core/Controllers/TypedMessageHandler.cs +++ b/WalletConnectSharp.Core/Controllers/TypedMessageHandler.cs @@ -213,7 +213,7 @@ async void InspectResponseRaw(object sender, DecodedMessageEvent e) /// The second type to check for /// constructed from the values found in the /// from either type T1 or T2 - /// If no is found in either type + /// If no is found in either type public PublishOptions RpcRequestOptionsFromType() { var opts = RpcRequestOptionsForType(); @@ -222,8 +222,10 @@ public PublishOptions RpcRequestOptionsFromType() opts = RpcRequestOptionsForType(); if (opts == null) { - throw new Exception( - $"No RpcRequestOptions attribute found in either {typeof(T1).FullName} or {typeof(T2).FullName}!"); + throw new InvalidOperationException( + $"No RpcRequestOptions attribute found on either {typeof(T1).FullName} or {typeof(T2).FullName}. " + + $"Ensure that at least one of these types is decorated with the {nameof(RpcRequestOptionsAttribute)}." + ); } } @@ -237,18 +239,28 @@ public PublishOptions RpcRequestOptionsFromType() /// The type to check for /// constructed from the values found in the /// from the given type T - /// If no is found in the type T + /// If no is found in the type T or if multiple are found public PublishOptions RpcRequestOptionsForType() { var attributes = typeof(T).GetCustomAttributes(typeof(RpcRequestOptionsAttribute), true); - if (attributes.Length > 1) - throw new Exception($"Type {typeof(T).FullName} has multiple RpcRequestOptions attributes!"); - if (attributes.Length == 0) - return null; + switch (attributes.Length) + { + case 0: + throw new InvalidOperationException($"Type {typeof(T).FullName} has no {nameof(RpcRequestOptionsAttribute)} defined."); + case > 1: + throw new InvalidOperationException($"Type {typeof(T).FullName} has multiple {nameof(RpcRequestOptionsAttribute)} definitions. Only one is allowed."); + } - var opts = attributes.Cast().First(); + var opts = attributes.Cast().SingleOrDefault(); + if (opts == null) + { + throw new InvalidOperationException($"Type {typeof(T).FullName} has multiple {nameof(RpcRequestOptionsAttribute)} definitions. Only one is allowed."); + } - return new PublishOptions() { Tag = opts.Tag, TTL = opts.TTL }; + return new PublishOptions + { + Tag = opts.Tag, TTL = opts.TTL + }; } /// @@ -259,20 +271,15 @@ public PublishOptions RpcRequestOptionsForType() /// The second type to check for /// constructed from the values found in the /// from either type T1 or T2 - /// If no is found in either type + /// If no is found in either type public PublishOptions RpcResponseOptionsFromTypes() { - var opts = RpcResponseOptionsForType(); - if (opts != null) - { - return opts; - } - - opts = RpcResponseOptionsForType(); + var opts = RpcResponseOptionsForType() ?? RpcResponseOptionsForType(); if (opts == null) { - throw new Exception( - $"No RpcResponseOptions attribute found in either {typeof(T1).FullName} or {typeof(T2).FullName}!"); + throw new InvalidOperationException( + $"No {nameof(RpcResponseOptionsAttribute)} found on either {typeof(T1).FullName} or {typeof(T2).FullName}. " + + "Ensure that at least one of these types is decorated with the RpcResponseOptionsAttribute."); } return opts; @@ -289,14 +296,24 @@ public PublishOptions RpcResponseOptionsFromTypes() public PublishOptions RpcResponseOptionsForType() { var attributes = typeof(T).GetCustomAttributes(typeof(RpcResponseOptionsAttribute), true); - if (attributes.Length > 1) - throw new Exception($"Type {typeof(T).FullName} has multiple RpcResponseOptions attributes!"); - if (attributes.Length == 0) - return null; + switch (attributes.Length) + { + case 0: + return null; + case > 1: + throw new InvalidOperationException($"Type {typeof(T).FullName} has multiple {nameof(RpcMethodAttribute)} definitions. Only one is allowed."); + } - var opts = attributes.Cast().First(); + var opts = attributes.Cast().SingleOrDefault(); + if (opts == null) + { + throw new InvalidOperationException($"Type {typeof(T).FullName} has multiple {nameof(RpcMethodAttribute)} definitions. Only one is allowed."); + } - return new PublishOptions() { Tag = opts.Tag, TTL = opts.TTL }; + return new PublishOptions() + { + Tag = opts.Tag, TTL = opts.TTL + }; } public void SetDecodeOptionsForTopic(DecodeOptions options, string topic) diff --git a/WalletConnectSharp.Core/Models/Expirer/ExpirerTarget.cs b/WalletConnectSharp.Core/Models/Expirer/ExpirerTarget.cs index feeb36e..ccbbdb8 100644 --- a/WalletConnectSharp.Core/Models/Expirer/ExpirerTarget.cs +++ b/WalletConnectSharp.Core/Models/Expirer/ExpirerTarget.cs @@ -30,31 +30,29 @@ public class ExpirerTarget /// be converted and stored to either the ID field or Topic field /// /// The to convert - /// If the format for the given is invalid + /// If the format for the given is invalid public ExpirerTarget(string target) { - var values = target.Split(":"); - var type = values[0]; - var value = values[1]; + var values = target.Split(':'); + if (values.Length != 2) + { + throw new FormatException($"Invalid target format: {target}. Expected format: 'type:value'."); + } + + var (type, value) = (values[0], values[1]); switch (type) { case "topic": Topic = value; break; - case "id": - { - long id; - var success = long.TryParse(value, out id); - - if (!success) - throw new Exception($"Cannot parse id {value}"); - + case "id" when long.TryParse(value, out var id): Id = id; break; - } + case "id": + throw new FormatException($"Cannot parse id {value} as a long."); default: - throw new Exception($"Invalid target, expected id:number or topic:string, got {type}:{value}"); + throw new FormatException($"Invalid target type: {type}. Expected 'id' or 'topic'."); } } } diff --git a/WalletConnectSharp.Core/Models/Verify/Verifier.cs b/WalletConnectSharp.Core/Models/Verify/Verifier.cs index 4d4d84b..02736f2 100644 --- a/WalletConnectSharp.Core/Models/Verify/Verifier.cs +++ b/WalletConnectSharp.Core/Models/Verify/Verifier.cs @@ -17,9 +17,7 @@ public async Task Resolve(string attestationId) try { var url = $"{VerifyServer}/attestation/{attestationId}"; - WCLogger.Log($"[Verifier] Resolving attestation {attestationId} from {url}"); var results = await _client.GetStringAsync(url); - WCLogger.Log($"[Verifier] Resolved attestation. Results: {results}"); var verifiedContext = JsonConvert.DeserializeObject(results); diff --git a/WalletConnectSharp.Sign/Engine.cs b/WalletConnectSharp.Sign/Engine.cs index deb2928..d02559a 100644 --- a/WalletConnectSharp.Sign/Engine.cs +++ b/WalletConnectSharp.Sign/Engine.cs @@ -543,10 +543,14 @@ public async Task Pair(string uri) return; if (args.VerifiedContext.Validation == Validation.Invalid) + { sessionProposeTask.SetException(new Exception( $"Could not validate, invalid validation status {args.VerifiedContext.Validation} for origin {args.VerifiedContext.Origin}")); + } else + { sessionProposeTask.SetResult(proposal); + } }, h => Client.SessionProposed += h, h => Client.SessionProposed -= h @@ -852,7 +856,7 @@ public async Task Ping(string topic) /// /// The topic of the session to disconnect /// An (optional) error reason for the disconnect - public async Task Disconnect(string topic, Error reason) + public async Task Disconnect(string topic, Error reason = null) { IsInitialized(); var error = reason ?? Error.FromErrorType(ErrorType.USER_DISCONNECTED); diff --git a/WalletConnectSharp.Sign/Models/ProposalStruct.cs b/WalletConnectSharp.Sign/Models/ProposalStruct.cs index 657e0cd..5a4e8ad 100644 --- a/WalletConnectSharp.Sign/Models/ProposalStruct.cs +++ b/WalletConnectSharp.Sign/Models/ProposalStruct.cs @@ -1,7 +1,3 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Threading.Tasks; using Newtonsoft.Json; using WalletConnectSharp.Common.Model.Errors; using WalletConnectSharp.Core.Interfaces; @@ -29,14 +25,8 @@ public struct ProposalStruct : IKeyHolder /// so this struct can be stored using /// [JsonIgnore] - public long Key - { - get - { - return (long) Id; - } - } - + public long Key => Id; + /// /// When this proposal expires /// @@ -106,14 +96,23 @@ public ApproveParams ApproveProposal(string approvedAccount, ProtocolOptions pro /// (optional) The protocol option to use. If left null, then the first protocol /// option in this proposal will be chosen. /// The that can be given to + /// If this proposal has no Id + /// If the requested protocol option does not exist in this proposal public ApproveParams ApproveProposal(string[] approvedAccounts, ProtocolOptions protocolOption = null) { - if (Id == null) - throw new Exception("Proposal has no set Id"); + if (Id == default) + { + throw new InvalidOperationException("Proposal has no Id."); + } + if (protocolOption == null) + { protocolOption = Relays[0]; - else if (Relays.All(r => r.Protocol != protocolOption.Protocol)) - throw new Exception("Requested protocol not in proposal"); + } + else if (Array.TrueForAll(Relays, r => r.Protocol != protocolOption.Protocol)) + { + throw new InvalidOperationException("Requested protocol option does not exist in this proposal."); + } var relayProtocol = protocolOption.Protocol; @@ -164,13 +163,18 @@ public ApproveParams ApproveProposal(string[] approvedAccounts, ProtocolOptions /// /// The error reason this proposal was rejected /// A new object which must be used in - /// If this proposal has no Id + /// If this proposal has no Id public RejectParams RejectProposal(Error error) { - if (Id == null) - throw new Exception("Proposal has no set Id"); + if (Id == default) + { + throw new InvalidOperationException("Proposal has no Id."); + } - return new RejectParams() {Id = Id, Reason = error}; + return new RejectParams + { + Id = Id, Reason = error + }; } /// From e129ad41f8d2867a2e8726e09fd2a88b299a4489 Mon Sep 17 00:00:00 2001 From: skibitsky Date: Mon, 15 Jul 2024 17:02:26 +0300 Subject: [PATCH 3/6] =?UTF-8?q?Fixed=20exceptions=20that=20can=E2=80=99t?= =?UTF-8?q?=20be=20handled=20by=20client?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Controllers/Expirer.cs | 5 +- .../Controllers/JsonRpcHistory.cs | 4 +- .../Controllers/Subscriber.cs | 49 ++++++++++++------- 3 files changed, 35 insertions(+), 23 deletions(-) diff --git a/WalletConnectSharp.Core/Controllers/Expirer.cs b/WalletConnectSharp.Core/Controllers/Expirer.cs index 26d8f60..efe9209 100644 --- a/WalletConnectSharp.Core/Controllers/Expirer.cs +++ b/WalletConnectSharp.Core/Controllers/Expirer.cs @@ -294,10 +294,7 @@ private async Task Restore() private Expiration GetExpiration(string target) { - if (!_expirations.ContainsKey(target)) - throw WalletConnectException.FromType(ErrorType.NO_MATCHING_KEY, $"{Name}: {target}"); - - return _expirations[target]; + return _expirations.GetValueOrDefault(target); } private void CheckExpiry(string target, Expiration expiration) diff --git a/WalletConnectSharp.Core/Controllers/JsonRpcHistory.cs b/WalletConnectSharp.Core/Controllers/JsonRpcHistory.cs index 1697e58..c024289 100644 --- a/WalletConnectSharp.Core/Controllers/JsonRpcHistory.cs +++ b/WalletConnectSharp.Core/Controllers/JsonRpcHistory.cs @@ -276,13 +276,13 @@ private JsonRpcRecord GetRecord(long id) { IsInitialized(); - if (!_records.ContainsKey(id)) + if (!_records.TryGetValue(id, out var record)) { throw WalletConnectException.FromType(ErrorType.NO_MATCHING_KEY, new Dictionary() { { "Tag", $"{Name}: {id}" } }); } - return _records[id]; + return record; } private async Task Persist() diff --git a/WalletConnectSharp.Core/Controllers/Subscriber.cs b/WalletConnectSharp.Core/Controllers/Subscriber.cs index 600a3e0..07b1e11 100644 --- a/WalletConnectSharp.Core/Controllers/Subscriber.cs +++ b/WalletConnectSharp.Core/Controllers/Subscriber.cs @@ -358,14 +358,14 @@ protected virtual void OnSubscribe(string id, PendingSubscription @params) { SetSubscription(id, new ActiveSubscription() { Id = id, Relay = @params.Relay, Topic = @params.Topic }); - pending.Remove(@params.Topic); + _ = pending.Remove(@params.Topic); } protected virtual void OnResubscribe(string id, PendingSubscription @params) { AddSubscription(id, new ActiveSubscription() { Id = id, Relay = @params.Relay, Topic = @params.Topic }); - pending.Remove(@params.Topic); + _ = pending.Remove(@params.Topic); } protected virtual async Task OnUnsubscribe(string topic, string id, Error reason) @@ -550,23 +550,24 @@ public Task IsSubscribed(string topic) protected virtual async Task RpcBatchSubscribe(string[] topics, ProtocolOptions relay) { - if (topics.Length == 0) return Array.Empty(); + if (topics.Length == 0) + { + return []; + } var api = RelayProtocols.GetRelayProtocol(relay.Protocol); - var request = new RequestArguments() + var request = new RequestArguments { - Method = api.BatchSubscribe, Params = new BatchSubscribeParams() { Topics = topics } + Method = api.BatchSubscribe, + Params = new BatchSubscribeParams + { + Topics = topics + } }; - try - { - return await this._relayer.Request(request) - .WithTimeout(TimeSpan.FromSeconds(45)); - } - catch (Exception e) - { - this._relayer.TriggerConnectionStalled(); - throw; - } + + return await _relayer + .Request(request) + .WithTimeout(TimeSpan.FromMinutes(1)); } protected virtual async Task BatchSubscribe(PendingSubscription[] subscriptions) @@ -574,9 +575,23 @@ protected virtual async Task BatchSubscribe(PendingSubscription[] subscriptions) if (subscriptions.Length == 0) return; var topics = subscriptions.Select(s => s.Topic).ToArray(); var relay = subscriptions[0].Relay; - var result = await this.RpcBatchSubscribe(topics, relay); + + string[] result; + try + { + result = await RpcBatchSubscribe(topics, relay); + } + catch (TimeoutException) + { + _relayer.TriggerConnectionStalled(); + return; + } + OnBatchSubscribe(result - .Select((r, i) => new ActiveSubscription() { Id = r, Relay = relay, Topic = topics[i] }) + .Select((r, i) => new ActiveSubscription + { + Id = r, Relay = relay, Topic = topics[i] + }) .ToArray()); } From 847c95ee73aa65c146d1b40490b0d787364e48c3 Mon Sep 17 00:00:00 2001 From: skibitsky Date: Tue, 16 Jul 2024 15:58:10 +0300 Subject: [PATCH 4/6] Refactor exception types --- .../Model/Errors/ErrorType.cs | 39 +- .../Model/Errors/ExpiredException.cs | 25 ++ .../Model/Errors/NamespacesException.cs | 25 ++ .../Model/Errors/SdkErrors.cs | 222 +++++------- .../Model/Errors/WalletConnectException.cs | 31 +- .../WalletConnectSharp.Crypto/Crypto.cs | 7 +- .../WalletConnectSharp.Crypto/KeyChain.cs | 43 +-- .../Models/Error.cs | 24 +- .../InMemoryStorage.cs | 2 +- .../Controllers/AuthEngine.cs | 7 +- .../Internals/AuthEngineValidations.cs | 2 +- .../Controllers/Expirer.cs | 8 +- .../Controllers/JsonRpcHistory.cs | 27 +- .../Controllers/MessageTracker.cs | 2 +- .../Controllers/Pairing.cs | 36 +- .../Controllers/Relayer.cs | 2 +- WalletConnectSharp.Core/Controllers/Store.cs | 12 +- .../Controllers/Subscriber.cs | 13 +- .../Controllers/TypedMessageHandler.cs | 32 +- WalletConnectSharp.Core/Utils.cs | 17 + WalletConnectSharp.Sign/Engine.cs | 2 +- .../Interfaces/IEnginePrivate.cs | 2 - .../Internals/EngineHandler.cs | 2 +- .../Internals/EngineValidation.cs | 333 ++++++++---------- .../Controllers/Web3WalletEngine.cs | 17 +- WalletConnectSharpV2.sln.DotSettings | 2 + 26 files changed, 410 insertions(+), 524 deletions(-) create mode 100644 Core Modules/WalletConnectSharp.Common/Model/Errors/ExpiredException.cs create mode 100644 Core Modules/WalletConnectSharp.Common/Model/Errors/NamespacesException.cs create mode 100644 WalletConnectSharpV2.sln.DotSettings diff --git a/Core Modules/WalletConnectSharp.Common/Model/Errors/ErrorType.cs b/Core Modules/WalletConnectSharp.Common/Model/Errors/ErrorType.cs index 994b30d..0ef2532 100644 --- a/Core Modules/WalletConnectSharp.Common/Model/Errors/ErrorType.cs +++ b/Core Modules/WalletConnectSharp.Common/Model/Errors/ErrorType.cs @@ -8,35 +8,6 @@ public enum ErrorType : uint { // 0 (Generic) GENERIC = 0, - - // 10 (Internal) - NON_CONFORMING_NAMESPACES = 9, - - // 1000 (Internal) - MISSING_OR_INVALID = 1000, - MISSING_RESPONSE = 1001, - MISSING_DECRYPT_PARAMS = 1002, - INVALID_UPDATE_REQUEST = 1003, - INVALID_UPGRADE_REQUEST = 1004, - INVALID_EXTEND_REQUEST = 1005, - INVALID_STORAGE_KEY_NAME = 1020, - RECORD_ALREADY_EXISTS = 1100, - RESTORE_WILL_OVERRIDE = 1200, - NO_MATCHING_ID = 1300, - NO_MATCHING_TOPIC = 1301, - NO_MATCHING_RESPONSE = 1302, - NO_MATCHING_KEY = 1303, - UNKNOWN_JSONRPC_METHOD = 1400, - MISMATCHED_TOPIC = 1500, - MISMATCHED_ACCOUNTS = 1501, - SETTLED = 1600, - NOT_APPROVED = 1601, - PROPOSAL_RESPONDED = 1602, - RESPONSE_ACKNOWLEDGED = 1603, - EXPIRED = 1604, - DELETED = 1605, - RESUBSCRIBED = 1606, - NOT_INITIALIZED = 1607, // 2000 (Timeout) SETTLE_TIMEOUT = 2000, @@ -58,7 +29,6 @@ public enum ErrorType : uint JSONRPC_REQUEST_METHOD_UNSUPPORTED = 4200, DISCONNECTED_ALL_CHAINS = 4900, DISCONNECTED_TARGET_CHAIN = 4901, - // 5000 (CAIP-25) DISAPPROVED_CHAINS = 5000, DISAPPROVED_JSONRPC = 5001, @@ -67,9 +37,14 @@ public enum ErrorType : uint UNSUPPORTED_JSONRPC = 5101, UNSUPPORTED_NOTIFICATION = 5102, UNSUPPORTED_ACCOUNTS = 5103, - USER_DISCONNECTED = 5900, + + // 6000 (Reason) + USER_DISCONNECTED = 6000, + + // 8000 (Session) + SESSION_REQUEST_EXPIRED = 8000, // 9000 (Unknown) UNKNOWN = 9000, } -} \ No newline at end of file +} diff --git a/Core Modules/WalletConnectSharp.Common/Model/Errors/ExpiredException.cs b/Core Modules/WalletConnectSharp.Common/Model/Errors/ExpiredException.cs new file mode 100644 index 0000000..7689f7b --- /dev/null +++ b/Core Modules/WalletConnectSharp.Common/Model/Errors/ExpiredException.cs @@ -0,0 +1,25 @@ +using System.Runtime.Serialization; + +namespace WalletConnectSharp.Common.Model.Errors; + +public class ExpiredException : Exception +{ + public ExpiredException() + { + } + + public ExpiredException(string message) + : base(message) + { + } + + public ExpiredException(string message, Exception innerException) + : base(message, innerException) + { + } + + protected ExpiredException(SerializationInfo info, StreamingContext context) + : base(info, context) + { + } +} diff --git a/Core Modules/WalletConnectSharp.Common/Model/Errors/NamespacesException.cs b/Core Modules/WalletConnectSharp.Common/Model/Errors/NamespacesException.cs new file mode 100644 index 0000000..d9e9942 --- /dev/null +++ b/Core Modules/WalletConnectSharp.Common/Model/Errors/NamespacesException.cs @@ -0,0 +1,25 @@ +using System.Runtime.Serialization; + +namespace WalletConnectSharp.Common.Model.Errors; + +public class NamespacesException : Exception +{ + public NamespacesException() + { + } + + public NamespacesException(string message) + : base(message) + { + } + + public NamespacesException(string message, Exception innerException) + : base(message, innerException) + { + } + + protected NamespacesException(SerializationInfo info, StreamingContext context) + : base(info, context) + { + } +} diff --git a/Core Modules/WalletConnectSharp.Common/Model/Errors/SdkErrors.cs b/Core Modules/WalletConnectSharp.Common/Model/Errors/SdkErrors.cs index db44bf6..e733b69 100644 --- a/Core Modules/WalletConnectSharp.Common/Model/Errors/SdkErrors.cs +++ b/Core Modules/WalletConnectSharp.Common/Model/Errors/SdkErrors.cs @@ -1,7 +1,4 @@ -using System.Collections.Generic; -using WalletConnectSharp.Common.Utils; - -namespace WalletConnectSharp.Common.Model.Errors +namespace WalletConnectSharp.Common.Model.Errors { /// /// A helper class for generating error messages @@ -9,47 +6,15 @@ namespace WalletConnectSharp.Common.Model.Errors /// public static class SdkErrors { - private static readonly Dictionary DefaultParameters = new Dictionary() - { - {"topic", "undefined"}, - {"message", "Something went wrong"}, - {"name", "parameter"}, - {"context", "session"}, - {"blockchain", "Ethereum"} - }; - - /// - /// Generate an error message using an ErrorType code and a dictionary - /// of parameters for the error message - /// An anonymous type can also be passed, which will be converted to a - /// dictionary - /// - /// The error type message to generate - /// A dictionary (or anonymous type) of parameters for the error message - /// The error message as a string - public static string MessageFromType(ErrorType type, Dictionary @params = null) - { - return MessageFromType(type, null, @params); - } - /// /// Generate an error message using an ErrorType code, a message parameters /// and a dictionary of parameters for the error message /// /// The error type message to generate - /// The message parameter - /// A dictionary of parameters for the error message + /// Additional context /// The error message as a string - public static string MessageFromType(ErrorType type, string message = null, Dictionary @params = null) + public static string MessageFromType(ErrorType type, string context = null) { - if (@params == null) - { - @params = new Dictionary(); - } - - if (!string.IsNullOrWhiteSpace(message)) - @params.TryAdd("message", message); - string errorMessage; switch (type) { @@ -57,81 +22,81 @@ public static string MessageFromType(ErrorType type, string message = null, Dict case ErrorType.GENERIC: errorMessage = "{message}"; break; - case ErrorType.MISSING_OR_INVALID: - errorMessage = "Missing or invalid {name}"; - break; - case ErrorType.MISSING_RESPONSE: - errorMessage = "Response is required for approved {context} proposals"; - break; - case ErrorType.MISSING_DECRYPT_PARAMS: - errorMessage = "Decrypt params required for {context}"; - break; - case ErrorType.INVALID_UPDATE_REQUEST: - errorMessage = "Invalid {context} update request"; - break; - case ErrorType.INVALID_UPGRADE_REQUEST: - errorMessage = "Invalid {context} upgrade request"; - break; - case ErrorType.INVALID_EXTEND_REQUEST: - errorMessage = "Invalid {context} extend request"; - break; - case ErrorType.INVALID_STORAGE_KEY_NAME: - errorMessage = "Invalid storage key name: {name}"; - break; - case ErrorType.RECORD_ALREADY_EXISTS: - errorMessage = "Record already exists for {context} matching id: {id}"; - break; - case ErrorType.RESTORE_WILL_OVERRIDE: - errorMessage = "Restore will override already set {context}"; - break; - case ErrorType.NO_MATCHING_ID: - errorMessage = "No matching {context} with id: {id}"; - break; - case ErrorType.NO_MATCHING_TOPIC: - errorMessage = "No matching {context} with topic {topic}"; - break; - case ErrorType.NO_MATCHING_RESPONSE: - errorMessage = "No response found in pending {context} proposal"; - break; - case ErrorType.NO_MATCHING_KEY: - errorMessage = "No matching key with tag: {tag}"; - break; - case ErrorType.UNKNOWN_JSONRPC_METHOD: - errorMessage = "Unknown JSON-RPC Method Requested: {method}"; - break; - case ErrorType.MISMATCHED_TOPIC: - errorMessage = "Mismatched topic for {context} with id: {id}"; - break; - case ErrorType.MISMATCHED_ACCOUNTS: - errorMessage = "Invalid accounts with mismatched chains: {mismatched}"; - break; - case ErrorType.SETTLED: - errorMessage = "{context} settled"; - break; - case ErrorType.NOT_APPROVED: - errorMessage = "{context} not approved"; - break; - case ErrorType.PROPOSAL_RESPONDED: - errorMessage = "{context} proposal responded"; - break; - case ErrorType.RESPONSE_ACKNOWLEDGED: - errorMessage = "{context} response acknowledge"; - break; - case ErrorType.EXPIRED: - errorMessage = "{context} expired"; - break; - case ErrorType.DELETED: - errorMessage = "{context} deleted"; - break; - case ErrorType.RESUBSCRIBED: - errorMessage = "Subscription resubscribed with topic: {topic}"; - break; - case ErrorType.NOT_INITIALIZED: - errorMessage = "{params} was not initialized"; - break; - case ErrorType.SETTLE_TIMEOUT: - errorMessage = "{context} failed to settle after {timeout} seconds"; - break; + // case ErrorType.MISSING_OR_INVALID: + // errorMessage = "Missing or invalid"; + // break; + // case ErrorType.MISSING_RESPONSE: + // errorMessage = "Response is required for approved {context} proposals"; + // break; + // case ErrorType.MISSING_DECRYPT_PARAMS: + // errorMessage = "Decrypt params required for {context}"; + // break; + // case ErrorType.INVALID_UPDATE_REQUEST: + // errorMessage = "Invalid {context} update request"; + // break; + // case ErrorType.INVALID_UPGRADE_REQUEST: + // errorMessage = "Invalid {context} upgrade request"; + // break; + // case ErrorType.INVALID_EXTEND_REQUEST: + // errorMessage = "Invalid {context} extend request"; + // break; + // case ErrorType.INVALID_STORAGE_KEY_NAME: + // errorMessage = "Invalid storage key name: {name}"; + // break; + // case ErrorType.RECORD_ALREADY_EXISTS: + // errorMessage = "Record already exists for {context} matching id: {id}"; + // break; + // case ErrorType.RESTORE_WILL_OVERRIDE: + // errorMessage = "Restore will override already set {context}"; + // break; + // case ErrorType.NO_MATCHING_ID: + // errorMessage = "No matching {context} with id: {id}"; + // break; + // case ErrorType.NO_MATCHING_TOPIC: + // errorMessage = "No matching {context} with topic {topic}"; + // break; + // case ErrorType.NO_MATCHING_RESPONSE: + // errorMessage = "No response found in pending {context} proposal"; + // break; + // case ErrorType.NO_MATCHING_KEY: + // errorMessage = "No matching key with tag: {tag}"; + // break; + // case ErrorType.UNKNOWN_JSONRPC_METHOD: + // errorMessage = "Unknown JSON-RPC Method Requested: {method}"; + // break; + // case ErrorType.MISMATCHED_TOPIC: + // errorMessage = "Mismatched topic for {context} with id: {id}"; + // break; + // case ErrorType.MISMATCHED_ACCOUNTS: + // errorMessage = "Invalid accounts with mismatched chains: {mismatched}"; + // break; + // case ErrorType.SETTLED: + // errorMessage = "{context} settled"; + // break; + // case ErrorType.NOT_APPROVED: + // errorMessage = "{context} not approved"; + // break; + // case ErrorType.PROPOSAL_RESPONDED: + // errorMessage = "{context} proposal responded"; + // break; + // case ErrorType.RESPONSE_ACKNOWLEDGED: + // errorMessage = "{context} response acknowledge"; + // break; + // case ErrorType.EXPIRED: + // errorMessage = "{context} expired"; + // break; + // case ErrorType.DELETED: + // errorMessage = "{context} deleted"; + // break; + // case ErrorType.RESUBSCRIBED: + // errorMessage = "Subscription resubscribed with topic: {topic}"; + // break; + // case ErrorType.NOT_INITIALIZED: + // errorMessage = "{params} was not initialized"; + // break; + // case ErrorType.SETTLE_TIMEOUT: + // errorMessage = "{context} failed to settle after {timeout} seconds"; + // break; case ErrorType.JSONRPC_REQUEST_TIMEOUT: errorMessage = "JSON-RPC Request timeout after {timeout} seconds: {method}"; break; @@ -196,39 +161,24 @@ public static string MessageFromType(ErrorType type, string message = null, Dict errorMessage = "{message}"; break; case ErrorType.USER_DISCONNECTED: - errorMessage = "User disconnected {context}"; + errorMessage = "User disconnected."; break; case ErrorType.UNKNOWN: errorMessage = "Unknown error {params}"; break; - case ErrorType.NON_CONFORMING_NAMESPACES: - errorMessage = @params["message"].ToString(); - break; + // case ErrorType.NON_CONFORMING_NAMESPACES: + // errorMessage = @params["message"].ToString(); + // break; } - errorMessage = FormatErrorText(errorMessage, DefaultParameters, @params); - - return errorMessage; - } - - private static string FormatErrorText(string formattedText, Dictionary defaultArgs, Dictionary args = null) - { - if (args == null) - args = new Dictionary(); - - string text = formattedText; - - foreach (var key in args.Keys) + if (context == null) { - text = text.Replace("{" + key.ToLower() + "}", args[key].ToString()); + return errorMessage; } - - foreach (var key in defaultArgs.Keys) + else { - text = text.Replace("{" + key.ToLower() + "}", defaultArgs[key].ToString()); + return $"{errorMessage} {context}"; } - - return text; } } } diff --git a/Core Modules/WalletConnectSharp.Common/Model/Errors/WalletConnectException.cs b/Core Modules/WalletConnectSharp.Common/Model/Errors/WalletConnectException.cs index 32b063a..4c9559c 100644 --- a/Core Modules/WalletConnectSharp.Common/Model/Errors/WalletConnectException.cs +++ b/Core Modules/WalletConnectSharp.Common/Model/Errors/WalletConnectException.cs @@ -1,7 +1,4 @@ -using System; -using System.Collections.Generic; using Newtonsoft.Json; -using WalletConnectSharp.Common.Utils; namespace WalletConnectSharp.Common.Model.Errors { @@ -18,7 +15,7 @@ public class WalletConnectException : Exception public uint Code { get; private set; } /// - /// The error tyep of this exception + /// The error type of this exception /// [JsonProperty("type")] public string Type { get; private set; } @@ -64,29 +61,17 @@ public WalletConnectException(string message, Exception innerException, ErrorTyp /// exception /// /// The error type of the exception - /// The message parameter - /// Additional (optional) parameters for the generated error message + /// An (optional) message for the error + /// An (optional) context for the error message /// An (optional) inner exception that caused this exception /// A new exception - public static WalletConnectException FromType(ErrorType type, string message = null, Dictionary @params = null, Exception innerException = null) + public static WalletConnectException FromType(ErrorType type, string message = null, string context = null, Exception innerException = null) { - string errorMessage = SdkErrors.MessageFromType(type, message, @params); + var errorMessage = message ?? SdkErrors.MessageFromType(type, context); - if (innerException != null) - return new WalletConnectException(errorMessage, innerException, type); - return new WalletConnectException(errorMessage, type); - } - - /// - /// A helper function that creates an exception given an ErrorType, and - /// an (optional) dictionary of parameters for the error message - /// - /// The error type of the exception - /// Additional (optional) parameters for the generated error message - /// A new exception - public static WalletConnectException FromType(ErrorType type, Dictionary @params = null) - { - return FromType(type, null, @params); + return innerException != null + ? new WalletConnectException(errorMessage, innerException, type) + : new WalletConnectException(errorMessage, type); } } } diff --git a/Core Modules/WalletConnectSharp.Crypto/Crypto.cs b/Core Modules/WalletConnectSharp.Crypto/Crypto.cs index fc2d730..ec39cd9 100644 --- a/Core Modules/WalletConnectSharp.Crypto/Crypto.cs +++ b/Core Modules/WalletConnectSharp.Crypto/Crypto.cs @@ -570,8 +570,7 @@ private void IsInitialized() { if (!this._initialized) { - throw WalletConnectException.FromType(ErrorType.NOT_INITIALIZED, - new Dictionary() { { "Name", Name } }); + throw new InvalidOperationException($"{nameof(Crypto)} module not initialized."); } } @@ -659,12 +658,12 @@ private string DeserializeAndDecrypt(string symKey, string encoded) private async Task GetClientSeed() { - var seed = ""; + string seed; try { seed = await KeyChain.Get(CryptoClientSeed); } - catch (Exception e) + catch (InvalidOperationException) { byte[] seedRaw = new byte[32]; RandomNumberGenerator.Fill(seedRaw); diff --git a/Core Modules/WalletConnectSharp.Crypto/KeyChain.cs b/Core Modules/WalletConnectSharp.Crypto/KeyChain.cs index d58720e..ba41e08 100644 --- a/Core Modules/WalletConnectSharp.Crypto/KeyChain.cs +++ b/Core Modules/WalletConnectSharp.Crypto/KeyChain.cs @@ -126,23 +126,15 @@ public async Task Set(string tag, string key) } /// - /// Get a saved key with the given tag. If no tag exists, then a WalletConnectException will - /// be thrown. + /// Get a saved key with the given tag. /// /// The tag of the key to retrieve /// The key with the given tag - /// Thrown if the given tag does not match any key + /// Thrown if the given tag does not match any key public async Task Get(string tag) { - this.IsInitialized(); - - if (!await Has(tag)) - { - throw WalletConnectException.FromType(ErrorType.NO_MATCHING_KEY, new Dictionary() - { - {"tag", tag} - }); - } + IsInitialized(); + await DoesTagExist(tag); return this._keyChain[tag]; } @@ -152,32 +144,29 @@ public async Task Get(string tag) /// be thrown. /// /// The tag of the key to delete - /// Thrown if the given tag does not match any key + /// Thrown if the given tag does not match any key public async Task Delete(string tag) { - this.IsInitialized(); - - if (!await Has(tag)) - { - throw WalletConnectException.FromType(ErrorType.NO_MATCHING_KEY, new Dictionary() - { - {"tag", tag} - }); - } + IsInitialized(); + await DoesTagExist(tag); _keyChain.Remove(tag); - await this.SaveKeyChain(); } + private async Task DoesTagExist(string tag) + { + if (!await Has(tag)) + { + throw new InvalidOperationException($"Keychain does not contain key with tag: {tag}."); + } + } + private void IsInitialized() { if (!this._initialized) { - throw WalletConnectException.FromType(ErrorType.NOT_INITIALIZED, new Dictionary() - { - {"Name", Name} - }); + throw new InvalidOperationException($"{nameof(Keychain)} module not initialized."); } } diff --git a/Core Modules/WalletConnectSharp.Network/Models/Error.cs b/Core Modules/WalletConnectSharp.Network/Models/Error.cs index aa68838..ed3a05b 100644 --- a/Core Modules/WalletConnectSharp.Network/Models/Error.cs +++ b/Core Modules/WalletConnectSharp.Network/Models/Error.cs @@ -1,7 +1,4 @@ -using System; -using System.Diagnostics.CodeAnalysis; using Newtonsoft.Json; -using WalletConnectSharp.Common; using WalletConnectSharp.Common.Model.Errors; namespace WalletConnectSharp.Network.Models @@ -28,30 +25,19 @@ public class Error /// [JsonProperty("data")] public string Data; - - /// - /// Create an ErrorResponse with a given ErrorType and (optional) parameters - /// - /// The error type of the ErrorResponse to create - /// The message to attach to the error - /// A new ErrorResponse - public static Error FromErrorType(ErrorType type, string message) - { - return FromErrorType(type, new Dictionary() { { "message", message } }); - } /// /// Create an ErrorResponse with a given ErrorType and (optional) parameters /// /// The error type of the ErrorResponse to create - /// Extra parameters for the error message + /// Extra context /// Extra data that is stored in the Data field of the newly created ErrorResponse /// A new ErrorResponse - public static Error FromErrorType(ErrorType type, Dictionary @params = null, string extraData = null) + public static Error FromErrorType(ErrorType type, string context = null, string extraData = null) { - string message = SdkErrors.MessageFromType(type, @params); + var message = SdkErrors.MessageFromType(type, context); - return new Error() + return new Error { Code = (long) type, Message = message, @@ -66,7 +52,7 @@ public static Error FromErrorType(ErrorType type, Dictionary @pa /// A new ErrorResponse object using values from the given exception public static Error FromException(WalletConnectException walletConnectException) { - return new Error() + return new Error { Code = walletConnectException.Code, Message = walletConnectException.Message, diff --git a/Core Modules/WalletConnectSharp.Storage/InMemoryStorage.cs b/Core Modules/WalletConnectSharp.Storage/InMemoryStorage.cs index 46b0f2b..4c5d513 100644 --- a/Core Modules/WalletConnectSharp.Storage/InMemoryStorage.cs +++ b/Core Modules/WalletConnectSharp.Storage/InMemoryStorage.cs @@ -78,7 +78,7 @@ protected void IsInitialized() { if (!Initialized) { - throw WalletConnectException.FromType(ErrorType.NOT_INITIALIZED, "Storage"); + throw new InvalidOperationException($"{nameof(Storage)} module not initialized."); } } diff --git a/WalletConnectSharp.Auth/Controllers/AuthEngine.cs b/WalletConnectSharp.Auth/Controllers/AuthEngine.cs index 1a0adb1..7698180 100644 --- a/WalletConnectSharp.Auth/Controllers/AuthEngine.cs +++ b/WalletConnectSharp.Auth/Controllers/AuthEngine.cs @@ -281,10 +281,7 @@ private async Task OnAuthResponse(string topic, JsonRpcResponse response) { this.Client.OnAuthResponse(new AuthErrorResponse() { - Id = id, Topic = topic, Error = Error.FromErrorType(ErrorType.GENERIC, new Dictionary() - { - {"Message", "Invalid signature"} - }) + Id = id, Topic = topic, Error = Error.FromErrorType(ErrorType.GENERIC, "Invalid signature") }); } else @@ -390,7 +387,7 @@ private void IsInitialized() { if (!this.initialized) { - throw WalletConnectException.FromType(ErrorType.NOT_INITIALIZED, Name); + throw new InvalidOperationException($"{nameof(AuthEngine)} module not initialized."); } } diff --git a/WalletConnectSharp.Auth/Internals/AuthEngineValidations.cs b/WalletConnectSharp.Auth/Internals/AuthEngineValidations.cs index 9210c71..f03aba4 100644 --- a/WalletConnectSharp.Auth/Internals/AuthEngineValidations.cs +++ b/WalletConnectSharp.Auth/Internals/AuthEngineValidations.cs @@ -24,7 +24,7 @@ internal bool IsValidRequest(RequestParams @params) var expiry = @params.Expiry; if (expiry != null && !Utils.IsValidRequestExpiry(expiry.Value, MinExpiry, MaxExpiry)) { - throw WalletConnectException.FromType(ErrorType.MISSING_OR_INVALID, $"request() expiry: {expiry}. Expiry must be a number (in seconds) between {MinExpiry} and {MaxExpiry}"); + throw new ArgumentException($"Request expiry: {expiry}. Expiry must be a number (in seconds) between {MinExpiry} and {MaxExpiry}"); } return validAudience && domainInAud && hasNonce && hasValidType; diff --git a/WalletConnectSharp.Core/Controllers/Expirer.cs b/WalletConnectSharp.Core/Controllers/Expirer.cs index efe9209..36eabff 100644 --- a/WalletConnectSharp.Core/Controllers/Expirer.cs +++ b/WalletConnectSharp.Core/Controllers/Expirer.cs @@ -1,4 +1,3 @@ -using WalletConnectSharp.Common.Model.Errors; using WalletConnectSharp.Common.Utils; using WalletConnectSharp.Core.Interfaces; using WalletConnectSharp.Core.Models.Expirer; @@ -19,7 +18,7 @@ public class Expirer : IExpirer protected bool Disposed; private Dictionary _expirations = new Dictionary(); - private bool initialized = false; + private bool initialized; private Expiration[] _cached = Array.Empty(); private ICore _core; @@ -286,7 +285,7 @@ private async Task Restore() if (persisted.Length == 0) return; if (_expirations.Count > 0) { - throw WalletConnectException.FromType(ErrorType.RESTORE_WILL_OVERRIDE, Name); + throw new InvalidOperationException($"Restoring will override existing data in {Name}."); } _cached = persisted; @@ -323,7 +322,6 @@ private void CheckExpirations(object sender, EventArgs args) private void RegisterEventListeners() { _core.HeartBeat.OnPulse += CheckExpirations; - //_core.HeartBeat.On(HeartbeatEvents.Pulse, CheckExpirations); this.Created += Persist; this.Expired += Persist; @@ -334,7 +332,7 @@ private void IsInitialized() { if (!initialized) { - throw WalletConnectException.FromType(ErrorType.NOT_INITIALIZED, Name); + throw new InvalidOperationException($"{nameof(Expirer)} module not initialized."); } } diff --git a/WalletConnectSharp.Core/Controllers/JsonRpcHistory.cs b/WalletConnectSharp.Core/Controllers/JsonRpcHistory.cs index c024289..624c993 100644 --- a/WalletConnectSharp.Core/Controllers/JsonRpcHistory.cs +++ b/WalletConnectSharp.Core/Controllers/JsonRpcHistory.cs @@ -192,7 +192,7 @@ public Task> Get(string topic, long id) throw WalletConnectException.FromType(ErrorType.MISMATCHED_TOPIC, $"{Name}: {id}"); }*/ - return Task.FromResult>(record); + return Task.FromResult(record); } /// @@ -243,19 +243,21 @@ public void Delete(string topic, long? id = null) /// True if the request with the given topic and id exists, false otherwise public Task Exists(string topic, long id) { + IsInitialized(); + + if (_records.ContainsKey(id)) + { + return Task.FromResult(false); + } + try { - IsInitialized(); - if (_records.ContainsKey(id)) return Task.FromResult(false); var record = GetRecord(id); - return Task.FromResult(record.Topic == topic); } - catch (WalletConnectException e) + catch (KeyNotFoundException) { - if (e.CodeType == ErrorType.NO_MATCHING_KEY) - return Task.FromResult(false); - throw; + return Task.FromResult(false); } } @@ -269,7 +271,7 @@ private async Task[]> GetJsonRpcRecords() if (await _core.Storage.HasItem(StorageKey)) return await _core.Storage.GetItem[]>(StorageKey); - return Array.Empty>(); + return []; } private JsonRpcRecord GetRecord(long id) @@ -278,8 +280,7 @@ private JsonRpcRecord GetRecord(long id) if (!_records.TryGetValue(id, out var record)) { - throw WalletConnectException.FromType(ErrorType.NO_MATCHING_KEY, - new Dictionary() { { "Tag", $"{Name}: {id}" } }); + throw new KeyNotFoundException($"No matching {Name} with id: {id}."); } return record; @@ -300,7 +301,7 @@ private async Task Restore() return; if (_records.Count > 0) { - throw WalletConnectException.FromType(ErrorType.RESTORE_WILL_OVERRIDE, Name); + throw new InvalidOperationException($"Restoring will override existing data in {Name}."); } _cached = persisted; @@ -322,7 +323,7 @@ private void IsInitialized() { if (!_initialized) { - throw WalletConnectException.FromType(ErrorType.NOT_INITIALIZED, Name); + throw new InvalidOperationException($"{nameof(JsonRpcHistory)} module not initialized."); } } diff --git a/WalletConnectSharp.Core/Controllers/MessageTracker.cs b/WalletConnectSharp.Core/Controllers/MessageTracker.cs index 242ce48..3127f44 100644 --- a/WalletConnectSharp.Core/Controllers/MessageTracker.cs +++ b/WalletConnectSharp.Core/Controllers/MessageTracker.cs @@ -202,7 +202,7 @@ private void IsInitialized() { if (!initialized) { - throw WalletConnectException.FromType(ErrorType.NOT_INITIALIZED, this.Name); + throw new InvalidOperationException($"{nameof(MessageTracker)} module not initialized."); } } diff --git a/WalletConnectSharp.Core/Controllers/Pairing.cs b/WalletConnectSharp.Core/Controllers/Pairing.cs index f5017cf..7f48667 100644 --- a/WalletConnectSharp.Core/Controllers/Pairing.cs +++ b/WalletConnectSharp.Core/Controllers/Pairing.cs @@ -126,7 +126,8 @@ private async Task RegisterTypedMessages() public async Task Pair(string uri, bool activatePairing = true) { IsInitialized(); - await IsValidPair(uri); + ValidateUri(uri); + var uriParams = ParseUri(uri); var topic = uriParams.Topic; @@ -175,7 +176,7 @@ public UriParameters ParseUri(string uri) var pathStart = uri.IndexOf(":", StringComparison.Ordinal); int? pathEnd = uri.IndexOf("?", StringComparison.Ordinal) != -1 ? uri.IndexOf("?", StringComparison.Ordinal) - : (int?)null; + : null; var protocol = uri.Substring(0, pathStart); string path; @@ -183,8 +184,9 @@ public UriParameters ParseUri(string uri) else path = uri.Substring(pathStart + 1); var requiredValues = path.Split("@"); - string queryString = pathEnd != null ? uri.Substring((int)pathEnd) : ""; - var queryParams = Regex.Matches(queryString, "([^?=&]+)(=([^&]*))?").Cast() + var queryString = pathEnd != null ? uri[(int)pathEnd..] : ""; + var queryParams = Regex + .Matches(queryString, "([^?=&]+)(=([^&]*))?") .ToDictionary(x => x.Groups[1].Value, x => x.Groups[3].Value); var result = new UriParameters() @@ -195,8 +197,7 @@ public UriParameters ParseUri(string uri) SymKey = queryParams["symKey"], Relay = new ProtocolOptions() { - Protocol = queryParams["relay-protocol"], - Data = queryParams.ContainsKey("relay-data") ? queryParams["relay-data"] : null, + Protocol = queryParams["relay-protocol"], Data = queryParams.GetValueOrDefault("relay-data") } }; @@ -351,7 +352,7 @@ await Task.WhenAll( private Task Cleanup() { List pairingTopics = (from pair in this.Store.Values.Where(e => e.Expiry != null) - where Clock.IsExpired(pair.Expiry.Value) + where pair.Expiry != null && Clock.IsExpired(pair.Expiry.Value) select pair.Topic).ToList(); return Task.WhenAll( @@ -362,22 +363,22 @@ where Clock.IsExpired(pair.Expiry.Value) private async Task IsValidPairingTopic(string topic) { if (string.IsNullOrWhiteSpace(topic)) - throw WalletConnectException.FromType(ErrorType.MISSING_OR_INVALID, - $"pairing topic should be a string {topic}"); + { + throw new ArgumentNullException(nameof(topic)); + } if (!this.Store.Keys.Contains(topic)) - throw WalletConnectException.FromType(ErrorType.NO_MATCHING_KEY, - $"pairing topic doesn't exist {topic}"); + throw new KeyNotFoundException($"Pairing topic {topic} not found."); var expiry = this.Store.Get(topic).Expiry; if (expiry != null && Clock.IsExpired(expiry.Value)) { await DeletePairing(topic); - throw WalletConnectException.FromType(ErrorType.EXPIRED, $"pairing topic: {topic}"); + throw new ExpiredException($"Pairing topic {topic} has expired."); } } - private bool IsValidUrl(string url) + private static bool IsValidUrl(string url) { if (string.IsNullOrWhiteSpace(url)) return false; @@ -392,18 +393,17 @@ private bool IsValidUrl(string url) } } - private Task IsValidPair(string uri) + private void ValidateUri(string uri) { if (!IsValidUrl(uri)) - throw WalletConnectException.FromType(ErrorType.MISSING_OR_INVALID, $"pair() uri: {uri}"); - return Task.CompletedTask; + throw new FormatException($"Invalid URI format: {uri}"); } private void IsInitialized() { if (!_initialized) { - throw WalletConnectException.FromType(ErrorType.NOT_INITIALIZED, this.Name); + throw new InvalidOperationException($"{nameof(Pairing)} module not initialized."); } } @@ -457,7 +457,7 @@ private async Task IsValidDisconnect(string topic, Error reason) { if (string.IsNullOrWhiteSpace(topic)) { - throw WalletConnectException.FromType(ErrorType.MISSING_OR_INVALID, $"disconnect() params: {topic}"); + throw new ArgumentNullException(nameof(topic)); } await IsValidPairingTopic(topic); diff --git a/WalletConnectSharp.Core/Controllers/Relayer.cs b/WalletConnectSharp.Core/Controllers/Relayer.cs index ee1f1d4..696cf34 100644 --- a/WalletConnectSharp.Core/Controllers/Relayer.cs +++ b/WalletConnectSharp.Core/Controllers/Relayer.cs @@ -489,7 +489,7 @@ protected virtual void IsInitialized() { if (!initialized) { - throw WalletConnectException.FromType(ErrorType.NOT_INITIALIZED, Name); + throw new InvalidOperationException($"{nameof(Relayer)} module not initialized."); } } diff --git a/WalletConnectSharp.Core/Controllers/Store.cs b/WalletConnectSharp.Core/Controllers/Store.cs index 0e04122..9ff63b4 100644 --- a/WalletConnectSharp.Core/Controllers/Store.cs +++ b/WalletConnectSharp.Core/Controllers/Store.cs @@ -134,7 +134,7 @@ public async Task Init() map.Add(value.Key, value); } - cached = Array.Empty(); + cached = []; initialized = true; } } @@ -275,12 +275,12 @@ protected virtual async Task GetDataStore() protected virtual TValue GetData(TKey key) { - if (!map.ContainsKey(key)) + if (!map.TryGetValue(key, out var data)) { - throw WalletConnectException.FromType(ErrorType.NO_MATCHING_KEY, $"{Name}: {key}"); + throw new KeyNotFoundException($"Key {key} not found in {Name}."); } - return map[key]; + return data; } protected virtual Task Persist() @@ -295,7 +295,7 @@ protected virtual async Task Restore() if (persisted.Length == 0) return; if (map.Count > 0) { - throw WalletConnectException.FromType(ErrorType.RESTORE_WILL_OVERRIDE, Name); + throw new InvalidOperationException($"Restoring will override existing data in {Name}."); } cached = persisted; @@ -305,7 +305,7 @@ protected virtual void IsInitialized() { if (!initialized) { - throw WalletConnectException.FromType(ErrorType.NOT_INITIALIZED, Name); + throw new InvalidOperationException($"{nameof(Store)} module not initialized."); } } diff --git a/WalletConnectSharp.Core/Controllers/Subscriber.cs b/WalletConnectSharp.Core/Controllers/Subscriber.cs index 07b1e11..0ee973e 100644 --- a/WalletConnectSharp.Core/Controllers/Subscriber.cs +++ b/WalletConnectSharp.Core/Controllers/Subscriber.cs @@ -1,5 +1,4 @@ using WalletConnectSharp.Common.Logging; -using WalletConnectSharp.Common.Model.Errors; using WalletConnectSharp.Common.Model.Relay; using WalletConnectSharp.Common.Utils; using WalletConnectSharp.Core.Interfaces; @@ -249,7 +248,7 @@ protected virtual async Task Restore() if (Subscriptions.Count > 0) { - throw WalletConnectException.FromType(ErrorType.RESTORE_WILL_OVERRIDE, Name); + throw new InvalidOperationException($"Restoring will override existing data in {Name}."); } _cached = persisted; @@ -441,10 +440,12 @@ protected virtual async Task UnsubscribeById(string topic, string id, Unsubscrib protected virtual ActiveSubscription GetSubscription(string id) { - if (!_subscriptions.ContainsKey(id)) - throw WalletConnectException.FromType(ErrorType.NO_MATCHING_KEY, Name + ": " + id); + if (!_subscriptions.TryGetValue(id, out var subscription)) + { + throw new KeyNotFoundException($"No subscription found with id: {id}."); + } - return _subscriptions[id]; + return subscription; } protected virtual bool HasSubscription(string id, string topic) @@ -467,7 +468,7 @@ protected virtual void IsInitialized() { if (!_initialized) { - throw WalletConnectException.FromType(ErrorType.NOT_INITIALIZED, Name); + throw new InvalidOperationException($"{nameof(Subscriber)} module not initialized."); } } diff --git a/WalletConnectSharp.Core/Controllers/TypedMessageHandler.cs b/WalletConnectSharp.Core/Controllers/TypedMessageHandler.cs index af3ed25..cf8ef1c 100644 --- a/WalletConnectSharp.Core/Controllers/TypedMessageHandler.cs +++ b/WalletConnectSharp.Core/Controllers/TypedMessageHandler.cs @@ -6,6 +6,7 @@ using WalletConnectSharp.Common.Utils; using WalletConnectSharp.Core.Interfaces; using WalletConnectSharp.Core.Models; +using WalletConnectSharp.Core.Models.History; using WalletConnectSharp.Core.Models.Relay; using WalletConnectSharp.Crypto.Models; using WalletConnectSharp.Network.Models; @@ -123,6 +124,7 @@ async void RequestCallback(object sender, MessageEvent e) } catch (JsonException) { + return; } } @@ -153,7 +155,7 @@ async void ResponseCallback(object sender, MessageEvent e) await responseCallback(topic, payload); } - catch (Exception ex) when (ex is JsonReaderException or JsonSerializationException) + catch (Exception ex) when (ex is JsonException) { if (!expectingResult) return; @@ -168,25 +170,25 @@ async void InspectResponseRaw(object sender, DecodedMessageEvent e) var payload = e.Payload; + JsonRpcRecord record; try { - var record = await rpcHistory.Get(topic, payload.Id); - - // ignored if we can't find anything in the history - if (record == null) return; - var resMethod = record.Request.Method; - - // Trigger the true response event, which will trigger ResponseCallback - messageEventHandlerMap[$"response_{resMethod}"](this, - new MessageEvent() { Topic = topic, Message = message }); + record = await rpcHistory.Get(topic, payload.Id); } - catch (WalletConnectException err) + catch (KeyNotFoundException) { - if (err.CodeType != ErrorType.NO_MATCHING_KEY) - throw; - - // ignored if we can't find anything in the history + // Ignored if we can't find anything in the history + return; } + + var resMethod = record.Request.Method; + + // Trigger the true response event, which will trigger ResponseCallback + messageEventHandlerMap[$"response_{resMethod}"](this, + new MessageEvent + { + Topic = topic, Message = message + }); } messageEventHandlerMap[$"request_{method}"] += RequestCallback; diff --git a/WalletConnectSharp.Core/Utils.cs b/WalletConnectSharp.Core/Utils.cs index f8804b8..946647e 100644 --- a/WalletConnectSharp.Core/Utils.cs +++ b/WalletConnectSharp.Core/Utils.cs @@ -27,6 +27,23 @@ public static bool IsValidChainId(string chainId) return SessionIdRegex.IsMatch(chainId); } + public static bool IsValidAccountId(string account) + { + if (string.IsNullOrWhiteSpace(account) || !account.Contains(':')) + { + return false; + } + + var split = account.Split(":"); + if (split.Length != 3) + { + return false; + } + + var chainId = split[0] + ":" + split[1]; + return !string.IsNullOrWhiteSpace(split[2]) && IsValidChainId(chainId); + } + public static bool IsValidRequestExpiry(long expiry, long min, long max) { return expiry <= max && expiry >= min; diff --git a/WalletConnectSharp.Sign/Engine.cs b/WalletConnectSharp.Sign/Engine.cs index d02559a..1520356 100644 --- a/WalletConnectSharp.Sign/Engine.cs +++ b/WalletConnectSharp.Sign/Engine.cs @@ -452,7 +452,7 @@ public async Task Connect(ConnectOptions options) if (string.IsNullOrWhiteSpace(topic)) { - throw WalletConnectException.FromType(ErrorType.NO_MATCHING_KEY, $"connect() pairing topic: {topic}"); + throw new InvalidOperationException("The pairing topic is empty"); } var id = await MessageHandler.SendRequest(topic, proposal); diff --git a/WalletConnectSharp.Sign/Interfaces/IEnginePrivate.cs b/WalletConnectSharp.Sign/Interfaces/IEnginePrivate.cs index 1f85e8d..f75023f 100644 --- a/WalletConnectSharp.Sign/Interfaces/IEnginePrivate.cs +++ b/WalletConnectSharp.Sign/Interfaces/IEnginePrivate.cs @@ -45,8 +45,6 @@ public interface IEnginePrivate internal Task IsValidConnect(ConnectOptions options); - internal Task IsValidPair(string uri); - internal Task IsValidSessionSettleRequest(SessionSettle settle); internal Task IsValidApprove(ApproveParams @params); diff --git a/WalletConnectSharp.Sign/Internals/EngineHandler.cs b/WalletConnectSharp.Sign/Internals/EngineHandler.cs index a437ac1..ef3475e 100644 --- a/WalletConnectSharp.Sign/Internals/EngineHandler.cs +++ b/WalletConnectSharp.Sign/Internals/EngineHandler.cs @@ -21,7 +21,7 @@ async void ExpiredCallback(object sender, ExpirerEventArgs e) if (target.Id != null && this.Client.PendingRequests.Keys.Contains((long)target.Id)) { await PrivateThis.DeletePendingSessionRequest((long)target.Id, - Error.FromErrorType(ErrorType.EXPIRED), true); + Error.FromErrorType(ErrorType.SESSION_REQUEST_EXPIRED), true); return; } diff --git a/WalletConnectSharp.Sign/Internals/EngineValidation.cs b/WalletConnectSharp.Sign/Internals/EngineValidation.cs index dcc9bbe..198d49a 100644 --- a/WalletConnectSharp.Sign/Internals/EngineValidation.cs +++ b/WalletConnectSharp.Sign/Internals/EngineValidation.cs @@ -2,6 +2,7 @@ using WalletConnectSharp.Common.Model.Errors; using WalletConnectSharp.Common.Utils; using WalletConnectSharp.Core; +using WalletConnectSharp.Core.Models.Relay; using WalletConnectSharp.Network.Models; using WalletConnectSharp.Sign.Interfaces; using WalletConnectSharp.Sign.Models; @@ -17,19 +18,16 @@ private void IsInitialized() { if (!_initialized) { - throw WalletConnectException.FromType(ErrorType.NOT_INITIALIZED, Name); + throw new InvalidOperationException($"{nameof(Engine)} module not initialized."); } } async Task IEnginePrivate.IsValidConnect(ConnectOptions options) { if (options == null) - throw WalletConnectException.FromType(ErrorType.MISSING_OR_INVALID, $"Connect() params: {JsonConvert.SerializeObject(options)}"); + throw new ArgumentNullException(nameof(options)); var pairingTopic = options.PairingTopic; - var requiredNamespaces = options.RequiredNamespaces; - var relays = options.Relays; - if (pairingTopic != null) await IsValidPairingTopic(pairingTopic); } @@ -37,34 +35,30 @@ async Task IEnginePrivate.IsValidConnect(ConnectOptions options) async Task IsValidPairingTopic(string topic) { if (string.IsNullOrWhiteSpace(topic)) - throw WalletConnectException.FromType(ErrorType.MISSING_OR_INVALID, - $"pairing topic should be a string {topic}"); + throw new ArgumentNullException(nameof(topic), "Pairing topic should be a valid string."); if (!this.Client.Core.Pairing.Store.Keys.Contains(topic)) - throw WalletConnectException.FromType(ErrorType.NO_MATCHING_KEY, - $"pairing topic doesn't exist {topic}"); + throw new KeyNotFoundException($"Paring topic {topic} doesn't exist in the pairing store."); var expiry = this.Client.Core.Pairing.Store.Get(topic).Expiry; if (expiry != null && Clock.IsExpired(expiry.Value)) { - throw WalletConnectException.FromType(ErrorType.EXPIRED, $"pairing topic: {topic}"); + throw new ExpiredException($"Pairing topic {topic} has expired."); } } Task IsValidSessionTopic(string topic) { if (string.IsNullOrWhiteSpace(topic)) - throw WalletConnectException.FromType(ErrorType.MISSING_OR_INVALID, - $"session topic should be a string {topic}"); + throw new ArgumentNullException(nameof(topic), "Session topic should be a valid string."); if (!this.Client.Session.Keys.Contains(topic)) - throw WalletConnectException.FromType(ErrorType.NO_MATCHING_KEY, - $"session topic doesn't exist {topic}"); + throw new KeyNotFoundException($"Session topic {topic} doesn't exist in the session store."); var expiry = this.Client.Session.Get(topic).Expiry; if (expiry != null && Clock.IsExpired(expiry.Value)) { - throw WalletConnectException.FromType(ErrorType.EXPIRED, $"session topic: {topic}"); + throw new ExpiredException($"Session topic {topic} has expired."); } return Task.CompletedTask; @@ -73,75 +67,91 @@ Task IsValidSessionTopic(string topic) async Task IsValidProposalId(long id) { if (!this.Client.Proposal.Keys.Contains(id)) - throw WalletConnectException.FromType(ErrorType.NO_MATCHING_KEY, - $"proposal id doesn't exist {id}"); + throw new KeyNotFoundException($"Proposal id {id} doesn't exist in the proposal store."); var expiry = this.Client.Proposal.Get(id).Expiry; if (expiry != null && Clock.IsExpired(expiry.Value)) { await PrivateThis.DeleteProposal(id); - throw WalletConnectException.FromType(ErrorType.EXPIRED, $"proposal id: {id}"); + throw new ExpiredException($"Proposal with id {id} has expired."); } } - async Task IsValidSessionOrPairingTopic(string topic) + private async Task ValidateSessionOrPairingTopic(string topic) { - if (this.Client.Session.Keys.Contains(topic)) await this.IsValidSessionTopic(topic); - else if (this.Client.Core.Pairing.Store.Keys.Contains(topic)) await this.IsValidPairingTopic(topic); - else if (string.IsNullOrWhiteSpace(topic)) - throw WalletConnectException.FromType(ErrorType.MISSING_OR_INVALID, - $"session or pairing topic should be a string {topic}"); - else + if (string.IsNullOrWhiteSpace(topic)) { - throw WalletConnectException.FromType(ErrorType.NO_MATCHING_KEY, - $"session or pairing topic doesn't exist {topic}"); + throw new ArgumentNullException(nameof(topic), "Session or pairing topic should be a valid string."); } - } - Task IEnginePrivate.IsValidPair(string uri) - { - if (!Utils.IsValidUrl(uri)) - throw WalletConnectException.FromType(ErrorType.MISSING_OR_INVALID, $"pair() uri: {uri}"); - return Task.CompletedTask; + if (Client.Session.Keys.Contains(topic)) + { + await IsValidSessionTopic(topic); + } + else if (Client.Core.Pairing.Store.Keys.Contains(topic)) + { + await IsValidPairingTopic(topic); + } + else + { + throw new KeyNotFoundException($"Session or pairing topic doesn't exist. Topic value: {topic}."); + } } Task IEnginePrivate.IsValidSessionSettleRequest(SessionSettle settle) { if (settle == null) { - throw WalletConnectException.FromType(ErrorType.MISSING_OR_INVALID, $"onSessionSettleRequest() params: {settle}"); + throw new ArgumentNullException(nameof(settle)); } var relay = settle.Relay; var controller = settle.Controller; var namespaces = settle.Namespaces; var expiry = settle.Expiry; - if (relay != null && string.IsNullOrWhiteSpace(relay.Protocol)) + + ValidateSessionSettleRelay(relay); + ValidateSessionSettleController(controller); + ValidateSessionSettleNamespaces(namespaces); + ValidateSessionSettleExpiry(expiry); + + return Task.CompletedTask; + + void ValidateSessionSettleRelay(ProtocolOptions relayToValidate) { - throw WalletConnectException.FromType(ErrorType.MISSING_OR_INVALID, - $"OnSessionSettleRequest() relay protocol should be a string"); + if (relayToValidate != null && string.IsNullOrWhiteSpace(relayToValidate.Protocol)) + { + throw new ArgumentException("Relay protocol should be a non-empty string."); + } } - if (string.IsNullOrWhiteSpace(controller.PublicKey)) + void ValidateSessionSettleController(Participant controllerToValidate) { - throw WalletConnectException.FromType(ErrorType.MISSING_OR_INVALID, - "OnSessionSettleRequest controller public key should be a string"); + if (string.IsNullOrWhiteSpace(controllerToValidate?.PublicKey)) + { + throw new ArgumentException("Controller public key should be a non-empty string."); + } } - var validNamespacesError = IsValidNamespaces(namespaces, "OnSessionSettleRequest()"); - if (validNamespacesError != null) - throw validNamespacesError.ToException(); + void ValidateSessionSettleNamespaces(Namespaces namespacesToValidate) + { + ValidateNamespaces(namespacesToValidate, "OnSessionSettleRequest()"); + } - if (Clock.IsExpired(expiry)) - throw WalletConnectException.FromType(ErrorType.EXPIRED, "OnSessionSettleRequest()"); - return Task.CompletedTask; + void ValidateSessionSettleExpiry(long expiryToValidate) + { + if (Clock.IsExpired(expiryToValidate)) + { + throw new InvalidOperationException("SessionSettleRequest has expired."); + } + } } async Task IEnginePrivate.IsValidApprove(ApproveParams @params) { if (@params == null) { - throw WalletConnectException.FromType(ErrorType.MISSING_OR_INVALID, $"approve() params: {@params}"); + throw new ArgumentNullException(nameof(@params)); } var id = @params.Id; @@ -152,24 +162,19 @@ async Task IEnginePrivate.IsValidApprove(ApproveParams @params) await IsValidProposalId(id); var proposal = this.Client.Proposal.Get(id); - var validNamespacesError = IsValidNamespaces(namespaces, "approve()"); - if (validNamespacesError != null) - throw validNamespacesError.ToException(); - - var conformingNamespacesError = IsConformingNamespaces(proposal.RequiredNamespaces, namespaces, "update()"); - if (conformingNamespacesError != null) - throw conformingNamespacesError.ToException(); + ValidateNamespaces(namespaces, "approve()"); + ValidateConformingNamespaces(proposal.RequiredNamespaces, namespaces, "update()"); if (relayProtocol != null && string.IsNullOrWhiteSpace(relayProtocol)) { - throw WalletConnectException.FromType(ErrorType.MISSING_OR_INVALID, $"approve() relayProtocol: {relayProtocol}"); + throw new ArgumentException("RelayProtocol should be a non-empty string."); } - if (@params.SessionProperties != null && @params.SessionProperties.Values.Any(string.IsNullOrWhiteSpace)) + if (@params.SessionProperties != null && properties.Values.Any(string.IsNullOrWhiteSpace)) { - throw WalletConnectException.FromType(ErrorType.MISSING_OR_INVALID, - $"sessionProperties must be in Dictionary format with no null or " + - $"empty/whitespace values. Received: {JsonConvert.SerializeObject(@params.SessionProperties)}"); + throw new ArgumentException($"SessionProperties must be in Dictionary format with no null or empty/whitespace values. " + + $"Received: {JsonConvert.SerializeObject(@params.SessionProperties)}" + ); } } @@ -177,7 +182,7 @@ async Task IEnginePrivate.IsValidReject(RejectParams @params) { if (@params == null) { - throw WalletConnectException.FromType(ErrorType.MISSING_OR_INVALID, $"reject() params: {@params}"); + throw new ArgumentNullException(nameof(@params)); } var id = @params.Id; @@ -187,8 +192,7 @@ async Task IEnginePrivate.IsValidReject(RejectParams @params) if (reason == null || string.IsNullOrWhiteSpace(reason.Message)) { - throw WalletConnectException.FromType(ErrorType.MISSING_OR_INVALID, - $"reject() reason: ${JsonConvert.SerializeObject(reason)}"); + throw new ArgumentException("Reject reason should be a non-empty string."); } } @@ -198,14 +202,8 @@ async Task IEnginePrivate.IsValidUpdate(string topic, Namespaces namespaces) var session = this.Client.Session.Get(topic); - var validNamespaceError = IsValidNamespaces(namespaces, "update()"); - if (validNamespaceError != null) - throw validNamespaceError.ToException(); - - var conformingNamespacesError = IsConformingNamespaces(session.RequiredNamespaces, namespaces, "update()"); - - if (conformingNamespacesError != null) - throw conformingNamespacesError.ToException(); + ValidateNamespaces(namespaces, "update()"); + ValidateConformingNamespaces(session.RequiredNamespaces, namespaces, "update()"); } async Task IEnginePrivate.IsValidExtend(string topic) @@ -217,39 +215,40 @@ async Task IEnginePrivate.IsValidRequest(string topic, JsonRpcRequest requ { await IsValidSessionTopic(topic); - var session = this.Client.Session.Get(topic); - var namespaces = session.Namespaces; - if (!IsValidNamespacesChainId(namespaces, chainId)) - { - throw WalletConnectException.FromType(ErrorType.MISSING_OR_INVALID, $"request() chainId: {chainId}"); - } - if (request == null || string.IsNullOrWhiteSpace(request.Method)) { - throw WalletConnectException.FromType(ErrorType.MISSING_OR_INVALID, - $"request() ${JsonConvert.SerializeObject(request)}"); + throw new ArgumentException("Request or request method is null.", nameof(request)); } + + var session = this.Client.Session.Get(topic); + var namespaces = session.Namespaces; + ValidateNamespacesChainId(namespaces, chainId); var validMethods = GetNamespacesMethodsForChainId(namespaces, chainId); if (!validMethods.Contains(request.Method)) - throw WalletConnectException.FromType(ErrorType.MISSING_OR_INVALID, - $"request() method: {request.Method}"); + { + throw new NamespacesException($"Method {request.Method} not found in namespaces for chainId {chainId}."); + } } async Task IEnginePrivate.IsValidRespond(string topic, JsonRpcResponse response) { await IsValidSessionTopic(topic); - if (response == null || (response.Result == null && response.Error == null)) + if (response == null) { - throw WalletConnectException.FromType(ErrorType.MISSING_OR_INVALID, - $"respond() response: ${JsonConvert.SerializeObject(response)}"); + throw new ArgumentNullException(nameof(response)); + } + + if (Equals(response.Result, default(T)) && response.Error == null) + { + throw new ArgumentException("Response result and error cannot both be null."); } } async Task IEnginePrivate.IsValidPing(string topic) { - await IsValidSessionOrPairingTopic(topic); + await ValidateSessionOrPairingTopic(topic); } private List GetNamespacesEventsForChainId(Namespaces namespaces, string chainId) @@ -267,101 +266,55 @@ private List GetNamespacesEventsForChainId(Namespaces namespaces, string async Task IEnginePrivate.IsValidEmit(string topic, EventData eventData, string chainId) { await IsValidSessionTopic(topic); - var session = this.Client.Session.Get(topic); - var namespaces = session.Namespaces; - if (!IsValidNamespacesChainId(namespaces, chainId)) + if (eventData == null) { - throw WalletConnectException.FromType(ErrorType.MISSING_OR_INVALID, $"emit() chainId: {chainId}"); + throw new ArgumentNullException(nameof(eventData)); } - if (eventData == null || string.IsNullOrWhiteSpace(eventData.Name)) + if (string.IsNullOrWhiteSpace(eventData.Name)) { - throw WalletConnectException.FromType(ErrorType.MISSING_OR_INVALID, $"emit() event: {JsonConvert.SerializeObject(eventData)}"); + throw new ArgumentException("Event name should be a non-empty string."); } + + var session = this.Client.Session.Get(topic); + var namespaces = session.Namespaces; + + ValidateNamespacesChainId(namespaces, chainId); if (!GetNamespacesEventsForChainId(namespaces, chainId).Contains(eventData.Name)) { - throw WalletConnectException.FromType(ErrorType.MISSING_OR_INVALID, - $"emit() event: {JsonConvert.SerializeObject(eventData)}"); + throw new NamespacesException($"Event {eventData.Name} not found in namespaces for chainId {chainId}."); } } async Task IEnginePrivate.IsValidDisconnect(string topic, Error reason) { - if (string.IsNullOrWhiteSpace(topic)) - { - throw WalletConnectException.FromType(ErrorType.MISSING_OR_INVALID, $"disconnect() params: {topic}"); - } - - await IsValidSessionOrPairingTopic(topic); + await ValidateSessionOrPairingTopic(topic); } - private bool IsValidAccountId(string account) + private static void ValidateAccounts(string[] accounts, string context) { - if (!string.IsNullOrWhiteSpace(account) && account.Contains(":")) - { - var split = account.Split(":"); - if (split.Length == 3) - { - var chainId = split[0] + ":" + split[1]; - return !string.IsNullOrWhiteSpace(split[2]) && IsValidChainId(chainId); - } - } - return false; - } - - private Error IsValidAccounts(string[] accounts, string context) - { - Error error = null; foreach (var account in accounts) { - if (error != null) - break; - - if (!IsValidAccountId(account)) + if (!Utils.IsValidAccountId(account)) { - error = Error.FromErrorType(ErrorType.UNSUPPORTED_ACCOUNTS, $"{context}, account {account} should be a string and conform to 'namespace:chainId:address' format"); + throw new FormatException($"{context}, account {account} should be a string and conform to 'namespace:chainId:address' format."); } } - - return error; } - private Error IsValidNamespaceAccounts(Namespaces namespaces, string method) + private static void ValidateNamespaces(Namespaces namespaces, string method) { - Error error = null; - foreach (var ns in namespaces.Values) + if (namespaces == null) { - if (error != null) break; - - var validAccountsError = IsValidAccounts(ns.Accounts, $"{method} namespace"); - if (validAccountsError != null) - { - error = validAccountsError; - } + throw new ArgumentNullException(nameof(namespaces)); } - return error; - } - - private Error IsValidNamespaces(Namespaces namespaces, string method) - { - Error error = null; - if (namespaces != null) - { - var validAccountsError = IsValidNamespaceAccounts(namespaces, method); - if (validAccountsError != null) - { - error = validAccountsError; - } - } - else + foreach (var ns in namespaces.Values) { - error = Error.FromErrorType(ErrorType.MISSING_OR_INVALID, $"{method}, namespaces should be an object with data"); + ValidateAccounts(ns.Accounts, $"{method} namespace"); } - - return error; } private List GetNamespacesMethodsForChainId(Namespaces namespaces, string chainId) @@ -376,20 +329,9 @@ private List GetNamespacesMethodsForChainId(Namespaces namespaces, strin return methods; } - private bool IsValidChainId(string chainId) - { - if (chainId.Contains(":")) - { - var split = chainId.Split(":"); - return split.Length == 2; - } - - return false; - } - private List GetNamespacesChains(Namespaces namespaces) { - List chains = new List(); + List chains = []; foreach (var ns in namespaces.Values) { chains.AddRange(GetAccountsChains(ns.Accounts)); @@ -398,50 +340,53 @@ private List GetNamespacesChains(Namespaces namespaces) return chains; } - private bool IsValidNamespacesChainId(Namespaces namespaces, string chainId) + private void ValidateNamespacesChainId(Namespaces namespaces, string chainId) { - if (!IsValidChainId(chainId)) return false; - var chains = GetNamespacesChains(namespaces); - if (!chains.Contains(chainId)) return false; + if (!Utils.IsValidChainId(chainId)) + { + throw new FormatException($"ChainId {chainId} should be a string and conform to 'chainId:chainId' format."); + } - return true; + var chains = GetNamespacesChains(namespaces); + if (!chains.Contains(chainId)) + { + throw new NamespacesException($"ChainId {chainId} is invalid or not found in namespaces."); + } } - private Error IsConformingNamespaces(RequiredNamespaces requiredNamespaces, Namespaces namespaces, + private void ValidateConformingNamespaces( + RequiredNamespaces requiredNamespaces, + Namespaces namespaces, string context) { - Error error = null; var requiredNamespaceKeys = requiredNamespaces.Keys.ToArray(); var namespaceKeys = namespaces.Keys.ToArray(); - + if (!HasOverlap(requiredNamespaceKeys, namespaceKeys)) - error = Error.FromErrorType(ErrorType.NON_CONFORMING_NAMESPACES, $"{context} namespaces keys don't satisfy requiredNamespaces"); - else { - foreach (var key in requiredNamespaceKeys) + throw new NamespacesException($"Namespaces keys don't satisfy requiredNamespaces, {context}."); + } + + foreach (var key in requiredNamespaceKeys) + { + var requiredNamespaceChains = requiredNamespaces[key].Chains; + var namespaceChains = GetAccountsChains(namespaces[key].Accounts); + + if (!HasOverlap(requiredNamespaceChains, namespaceChains)) { - if (error != null) - break; - - var requiredNamespaceChains = requiredNamespaces[key].Chains; - var namespaceChains = GetAccountsChains(namespaces[key].Accounts); - - if (!HasOverlap(requiredNamespaceChains, namespaceChains)) - { - error = Error.FromErrorType(ErrorType.NON_CONFORMING_NAMESPACES, $"{context} namespaces accounts don't satisfy requiredNamespaces chains for {key}"); - } - else if (!HasOverlap(requiredNamespaces[key].Methods, namespaces[key].Methods)) - { - error = Error.FromErrorType(ErrorType.NON_CONFORMING_NAMESPACES, $"{context} namespaces methods don't satisfy requiredNamespaces methods for {key}"); - } - else if (!HasOverlap(requiredNamespaces[key].Events, namespaces[key].Events)) - { - error = Error.FromErrorType(ErrorType.NON_CONFORMING_NAMESPACES, $"{context} namespaces events don't satisfy requiredNamespaces events for {key}"); - } + throw new NamespacesException($"Namespaces chains don't satisfy requiredNamespaces chains for {key}, {context}."); } - } - return error; + if (!HasOverlap(requiredNamespaces[key].Methods, namespaces[key].Methods)) + { + throw new NamespacesException($"Namespaces methods don't satisfy requiredNamespaces methods for {key}, {context}."); + } + + if (!HasOverlap(requiredNamespaces[key].Events, namespaces[key].Events)) + { + throw new NamespacesException($"Namespaces events don't satisfy requiredNamespaces events for {key}, {context}."); + } + } } private bool HasOverlap(string[] a, string[] b) @@ -452,7 +397,7 @@ private bool HasOverlap(string[] a, string[] b) private string[] GetAccountsChains(string[] accounts) { - List chains = new List(); + List chains = []; foreach (var account in accounts) { var values = account.Split(":"); diff --git a/WalletConnectSharp.Web3Wallet/Controllers/Web3WalletEngine.cs b/WalletConnectSharp.Web3Wallet/Controllers/Web3WalletEngine.cs index 737b6c5..9975bac 100644 --- a/WalletConnectSharp.Web3Wallet/Controllers/Web3WalletEngine.cs +++ b/WalletConnectSharp.Web3Wallet/Controllers/Web3WalletEngine.cs @@ -200,7 +200,7 @@ private void IsInitialized() { if (!_initialized) { - throw WalletConnectException.FromType(ErrorType.NOT_INITIALIZED, "Web3WalletEngine"); + throw new InvalidOperationException($"{nameof(Web3WalletEngine)} module not initialized."); } } @@ -210,25 +210,16 @@ private void IsInitialized() void OnAuthRequest(object sender, AuthRequest request) { - if (AuthRequested != null) - { - AuthRequested(sender, request); - } + AuthRequested?.Invoke(sender, request); } void OnAuthResponse(object sender, AuthErrorResponse errorResponse) { - if (AuthError != null) - { - AuthError(sender, errorResponse); - } + AuthError?.Invoke(sender, errorResponse); } void OnAuthResponse(object sender, AuthResponse response) { - if (AuthResponded != null) - { - AuthResponded(sender, response); - } + AuthResponded?.Invoke(sender, response); } } diff --git a/WalletConnectSharpV2.sln.DotSettings b/WalletConnectSharpV2.sln.DotSettings new file mode 100644 index 0000000..2da1370 --- /dev/null +++ b/WalletConnectSharpV2.sln.DotSettings @@ -0,0 +1,2 @@ + + True \ No newline at end of file From 192c6b1e1452ab42872eee0ae7aebeef1d4e7848 Mon Sep 17 00:00:00 2001 From: skibitsky Date: Tue, 16 Jul 2024 16:26:13 +0300 Subject: [PATCH 5/6] Simplify query params processing --- .../Model/Errors/ErrorType.cs | 1 + .../Model/Errors/SdkErrors.cs | 84 +------------------ .../Utils/UrlUtils.cs | 43 ++++------ .../Controllers/Pairing.cs | 4 +- WalletConnectSharp.Sign/Engine.cs | 9 +- 5 files changed, 26 insertions(+), 115 deletions(-) diff --git a/Core Modules/WalletConnectSharp.Common/Model/Errors/ErrorType.cs b/Core Modules/WalletConnectSharp.Common/Model/Errors/ErrorType.cs index 0ef2532..8330688 100644 --- a/Core Modules/WalletConnectSharp.Common/Model/Errors/ErrorType.cs +++ b/Core Modules/WalletConnectSharp.Common/Model/Errors/ErrorType.cs @@ -29,6 +29,7 @@ public enum ErrorType : uint JSONRPC_REQUEST_METHOD_UNSUPPORTED = 4200, DISCONNECTED_ALL_CHAINS = 4900, DISCONNECTED_TARGET_CHAIN = 4901, + // 5000 (CAIP-25) DISAPPROVED_CHAINS = 5000, DISAPPROVED_JSONRPC = 5001, diff --git a/Core Modules/WalletConnectSharp.Common/Model/Errors/SdkErrors.cs b/Core Modules/WalletConnectSharp.Common/Model/Errors/SdkErrors.cs index e733b69..fd1810e 100644 --- a/Core Modules/WalletConnectSharp.Common/Model/Errors/SdkErrors.cs +++ b/Core Modules/WalletConnectSharp.Common/Model/Errors/SdkErrors.cs @@ -22,92 +22,17 @@ public static string MessageFromType(ErrorType type, string context = null) case ErrorType.GENERIC: errorMessage = "{message}"; break; - // case ErrorType.MISSING_OR_INVALID: - // errorMessage = "Missing or invalid"; - // break; - // case ErrorType.MISSING_RESPONSE: - // errorMessage = "Response is required for approved {context} proposals"; - // break; - // case ErrorType.MISSING_DECRYPT_PARAMS: - // errorMessage = "Decrypt params required for {context}"; - // break; - // case ErrorType.INVALID_UPDATE_REQUEST: - // errorMessage = "Invalid {context} update request"; - // break; - // case ErrorType.INVALID_UPGRADE_REQUEST: - // errorMessage = "Invalid {context} upgrade request"; - // break; - // case ErrorType.INVALID_EXTEND_REQUEST: - // errorMessage = "Invalid {context} extend request"; - // break; - // case ErrorType.INVALID_STORAGE_KEY_NAME: - // errorMessage = "Invalid storage key name: {name}"; - // break; - // case ErrorType.RECORD_ALREADY_EXISTS: - // errorMessage = "Record already exists for {context} matching id: {id}"; - // break; - // case ErrorType.RESTORE_WILL_OVERRIDE: - // errorMessage = "Restore will override already set {context}"; - // break; - // case ErrorType.NO_MATCHING_ID: - // errorMessage = "No matching {context} with id: {id}"; - // break; - // case ErrorType.NO_MATCHING_TOPIC: - // errorMessage = "No matching {context} with topic {topic}"; - // break; - // case ErrorType.NO_MATCHING_RESPONSE: - // errorMessage = "No response found in pending {context} proposal"; - // break; - // case ErrorType.NO_MATCHING_KEY: - // errorMessage = "No matching key with tag: {tag}"; - // break; - // case ErrorType.UNKNOWN_JSONRPC_METHOD: - // errorMessage = "Unknown JSON-RPC Method Requested: {method}"; - // break; - // case ErrorType.MISMATCHED_TOPIC: - // errorMessage = "Mismatched topic for {context} with id: {id}"; - // break; - // case ErrorType.MISMATCHED_ACCOUNTS: - // errorMessage = "Invalid accounts with mismatched chains: {mismatched}"; - // break; - // case ErrorType.SETTLED: - // errorMessage = "{context} settled"; - // break; - // case ErrorType.NOT_APPROVED: - // errorMessage = "{context} not approved"; - // break; - // case ErrorType.PROPOSAL_RESPONDED: - // errorMessage = "{context} proposal responded"; - // break; - // case ErrorType.RESPONSE_ACKNOWLEDGED: - // errorMessage = "{context} response acknowledge"; - // break; - // case ErrorType.EXPIRED: - // errorMessage = "{context} expired"; - // break; - // case ErrorType.DELETED: - // errorMessage = "{context} deleted"; - // break; - // case ErrorType.RESUBSCRIBED: - // errorMessage = "Subscription resubscribed with topic: {topic}"; - // break; - // case ErrorType.NOT_INITIALIZED: - // errorMessage = "{params} was not initialized"; - // break; - // case ErrorType.SETTLE_TIMEOUT: - // errorMessage = "{context} failed to settle after {timeout} seconds"; - // break; case ErrorType.JSONRPC_REQUEST_TIMEOUT: errorMessage = "JSON-RPC Request timeout after {timeout} seconds: {method}"; break; case ErrorType.UNAUTHORIZED_TARGET_CHAIN: - errorMessage = "Unauthorized Target ChainId Requested: {chainId}"; + errorMessage = "Unauthorized Target ChainId Requested/"; break; case ErrorType.UNAUTHORIZED_JSON_RPC_METHOD: - errorMessage = "Unauthorized JSON-RPC Method Requested: {method}"; + errorMessage = "Unauthorized JSON-RPC Method Requested."; break; case ErrorType.UNAUTHORIZED_NOTIFICATION_TYPE: - errorMessage = "Unauthorized Notification Type Requested: {type}"; + errorMessage = "Unauthorized Notification Type Requested."; break; case ErrorType.UNAUTHORIZED_UPDATE_REQUEST: errorMessage = "Unauthorized {context} update request"; @@ -166,9 +91,6 @@ public static string MessageFromType(ErrorType type, string context = null) case ErrorType.UNKNOWN: errorMessage = "Unknown error {params}"; break; - // case ErrorType.NON_CONFORMING_NAMESPACES: - // errorMessage = @params["message"].ToString(); - // break; } if (context == null) diff --git a/Core Modules/WalletConnectSharp.Common/Utils/UrlUtils.cs b/Core Modules/WalletConnectSharp.Common/Utils/UrlUtils.cs index 3d3604e..71c17ff 100644 --- a/Core Modules/WalletConnectSharp.Common/Utils/UrlUtils.cs +++ b/Core Modules/WalletConnectSharp.Common/Utils/UrlUtils.cs @@ -1,5 +1,3 @@ -using System.Collections.Specialized; -using System.Linq; using System.Net; using System.Text; using System.Text.RegularExpressions; @@ -12,43 +10,36 @@ namespace WalletConnectSharp.Common.Utils public static class UrlUtils { /// - /// Parse query strings encoded parameters and return a NameValueCollection + /// Parse query strings encoded parameters and return a dictionary /// /// The query string to parse - /// The NameValueCollection containing all parameters in the query parameter - public static NameValueCollection ParseQs(string queryString) + public static Dictionary ParseQs(string queryString) { - return Regex.Matches(queryString, "([^?=&]+)(=([^&]*))?") - .ToDictionary(x => x.Groups[1].Value, x => x.Groups[3].Value) - .Aggregate(new NameValueCollection(), (seed, current) => - { - seed.Add(current.Key, current.Value); - return seed; - }); + return Regex + .Matches(queryString, "([^?=&]+)(=([^&]*))?", RegexOptions.None, TimeSpan.FromMilliseconds(100)) + .ToDictionary(x => x.Groups[1].Value, x => x.Groups[3].Value); } /// - /// Convert a NameValueCollection to a query string + /// Convert a dictionary to a query string /// - /// The NameValueCollection to convert to a query string - /// A query string containing all parameters from the NameValueCollection - public static string StringifyQs(NameValueCollection @params) + /// A dictionary to convert to a query string + /// A query string containing all parameters from the dictionary + public static string StringifyQs(Dictionary @params) { - StringBuilder sb = new StringBuilder(); - foreach (var key in @params.AllKeys) + var sb = new StringBuilder(); + foreach (var kvp in @params) { - var values = @params.GetValues(key); - if (values == null) - continue; - - foreach (var value in values) + if (kvp.Value == null) { - sb.Append(sb.Length == 0 ? "?" : "&"); - sb.AppendFormat("{0}={1}", WebUtility.UrlEncode(key), WebUtility.UrlEncode(value)); + continue; } + + sb.Append(sb.Length == 0 ? "?" : "&"); + sb.Append($"{WebUtility.UrlEncode(kvp.Key)}={WebUtility.UrlEncode(kvp.Value)}"); } return sb.ToString(); } } -} \ No newline at end of file +} diff --git a/WalletConnectSharp.Core/Controllers/Pairing.cs b/WalletConnectSharp.Core/Controllers/Pairing.cs index 7f48667..1499310 100644 --- a/WalletConnectSharp.Core/Controllers/Pairing.cs +++ b/WalletConnectSharp.Core/Controllers/Pairing.cs @@ -185,9 +185,7 @@ public UriParameters ParseUri(string uri) var requiredValues = path.Split("@"); var queryString = pathEnd != null ? uri[(int)pathEnd..] : ""; - var queryParams = Regex - .Matches(queryString, "([^?=&]+)(=([^&]*))?") - .ToDictionary(x => x.Groups[1].Value, x => x.Groups[3].Value); + var queryParams = UrlUtils.ParseQs(queryString); var result = new UriParameters() { diff --git a/WalletConnectSharp.Sign/Engine.cs b/WalletConnectSharp.Sign/Engine.cs index 1520356..56dd1ca 100644 --- a/WalletConnectSharp.Sign/Engine.cs +++ b/WalletConnectSharp.Sign/Engine.cs @@ -358,9 +358,9 @@ public Task Disconnect(Error reason = null) /// parsed from the given uri public UriParameters ParseUri(string uri) { - var pathStart = uri.IndexOf(":", StringComparison.Ordinal); - int? pathEnd = uri.IndexOf("?", StringComparison.Ordinal) != -1 - ? uri.IndexOf("?", StringComparison.Ordinal) + var pathStart = uri.IndexOf(':'); + var pathEnd = uri.IndexOf('?') != -1 + ? uri.IndexOf('?') : (int?)null; var protocol = uri.Substring(0, pathStart); @@ -370,8 +370,7 @@ public UriParameters ParseUri(string uri) var requiredValues = path.Split("@"); string queryString = pathEnd != null ? uri.Substring((int)pathEnd) : ""; - var queryParams = Regex.Matches(queryString, "([^?=&]+)(=([^&]*))?").Cast() - .ToDictionary(x => x.Groups[1].Value, x => x.Groups[3].Value); + var queryParams = UrlUtils.ParseQs(queryString); var result = new UriParameters() { From 911fbbef6e582ea147cc434d6ed9a41b36e6fc40 Mon Sep 17 00:00:00 2001 From: skibitsky Date: Tue, 16 Jul 2024 17:29:33 +0300 Subject: [PATCH 6/6] Tests --- .../WalletConnectSharp.Sign.Test/SignTests.cs | 73 ++++++++++++++++++- .../Internals/EngineValidation.cs | 37 ++++++---- 2 files changed, 95 insertions(+), 15 deletions(-) diff --git a/Tests/WalletConnectSharp.Sign.Test/SignTests.cs b/Tests/WalletConnectSharp.Sign.Test/SignTests.cs index 55d136a..f627043 100644 --- a/Tests/WalletConnectSharp.Sign.Test/SignTests.cs +++ b/Tests/WalletConnectSharp.Sign.Test/SignTests.cs @@ -159,7 +159,6 @@ public static async Task TestConnectMethod(ISignClient clientA, I Assert.NotNull(proposal.OptionalNamespaces); Assert.True(proposal.SessionProperties == null || proposal.SessionProperties.Count > 0); Assert.NotNull(proposal.Expiry); - Assert.NotNull(proposal.Id); Assert.NotNull(proposal.Relays); Assert.NotNull(proposal.Proposer); Assert.NotNull(proposal.PairingTopic); @@ -169,6 +168,9 @@ public static async Task TestConnectMethod(ISignClient clientA, I var sessionData = await connectData.Approval; await approveData.Acknowledged(); + Assert.True(clientA.Find(dappConnectOptions.RequiredNamespaces).Length != 0); + Assert.True(clientA.Find(new RequiredNamespaces()).Length == 0); + return sessionData; } @@ -324,6 +326,75 @@ public async Task TestSessionRequestResponse() Assert.Equal(eventResult, testData.a * testData.b); Assert.Equal(eventResult, responseReturned.result); } + + [Fact] [Trait("Category", "integration")] + public async Task TestSessionRequestInvalidMethod() + { + await _cryptoFixture.WaitForClientsReady(); + + var validMethod = "test_method"; + + var dappConnectOptions = new ConnectOptions() + { + RequiredNamespaces = new RequiredNamespaces() + { + { + "eip155", new ProposedNamespace() + { + Methods = new[] + { + validMethod + }, + Chains = new[] + { + "eip155:1", + "eip155:10" + }, + Events = new[] + { + "chainChanged", + "accountsChanged" + } + } + } + } + }; + + var dappClient = ClientA; + var connectData = await dappClient.Connect(dappConnectOptions); + + var walletClient = ClientB; + var proposal = await walletClient.Pair(connectData.Uri); + + var approveData = await walletClient.Approve(proposal, "0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045"); + + var sessionData = await connectData.Approval; + await approveData.Acknowledged(); + + var testData = new TestRequest2 + { + x = "test", y = 4 + }; + + // Use TestRequest2 which isn't included in the required namespaces + await Assert.ThrowsAsync(() => dappClient.Engine.Request(sessionData.Topic, testData)); + } + + [Fact] [Trait("Category", "integration")] + public async Task TestInvalidConnect() + { + await _cryptoFixture.WaitForClientsReady(); + var dappClient = ClientA; + + await Assert.ThrowsAsync(() => dappClient.Connect(null)); + + var connectOptions = new ConnectOptions + { + PairingTopic = "123" + }; + + await Assert.ThrowsAsync(() => dappClient.Connect(connectOptions)); + } [Fact, Trait("Category", "integration")] public async Task TestTwoUniqueSessionRequestResponse() diff --git a/WalletConnectSharp.Sign/Internals/EngineValidation.cs b/WalletConnectSharp.Sign/Internals/EngineValidation.cs index 198d49a..800757d 100644 --- a/WalletConnectSharp.Sign/Internals/EngineValidation.cs +++ b/WalletConnectSharp.Sign/Internals/EngineValidation.cs @@ -395,7 +395,7 @@ private bool HasOverlap(string[] a, string[] b) return matches.Count() == a.Length; } - private string[] GetAccountsChains(string[] accounts) + private static string[] GetAccountsChains(string[] accounts) { List chains = []; foreach (var account in accounts) @@ -419,21 +419,30 @@ private bool IsSessionCompatible(SessionStruct session, RequiredNamespaces requi if (!HasOverlap(paramsKeys, sessionKeys)) return false; - foreach (var key in sessionKeys) + try { - var value = session.Namespaces[key]; - var accounts = value.Accounts; - var methods = value.Methods; - var events = value.Events; - var chains = GetAccountsChains(accounts); - var requiredNamespace = requiredNamespaces[key]; - - if (!HasOverlap(requiredNamespace.Chains, chains) || - !HasOverlap(requiredNamespace.Methods, methods) || - !HasOverlap(requiredNamespace.Events, events)) - compatible = false; + foreach (var key in sessionKeys) + { + var value = session.Namespaces[key]; + var accounts = value.Accounts; + var methods = value.Methods; + var events = value.Events; + var chains = GetAccountsChains(accounts); + var requiredNamespace = requiredNamespaces[key]; + + if (!HasOverlap(requiredNamespace.Chains, chains) || + !HasOverlap(requiredNamespace.Methods, methods) || + !HasOverlap(requiredNamespace.Events, events)) + { + compatible = false; + } + } } - + catch (KeyNotFoundException e) + { + return false; + } + return compatible; } }