-
Notifications
You must be signed in to change notification settings - Fork 102
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge branch 'master' into fix-object-creation
- Loading branch information
Showing
99 changed files
with
16,619 additions
and
117 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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
27
src/Neo.Disassembler.CSharp/Neo.Disassembler.CSharp.csproj
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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> |
Oops, something went wrong.