Skip to content

Commit

Permalink
Merge branch 'master' into fix-object-creation
Browse files Browse the repository at this point in the history
  • Loading branch information
Jim8y authored Oct 8, 2024
2 parents e8b4378 + 6e49189 commit 8829acf
Show file tree
Hide file tree
Showing 99 changed files with 16,619 additions and 117 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/main.yml
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ on:
env:
DOTNET_VERSION: 8.0.x
DOTNET_TEST_PARAMETERS: --no-build /p:CollectCoverage=true -l "console;verbosity=detailed"
COVERLET_EXCLUDE_COVERAGE: /p:Exclude=\"[Neo.SmartContract.TestEngine]*,[Neo.Compiler.CSharp.UnitTests]*,[Neo]*,[Neo.IO]*,[Neo.Json]*,[Neo.VM]*,[Neo.Extensions]*,[Neo.Cryptography.BLS12_381]*\"
COVERLET_EXCLUDE_COVERAGE: /p:Exclude=\"[Neo.SmartContract.TestEngine]*,[Neo.Disassembler.CSharp]*,[Neo.Compiler.CSharp.UnitTests]*,[Neo]*,[Neo.IO]*,[Neo.Json]*,[Neo.VM]*,[Neo.Extensions]*,[Neo.Cryptography.BLS12_381]*\"
COVERLET_OUTPUT: /p:CoverletOutput=${{ github.workspace }}/coverage-join/
COVERLET_MERGE_WITH: /p:MergeWith=${{ github.workspace }}/coverage-join/coverage.json

Expand Down
75 changes: 75 additions & 0 deletions docs/framework/ReentrantAttack.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
# Neo C# Smart Contract Reentrancy Protection

## Overview

Neo smart contracts written in C# provide a built-in mechanism to prevent reentrancy attacks. This protection is implemented through custom attributes that can be applied to methods within smart contracts. There are two main attributes available:

1. `NoReentrantAttribute`
2. `NoReentrantMethodAttribute`

Both of these attributes help ensure that a method cannot be re-entered while it's still executing, preventing potential vulnerabilities that could be exploited in reentrancy attacks.

## How It Works

### The Concept

Reentrancy protection works by using a flag in contract storage to indicate whether a protected method is currently executing. When a protected method is called, it checks this flag. If the flag is not set, it sets the flag and proceeds with execution. If the flag is already set, the method call is rejected.

### Implementation Details

1. **Storage Context**: Both attributes use a `StorageMap` to store the reentrancy flag. This map is associated with the current storage context of the contract.

2. **Unique Keys**: Each protected method (or group of methods) uses a unique key in the storage map. This allows for fine-grained control over which methods can or cannot be reentered.

3. **Enter and Exit Logic**: The protection is implemented in two parts:
- `Enter()`: Checks if the method is already executing and throws an exception if it is. Otherwise, it sets the flag to indicate the method is now executing.
- `Exit()`: Clears the flag after the method finishes executing.

4. **Automatic Application**: The Neo smart contract framework automatically calls `Enter()` before the method execution and `Exit()` after the method completes (or if an exception is thrown).

## Usage

### NoReentrantAttribute

This attribute provides global reentrancy protection. It uses a single key for all methods it's applied to.

```csharp
[NoReentrant]
public static void ProtectedMethod()
{
// Method implementation
}
```

### NoReentrantMethodAttribute

This attribute provides method-specific reentrancy protection. By default, it uses the method name as the key, allowing for more granular control.

```csharp
[NoReentrantMethod]
public static void SpecificProtectedMethod()
{
// Method implementation
}
```

## Customization

Both attributes allow for customization:

- You can specify a custom storage prefix (default is 0xFF).
- For `NoReentrantAttribute`, you can specify a custom key.
- For `NoReentrantMethodAttribute`, the key defaults to the method name, but you can override it if needed.

## How It Takes Effect

1. When a protected method is called, the Neo execution engine automatically invokes the `Enter()` method of the attribute.
2. `Enter()` checks the storage map for the presence of the key.
3. If the key is not present, it's added, and the method proceeds.
4. If the key is present, an exception is thrown with the message "Already entered".
5. After the method completes (or if an exception is thrown), the `Exit()` method is automatically called, which removes the key from storage.

- Be mindful of gas costs, as each storage operation consumes GAS.
- Consider the trade-offs between using global (`NoReentrantAttribute`) and method-specific (`NoReentrantMethodAttribute`) protection based on your contract's needs.

By using these attributes, Neo C# smart contract developers can easily add reentrancy protection to their contracts, significantly reducing the risk of reentrancy attacks and improving overall contract security.
7 changes: 7 additions & 0 deletions neo-devpack-dotnet.sln
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Neo.IO", "neo\src\Neo.IO\Ne
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Neo.SmartContract.Analyzer.UnitTests", "tests\Neo.SmartContract.Analyzer.UnitTests\Neo.SmartContract.Analyzer.UnitTests.csproj", "{F30E2375-012A-4A38-985B-31CB7DBA4D28}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Neo.Disassembler.CSharp", "src\Neo.Disassembler.CSharp\Neo.Disassembler.CSharp.csproj", "{FA988C67-43CF-4AE4-94FE-023AADFF88D6}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Expand Down Expand Up @@ -122,6 +124,10 @@ Global
{F30E2375-012A-4A38-985B-31CB7DBA4D28}.Debug|Any CPU.Build.0 = Debug|Any CPU
{F30E2375-012A-4A38-985B-31CB7DBA4D28}.Release|Any CPU.ActiveCfg = Release|Any CPU
{F30E2375-012A-4A38-985B-31CB7DBA4D28}.Release|Any CPU.Build.0 = Release|Any CPU
{FA988C67-43CF-4AE4-94FE-023AADFF88D6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{FA988C67-43CF-4AE4-94FE-023AADFF88D6}.Debug|Any CPU.Build.0 = Debug|Any CPU
{FA988C67-43CF-4AE4-94FE-023AADFF88D6}.Release|Any CPU.ActiveCfg = Release|Any CPU
{FA988C67-43CF-4AE4-94FE-023AADFF88D6}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
Expand All @@ -145,6 +151,7 @@ Global
{E5EFB018-810D-4297-8921-940FA0B1ED97} = {49D5873D-7B38-48A5-B853-85146F032091}
{C2B7927F-AAA5-432A-8E76-B5080BD7EFB9} = {49D5873D-7B38-48A5-B853-85146F032091}
{F30E2375-012A-4A38-985B-31CB7DBA4D28} = {D5266066-0AFD-44D5-A83E-2F73668A63C8}
{FA988C67-43CF-4AE4-94FE-023AADFF88D6} = {79389FC0-C621-4CEA-AD2B-6074C32E7BCA}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {6DA935E1-C674-4364-B087-F1B511B79215}
Expand Down
2 changes: 1 addition & 1 deletion src/Neo.Compiler.CSharp/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -254,7 +254,7 @@ private static int ProcessOutput(Options options, string folder, CompilationCont

if (options.GenerateArtifacts != Options.GenerateArtifactsKind.None)
{
var artifact = manifest.GetArtifactsSource(baseName, nef);
var artifact = manifest.GetArtifactsSource(baseName, nef, debugInfo: debugInfo);

if (options.GenerateArtifacts.HasFlag(Options.GenerateArtifactsKind.Source))
{
Expand Down
75 changes: 75 additions & 0 deletions src/Neo.Disassembler.CSharp/Disassembler.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection.Emit;
using System.Text.RegularExpressions;
using Neo.Json;
using Neo.SmartContract;
using Neo.VM;
using OpCode = Neo.VM.OpCode;

namespace Neo.Disassembler.CSharp;

public static class Disassembler
{
private static readonly Regex RangeRegex = new(@"(\d+)\-(\d+)", RegexOptions.Compiled);

public static List<Instruction> ConvertScriptToInstructions(byte[] script)
{
var res = EnumerateInstructions(script);

return res.Select(x => x.instruction).ToList();
}

public static List<(int address, Instruction instruction)> ConvertMethodToInstructions(NefFile nef, JToken DebugInfo, string method)
{
var (start, end) = GetMethodStartEndAddress(method, DebugInfo);
var instructions = EnumerateInstructions(nef.Script).ToList();
return instructions.Where(
ai => ai.address >= start && ai.address <= end).Select(ai => (ai.address - start, ai.instruction)).ToList();
}

private static (int start, int end) GetMethodStartEndAddress(string name, JToken debugInfo)
{
name = name.Length == 0 ? string.Empty : string.Concat(name[0].ToString().ToUpper(), name.AsSpan(1)); // first letter uppercase
int start = -1, end = -1;
foreach (var method in (JArray)debugInfo["methods"]!)
{
var methodName = method!["name"]!.AsString().Split(",")[1];
if (methodName == name)
{
var rangeGroups = RangeRegex.Match(method["range"]!.AsString()).Groups;
(start, end) = (int.Parse(rangeGroups[1].ToString()), int.Parse(rangeGroups[2].ToString()));
}
}
return (start, end);
}

private static IEnumerable<(int address, Instruction instruction)> EnumerateInstructions(this Script script)
{
var address = 0;
var opcode = OpCode.PUSH0;
Instruction instruction;
for (; address < script.Length; address += instruction.Size)
{
instruction = script.GetInstruction(address);
opcode = instruction.OpCode;
yield return (address, instruction);
}
if (opcode != OpCode.RET)
yield return (address, Instruction.RET);
}

public static string InstructionToString(this Instruction instruction)
{
var opcode = instruction.OpCode.ToString();
var operand = instruction.Operand;

if (operand.IsEmpty || operand.Length == 0)
{
return $"OpCode.{opcode}";
}
var operandString = BitConverter.ToString(operand.ToArray()).Replace("-", "");
return $"OpCode.{opcode} {operandString}";
}
}
27 changes: 27 additions & 0 deletions src/Neo.Disassembler.CSharp/Neo.Disassembler.CSharp.csproj
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<Title>Neo.Disassembler.CSharp</Title>
<LangVersion>latest</LangVersion>
<AssemblyTitle>Neo.Disassembler.CSharp</AssemblyTitle>
<AssemblyName>Neo.Disassembler.CSharp</AssemblyName>
<RootNamespace>Neo.Disassembler.CSharp</RootNamespace>
<PackageId>Neo.Disassembler.CSharp</PackageId>
<PackageTags>NEO;Blockchain;Smart Contract</PackageTags>
<Description>Disassembler for NEO smart contract.</Description>
<IncludeContentInPack>true</IncludeContentInPack>
<ContentTargetFolders>content</ContentTargetFolders>
<Nullable>enable</Nullable>
<NoWarn>$(NoWarn);NU5128</NoWarn>
</PropertyGroup>

<ItemGroup>
<ProjectReference Include="..\..\neo\src\Neo.Extensions\Neo.Extensions.csproj" />
<ProjectReference Include="..\..\neo\src\Neo.IO\Neo.IO.csproj" />
<ProjectReference Include="..\..\neo\src\Neo.Json\Neo.Json.csproj" />
<ProjectReference Include="..\..\neo\src\Neo.VM\Neo.VM.csproj" />
<ProjectReference Include="..\..\neo\src\Neo\Neo.csproj" />
</ItemGroup>

</Project>
Loading

0 comments on commit 8829acf

Please sign in to comment.