using System.Numerics;
using Neo.SmartContract.Framework;
using Neo.SmartContract.Framework.Services;
using Neo.Sol.Runtime.ABI;
using Neo.Sol.Runtime.Crypto;
using NeoFrameworkRuntime = Neo.SmartContract.Framework.Services.Runtime;
using UInt160 = Neo.SmartContract.Framework.UInt160;
using UInt256 = Neo.SmartContract.Framework.UInt256;
namespace Neo.Sol.Runtime.Events;
public sealed class EventManager
{
private readonly UInt160 _contractHash;
private uint _eventCount;
private const string EVENT_PREFIX = "EVM_Event";
public EventManager(UInt160 contractHash)
{
_contractHash = contractHash;
_eventCount = 0;
}
private static void TryLog(string message)
{
try
{
NeoFrameworkRuntime.Log(message);
}
catch
{
}
}
public uint GetEventCount() => _eventCount;
public void EmitEvent(string eventSignature, object[] indexedParams, params object[] nonIndexedParams)
{
if (indexedParams.Length > 4)
throw new ArgumentException("Maximum 4 indexed parameters allowed");
var eventHash = CalculateEventHash(eventSignature);
var topics = new System.Collections.Generic.List<byte[]> { eventHash };
foreach (var param in indexedParams)
{
topics.Add(EncodeIndexedParameter(param));
}
var data = nonIndexedParams.Length > 0 ? AbiEncoder.EncodeParameters(nonIndexedParams) : Array.Empty<byte>();
var eventLog = new EventLog
{
Address = _contractHash,
Topics = topics.ToArray(),
Data = data,
EventSignature = eventSignature
};
_eventCount++;
TryLog($"{EVENT_PREFIX}:{eventSignature}");
}
public void Log1(string eventSignature, object topic1, params object[] data)
{
EmitEvent(eventSignature, new[] { topic1 }, data);
}
public void Log2(string eventSignature, object topic1, object topic2, params object[] data)
{
EmitEvent(eventSignature, new[] { topic1, topic2 }, data);
}
public void Log3(string eventSignature, object topic1, object topic2, object topic3, params object[] data)
{
EmitEvent(eventSignature, new[] { topic1, topic2, topic3 }, data);
}
public void Log4(string eventSignature, object topic1, object topic2, object topic3, object topic4, params object[] data)
{
EmitEvent(eventSignature, new[] { topic1, topic2, topic3, topic4 }, data);
}
private static byte[] CalculateEventHash(string eventSignature)
{
return CryptoLib.Keccak256(System.Text.Encoding.UTF8.GetBytes(eventSignature));
}
private static byte[] EncodeIndexedParameter(object parameter)
{
return parameter switch
{
bool b => AbiEncoder.EncodeUint256(b ? 1 : 0),
byte b => AbiEncoder.EncodeUint256(b),
ushort u => AbiEncoder.EncodeUint256(u),
uint u => AbiEncoder.EncodeUint256(u),
ulong u => AbiEncoder.EncodeUint256(u),
BigInteger bi => AbiEncoder.EncodeUint256(bi),
int i => AbiEncoder.EncodeInt256(i),
long l => AbiEncoder.EncodeInt256(l),
UInt160 addr => AbiEncoder.EncodeAddress(addr),
string s => CryptoLib.Keccak256(System.Text.Encoding.UTF8.GetBytes(s)),
byte[] bytes => CryptoLib.Keccak256(bytes),
_ => CryptoLib.Keccak256(System.Text.Encoding.UTF8.GetBytes(parameter.ToString() ?? ""))
};
}
}
public sealed class EventLog
{
public UInt160 Address { get; init; } = UInt160.Zero;
public byte[][] Topics { get; init; } = Array.Empty<byte[]>();
public byte[] Data { get; init; } = Array.Empty<byte>();
public string EventSignature { get; init; } = "";
public object[] ToNotificationObject()
{
var topicsHex = Topics.Select(topic => Convert.ToHexString(topic)).ToArray();
ulong timestamp;
try
{
timestamp = NeoFrameworkRuntime.Time;
}
catch
{
timestamp = 0;
}
return new object[]
{
Address.ToString(),
topicsHex,
Convert.ToHexString(Data),
EventSignature,
timestamp };
}
public static EventLog FromNotificationObject(object[] notificationData)
{
if (notificationData.Length < 4)
throw new ArgumentException("Invalid notification data format");
var address = UInt160.Parse(notificationData[0].ToString()!);
var topicsHex = (string[])notificationData[1];
var dataHex = notificationData[2].ToString()!;
var eventSignature = notificationData[3].ToString()!;
var topics = topicsHex.Select(hex => Convert.FromHexString(hex)).ToArray();
var data = Convert.FromHexString(dataHex);
return new EventLog
{
Address = address,
Topics = topics,
Data = data,
EventSignature = eventSignature
};
}
}
public sealed class EventFilter
{
public UInt160? Address { get; init; }
public byte[]? Topic0 { get; init; } public byte[]? Topic1 { get; init; }
public byte[]? Topic2 { get; init; }
public byte[]? Topic3 { get; init; }
public uint? FromBlock { get; init; }
public uint? ToBlock { get; init; }
public bool Matches(EventLog log)
{
if (Address != null && !log.Address.Equals(Address))
return false;
if (Topic0 != null && (log.Topics.Length == 0 || !log.Topics[0].SequenceEqual(Topic0)))
return false;
if (Topic1 != null && (log.Topics.Length < 2 || !log.Topics[1].SequenceEqual(Topic1)))
return false;
if (Topic2 != null && (log.Topics.Length < 3 || !log.Topics[2].SequenceEqual(Topic2)))
return false;
if (Topic3 != null && (log.Topics.Length < 4 || !log.Topics[3].SequenceEqual(Topic3)))
return false;
return true;
}
}
public static class StandardEvents
{
public static void EmitTransfer(EventManager eventManager, UInt160 from, UInt160 to, BigInteger value)
{
eventManager.Log3("Transfer(address,address,uint256)", from, to, value);
}
public static void EmitApproval(EventManager eventManager, UInt160 owner, UInt160 spender, BigInteger value)
{
eventManager.Log3("Approval(address,address,uint256)", owner, spender, value);
}
public static void EmitOwnershipTransferred(EventManager eventManager, UInt160 previousOwner, UInt160 newOwner)
{
eventManager.Log2("OwnershipTransferred(address,address)", previousOwner, newOwner);
}
public static void EmitPaused(EventManager eventManager, UInt160 account)
{
eventManager.Log1("Paused(address)", account);
}
public static void EmitUnpaused(EventManager eventManager, UInt160 account)
{
eventManager.Log1("Unpaused(address)", account);
}
}