|
8 | 8 | // Redistribution and use in source and binary forms with or without
|
9 | 9 | // modifications are permitted.
|
10 | 10 |
|
| 11 | +using System; |
| 12 | +using System.Collections.Generic; |
| 13 | +using System.IO; |
| 14 | +using System.Linq; |
| 15 | +using System.Numerics; |
11 | 16 | using Neo.Cryptography.ECC;
|
12 | 17 | using Neo.IO;
|
13 | 18 | using Neo.Network.P2P.Payloads;
|
14 | 19 | using Neo.SmartContract.Native;
|
15 | 20 | using Neo.VM;
|
16 | 21 | using Neo.VM.Types;
|
17 |
| -using System; |
18 |
| -using System.Collections.Generic; |
19 |
| -using System.IO; |
20 |
| -using System.Linq; |
21 |
| -using System.Numerics; |
22 | 22 | using Array = Neo.VM.Types.Array;
|
23 | 23 |
|
24 | 24 | namespace Neo.SmartContract
|
@@ -332,6 +332,35 @@ protected internal void RuntimeLog(byte[] state)
|
332 | 332 | /// <param name="eventName">The name of the event.</param>
|
333 | 333 | /// <param name="state">The arguments of the event.</param>
|
334 | 334 | protected internal void RuntimeNotify(byte[] eventName, Array state)
|
| 335 | + { |
| 336 | + if (!IsHardforkEnabled(Hardfork.HF_Basilisk)) |
| 337 | + { |
| 338 | + RuntimeNotifyV1(eventName, state); |
| 339 | + return; |
| 340 | + } |
| 341 | + if (eventName.Length > MaxEventName) throw new ArgumentException(null, nameof(eventName)); |
| 342 | + string name = Utility.StrictUTF8.GetString(eventName); |
| 343 | + ContractState contract = CurrentContext.GetState<ExecutionContextState>().Contract; |
| 344 | + if (contract is null) |
| 345 | + throw new InvalidOperationException("Notifications are not allowed in dynamic scripts."); |
| 346 | + var @event = contract.Manifest.Abi.Events.FirstOrDefault(p => string.Equals(p.Name, name, StringComparison.Ordinal)); |
| 347 | + if (@event is null) |
| 348 | + throw new InvalidOperationException($"Event `{name}` does not exist."); |
| 349 | + if (@event.Parameters.Length != state.Count) |
| 350 | + throw new InvalidOperationException("The number of the arguments does not match the formal parameters of the event."); |
| 351 | + for (int i = 0; i < @event.Parameters.Length; i++) |
| 352 | + { |
| 353 | + var p = @event.Parameters[i]; |
| 354 | + if (!CheckItemType(state[i], p.Type)) |
| 355 | + throw new InvalidOperationException($"The type of the argument `{p.Name}` does not match the formal parameter."); |
| 356 | + } |
| 357 | + using MemoryStream ms = new(MaxNotificationSize); |
| 358 | + using BinaryWriter writer = new(ms, Utility.StrictUTF8, true); |
| 359 | + BinarySerializer.Serialize(writer, state, MaxNotificationSize); |
| 360 | + SendNotification(CurrentScriptHash, name, state); |
| 361 | + } |
| 362 | + |
| 363 | + protected internal void RuntimeNotifyV1(byte[] eventName, Array state) |
335 | 364 | {
|
336 | 365 | if (eventName.Length > MaxEventName) throw new ArgumentException(null, nameof(eventName));
|
337 | 366 | if (CurrentContext.GetState<ExecutionContextState>().Contract is null)
|
@@ -384,5 +413,59 @@ protected internal void BurnGas(long gas)
|
384 | 413 | throw new InvalidOperationException("GAS must be positive.");
|
385 | 414 | AddGas(gas);
|
386 | 415 | }
|
| 416 | + |
| 417 | + private static bool CheckItemType(StackItem item, ContractParameterType type) |
| 418 | + { |
| 419 | + StackItemType aType = item.Type; |
| 420 | + if (aType == StackItemType.Pointer) return false; |
| 421 | + switch (type) |
| 422 | + { |
| 423 | + case ContractParameterType.Any: |
| 424 | + return true; |
| 425 | + case ContractParameterType.Boolean: |
| 426 | + return aType == StackItemType.Boolean; |
| 427 | + case ContractParameterType.Integer: |
| 428 | + return aType == StackItemType.Integer; |
| 429 | + case ContractParameterType.ByteArray: |
| 430 | + return aType is StackItemType.Any or StackItemType.ByteString or StackItemType.Buffer; |
| 431 | + case ContractParameterType.String: |
| 432 | + { |
| 433 | + if (aType is StackItemType.ByteString or StackItemType.Buffer) |
| 434 | + { |
| 435 | + try |
| 436 | + { |
| 437 | + _ = Utility.StrictUTF8.GetString(item.GetSpan()); // Prevent any non-UTF8 string |
| 438 | + return true; |
| 439 | + } |
| 440 | + catch { } |
| 441 | + } |
| 442 | + return false; |
| 443 | + } |
| 444 | + case ContractParameterType.Hash160: |
| 445 | + if (aType == StackItemType.Any) return true; |
| 446 | + if (aType != StackItemType.ByteString && aType != StackItemType.Buffer) return false; |
| 447 | + return item.GetSpan().Length == UInt160.Length; |
| 448 | + case ContractParameterType.Hash256: |
| 449 | + if (aType == StackItemType.Any) return true; |
| 450 | + if (aType != StackItemType.ByteString && aType != StackItemType.Buffer) return false; |
| 451 | + return item.GetSpan().Length == UInt256.Length; |
| 452 | + case ContractParameterType.PublicKey: |
| 453 | + if (aType == StackItemType.Any) return true; |
| 454 | + if (aType != StackItemType.ByteString && aType != StackItemType.Buffer) return false; |
| 455 | + return item.GetSpan().Length == 33; |
| 456 | + case ContractParameterType.Signature: |
| 457 | + if (aType == StackItemType.Any) return true; |
| 458 | + if (aType != StackItemType.ByteString && aType != StackItemType.Buffer) return false; |
| 459 | + return item.GetSpan().Length == 64; |
| 460 | + case ContractParameterType.Array: |
| 461 | + return aType is StackItemType.Any or StackItemType.Array or StackItemType.Struct; |
| 462 | + case ContractParameterType.Map: |
| 463 | + return aType is StackItemType.Any or StackItemType.Map; |
| 464 | + case ContractParameterType.InteropInterface: |
| 465 | + return aType is StackItemType.Any or StackItemType.InteropInterface; |
| 466 | + default: |
| 467 | + return false; |
| 468 | + } |
| 469 | + } |
387 | 470 | }
|
388 | 471 | }
|
0 commit comments