Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Optimization] Parsing Smart Contract Script Analysis #3420

Merged
merged 11 commits into from
Jul 30, 2024
78 changes: 29 additions & 49 deletions src/Neo.CLI/CLI/MainService.Tools.cs
Original file line number Diff line number Diff line change
Expand Up @@ -13,14 +13,14 @@
using Neo.Cryptography.ECC;
using Neo.IO;
using Neo.SmartContract;
using Neo.VM;
using Neo.Wallets;
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Numerics;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Text;

namespace Neo.CLI
Expand Down Expand Up @@ -56,7 +56,8 @@ private void OnParseCommand(string value)

if (result != null)
{
Console.WriteLine($"{pair.Key,-30}\t{result}");
ConsoleHelper.Info("", "-----", pair.Key, "-----");
ConsoleHelper.Info("", result, Environment.NewLine);
any = true;
}
}
Expand Down Expand Up @@ -417,62 +418,41 @@ private static string Base64Fixed(string str)
[ParseFunction("Base64 Smart Contract Script Analysis")]
private string? ScriptsToOpCode(string base64)
{
Script script;
try
{
var scriptData = Convert.FromBase64String(base64);
script = new Script(scriptData.ToArray(), true);
var bytes = Convert.FromBase64String(base64);
var sb = new StringBuilder();

foreach (var instruct in new VMInstruction(bytes))
{
if (instruct.OperandSize == 0)
sb.AppendFormat("{0:X04} {1}{2}", instruct.Position, instruct.OpCode, Environment.NewLine);
else
sb.AppendFormat("{0:X04} {1,-10}{2}{3}", instruct.Position, instruct.OpCode, DecodeOperand(instruct), Environment.NewLine);
}

return sb.ToString();
}
catch (Exception)
catch
{
return null;
}
return ScriptsToOpCode(script);
}

private string ScriptsToOpCode(Script script)
private string DecodeOperand(VMInstruction instruction)
{
//Initialize all InteropService
var dic = new Dictionary<uint, string>();
ApplicationEngine.Services.ToList().ForEach(p => dic.Add(p.Value.Hash, p.Value.Name));

//Analyzing Scripts
var ip = 0;
Instruction instruction;
var result = new List<string>();
while (ip < script.Length && (instruction = script.GetInstruction(ip)) != null)
{
ip += instruction.Size;

var op = instruction.OpCode;

if (op.ToString().StartsWith("PUSHINT"))
{
var operand = instruction.Operand.ToArray();
result.Add($"{op} {new BigInteger(operand)}");
}
else if (op == OpCode.SYSCALL)
{
var operand = instruction.Operand.ToArray();
result.Add($"{op} {dic[BitConverter.ToUInt32(operand)]}");
}
else
{
if (!instruction.Operand.IsEmpty && instruction.Operand.Length > 0)
{
var operand = instruction.Operand.ToArray();
var ascii = Encoding.Default.GetString(operand);
ascii = ascii.Any(p => p < '0' || p > 'z') ? operand.ToHexString() : ascii;

result.Add($"{op} {(operand.Length == 20 ? new UInt160(operand).ToString() : ascii)}");
}
else
{
result.Add($"{op}");
}
}
}
return Environment.NewLine + string.Join("\r\n", result.ToArray());
var operand = instruction.Operand[instruction.OperandPrefixSize..].ToArray();
var asStr = Encoding.UTF8.GetString(operand);
return instruction.OpCode switch
{
VM.OpCode.PUSHINT8 => $"{Unsafe.As<byte, sbyte>(ref operand[0])}",
VM.OpCode.PUSHINT16 => $"{Unsafe.As<byte, short>(ref operand[0])}",
VM.OpCode.PUSHINT32 => $"{Unsafe.As<byte, int>(ref operand[0])}",
VM.OpCode.PUSHINT64 => $"{Unsafe.As<byte, long>(ref operand[0])}",
VM.OpCode.PUSHINT128 or VM.OpCode.PUSHINT256 => $"{new BigInteger(operand)}",
VM.OpCode.SYSCALL => $"[{ApplicationEngine.Services[Unsafe.As<byte, uint>(ref operand[0])].Name}]",
_ => asStr.All(a => char.IsAscii(a)) ? $"\"{asStr}\"" : Convert.ToHexString(operand),
};
}

/// <summary>
Expand Down
98 changes: 98 additions & 0 deletions src/Neo.CLI/Tools/VMInstruction.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
// Copyright (C) 2015-2024 The Neo Project.
//
// VMInstruction.cs file belongs to the neo project and is free
// software distributed under the MIT software license, see the
// accompanying file LICENSE in the main directory of the
// repository or http://www.opensource.org/licenses/mit-license.php
// for more details.
//
// Redistribution and use in source and binary forms with or without
// modifications are permitted.

using System;
using System.Buffers.Binary;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Reflection;

namespace Neo.CLI
{
using Neo.VM;

[DebuggerDisplay("OpCode={OpCode}, OperandSize={OperandSize}")]
internal sealed class VMInstruction : IEnumerable<VMInstruction>
{
private const int OpCodeSize = 1;

public int Position { get; private init; }
public OpCode OpCode { get; private init; }
public ReadOnlyMemory<byte> Operand { get; private init; }
public int OperandSize { get; private init; }
public int OperandPrefixSize { get; private init; }

private static readonly int[] s_operandSizeTable = new int[256];
private static readonly int[] s_operandSizePrefixTable = new int[256];

private readonly ReadOnlyMemory<byte> _script;

public VMInstruction(ReadOnlyMemory<byte> script, int start = 0)
{
if (script.IsEmpty)
throw new Exception("Bad Script.");

var opcode = (OpCode)script.Span[start];

if (Enum.IsDefined(opcode) == false)
throw new InvalidDataException($"Invalid opcode at Position: {start}.");

OperandPrefixSize = s_operandSizePrefixTable[(int)opcode];
OperandSize = OperandPrefixSize switch
{
0 => s_operandSizeTable[(int)opcode],
1 => script.Span[start + 1],
2 => BinaryPrimitives.ReadUInt16LittleEndian(script.Span[(start + 1)..]),
4 => unchecked((int)BinaryPrimitives.ReadUInt32LittleEndian(script.Span[(start + 1)..])),
_ => throw new InvalidDataException($"Invalid opcode prefix at Position: {start}."),
};

OperandSize += OperandPrefixSize;

if (start + OperandSize + OpCodeSize > script.Length)
throw new IndexOutOfRangeException("Operand size exceeds end of script.");

Operand = script.Slice(start + OpCodeSize, OperandSize);

_script = script;
OpCode = opcode;
Position = start;
}

static VMInstruction()
{
foreach (var field in typeof(OpCode).GetFields(BindingFlags.Public | BindingFlags.Static))
{
var attr = field.GetCustomAttribute<OperandSizeAttribute>();
if (attr == null) continue;

var index = (uint)(OpCode)field.GetValue(null)!;
s_operandSizeTable[index] = attr.Size;
s_operandSizePrefixTable[index] = attr.SizePrefix;
}
}

public IEnumerator<VMInstruction> GetEnumerator()
{
var nip = Position + OperandSize + OpCodeSize;
yield return this;

VMInstruction? instruct;
for (var ip = nip; ip < _script.Length; ip += instruct.OperandSize + OpCodeSize)
yield return instruct = new VMInstruction(_script, ip);
}

IEnumerator IEnumerable.GetEnumerator() =>
GetEnumerator();
}
}
Loading