From 897b4e08783b8490cca1dc31fea9b83684e66079 Mon Sep 17 00:00:00 2001 From: Jimmy Date: Tue, 1 Apr 2025 20:15:52 +0800 Subject: [PATCH] add documents --- docs/compiler/01-using-compiler.md | 113 +++++++ docs/compiler/02-compiler-options.md | 94 ++++++ docs/compiler/03-debugging.md | 50 ++++ docs/compiler/04-method-conversion.md | 57 ++++ docs/compiler/05-engine-internals.md | 51 ++++ docs/compiler/06-diagnostics.md | 60 ++++ docs/compiler/07-security-analysis.md | 59 ++++ docs/compiler/08-optimization.md | 46 +++ docs/compiler/09-manifest-abi.md | 67 +++++ docs/compiler/10-maintenance-guide.md | 83 +++++ docs/compiler/README.md | 18 ++ .../01-introduction/01-what-is-neo.md | 18 ++ .../01-introduction/02-smart-contracts.md | 26 ++ .../01-introduction/03-why-csharp.md | 23 ++ docs/framework/01-introduction/README.md | 11 + docs/framework/02-getting-started/01-setup.md | 45 +++ .../02-getting-started/02-installation.md | 87 ++++++ .../02-getting-started/03-first-contract.md | 175 +++++++++++ docs/framework/02-getting-started/README.md | 11 + .../03-core-concepts/01-neovm-gas.md | 34 +++ .../03-core-concepts/02-transactions.md | 81 +++++ .../03-core-concepts/03-contract-structure.md | 125 ++++++++ .../03-core-concepts/04-entry-points.md | 87 ++++++ .../03-core-concepts/05-data-types.md | 67 +++++ .../03-core-concepts/06-deployment-files.md | 109 +++++++ .../03-core-concepts/07-error-handling.md | 127 ++++++++ docs/framework/03-core-concepts/README.md | 15 + .../04-framework-features/01-storage.md | 220 ++++++++++++++ .../04-framework-features/02-runtime.md | 179 +++++++++++ .../04-framework-features/03-events.md | 101 +++++++ .../04-contract-interaction.md | 136 +++++++++ .../05-native-contracts/ContractManagement.md | 92 ++++++ .../05-native-contracts/CryptoLib.md | 72 +++++ .../05-native-contracts/GasToken.md | 88 ++++++ .../05-native-contracts/Ledger.md | 88 ++++++ .../05-native-contracts/NameService.md | 125 ++++++++ .../05-native-contracts/NeoToken.md | 98 ++++++ .../05-native-contracts/Oracle.md | 134 +++++++++ .../05-native-contracts/Policy.md | 78 +++++ .../05-native-contracts/README.md | 42 +++ .../05-native-contracts/RoleManagement.md | 55 ++++ .../05-native-contracts/StdLib.md | 96 ++++++ .../04-framework-features/06-attributes.md | 167 +++++++++++ .../07-helper-methods.md | 1 + .../framework/04-framework-features/README.md | 17 ++ .../05-compilation/01-using-compiler.md | 56 ++++ .../05-compilation/02-compiler-options.md | 84 ++++++ docs/framework/05-compilation/03-debugging.md | 57 ++++ docs/framework/05-compilation/README.md | 13 + .../06-advanced-topics/01-contract-upgrade.md | 147 +++++++++ .../06-advanced-topics/02-security.md | 146 +++++++++ .../06-advanced-topics/03-optimization.md | 57 ++++ .../06-advanced-topics/04-interop.md | 52 ++++ docs/framework/06-advanced-topics/README.md | 12 + docs/framework/07-tutorials/01-nep17-token.md | 283 ++++++++++++++++++ .../07-tutorials/02-voting-contract.md | 193 ++++++++++++ .../framework/07-tutorials/03-oracle-usage.md | 210 +++++++++++++ docs/framework/07-tutorials/README.md | 13 + .../08-testing-deployment/01-unit-testing.md | 142 +++++++++ .../02-blockchain-testing.md | 56 ++++ .../08-testing-deployment/03-deployment.md | 64 ++++ .../08-testing-deployment/04-interaction.md | 63 ++++ .../framework/08-testing-deployment/README.md | 12 + docs/framework/09-reference/01-opcodes.md | 15 + docs/framework/09-reference/02-limits.md | 60 ++++ docs/framework/09-reference/README.md | 13 + docs/framework/README.md | 78 +++++ 67 files changed, 5354 insertions(+) create mode 100644 docs/compiler/01-using-compiler.md create mode 100644 docs/compiler/02-compiler-options.md create mode 100644 docs/compiler/03-debugging.md create mode 100644 docs/compiler/04-method-conversion.md create mode 100644 docs/compiler/05-engine-internals.md create mode 100644 docs/compiler/06-diagnostics.md create mode 100644 docs/compiler/07-security-analysis.md create mode 100644 docs/compiler/08-optimization.md create mode 100644 docs/compiler/09-manifest-abi.md create mode 100644 docs/compiler/10-maintenance-guide.md create mode 100644 docs/compiler/README.md create mode 100644 docs/framework/01-introduction/01-what-is-neo.md create mode 100644 docs/framework/01-introduction/02-smart-contracts.md create mode 100644 docs/framework/01-introduction/03-why-csharp.md create mode 100644 docs/framework/01-introduction/README.md create mode 100644 docs/framework/02-getting-started/01-setup.md create mode 100644 docs/framework/02-getting-started/02-installation.md create mode 100644 docs/framework/02-getting-started/03-first-contract.md create mode 100644 docs/framework/02-getting-started/README.md create mode 100644 docs/framework/03-core-concepts/01-neovm-gas.md create mode 100644 docs/framework/03-core-concepts/02-transactions.md create mode 100644 docs/framework/03-core-concepts/03-contract-structure.md create mode 100644 docs/framework/03-core-concepts/04-entry-points.md create mode 100644 docs/framework/03-core-concepts/05-data-types.md create mode 100644 docs/framework/03-core-concepts/06-deployment-files.md create mode 100644 docs/framework/03-core-concepts/07-error-handling.md create mode 100644 docs/framework/03-core-concepts/README.md create mode 100644 docs/framework/04-framework-features/01-storage.md create mode 100644 docs/framework/04-framework-features/02-runtime.md create mode 100644 docs/framework/04-framework-features/03-events.md create mode 100644 docs/framework/04-framework-features/04-contract-interaction.md create mode 100644 docs/framework/04-framework-features/05-native-contracts/ContractManagement.md create mode 100644 docs/framework/04-framework-features/05-native-contracts/CryptoLib.md create mode 100644 docs/framework/04-framework-features/05-native-contracts/GasToken.md create mode 100644 docs/framework/04-framework-features/05-native-contracts/Ledger.md create mode 100644 docs/framework/04-framework-features/05-native-contracts/NameService.md create mode 100644 docs/framework/04-framework-features/05-native-contracts/NeoToken.md create mode 100644 docs/framework/04-framework-features/05-native-contracts/Oracle.md create mode 100644 docs/framework/04-framework-features/05-native-contracts/Policy.md create mode 100644 docs/framework/04-framework-features/05-native-contracts/README.md create mode 100644 docs/framework/04-framework-features/05-native-contracts/RoleManagement.md create mode 100644 docs/framework/04-framework-features/05-native-contracts/StdLib.md create mode 100644 docs/framework/04-framework-features/06-attributes.md create mode 100644 docs/framework/04-framework-features/07-helper-methods.md create mode 100644 docs/framework/04-framework-features/README.md create mode 100644 docs/framework/05-compilation/01-using-compiler.md create mode 100644 docs/framework/05-compilation/02-compiler-options.md create mode 100644 docs/framework/05-compilation/03-debugging.md create mode 100644 docs/framework/05-compilation/README.md create mode 100644 docs/framework/06-advanced-topics/01-contract-upgrade.md create mode 100644 docs/framework/06-advanced-topics/02-security.md create mode 100644 docs/framework/06-advanced-topics/03-optimization.md create mode 100644 docs/framework/06-advanced-topics/04-interop.md create mode 100644 docs/framework/06-advanced-topics/README.md create mode 100644 docs/framework/07-tutorials/01-nep17-token.md create mode 100644 docs/framework/07-tutorials/02-voting-contract.md create mode 100644 docs/framework/07-tutorials/03-oracle-usage.md create mode 100644 docs/framework/07-tutorials/README.md create mode 100644 docs/framework/08-testing-deployment/01-unit-testing.md create mode 100644 docs/framework/08-testing-deployment/02-blockchain-testing.md create mode 100644 docs/framework/08-testing-deployment/03-deployment.md create mode 100644 docs/framework/08-testing-deployment/04-interaction.md create mode 100644 docs/framework/08-testing-deployment/README.md create mode 100644 docs/framework/09-reference/01-opcodes.md create mode 100644 docs/framework/09-reference/02-limits.md create mode 100644 docs/framework/09-reference/README.md create mode 100644 docs/framework/README.md diff --git a/docs/compiler/01-using-compiler.md b/docs/compiler/01-using-compiler.md new file mode 100644 index 000000000..bac709c36 --- /dev/null +++ b/docs/compiler/01-using-compiler.md @@ -0,0 +1,113 @@ +# Using the Compiler (`nccs`) + +The Neo C# Compiler, often invoked as `nccs` (Neo Compiler C Sharp), is the command-line tool responsible for transforming your C# smart contract project into executable Neo Virtual Machine (NeoVM) bytecode and associated metadata files. + +## Installation + +The compiler is typically used as part of the `neo-devpack-dotnet` package or SDK installation. Ensure you have the .NET SDK installed. Depending on your setup, you might invoke the compiler via: + +1. **Direct DLL Execution:** `dotnet /nccs.dll ` +2. **.NET Tool (if installed):** `nccs ` +3. **Within Visual Studio/IDE:** Build process might automatically invoke the compiler. + +Refer to the [Getting Started](./../02-getting-started/02-installation.md) section for detailed installation instructions. + +## Command-Line Interface (CLI) + +The basic syntax for the compiler is: + +```bash +nccs [paths...] [options...] +``` + +### Input Paths (`paths...`) + +You need to provide the compiler with the C# source code to compile. This can be done in several ways: + +* **Project File (`.csproj`):** + ```bash + nccs MyContractProject.csproj [options...] + ``` + This is the recommended approach as it uses the project file to determine source files, dependencies, and some compilation settings. + +* **Directory:** + ```bash + nccs ./path/to/contract/directory/ [options...] + ``` + The compiler will search for a `.csproj` file in the specified directory. If found, it processes it. If not, it searches for `.cs` files recursively (excluding `obj` folders) and compiles them. + +* **Specific Source Files (`.cs`):** + ```bash + nccs Contract1.cs Helper.cs [options...] + ``` + You can specify one or more C# source files directly. The output base name will typically be derived from the first `.cs` file unless overridden with `--base-name`. + +* **No Path Specified:** + ```bash + nccs [options...] + ``` + If no path is provided, the compiler attempts to process the current working directory, looking for `.csproj` or `.cs` files as described above. + +* **NEF File for Optimization (`.nef`):** + ```bash + nccs MyContract.nef --optimize=Experimental [other_options...] + ``` + The compiler can also take a compiled `.nef` file as input *specifically for optimization* using the `--optimize=Experimental` flag. It requires the corresponding `.manifest.json` and optionally the `.nefdbgnfo` file to be present in the same directory. + +### Compiler Outputs + +Upon successful compilation, `nccs` generates several files, typically in the output directory (default: `bin/sc/` relative to the project/source location): + +1. **`.nef` (Neo Executable Format):** + * **File:** `.nef` (e.g., `MyContract.nef`) + * **Content:** The compiled NeoVM bytecode that can be deployed to the Neo blockchain. + +2. **`.manifest.json` (Contract Manifest):** + * **File:** `.manifest.json` (e.g., `MyContract.manifest.json`) + * **Content:** JSON metadata describing the contract's ABI (methods, parameters, return types, events), permissions, supported standards, and other essential information needed for interaction and deployment. + +3. **`.nefdbgnfo` / `.debug.json` (Debug Information):** + * **File:** `.nefdbgnfo` (e.g., `MyContract.nefdbgnfo`) + * **Content:** A zip archive containing a `.debug.json` file. This JSON file maps NeoVM instructions back to the original C# source code lines and includes information about variables and method entry/exit points. This is crucial for debugging smart contracts using tools like Neo Debugger. Generated when the `--debug` option is enabled. + +### Optional Outputs + +Depending on the compiler options used, additional files might be generated: + +* **`.asm` (Assembly Listing):** + * **File:** `.asm` (e.g., `MyContract.asm`) + * **Content:** Human-readable representation of the generated NeoVM instructions (opcodes). Generated with the `--assembly` option. + +* **`.nef.txt` (DumpNef Output):** + * **File:** `.nef.txt` (e.g., `MyContract.nef.txt`) + * **Content:** Detailed breakdown of the NEF file structure, including script hash, opcodes with offsets, and potentially integrated debug information. Generated with the `--assembly` option. + +* **Artifacts (`.cs`, `.dll`):** + * **Files:** `.artifacts.cs`, `.artifacts.dll` + * **Content:** C# source code (`.cs`) or a compiled library (`.dll`) generated from the contract's manifest. These artifacts can simplify contract interaction in off-chain C# applications or testing environments. Generated with the `--generate-artifacts` option. + +### Example Usage + +* **Compile a project file with debug info:** + ```bash + nccs MyProject.csproj --debug + ``` + +* **Compile specific C# files into a specific output directory:** + ```bash + nccs Contract.cs Storage.cs --output ./build --base-name MyAwesomeContract + ``` + +* **Compile a project and generate assembly listing:** + ```bash + nccs . --assembly + ``` + +## Integration + +While `nccs` is a powerful command-line tool, it's often integrated into development workflows: + +* **Visual Studio / Rider:** Building a Neo Smart Contract project within these IDEs typically triggers the `nccs` compiler automatically as part of the build process defined in the `.csproj` file. +* **Build Scripts:** Custom build scripts can incorporate `nccs` commands for automated compilation and deployment pipelines. + +See the [Compiler Options](./02-compiler-options.md) page for a detailed list of available flags and settings. diff --git a/docs/compiler/02-compiler-options.md b/docs/compiler/02-compiler-options.md new file mode 100644 index 000000000..1570cf2a1 --- /dev/null +++ b/docs/compiler/02-compiler-options.md @@ -0,0 +1,94 @@ +# Compiler Options + +The Neo C# Compiler (`nccs`) provides several command-line options to control the compilation process, output generation, and optimization levels. + +## Syntax + +```bash +nccs [paths...] [options...] +``` + +## Options + +Here is a list of the available options: + +* **`-o|--output `** + * **Purpose:** Specifies the directory where the output files (`.nef`, `.manifest.json`, etc.) will be generated. + * **Default:** If not specified, defaults typically to `./bin/sc/` relative to the input project or source file location. + * **Example:** `nccs MyContract.csproj --output ./build/` + +* **`--base-name `** + * **Purpose:** Sets the base name for the output files. For example, using `--base-name MyToken` will produce `MyToken.nef`, `MyToken.manifest.json`, etc. + * **Default:** If not specified, the compiler usually infers the base name from the project file name or the first source file name. + * **Example:** `nccs Contract.cs --base-name AwesomeContract` + +* **`-d|--debug [LEVEL]`** + * **Purpose:** Enables the generation of debugging information (`.nefdbgnfo` file containing `.debug.json`). This is essential for debugging contracts. + * **Levels:** + * `None` (Default if option omitted): No debug info. + * `Extended` (Default if option present without value, e.g., `--debug`): Includes sequence points, variable information, and method entry/exit points. + * *(Other potential levels might exist depending on the compiler version, but `Extended` is the most common for debugging)*. + * **Example (Extended Debug):** `nccs MyContract.csproj --debug` + * **Example (Specific Level):** `nccs MyContract.csproj --debug Extended` + +* **`--nullable `** + * **Purpose:** Controls the handling of C# nullable reference types analysis during compilation. + * **States:** + * `Disable`: Nullable analysis is disabled. + * `Enable`: Nullable analysis is enabled. + * `Warnings`: Nullable analysis warnings are treated as warnings. + * `Annotations` (Default): Respects nullable annotations (`?`) in the code. + * `SafeOnly`: Enables stricter nullable analysis. + * **Default:** `Annotations` + * **Example:** `nccs MyContract.csproj --nullable Enable` + +* **`--checked`** + * **Purpose:** Instructs the compiler to generate code that checks for arithmetic overflow and underflow exceptions at runtime. + * **Default:** Disabled (unchecked). + * **Example:** `nccs MyContract.csproj --checked` + +* **`--assembly`** + * **Purpose:** Generates additional output files containing human-readable NeoVM assembly code (`.asm`) and a detailed NEF file dump (`.nef.txt`). Useful for low-level analysis and understanding the generated bytecode. + * **Default:** Disabled. + * **Example:** `nccs MyContract.csproj --assembly` + +* **`--generate-artifacts [KIND]`** + * **Purpose:** Creates helper artifacts based on the contract manifest, simplifying off-chain interaction or testing. + * **Kinds:** + * `None` (Default): No artifacts generated. + * `Source`: Generates a C# source file (`.artifacts.cs`). + * `Library`: Generates a compiled C# library (`.artifacts.dll`). + * `All`: Generates both `Source` and `Library`. + * **Default:** `None` + * **Example (Generate C# Source):** `nccs MyContract.csproj --generate-artifacts Source` + * **Example (Generate Both):** `nccs MyContract.csproj --generate-artifacts All` + +* **`--security-analysis`** + * **Purpose:** Performs a basic static security analysis on the compiled contract, checking for common pitfalls. + * **Note:** This analysis may produce false positives and should be used as a supplementary check, not a replacement for thorough security auditing. + * **Default:** Disabled. + * **Example:** `nccs MyContract.csproj --security-analysis` + +* **`--optimize `** + * **Purpose:** Specifies the level of optimization to apply during compilation. + * **Levels (Common):** + * `None`: No optimizations. + * `Basic`: Basic optimizations (peephole, etc.). + * `All`: More comprehensive optimizations. + * `Experimental`: Used specifically when providing a `.nef` file as input for optimization. + * **Default:** Varies, often `Basic` or `All` depending on the compiler version/defaults. + * **Example:** `nccs MyContract.csproj --optimize All` + * **Example (Optimize existing NEF):** `nccs MyContract.nef --optimize=Experimental` + +* **`--no-inline`** + * **Purpose:** Prevents the compiler from inlining method calls. Inlining can sometimes reduce call overhead but increase overall script size. + * **Default:** Inlining is generally enabled. + * **Example:** `nccs MyContract.csproj --no-inline` + +* **`--address-version `** + * **Purpose:** Specifies the address version to be used by the compiler, particularly when generating addresses or interacting with address-related features. + * **Default:** Uses the default address version from the Neo `ProtocolSettings` (typically 53 for N3). + * **Example:** `nccs MyContract.csproj --address-version 53` + +* **`-?|-h|--help`** + * **Purpose:** Displays the help message listing all available commands and options. diff --git a/docs/compiler/03-debugging.md b/docs/compiler/03-debugging.md new file mode 100644 index 000000000..e468b2e31 --- /dev/null +++ b/docs/compiler/03-debugging.md @@ -0,0 +1,50 @@ +# Debugging Information + +Effective debugging is crucial for developing reliable smart contracts. The Neo C# Compiler (`nccs`) facilitates debugging by generating detailed mapping information when the `--debug` option is enabled. + +## Enabling Debug Info Generation + +To generate debugging information, use the `-d` or `--debug` flag during compilation: + +```bash +nccs MyContractProject.csproj --debug +``` + +This command instructs the compiler to produce an additional output file. + +## The `.nefdbgnfo` File + +When debug generation is enabled, the compiler creates a file named `.nefdbgnfo` (e.g., `MyContract.nefdbgnfo`) in the output directory. + +This file is a standard Zip archive containing one primary file: + +* **`.debug.json`**: A JSON file holding the actual debug metadata. + +## The `.debug.json` File Format + +The `.debug.json` file contains the core information needed by debuggers to correlate the compiled NeoVM bytecode with the original C# source code. Its structure typically includes: + +* **`hash`**: The script hash of the compiled contract (`.nef` file). +* **`entrypoint`**: The instruction pointer (offset) of the contract's entry point. +* **`documents`**: An array of paths to the original C# source files involved in the compilation. +* **`methods`**: An array describing the compiled methods: + * **`id`**: A unique identifier for the method (e.g., `Namespace.ClassName,MethodName`). + * **`name`**: The method's name and signature. + * **`range`**: The start and end instruction offsets for the method's bytecode. + * **`params`**: An array listing the method's parameters (name and type). + * **`return`**: The method's return type. + * **`variables`**: An array listing the local variables within the method (name, type, and scope range). + * **`sequence-points`**: An array of critical mappings. Each point links a bytecode instruction offset to a specific location (document index, start line/column, end line/column) in the original C# source code. This is the core data used for stepping through code. +* **`events`**: An array describing the events defined in the contract (similar structure to methods, listing parameters). +* **`static-variables`**: An array listing static variables used in the contract. + +*(Note: The exact JSON structure might evolve slightly between compiler versions, but the core concepts remain the same.)* + +## Using Debug Information + +The `.nefdbgnfo` file (containing the `.debug.json`) is essential for debugging tools like: + +* **Neo Debugger (Visual Studio Code Extension):** This tool reads the `.nefdbgnfo` file alongside the `.nef` and source code to allow setting breakpoints, stepping through C# code, inspecting variables, and examining the contract's state during execution in a simulated environment (like `Neo Express` or a `TestEngine`). +* **Other Debugging Environments:** Custom testing or debugging setups can parse the `.debug.json` to provide similar source-level debugging capabilities. + +Without the debug information generated by the `--debug` flag, debugging is limited to the NeoVM instruction level, which is significantly more complex and less intuitive than working directly with the C# source code. diff --git a/docs/compiler/04-method-conversion.md b/docs/compiler/04-method-conversion.md new file mode 100644 index 000000000..c8348c075 --- /dev/null +++ b/docs/compiler/04-method-conversion.md @@ -0,0 +1,57 @@ +# Method Conversion to NeoVM Bytecode + +One of the core responsibilities of the Neo C# Compiler (`nccs`) is to translate the C# methods within your smart contract into executable Neo Virtual Machine (NeoVM) bytecode. This process involves analyzing the C# code and generating a corresponding sequence of NeoVM opcodes. + +## The Conversion Process + +The compiler leverages the .NET Compiler Platform (Roslyn) to parse and analyze your C# code. For each method identified as part of the smart contract, it performs the following steps: + +1. **Semantic Analysis:** Roslyn provides a detailed semantic model of the code, understanding types, method calls, variable scopes, and control flow. +2. **Syntax Tree Traversal:** The compiler walks through the Abstract Syntax Tree (AST) of the method provided by Roslyn. +3. **Opcode Emission:** As it traverses the tree, it converts C# statements and expressions into sequences of NeoVM instructions. + +## Handling C# Constructs + +The compiler maps various C# language features to NeoVM equivalents: + +* **Variable Declarations & Assignments:** Uses NeoVM stack operations (`PUSH`, `STLOC`, `LDLOC`) to manage local variables. `INITSLOT` is used at the method start to reserve space for parameters and locals. +* **Arithmetic & Logical Operations:** Maps C# operators (`+`, `-`, `*`, `/`, `%`, `&`, `|`, `^`, `!`, `==`, `!=`, `<`, `>`, `<=`, `>=`) to corresponding NeoVM opcodes (e.g., `ADD`, `SUB`, `MUL`, `DIV`, `MOD`, `AND`, `OR`, `XOR`, `NOT`, `NUMEQUAL`, `NUMNOTEQUAL`, `LT`, `GT`, `LE`, `GE`). The `--checked` option influences whether overflow checks (`ADDA`, `SUBA`, etc.) are emitted. +* **Control Flow:** + * **`if`/`else`:** Uses conditional jump opcodes (`JMPIF_L`, `JMPIFNOT_L`, `JMP_L`). + * **`while`/`for`/`do-while` Loops:** Employs jump opcodes to create looping structures. `break` and `continue` statements are handled by jumping to specific target instructions. + * **`switch` Statements:** Can be complex, often involving a series of comparisons and jumps or sometimes optimized using `SWITCH_L` if applicable. + * **`try`/`catch`/`finally`:** Managed using NeoVM's exception handling opcodes (`TRY_L`, `ENDTRY_L`, `ENDFINALLY`). The compiler sets up exception handling contexts to route execution flow correctly. +* **Method Calls:** + * **Internal Calls:** Uses `CALL_L` to invoke other methods within the same contract, pushing arguments onto the stack according to the calling convention. + * **Framework/Interop Calls:** Maps calls to `Neo.SmartContract.Framework` APIs (like `Runtime.Log`, `Storage.Put`, native contract methods) to specific `SYSCALL` instructions. The compiler identifies the target API by its name/hash and emits the corresponding syscall hash. + * **External Contract Calls:** Uses `CALLT` and the `Contract.Call` method, requiring the target contract hash, method name, call flags, and arguments to be pushed onto the stack. +* **Object Creation (`new`):** + * **Simple Types:** Often involves direct stack manipulation (e.g., pushing `0` for numbers, empty byte array for strings). + * **Complex Types (Structs/Classes):** Typically creates a `Struct` or `Array` on the NeoVM stack and initializes its members. + * **Arrays:** Uses `NEWARRAY`, `NEWSTRUCT`, `PACK` depending on the context and type. +* **Type Conversions (Casting):** May involve opcodes like `CONVERT` if a specific NeoVM type conversion is needed, or might be handled implicitly by stack manipulation. +* **String/Byte Array Operations:** Maps C# string/byte array methods to NeoVM opcodes (`CAT`, `SUBSTR`, `LEFT`, `RIGHT`) or sequences of stack operations. + +## Mapping C# Types to NeoVM Types + +The compiler translates C# types into their corresponding NeoVM stack item types: + +| C# Type | NeoVM Type | Notes | +| :---------------------- | :---------------- | :----------------------------------------- | +| `void` | (No value) | | +| `bool` | `Boolean` | | +| `sbyte`, `byte`, `short`, `ushort`, `int`, `uint`, `long`, `ulong`, `BigInteger` | `Integer` | | +| `char` | `Integer` | Stored as its numeric UTF-16 value | +| `string` | `ByteString` | UTF-8 encoded | +| `byte[]` | `ByteString` | | +| `object`, `dynamic` | `Any` | Represents any type | +| `Array`, `List`, etc. | `Array`/`Struct` | Often represented as `Array` or `Struct` | | +| `Map` | `Map` | | +| Framework Types (e.g., `UInt160`, `ECPoint`) | `ByteString` | Often serialized to byte arrays | +| Other Classes/Structs | `Array`/`Struct` | Usually packed into `Array` or `Struct` | + +## Optimization + +The conversion process may include basic optimizations (`--optimize Basic` or higher) like removing redundant `NOP` instructions. More advanced optimizations can further refine the generated bytecode. + +Understanding this conversion process helps in writing C# code that translates efficiently to NeoVM bytecode, potentially optimizing for GAS costs and execution performance. diff --git a/docs/compiler/05-engine-internals.md b/docs/compiler/05-engine-internals.md new file mode 100644 index 000000000..0a02152a7 --- /dev/null +++ b/docs/compiler/05-engine-internals.md @@ -0,0 +1,51 @@ +# Compilation Engine Internals + +The `CompilationEngine` is the central component within the Neo C# Compiler (`nccs`) that orchestrates the entire compilation process, from parsing C# source code to generating the final `.nef` file and associated artifacts. + +## Core Responsibilities + +The `CompilationEngine` is responsible for: + +1. **Loading Source Code:** Reading C# source files (`.cs`) or entire C# projects (`.csproj`). +2. **Setting up Compilation Environment:** Integrating with the .NET Compiler Platform (Roslyn) to create a `Compilation` object. This involves: + * Parsing source code into Abstract Syntax Trees (ASTs). + * Resolving dependencies (NuGet packages, project references) using `project.assets.json` and MSBuild information. + * Creating `SemanticModel` instances for each syntax tree, enabling deep code analysis. +3. **Identifying Smart Contracts:** Locating public, non-abstract classes within the compilation that inherit from `Neo.SmartContract.Framework.SmartContract`. +4. **Managing Compilation Contexts:** Creating a `CompilationContext` for each identified smart contract class. Each context manages the state and artifacts for compiling a single contract. +5. **Handling Contract Dependencies:** Detecting dependencies between smart contracts within the same compilation (e.g., one contract referencing another via a static field or property). It performs a topological sort to ensure contracts are compiled in the correct order, preventing issues with cyclic dependencies. +6. **Orchestrating Conversion:** Triggering the compilation process within each `CompilationContext`, which in turn uses components like `MethodConvert`, `TypeConvert`, etc., to translate C# code into NeoVM instructions. +7. **Collecting Results:** Gathering the generated NEF bytecode, manifest, debug information, and diagnostics (errors/warnings) from each `CompilationContext`. +8. **Output Generation:** Coordinating the writing of the final output files (`.nef`, `.manifest.json`, `.nefdbgnfo`, `.asm`, etc.) based on the specified compiler options. + +## Key Components & Workflow + +1. **Initialization:** A `CompilationEngine` instance is created with `CompilationOptions`. +2. **Input Processing:** The engine receives input paths (source files, project files, or directories). +3. **Roslyn Integration:** + * It parses source files into `SyntaxTree` objects. + * For projects, it parses the `.csproj` file, restores dependencies (`dotnet restore`), reads the `project.assets.json` file, and resolves necessary `MetadataReference` objects for referenced assemblies (like `Neo.SmartContract.Framework`). + * A Roslyn `Compilation` object is created, representing the entire set of code and references. +4. **Contract Discovery & Ordering:** + * The engine iterates through the syntax trees and semantic models to find classes derived from `SmartContract`. + * It builds a dependency graph if contracts reference each other. + * A topological sort ensures compilation order respects these dependencies. +5. **Per-Contract Compilation:** + * For each contract (in the sorted order), a `CompilationContext` is created. + * The `CompilationContext.Compile()` method is invoked. + * Inside the context, `MethodConvert` is used for each method, `FieldConvert` for fields, etc. These converters interact with the semantic model and emit NeoVM instructions. +6. **Result Aggregation:** The `CompilationEngine` collects the `CompilationContext` results. +7. **Output Writing (handled by `Program.cs`):** The main program logic takes the results from the engine and writes the `.nef`, `.manifest.json`, and other files to the designated output directory. + +## Role of `CompilationContext` + +While the `CompilationEngine` manages the overall process and multiple contracts, the `CompilationContext` focuses on a *single* smart contract. It holds the state specific to that contract's compilation, including: + +* The target contract's `INamedTypeSymbol`. +* Lists of methods, events, and fields to be included. +* The generated NeoVM script (`byte[]`). +* The contract manifest details. +* Debug information mappings. +* Any diagnostics (errors/warnings) encountered during its compilation. + +Understanding the `CompilationEngine` provides insight into how `nccs` handles different input types, manages dependencies, and coordinates the complex task of translating a .NET project into executable Neo smart contracts. diff --git a/docs/compiler/06-diagnostics.md b/docs/compiler/06-diagnostics.md new file mode 100644 index 000000000..b7de7cb3c --- /dev/null +++ b/docs/compiler/06-diagnostics.md @@ -0,0 +1,60 @@ +# Compiler Diagnostic System + +The Neo C# Compiler (`nccs`) includes a diagnostic system to report issues found during compilation. These diagnostics help developers identify and fix problems in their smart contract code, ranging from critical errors that prevent compilation to warnings about potential issues or informational messages. + +## How Diagnostics are Generated + +Diagnostics are generated at various stages of the compilation process: + +1. **Roslyn Analysis:** The underlying Roslyn compiler performs standard C# syntax and semantic analysis. Errors like syntax mistakes, type mismatches, or unresolved symbols are caught here. +2. **Neo Specific Analysis:** The `nccs` compiler adds its own layer of analysis specific to Neo smart contract development. This includes: + * Checking for the use of unsupported .NET APIs or language features. + * Validating the correct usage of `Neo.SmartContract.Framework` attributes and methods. + * Ensuring adherence to NeoVM limitations (e.g., call stack depth, instruction limits). + * Checking for potential security vulnerabilities (if `--security-analysis` is enabled). + * Verifying contract structure and required methods (like `_deploy`). + +These diagnostics are collected within the `CompilationContext` for each contract being compiled. + +## Diagnostic Information + +Each diagnostic typically includes: + +* **Severity Level:** Indicates the importance of the diagnostic. + * `Error`: A critical issue that prevents successful compilation (e.g., invalid syntax, use of forbidden API). The compilation will fail. + * `Warning`: A potential issue that doesn't stop compilation but should be reviewed (e.g., potential inefficiency, use of deprecated features, possible security risks). + * `Info`: An informational message (less common). + * `Hidden`: Diagnostics not typically shown to the user. +* **ID:** A unique identifier code for the specific diagnostic type (e.g., `NC1001`, `NC2005`). These IDs help in searching for documentation or specific information about the issue. +* **Message:** A human-readable description of the problem. +* **Location:** The source file path, line number, and column number where the issue was detected. This allows developers to quickly pinpoint the relevant code. + +## Reporting Diagnostics + +After attempting to compile a contract, `nccs` reports the collected diagnostics: + +* **Console Output:** Diagnostics are printed to the console. + * `Error` severity messages are typically written to the standard error stream (`stderr`). + * `Warning` and `Info` severity messages are usually written to the standard output stream (`stdout`). +* **IDE Integration:** When compiling within an IDE like Visual Studio or Rider, diagnostics are usually displayed in the "Error List" or "Problems" panel, allowing for easy navigation to the source code location. + +## Example Diagnostic Output + +A typical warning message might look like this in the console: + +``` +MyContract.cs(25,10): warning NC2008: Method 'System.Console.WriteLine(string)' is not supported, please use 'Runtime.Log(string)' instead. +``` + +An error message would look similar but indicate `error` severity: + +``` +MyContract.cs(30,5): error CS0103: The name 'undefinedVariable' does not exist in the current context +``` + +## Handling Diagnostics + +* **Errors:** Must be fixed for the compilation to succeed and produce a `.nef` file. +* **Warnings:** Should be carefully reviewed. While they don't block compilation, they often indicate potential bugs, security risks, or areas for improvement. It's good practice to address or explicitly suppress warnings. + +Understanding and addressing compiler diagnostics is a critical part of the smart contract development cycle, ensuring code correctness, security, and adherence to the Neo platform's constraints. diff --git a/docs/compiler/07-security-analysis.md b/docs/compiler/07-security-analysis.md new file mode 100644 index 000000000..ddee7e7cb --- /dev/null +++ b/docs/compiler/07-security-analysis.md @@ -0,0 +1,59 @@ +# Security Analysis + +The Neo C# Compiler (`nccs`) includes an optional security analysis feature that performs basic static checks on the compiled bytecode (`.nef` file) to identify common patterns associated with potential vulnerabilities. + +## Enabling Security Analysis + +To enable the security checks, use the `--security-analysis` flag during compilation: + +```bash +nccs MyContractProject.csproj --security-analysis +``` + +When enabled, the analysis runs after the contract has been successfully compiled into bytecode. + +## Checks Performed + +The security analyzer currently performs several checks, including: + +1. **Re-entrancy Vulnerability:** + * **Check:** Detects if the contract calls an external contract (using `System.Contract.Call`) *before* subsequently writing to its own storage (using `System.Storage.Put` or `System.Storage.Delete`) within the same potential execution path. + * **Risk:** If the external contract called is malicious or buggy, it could call back into the original contract *before* the state update (storage write) has occurred, potentially leading to unexpected behavior, double-spending, or inconsistent state. + * **Analyzer:** `ReEntrancyAnalyzer` + +2. **Storage Writes within `try` Blocks:** + * **Check:** Identifies if `System.Storage.Put` or `System.Storage.Delete` operations occur inside a `try` block. + * **Risk:** If an exception happens *after* the storage write but still within the `try` block (or a subsequent `catch`/`finally`), the storage change might persist even if the transaction logic intended for it to be atomic or conditional upon the success of the entire operation within the `try`. + * **Analyzer:** `WriteInTryAnalyzer` (inferred name) + +3. **`CheckWitness` Usage:** + * **Check:** Analyzes how the `Runtime.CheckWitness` syscall is used. + * **Risk:** Incorrect usage of `CheckWitness` (e.g., checking the wrong address, checking witness inefficiently, or not checking it at all when required for authorization) can lead to unauthorized access or actions. + * **Analyzer:** `CheckWitnessAnalyzer` (inferred name) + +4. **Contract Update Method:** + * **Check:** Examines the contract's `_deploy` method to see if it contains the standard logic for handling contract updates (checking witness of the deployer and calling `ContractManagement.Update`). + * **Risk:** If the standard update logic is missing or significantly altered, the contract may not be updatable using the standard deployment tools and procedures. + * **Analyzer:** `UpdateAnalyzer` (inferred name) + +## Interpreting Results + +The security analyzer prints its findings directly to the console as warnings. For example: + +``` +[SEC] Potential Re-entrancy: Calling contracts at instruction address: 123 before writing storage at + 456 +``` + +``` +[SEC] This contract cannot be updated, or maybe you used abstract code styles to update it. +``` + +## Important Considerations + +* **Static Analysis Limitations:** This is a *static* analysis tool, meaning it examines the bytecode without actually executing it. It looks for specific patterns and cannot understand the full context or intent of the code. +* **False Positives:** The analyzer **can generate false positives**. A reported warning does not definitively mean there is a vulnerability. The identified pattern might be intentional and safe within the specific context of your contract logic. +* **Not Exhaustive:** The analyzer only checks for a limited set of known patterns. **It does not guarantee your contract is secure.** +* **Supplementary Tool:** Use the security analyzer as **one tool among many** in your security process. It is **not a substitute** for thorough manual code review, security audits by experts, and comprehensive testing. + +Always critically evaluate the warnings produced by the analyzer in the context of your contract's specific design and requirements. diff --git a/docs/compiler/08-optimization.md b/docs/compiler/08-optimization.md new file mode 100644 index 000000000..77dcab3b5 --- /dev/null +++ b/docs/compiler/08-optimization.md @@ -0,0 +1,46 @@ +# Compiler Optimization Strategies + +The Neo C# Compiler (`nccs`) includes optimization capabilities to reduce the size and potentially improve the execution efficiency (and thus GAS cost) of the generated NeoVM bytecode. Optimization levels are controlled using the `--optimize ` command-line option. + +## Optimization Levels + +* **`--optimize None` (or omitting the flag):** No optimizations are applied. +* **`--optimize Basic`:** Applies fundamental optimizations. +* **`--optimize All`:** (Behavior might vary by version) Typically applies `Basic` optimizations plus potentially more aggressive strategies. +* **`--optimize Experimental`:** Used specifically for optimizing an existing `.nef` file as input. This usually involves more advanced analysis and rewriting of the bytecode, potentially using strategies from the `Neo.Optimizer` library. + +## Basic Optimizations (`--optimize Basic`) + +When `Basic` optimization is enabled, the following steps are typically performed after the initial bytecode generation: + +1. **Remove NOPs (`RemoveNops`):** + * **Purpose:** Eliminates `NOP` (No Operation) instructions from the bytecode sequence. + * **Mechanism:** Iterates through the instructions. If a `NOP` is found, it's removed. Any jump instructions (`JMP`, `CALL`, etc.) that previously targeted the `NOP` are updated to target the instruction immediately following the removed `NOP`. + * **Benefit:** Reduces script size slightly by removing unnecessary instructions. + +2. **Compress Jumps (`CompressJumps`):** + * **Purpose:** Reduces the size of jump, call, and try instructions where possible. + * **Mechanism:** Checks instructions like `JMP_L`, `CALL_L`, `JMPIF_L`, `JMPIFNOT_L`, `TRY_L`, etc. These instructions use a 4-byte offset to specify the jump target address. If the actual distance to the target instruction (relative offset) can fit within a single signed byte (-128 to +127), the instruction is converted to its shorter, 1-byte offset version (e.g., `JMP_L` becomes `JMP`, `CALL_L` becomes `CALL`, `TRY_L` becomes `TRY`). This process might be repeated as shortening one jump can bring other targets within range. + * **Benefit:** Significantly reduces script size, as jump instructions are common. + +## Advanced Optimizations (`--optimize All`, `--optimize Experimental`) + +These levels often engage more sophisticated optimization strategies, potentially including: + +* **Peephole Optimization:** Examines small, fixed-size sequences ("windows") of instructions and replaces them with shorter or faster equivalent sequences. Examples: + * Replacing `PUSH1`, `PUSH2`, `ADD` with `PUSH3`. + * Simplifying redundant stack manipulations (e.g., `DROP`, `DROP`). + * Optimizing constant conditional jumps. +* **Reachability Analysis / Dead Code Elimination:** Analyzes the control flow graph to identify blocks of code that can never be reached during execution and removes them. +* **Advanced Jump Compression:** More thorough analysis and rewriting of jump sequences. +* **Miscellaneous Optimizations:** Other techniques specific to NeoVM bytecode patterns. + +These advanced optimizations typically occur when the `Experimental` level is used on an existing `.nef` file, leveraging the separate `Neo.Optimizer` logic. + +## Impact + +* **Script Size:** Optimizations, especially jump compression and dead code elimination, can significantly reduce the final `.nef` file size, lowering deployment costs. +* **GAS Cost:** While optimizations primarily target size, some peephole optimizations can replace multiple instructions with fewer, potentially reducing execution GAS costs. +* **Debugging:** Aggressive optimizations (especially at `Experimental` level) can sometimes make debugging harder, as the final bytecode might map less directly to the original source code structure. + +Choosing the appropriate optimization level involves balancing the desire for smaller/cheaper contracts with compilation time and debugging ease. diff --git a/docs/compiler/09-manifest-abi.md b/docs/compiler/09-manifest-abi.md new file mode 100644 index 000000000..149865a26 --- /dev/null +++ b/docs/compiler/09-manifest-abi.md @@ -0,0 +1,67 @@ +# Manifest and ABI Generation + +The contract manifest (`.manifest.json`) is a crucial file generated by the Neo C# Compiler (`nccs`). It acts as a standardized description of the smart contract, providing essential metadata and defining its Application Binary Interface (ABI). + +## Purpose of the Manifest + +The manifest serves several key purposes: + +* **Interoperability:** It allows wallets, explorers, dApps, and other smart contracts to understand how to interact with the compiled contract without needing its source code. +* **Deployment:** It contains information required during the deployment process, such as required permissions. +* **Metadata:** It provides human-readable information like the contract's name and supported standards. +* **Security:** Declares permissions and trusts, enabling nodes and users to enforce security policies. + +## Manifest Structure + +A typical manifest JSON file includes the following sections: + +* **`name`**: The human-readable name of the contract. Often derived from the C# class name or a `[DisplayName]` attribute. +* **`groups`**: (Used for managing update permissions across multiple contracts, typically empty for single contracts). +* **`features`**: Describes optional features enabled for the contract (e.g., storage, payable). The compiler usually determines these automatically based on framework usage. +* **`supportedstandards`**: An array of NEP standard identifiers (e.g., `"NEP-17"`, `"NEP-11"`) that the contract claims to implement. Derived from `[SupportedStandards]` attributes. +* **`abi`**: Defines the contract's interface. + * **`methods`**: An array describing the publicly callable methods. + * `name`: The name of the method (as defined in the C# code). + * `offset`: The starting instruction offset of the method within the compiled `.nef` script. + * `parameters`: An array describing the method's parameters (`name`, `type`). + * `returntype`: The type of the value returned by the method. + * `safe`: A boolean indicating if the method is read-only (a "safe" method, typically marked with `[Safe]`). + * **`events`**: An array describing the events the contract might emit. + * `name`: The name of the event (from the C# `delegate` or `event` definition). + * `parameters`: An array describing the event's parameters (`name`, `type`). +* **`permissions`**: An array defining the permissions the contract requests. + * `contract`: The contract hash (or `*` for wildcard) the permission applies to. + * `methods`: The methods (or `*` for wildcard) the contract needs to call on the target `contract`. + Derived from `[ContractPermission]` attributes. +* **`trusts`**: An array declaring which contract hashes or public keys are trusted by this contract (e.g., allowed to call sensitive methods). Use `*` for wildcard trust (generally discouraged). Derived from `[ContractTrust]` attributes. +* **`extra`**: A flexible field for arbitrary JSON metadata. Can be populated using attributes inheriting from `[ManifestExtraAttribute]` (like `[Author]`, `[Email]`, `[Description]`, `[Version]`). The compiler also adds optimization information here if optimizations were enabled. + +## ABI (Application Binary Interface) + +The `abi` section within the manifest is the contract's public interface definition. It details: + +* **Methods:** Which functions can be invoked, what parameters they expect (name and type), and what type they return. +* **Events:** Which events the contract can emit and the structure of their payloads. + +Type information in the ABI uses `ContractParameterType` enums (e.g., `Integer`, `String`, `Hash160`, `Array`, `Boolean`, `Void`, `PublicKey`, `Signature`, `ByteArray`, `Map`, `Any`). The compiler automatically maps C# types to these ABI types. + +## How Information is Gathered + +The compiler gathers information for the manifest during the compilation process: + +* **Attributes:** It inspects C# attributes applied to the contract class, methods, events, and parameters: + * `[DisplayName("MyContract")]` -> `name` + * `[SupportedStandards("NEP-17")]` -> `supportedstandards` + * `[Safe]` on a method -> `abi.methods[].safe = true` + * `[ContractPermission("*", "*")]` -> `permissions` + * `[ContractTrust("0x...")]` -> `trusts` + * `[Author("...")]`, `[Email("...")]`, etc. -> `extra` +* **Code Analysis:** + * Public methods become ABI methods. + * Public events (`delegate` + `event`) become ABI events. + * Method/event parameter names and types are extracted. + * Method return types are determined. + * The starting offset of each public method in the bytecode is calculated. +* **Default Conventions:** If attributes are missing, the compiler uses defaults (e.g., class name for contract name). + +Understanding the manifest and ABI is essential for anyone interacting with a deployed Neo smart contract. diff --git a/docs/compiler/10-maintenance-guide.md b/docs/compiler/10-maintenance-guide.md new file mode 100644 index 000000000..e3811b07b --- /dev/null +++ b/docs/compiler/10-maintenance-guide.md @@ -0,0 +1,83 @@ +# Compiler Maintenance Guide (`Neo.Compiler.CSharp`) + +This guide provides recommendations and insights for developers maintaining or contributing to the Neo C# Compiler (`nccs`). Understanding the compiler's architecture and following best practices is crucial for ensuring its stability, correctness, and performance. + +## Code Structure Overview + +Familiarize yourself with the main components: + +* **`CompilationEngine/`**: Orchestrates the overall compilation process, manages contexts for individual contracts, and integrates with Roslyn. + * `CompilationEngine.cs`: The main entry point for driving compilation. + * `CompilationContext.cs`: Manages the state and results for compiling a single contract (methods, fields, manifest details, diagnostics). +* **`MethodConvert/`**: Handles the core translation of C# method bodies, statements, and expressions into NeoVM bytecode. Contains subdirectories for specific constructs (e.g., `Expression/`, `Statement/`). +* **`ABI/`**: Defines structures (`AbiMethod`, `AbiEvent`) used for building the contract manifest's ABI section. +* **`Optimizer/`**: Contains logic for optimizing the generated NeoVM bytecode. Includes basic optimizations and potentially more advanced strategies. +* **`SecurityAnalyzer/`**: Implements static analysis checks for common security pitfalls. +* **`Diagnostics/`**: (If present or relevant) Defines error/warning codes and messages. +* **`Program.cs`**: Handles command-line argument parsing and orchestrates the overall execution flow, including calling the `CompilationEngine` and writing output files. +* **`Options.cs`**: Defines the command-line options. + +## Key Technologies & Concepts + +* **Roslyn (.NET Compiler Platform):** The compiler heavily relies on Roslyn for: + * Parsing C# source code (`SyntaxTree`). + * Semantic analysis (`SemanticModel`, `ISymbol` hierarchy - e.g., `IMethodSymbol`, `IFieldSymbol`, `ITypeSymbol`). + * Understanding code structure, type information, and method calls. + * *Maintenance Tip:* A good understanding of Roslyn APIs is essential for modifying code analysis or adding support for new C# features. +* **NeoVM:** Deep knowledge of NeoVM is critical: + * Opcodes and their behavior. + * Stack manipulation rules. + * Memory management (`INITSLOT`, etc.). + * Syscalls (mapping framework methods to `SYSCALL` hashes). + * GAS costs associated with instructions. + * *Maintenance Tip:* Changes in code generation must produce valid and efficient NeoVM bytecode. Refer to NeoVM specifications. +* **Neo Smart Contract Framework (`Neo.SmartContract.Framework`):** The compiler must correctly interpret and map framework elements: + * Attributes (`[DisplayName]`, `[SupportedStandards]`, `[Safe]`, `[ContractPermission]`, etc.) drive manifest generation and behavior. + * Framework method calls often map directly to NeoVM Syscalls. + * *Maintenance Tip:* Keep the compiler synchronized with changes or additions to the Framework. New framework features might require compiler updates. + +## Development & Maintenance Practices + +1. **Testing is Crucial:** + * **Unit Tests:** Add unit tests for specific conversion logic (e.g., how a specific C# expression translates to opcodes), diagnostic reporting, or optimization patterns. + * **Integration Tests:** Maintain comprehensive integration tests that compile whole C# contracts (covering various language features and framework APIs) and verify: + * Correctness of generated `.nef` bytecode (often by executing it in a `TestEngine`). + * Accuracy of the `.manifest.json` file. + * Validity of the `.debug.json` information. + * Expected diagnostics (errors/warnings). + * *Maintenance Tip:* Aim for high test coverage. Every bug fix or new feature should be accompanied by relevant tests. + +2. **Understand the Conversion Flow:** When modifying code generation (primarily in `MethodConvert/`), trace how C# syntax nodes are visited and how corresponding `Instruction` objects are emitted. + +3. **Manage State Carefully:** Be mindful of the state managed within `MethodConvert` (scopes, variable indices, jump targets, try/catch contexts) and `CompilationContext` (static fields, ABI methods/events). + +4. **Diagnostics:** When adding new error or warning diagnostics: + * Define a clear `DiagnosticId`. + * Provide a concise and informative message. + * Ensure the diagnostic is associated with the correct source code `Location` using Roslyn APIs. + +5. **Optimization:** Modifying optimizers (`Optimizer/`) requires extra care: + * Ensure optimizations preserve the original program's semantics. + * Add tests specifically for optimization patterns. + * Consider the trade-offs between optimization effectiveness and compilation time. + * Benchmark the impact of optimizations on script size and potentially GAS cost. + +6. **Framework Synchronization:** Regularly align the compiler with the target version(s) of `Neo.SmartContract.Framework`. Updates to Syscall hashes or framework attribute behavior might necessitate compiler changes. + +7. **Code Style & Conventions:** Follow the existing coding style, naming conventions, and project structure. + +8. **Dependency Management:** Keep Roslyn and other dependencies updated, testing thoroughly after updates. + +9. **Documentation:** Update relevant documentation (like the files in this directory) when making significant changes to compiler behavior, options, or internal logic. + +## Contributing + +* **Issues:** Use the project's issue tracker to report bugs or suggest features. +* **Pull Requests:** + * Discuss significant changes via an issue first. + * Ensure code builds and all tests pass. + * Include new tests for your changes. + * Follow coding conventions. + * Update documentation if necessary. + +By adhering to these guidelines, developers can contribute to a robust, reliable, and maintainable Neo C# Compiler. diff --git a/docs/compiler/README.md b/docs/compiler/README.md new file mode 100644 index 000000000..6a80d0c26 --- /dev/null +++ b/docs/compiler/README.md @@ -0,0 +1,18 @@ +# Neo C# Compiler (`Neo.Compiler.CSharp`) + +Welcome to the documentation for the Neo C# Compiler (`Neo.Compiler.CSharp`). This compiler is responsible for transforming C# smart contract code into NeoVM bytecode (`.nef` file) and generating the associated contract manifest (`.manifest.json`) and debug information (`.debug.json`). + +This section covers the compiler's features, usage, and internal workings. + +## Table of Contents + +* [Using the Compiler](./01-using-compiler.md) +* [Compiler Options](./02-compiler-options.md) +* [Debugging Information](./03-debugging.md) +* [Method Conversion](./04-method-conversion.md) +* [Compilation Engine Internals](./05-engine-internals.md) +* [Diagnostic System](./06-diagnostics.md) +* [Security Analysis](./07-security-analysis.md) +* [Optimization Strategies](./08-optimization.md) +* [Manifest and ABI Generation](./09-manifest-abi.md) +* [Compiler Maintenance Guide](./10-maintenance-guide.md) diff --git a/docs/framework/01-introduction/01-what-is-neo.md b/docs/framework/01-introduction/01-what-is-neo.md new file mode 100644 index 000000000..713f328ac --- /dev/null +++ b/docs/framework/01-introduction/01-what-is-neo.md @@ -0,0 +1,18 @@ +# What is Neo? + +Neo is an open-source, community-driven blockchain platform designed to create the foundation for the next generation internet - the "Smart Economy". It aims to automate the management of digital assets using smart contracts and build a distributed network-based smart economy system. + +## Key Features of Neo N3 + +* **High Performance:** Capable of handling thousands of transactions per second. +* **Enhanced Stability:** Features a robust and optimized architecture. +* **Optimized Smart Contract System:** Offers a powerful and developer-friendly environment. +* **Built-in Oracle:** Provides secure access to off-chain data. +* **Decentralized Storage (NeoFS):** Integrates distributed object storage. +* **One Block Finality:** Uses the dBFT 2.0 consensus mechanism for single-block finality, preventing forks. +* **Economic Model:** Utilizes a dual-token model (NEO and GAS) for governance and network resource payment. +* **Multi-Language Support:** Allows development in familiar languages like C#, Python, Go, Java, and TypeScript. + +Neo N3 represents a significant upgrade from previous versions, focusing on performance, flexibility, and developer experience. + +[Previous: Introduction](./README.md) | [Next: What are Smart Contracts?](./02-smart-contracts.md) \ No newline at end of file diff --git a/docs/framework/01-introduction/02-smart-contracts.md b/docs/framework/01-introduction/02-smart-contracts.md new file mode 100644 index 000000000..2f2231439 --- /dev/null +++ b/docs/framework/01-introduction/02-smart-contracts.md @@ -0,0 +1,26 @@ +# What are Smart Contracts? + +Smart contracts are self-executing contracts with the terms of the agreement directly written into code. They run on a blockchain, making them immutable (unchangeable) and distributed (spread across many computers). + +## Core Principles + +* **Automation:** Code executes automatically when predefined conditions are met, removing the need for intermediaries. +* **Trustlessness:** Parties can interact directly without relying on a central authority because the blockchain enforces the contract rules. +* **Transparency:** Contract code and transaction history are often publicly viewable on the blockchain (though privacy mechanisms can exist). +* **Security:** Cryptography secures transactions and the contract's integrity. +* **Immutability:** Once deployed on the blockchain, the core logic of a smart contract typically cannot be altered (though upgrade mechanisms exist). + +## Use Cases + +Smart contracts enable a wide range of decentralized applications (dApps), including: + +* Decentralized Finance (DeFi) +* Non-Fungible Tokens (NFTs) +* Supply Chain Management +* Voting Systems +* Gaming +* Digital Identity + +On Neo, smart contracts are executed by the Neo Virtual Machine (NeoVM). + +[Previous: What is Neo?](./01-what-is-neo.md) | [Next: Why C# for Neo?](./03-why-csharp.md) \ No newline at end of file diff --git a/docs/framework/01-introduction/03-why-csharp.md b/docs/framework/01-introduction/03-why-csharp.md new file mode 100644 index 000000000..0eb3bf05d --- /dev/null +++ b/docs/framework/01-introduction/03-why-csharp.md @@ -0,0 +1,23 @@ +# Why C# for Neo Smart Contracts? + +Neo stands out by offering multi-language support for smart contract development, and C# is a first-class citizen in this ecosystem. Using C# provides several advantages: + +## Advantages + +1. **Mature Language & Ecosystem:** C# is a strongly-typed, object-oriented language developed by Microsoft. It benefits from decades of development, a rich standard library (.NET), and powerful tooling (Visual Studio, VS Code). +2. **Developer Productivity:** Features like LINQ, async/await (though used differently in contracts), extension methods, and a robust type system enhance productivity and code maintainability. +3. **Tooling Support:** Excellent IDEs like Visual Studio and VS Code offer features like IntelliSense, debugging (including specific Neo contract debugging extensions), and integrated testing frameworks. +4. **Large Developer Pool:** Many developers are already familiar with C#, reducing the learning curve for entering the blockchain space. +5. **Performance:** The `Neo.Compiler.CSharp` specifically targets the NeoVM, performing optimizations to translate C# code into efficient bytecode. +6. **`Neo.SmartContract.Framework`:** This dedicated framework provides a high-level abstraction over NeoVM internals, making it easier to interact with blockchain features like storage, runtime context, and native contracts directly from C#. + +## The `neo-devpack-dotnet` Project + +The `neo-devpack-dotnet` repository, which includes `Neo.SmartContract.Framework` and `Neo.Compiler.CSharp`, is the official toolchain for C# development on Neo N3. + +* **`Neo.SmartContract.Framework`:** Provides the necessary APIs (classes, methods, attributes) to write contracts in C#. +* **`Neo.Compiler.CSharp`:** Compiles your C# smart contract code into NeoVM bytecode (`.nef` file) and generates the contract manifest (`.manifest.json`). + +By using C#, developers can leverage their existing skills and the power of the .NET ecosystem to build sophisticated smart contracts on the Neo platform. + +[Previous: What are Smart Contracts?](./02-smart-contracts.md) | [Next Section: Getting Started](../02-getting-started/README.md) \ No newline at end of file diff --git a/docs/framework/01-introduction/README.md b/docs/framework/01-introduction/README.md new file mode 100644 index 000000000..0c0454876 --- /dev/null +++ b/docs/framework/01-introduction/README.md @@ -0,0 +1,11 @@ +# Introduction + +This section provides a high-level overview of the Neo blockchain, smart contracts in general, and why C# is a powerful choice for developing on Neo N3. + +## Topics + +* [What is Neo?](./01-what-is-neo.md): Understanding the Neo platform and its vision. +* [What are Smart Contracts?](./02-smart-contracts.md): A general explanation of smart contract technology. +* [Why C# for Neo?](./03-why-csharp.md): Advantages of using C# and .NET for Neo smart contracts. + +[Next: What is Neo?](./01-what-is-neo.md) \ No newline at end of file diff --git a/docs/framework/02-getting-started/01-setup.md b/docs/framework/02-getting-started/01-setup.md new file mode 100644 index 000000000..bd847f14c --- /dev/null +++ b/docs/framework/02-getting-started/01-setup.md @@ -0,0 +1,45 @@ +# Environment Setup + +Before you start writing Neo smart contracts in C#, you need to set up your development environment. The primary requirement is the .NET SDK. + +## 1. Install .NET SDK + +Neo N3 smart contract development with C# requires the .NET SDK. The specific version compatibility might evolve, but generally, recent LTS (Long-Term Support) versions are recommended. + +* **Check for Existing Installation:** Open your terminal or command prompt and run: + ```bash + dotnet --version + ``` + If this command outputs a version number (e.g., 8.x.x, 9.x.x, or later), you likely have it installed. +* **Download and Install:** If you don't have it, download the appropriate .NET SDK installer for your operating system from the official Microsoft website: + [https://dotnet.microsoft.com/download/dotnet/9.0](https://dotnet.microsoft.com/download/dotnet/9.0) + Follow the installation instructions for your OS. +* **Verify Installation:** After installation, close and reopen your terminal and run `dotnet --version` again to confirm. + +## 2. Choose a Code Editor/IDE + +You can write C# code in any text editor, but using an IDE with C# support significantly improves the development experience. + +* **Visual Studio Code (Recommended):** A free, lightweight, and powerful editor with excellent C# support via extensions. + * Install the [C# Dev Kit](https://marketplace.visualstudio.com/items?itemName=ms-dotnettools.csdevkit) extension from Microsoft for the best experience. +* **Visual Studio (Windows/Mac):** A full-featured IDE offering comprehensive debugging, testing, and development tools for .NET. +* **JetBrains Rider (Cross-Platform):** Another powerful, paid IDE for .NET development. + +## 3. (Optional) Install Neo Blockchain Toolkit + +While not strictly required for *writing* C# code, the [Neo Blockchain Toolkit for .NET](https://github.com/neo-project/neo-blockchain-toolkit) provides essential tools for debugging, testing, and deploying contracts. It includes: + +* **Neo Express:** A private Neo blockchain instance for local development and testing. +* **Neo Contract Debugger:** Enables stepping through your C# code as it executes on Neo Express. + +Installation is typically done via the .NET tool command: + +```bash +dotnet tool install Neo.Express -g +``` + +Refer to the toolkit's documentation for detailed setup instructions. + +With the .NET SDK installed and an editor chosen, you're ready to proceed with setting up your first Neo C# smart contract project. + +[Previous: Getting Started](./README.md) | [Next: Framework & Compiler](./02-installation.md) \ No newline at end of file diff --git a/docs/framework/02-getting-started/02-installation.md b/docs/framework/02-getting-started/02-installation.md new file mode 100644 index 000000000..ad8d17e17 --- /dev/null +++ b/docs/framework/02-getting-started/02-installation.md @@ -0,0 +1,87 @@ +# Framework & Compiler Installation/Setup + +To develop Neo smart contracts in C#, you need two key components from the `neo-devpack-dotnet` project: + +1. **`Neo.SmartContract.Framework`:** A NuGet package containing the necessary classes, methods, and attributes to interact with the Neo blockchain environment within your C# code. +2. **`Neo.Compiler.CSharp`:** A tool (often used as an MSBuild task) that compiles your C# smart contract project into NeoVM bytecode (`.nef`) and a manifest file (`.manifest.json`). + +## Referencing the Framework + +You add the framework to your C# smart contract project like any other NuGet package. + +1. **Create a Project:** Create a new .NET Class Library project. + ```bash + dotnet new classlib -n MyFirstContract + cd MyFirstContract + ``` +2. **Add NuGet Package:** Add a reference to the `Neo.SmartContract.Framework` package. + ```bash + dotnet add package Neo.SmartContract.Framework + ``` + This command modifies your project's `.csproj` file to include the framework dependency. + +Your `.csproj` file should now look something like this (version numbers may vary): + +```xml + + + + net9.0 + enable + enable + + + + + + + +``` + +## Setting up the Compiler (`nccs`) + +The Neo C# compiler (`nccs`) is typically invoked during the build process of your smart contract project. + +1. **Add Compiler Package:** Add a reference to the `Neo.Compiler.CSharp` package. This package includes MSBuild tasks that automatically trigger the compilation during `dotnet build`. + ```bash + dotnet add package Neo.Compiler.CSharp + ``` + +2. **Configure `.csproj` for Smart Contract Compilation:** + You need to modify your `.csproj` file slightly to tell MSBuild that this is a Neo smart contract project and configure the compiler. + + ```xml + + + + net9.0 + + MyFirstContract + O1 + 3.x.x + false + false + false + + + + + + + + + + true + + + + ``` + + * **Key Properties:** + * ``: Sets the base name for the output `.nef` and `.manifest.json` files. + * ``: Controls compiler optimization level (default is often `O1`). + * ``: Set to `true` (usually in Debug configuration) to generate a `.nefdbgnfo` file for debugging with tools like Neo Express. + +With these packages referenced and the `.csproj` configured, running `dotnet build` on your project will automatically invoke the `nccs` compiler after the standard C# compilation, producing the necessary Neo deployment artifacts. + +[Previous: Environment Setup](./01-setup.md) | [Next: Your First Contract](./03-first-contract.md) \ No newline at end of file diff --git a/docs/framework/02-getting-started/03-first-contract.md b/docs/framework/02-getting-started/03-first-contract.md new file mode 100644 index 000000000..ee1a996b3 --- /dev/null +++ b/docs/framework/02-getting-started/03-first-contract.md @@ -0,0 +1,175 @@ +# Your First Neo C# Smart Contract + +Let's create a very simple "Hello World" smart contract to illustrate the basic structure and compilation process. + +## 1. Create the Project + +Follow the steps from the previous section ([Framework & Compiler Setup](./02-installation.md)) to create a new .NET class library project and add the `Neo.SmartContract.Framework` and `Neo.Compiler.CSharp` NuGet packages. + +```bash +dotnet new classlib -n HelloWorldContract +cd HelloWorldContract +dotnet add package Neo.SmartContract.Framework +dotnet add package Neo.Compiler.CSharp +``` + +## 2. Configure the `.csproj` File + +Edit the `HelloWorldContract.csproj` file to configure it for Neo compilation: + +```xml + + + + net9.0 + disable + disable + + + HelloWorld + Neo.SmartContract.Examples + + + false + false + false + + + + + + + + + + true + + + +``` + +*Note: We disabled `ImplicitUsings` and `Nullable` for this basic example to keep the code explicit.* Replace `3.*` with the specific latest versions if desired. + +## 3. Write the Smart Contract Code + +Delete the default `Class1.cs` file. + +Create a new file named `HelloWorldContract.cs` with the following code: + +```csharp +using Neo.SmartContract.Framework; +using Neo.SmartContract.Framework.Attributes; +using Neo.SmartContract.Framework.Services; +using System.ComponentModel; + +namespace Neo.SmartContract.Examples +{ + [DisplayName("HelloWorldContract")] + [ManifestExtra("Author", "Your Name Here")] + [ManifestExtra("Description", "A simple Hello World contract")] + [SupportedStandards("NEP-17")] // Example: Declare standards compliance if any + [ContractPermission("*", "*")] // Allow calling any contract/method (use cautiously!) + public class HelloWorldContract : SmartContract + { + // Define a constant prefix for storage + private static readonly StorageMap MessageMap = new StorageMap(Storage.CurrentContext, "MSG"); + + // Event declaration + public delegate void MessageChangedDelegate(string oldValue, string newValue); + [DisplayName("MessageChanged")] + public static event MessageChangedDelegate OnMessageChanged; + + // Method to say hello + public static string SayHello() + { + return "Hello, Neo World!"; + } + + // Method to get the stored message + public static string GetMessage() + { + return MessageMap.GetString("CurrentMessage") ?? "No message set."; // Use GetString for string type + } + + // Method to set the message (requires witness verification) + public static bool SetMessage(string newMessage) + { + // Example: Basic Access Control - only contract deployer can set message + // Replace with your actual owner address check logic + // if (!Runtime.CheckWitness(GetOwner())) return false; + + string oldValue = GetMessage(); + MessageMap.Put("CurrentMessage", newMessage); + + // Fire the event + OnMessageChanged(oldValue, newMessage); + Runtime.Log("Message updated"); // Log the action + return true; + } + + // Example: A private helper method (won't be in the manifest) + private static ByteString GetOwner() + { + // In a real contract, this would be the deployer's address, often stored during deploy + // return (ByteString)"NiNmXL8FjEUEs1nfX9uHFBNaenxDHJtmuB".ToScriptHash(); // Example address + return default; // Placeholder + } + + // _deploy method (optional, executed only once on deployment/update) + public static void _deploy(object data, bool update) + { + if (update) return; // Don't re-run on update + + // Initial setup on first deployment + MessageMap.Put("CurrentMessage", "Initial Message"); + Runtime.Log("Contract deployed and initialized."); + } + + // onNEP17Payment method (if supporting NEP-17 deposits) + public static void OnNEP17Payment(UInt160 from, BigInteger amount, object data) + { + Runtime.Log($"Received {amount} NEP-17 tokens from {from}"); + // Handle received payment + } + + // update method (optional, for contract updates) + // public static void update(ByteString nefFile, string manifest) { ... } + + // destroy method (optional, to allow contract self-destruction) + // public static void destroy() { ... } + } +} +``` + +**Explanation:** + +* **Namespaces:** We import necessary framework namespaces. +* **Attributes:** Decorate the class with attributes like `DisplayName`, `ManifestExtra`, `SupportedStandards`, `ContractPermission` which populate the contract's manifest file. +* **Inheritance:** The contract class inherits from `SmartContract` (optional but common). +* **`StorageMap`:** Used for easy access to the contract's persistent storage. +* **Events:** Declared using delegates and the `event` keyword. Marked with `DisplayName` for the manifest. +* **Methods:** Public static methods (`SayHello`, `GetMessage`, `SetMessage`) become callable contract methods. +* **`Runtime.Log`:** Used to emit log messages during execution (visible in some explorers/tools). +* **`_deploy`:** A special method executed automatically only when the contract is first deployed or updated. +* **`OnNEP17Payment`:** A special method automatically called if the contract receives NEP-17 tokens. + +## 4. Compile the Contract + +Open your terminal in the `HelloWorldContract` directory and run the build command: + +```bash +dotnet build +``` + +If successful, this will: + +1. Compile the C# code into a standard .NET DLL. +2. Invoke the `Neo.Compiler.CSharp` (`nccs`). +3. Generate the following files in the `bin/Debug/net6.0/` (or similar) directory: + * `HelloWorld.nef`: The NeoVM bytecode. + * `HelloWorld.manifest.json`: The contract manifest describing methods, events, etc. + * `HelloWorld.nefdbgnfo`: Debug information (if compiled in Debug configuration with `true`). + +Congratulations! You have successfully created and compiled your first Neo N3 smart contract using C#. + +[Previous: Framework & Compiler](./02-installation.md) | [Next Section: Core Concepts](../03-core-concepts/README.md) \ No newline at end of file diff --git a/docs/framework/02-getting-started/README.md b/docs/framework/02-getting-started/README.md new file mode 100644 index 000000000..6db08d25c --- /dev/null +++ b/docs/framework/02-getting-started/README.md @@ -0,0 +1,11 @@ +# Getting Started + +This section guides you through setting up your development environment and creating your first simple Neo smart contract using C#. + +## Topics + +* [Environment Setup](./01-setup.md): Installing necessary tools like the .NET SDK. +* [Framework & Compiler](./02-installation.md): How to reference the Neo SmartContract Framework and use the C# compiler. +* [Your First Contract](./03-first-contract.md): A step-by-step guide to creating, compiling, and understanding a basic "Hello World" contract. + +[Next: Environment Setup](./01-setup.md) \ No newline at end of file diff --git a/docs/framework/03-core-concepts/01-neovm-gas.md b/docs/framework/03-core-concepts/01-neovm-gas.md new file mode 100644 index 000000000..a84eaec67 --- /dev/null +++ b/docs/framework/03-core-concepts/01-neovm-gas.md @@ -0,0 +1,34 @@ +# NeoVM & GAS + +Two fundamental concepts in Neo smart contract development are the Neo Virtual Machine (NeoVM) and GAS. + +## Neo Virtual Machine (NeoVM) + +The NeoVM is the execution engine for Neo smart contracts. It's a lightweight, stack-based virtual machine designed specifically for blockchain environments. + +* **Deterministic Execution:** Given the same contract code and input state, the NeoVM will always produce the same output. This is crucial for blockchain consensus. +* **Bytecode:** Smart contracts written in high-level languages like C# are compiled into NeoVM bytecode (`.nef` file). This bytecode is what actually runs on the Neo nodes. +* **Stack-Based:** Operations manipulate data primarily on a stack. +* **OpCodes:** The VM executes a specific set of instructions called Opcodes (Operation Codes). Each opcode performs a small task (e.g., push data onto the stack, perform arithmetic, access storage, call another contract). +* **Interop Service Layer:** Provides a bridge between the NeoVM and the underlying blockchain state (e.g., accessing storage, getting current block time, checking witness signatures). The `Neo.SmartContract.Framework` provides C# wrappers for these interop services. +* **Resource Limits:** Execution is constrained by GAS limits to prevent infinite loops or excessive resource consumption. + +Understanding that your C# code ultimately translates into NeoVM opcodes helps in writing efficient and optimized contracts. + +## GAS Token + +GAS is one of the two native tokens on the Neo blockchain (the other being NEO). It serves as the fuel for the network. + +* **Network Fees:** Users pay GAS to execute transactions and deploy/invoke smart contracts. This compensates the nodes (Consensus Nodes) for processing and storing data. +* **Execution Cost:** Every NeoVM opcode has an associated GAS cost. More complex operations or those consuming more resources (like storage) cost more GAS. +* **System Fees & Network Fees:** Transaction fees are divided into System Fees (burned) and Network Fees (distributed to Consensus Nodes). +* **Preventing Abuse:** The GAS mechanism prevents malicious actors from spamming the network or running computationally expensive code indefinitely, as they would need to pay for the resources consumed. +* **Generation:** GAS is generated over time and distributed to NEO token holders. + +**Implications for Developers:** + +* **Efficiency Matters:** Writing GAS-efficient code is crucial. Unoptimized code can make your contract expensive or even impossible to use if the required GAS exceeds transaction or block limits. +* **Cost Awareness:** Be mindful of the GAS cost of different framework methods and opcodes, especially storage operations (`Storage.Put`, `Storage.Find`) and complex computations. +* **Fee Management:** Users invoking your contract must attach enough GAS to cover the system and network fees for the operations performed. + +[Previous: Core Concepts](./README.md) | [Next: Transactions](./02-transactions.md) \ No newline at end of file diff --git a/docs/framework/03-core-concepts/02-transactions.md b/docs/framework/03-core-concepts/02-transactions.md new file mode 100644 index 000000000..d54fd1fee --- /dev/null +++ b/docs/framework/03-core-concepts/02-transactions.md @@ -0,0 +1,81 @@ +# Transactions + +Transactions are the fundamental way users interact with the Neo blockchain. They are cryptographically signed data packages that instruct the network to perform actions, such as transferring assets or invoking smart contracts. Understanding their structure, especially the authorization mechanism, is key. + +## Transaction Structure (Neo N3) + +A typical Neo N3 transaction (`Neo.Network.P2P.Payloads.Transaction`) includes several key fields: + +* **`Version`**: Transaction format version (currently 0 for N3). +* **`Nonce`**: A random number generated by the sender, unique to this transaction for a given sender and network. Prevents replay attacks. +* **`SystemFee`**: GAS paid to the network for processing workload (CPU time, storage usage). This fee is burned. +* **`NetworkFee`**: GAS paid to the consensus nodes for including the transaction in a block. This fee rewards the nodes. +* **`ValidUntilBlock`**: The transaction is only valid up to (and including) this block height. Prevents transactions from remaining pending indefinitely. Senders calculate this based on the current block height plus an offset (e.g., `currentHeight + 100`). +* **`Signers`**: An array defining **who needs to authorize** this transaction and the **scope** of their authorization. This declares *intent*. (Detailed below). +* **`Attributes`**: An array for optional extra data. Can include remarks, oracle response identifiers, or other application-specific data. +* **`Script`**: The NeoVM bytecode to be executed when the transaction is processed (e.g., a script calling `Contract.Call` to invoke a smart contract method). +* **`Witnesses`**: An array containing the **proof of authorization** corresponding to each `Signer`. (Detailed below). + +## Signers: Declaring Authorization Intent + +The `Signers` array lists one or more accounts (or contracts acting as accounts) that must authorize the transaction for it to be valid under certain conditions. Each `Signer` object has: + +* **`Account` (`UInt160`)**: The script hash (address) of the account providing authorization. +* **`Scopes` (`WitnessScope` enum)**: Defines *how broadly* the signature associated with this signer is valid within the execution context. This prevents a signature from being misused to authorize unintended actions. Key scopes include: + * **`None` (0)**: Not allowed. Must specify a scope. + * **`CalledByEntry` (1)**: (Most common for user invocations) The signature is valid **only if** the `Account` matches the initial contract being invoked (the `EntryScriptHash` in the `Runtime` context). This means the user directly called the contract specified in the transaction's script. The signature cannot authorize actions initiated by *other* contracts called subsequently within the same transaction. + * **`CustomContracts` (16)**: The signature is valid only when calling specific contract script hashes listed in the `AllowedContracts` field of the Signer object. + * **`CustomGroups` (32)**: The signature is valid only when calling contracts belonging to specific public key groups listed in the `AllowedGroups` field (using groups defined by `[ContractGroup]` attribute - less common). + * **`Global` (128)**: The signature is valid for the entire transaction, regardless of which contract is executing or being called. **Use with extreme caution**, as this grants broad authority. Often used by system transactions or when an account needs to authorize actions across multiple contract calls within a single transaction (e.g., withdrawing from Contract A and depositing into Contract B). +* **`AllowedContracts` (`UInt160[]`)**: Used only when `Scopes` includes `CustomContracts`. Lists the script hashes this signature authorizes calls to. +* **`AllowedGroups` (`ECPoint[]`)**: Used only when `Scopes` includes `CustomGroups`. Lists the public keys defining groups this signature authorizes calls to. + +By adding a `Signer` entry, the transaction creator declares, "I expect account `0x...` to authorize this transaction according to scope `X`." + +## Witnesses: Providing Authorization Proof + +The `Witnesses` array provides the cryptographic proof corresponding to each `Signer`. For a transaction to be valid, there must typically be one witness for each signer, proving that the owner of the private key for that signer's account actually approved the transaction. Each `Witness` object has: + +* **`InvocationScript` (`byte[]`)**: Contains the *data* needed to satisfy the verification logic. For standard accounts, this is primarily the **cryptographic signature** (e.g., ECDSA signature) generated by signing the transaction data (excluding the witnesses themselves) with the account's private key. For multi-sig accounts, it contains multiple signatures. For custom contracts acting as witnesses, it might contain other parameters. +* **`VerificationScript` (`byte[]`)**: Contains the *logic* (NeoVM bytecode) that verifies the `InvocationScript`. + * For **standard accounts**, this script is implicitly defined by the account's address (script hash). When executed, it typically pushes the account's public key onto the stack and calls the `CheckSig` opcode, which uses the signature from the `InvocationScript`. + * For **multi-sig accounts**, the script contains the logic to check multiple public keys against multiple signatures using `CheckMultiSig`. + * For **contracts acting as accounts/signers** (if `verify` method is implemented), this script *is* the contract's script hash, and executing the witness involves calling the contract's `verify()` method. + +**How Verification Works:** + +1. A node processing the transaction iterates through the `Signers`. +2. For each `Signer`, it finds the corresponding `Witness`. +3. It executes the `Witness.VerificationScript` using the `Witness.InvocationScript` as input. +4. If the `VerificationScript` executes successfully and returns `true` on the NeoVM stack, the witness is considered valid for that signer. +5. If all witnesses are valid, the transaction proceeds to execute its main `Script`. + +## `Runtime.CheckWitness()` in Smart Contracts + +Inside your smart contract's method, you use `Runtime.CheckWitness(hashOrPubkey)` to verify authorization *at runtime*. This crucial function checks: + +1. Is the provided `hashOrPubkey` listed in the transaction's `Signers` array? +2. Was a valid `Witness` provided for that `Signer`? +3. Does the `Scope` defined for that `Signer` permit authorization in the *current* execution context (e.g., if `CalledByEntry` was used, does the signer match `Runtime.EntryScriptHash`)? + +If all three conditions are met, `CheckWitness` returns `true`, confirming that the specified account legitimately authorized the action being performed *within the allowed scope*. This is the cornerstone of access control in Neo N3 smart contracts. + +## Invoking Smart Contracts via Transactions (Refined Flow) + +1. **Construct Script:** A wallet/SDK creates a NeoVM script targeting your contract, usually calling `Contract.Call(yourContractHash, "methodName", arg1, ...)`. +2. **Build Transaction:** This script is placed in the `Script` field of a transaction. +3. **Define Signers:** The user's account (`userAccountHash`) is added to the `Signers` array, typically with `Scopes = WitnessScope.CalledByEntry`. +4. **Set Fees & Other Fields:** Sufficient GAS, Nonce, `ValidUntilBlock`, etc., are set. +5. **Sign:** The wallet uses the user's private key to sign the transaction data (excluding witnesses) creating a signature. +6. **Add Witness:** A `Witness` is created: + * `InvocationScript`: Contains the signature. + * `VerificationScript`: Corresponds to the user's account type (e.g., standard single-sig verification). + This Witness is added to the `Witnesses` array. +7. **Broadcast:** The complete, signed transaction is sent to a Neo node. +8. **Node Verification:** The node verifies all witnesses against the signers. +9. **Execute Script:** If verification passes, the transaction `Script` (containing `Contract.Call`) is executed. +10. **`CheckWitness` Inside Contract:** When your contract's method executes `Runtime.CheckWitness(userAccountHash)`, the VM checks if `userAccountHash` was a verified signer with a scope allowing the current call (which `CalledByEntry` typically does if this is the first contract called). + +Understanding this interplay between Signers (intent), Witnesses (proof), Scopes (context), and `Runtime.CheckWitness` (runtime verification) is crucial for designing and interacting with secure Neo N3 contracts. + +[Previous: NeoVM & GAS](./01-neovm-gas.md) | [Next: Contract Structure](./03-contract-structure.md) \ No newline at end of file diff --git a/docs/framework/03-core-concepts/03-contract-structure.md b/docs/framework/03-core-concepts/03-contract-structure.md new file mode 100644 index 000000000..b01305f4b --- /dev/null +++ b/docs/framework/03-core-concepts/03-contract-structure.md @@ -0,0 +1,125 @@ +# C# Smart Contract Structure + +A Neo smart contract written in C# typically follows a standard structure based on a public class. + +```csharp +// 1. Import necessary namespaces +using Neo.SmartContract.Framework; +using Neo.SmartContract.Framework.Attributes; +using Neo.SmartContract.Framework.Services; +using System.ComponentModel; // For DisplayName +using System.Numerics; // For BigInteger + +// 2. Define the namespace for your contract +namespace MyNeoContract.Example +{ + // 3. Apply Manifest Attributes to the class + [DisplayName("MyContractDisplayName")] // User-friendly name in manifest + [ManifestExtra("Author", "Your Name")] + [ManifestExtra("Email", "your.email@example.com")] + [ManifestExtra("Description", "This is my example contract.")] + [SupportedStandards("NEP-17")] // If applicable + [ContractPermission("*", "*")] // Define permissions (use carefully!) + // 4. Define the main contract class (often inherits from SmartContract) + public class MyContract : SmartContract + { + // 5. Constants and Static Readonly Fields (e.g., for storage prefixes) + private const byte Prefix_Balance = 0x01; + private static readonly byte[] OwnerKey = { 0x02 }; + private static readonly StorageMap Balances = new StorageMap(Storage.CurrentContext, Prefix_Balance); + + // 6. Events + public delegate void TransferredDelegate(UInt160 from, UInt160 to, BigInteger amount); + [DisplayName("Transfer")] // Event name in manifest + public static event TransferredDelegate OnTransfer; + + // 7. Public Static Methods (Contract Entry Points / Callable Methods) + public static string GetName() + { + return "MyContract"; + } + + public static bool Transfer(UInt160 from, UInt160 to, BigInteger amount) + { + // ... implementation ... + if (!Runtime.CheckWitness(from)) return false; + // ... logic ... + Balances.Put(from, currentFromBalance - amount); + Balances.Put(to, currentToBalance + amount); + // Fire event + OnTransfer(from, to, amount); + Runtime.Log("Transfer successful"); + return true; + } + + // 8. Private Helper Methods (Not exposed in manifest) + private static BigInteger GetBalance(UInt160 account) + { + return (BigInteger)Balances.Get(account); + } + + // 9. Special Methods (Optional) + + // Executed on deployment/update + public static void _deploy(object data, bool update) + { + if (!update) // Only run on initial deployment + { + // Initialize storage, set owner, etc. + Storage.Put(Storage.CurrentContext, OwnerKey, (ByteString)"NiNmXL8FjEUEs1nfX9uHFBNaenxDHJtmuB".ToScriptHash()); + } + } + + // Called when contract receives NEP-17 tokens + public static void OnNEP17Payment(UInt160 from, BigInteger amount, object data) + { + // Handle incoming payment + } + + // Called during contract update (requires ContractManagement native contract) + // public static void update(ByteString nefFile, string manifest) { ... } + + // Called during contract destruction (requires ContractManagement native contract) + // public static void destroy() { ... } + + // Optional: Verification method, runs on transfers TO the contract address + // public static bool verify() { ... } + } +} +``` + +## Key Components Explained + +1. **Namespaces:** Import framework classes (`Neo.SmartContract.Framework.*`) and standard .NET types (`System.Numerics`, `System.ComponentModel`). +2. **Namespace:** Organize your contract code within a C# namespace. +3. **Manifest Attributes:** Class-level attributes (`DisplayName`, `ManifestExtra`, `SupportedStandards`, `ContractPermission`, etc.) populate the `.manifest.json` file, providing metadata about your contract. +4. **Contract Class:** The main public class containing your contract's logic. Inheriting from `SmartContract` is common practice but not strictly required; it provides convenient access to helper methods. +5. **Constants/Static Fields:** Define constants or static fields, often used for storage map prefixes or fixed values. `StorageMap` provides a convenient way to segment storage. +6. **Events:** Define events using C# delegates and the `event` keyword. Use `DisplayName` to control the event name in the manifest. Events are crucial for notifying off-chain applications about state changes. +7. **Public Static Methods:** These become the callable methods of your smart contract. They are listed in the manifest and can be invoked by users or other contracts. +8. **Private Helper Methods:** Standard C# private methods for internal logic. They are *not* exposed in the manifest and cannot be called directly from outside the contract. +9. **Special Methods:** Methods with specific names recognized by the NeoVM or framework: + * `_deploy(object data, bool update)`: Executed once upon deployment or update. Ideal for initialization. + * `OnNEP17Payment(UInt160 from, BigInteger amount, object data)`: Triggered when the contract address receives NEP-17 tokens. + * `verify()`: If present, this method is executed whenever someone tries to use the contract's address as a witness (e.g., sending assets *from* the contract address, or if the contract address is listed as a signer in a transaction). It must return `true` for the verification to pass. + * `update(ByteString nef, string manifest)`: Handles contract updates (requires `ContractManagement.Update` permissions). + * `destroy()`: Handles contract destruction (requires `ContractManagement.Destroy` permissions). + +This structure provides a clear and organized way to write C# smart contracts for the Neo platform. + +## Advanced C# Features + +Modern versions of the Neo C# compiler (`nccs`) support a growing subset of advanced C# features, including: + +* **LINQ:** Some LINQ query operations on collections (like `Where`, `Select`, `Count`, `Any`) can be compiled, often translating to underlying loops or specific opcodes. +* **Lambda Expressions:** Anonymous functions can often be used where delegates are expected. +* **Records and Tuples:** These provide convenient ways to structure data. +* **Pattern Matching:** C# pattern matching constructs (e.g., in `switch` statements or expressions) are often supported. + +While these features enhance expressiveness, be mindful that: + +1. **Not all .NET BCL methods are supported:** Only methods with corresponding NeoVM implementations or translatable logic can be used. +2. **GAS Costs:** The GAS cost of complex LINQ queries or intricate lambda logic might be less predictable than explicit loops. Always test performance if using these features heavily in GAS-sensitive areas. +3. **Compiler Limitations:** Check the specific `nccs` version for the exact level of support for the latest C# features. + +[Previous: Transactions](./02-transactions.md) | [Next: Entry Points & Methods](./04-entry-points.md) \ No newline at end of file diff --git a/docs/framework/03-core-concepts/04-entry-points.md b/docs/framework/03-core-concepts/04-entry-points.md new file mode 100644 index 000000000..45b6e87ef --- /dev/null +++ b/docs/framework/03-core-concepts/04-entry-points.md @@ -0,0 +1,87 @@ +# Entry Points & Methods + +Smart contract methods are the functions that allow users and other contracts to interact with your deployed code. In C# contracts for Neo, these are defined as public static methods within your contract class. + +## Defining Callable Methods + +Any `public static` method within your main contract class will typically be compiled into a callable function in the NeoVM and listed in the contract's manifest file. + +```csharp +using Neo.SmartContract.Framework; +using Neo.SmartContract.Framework.Attributes; +using Neo.SmartContract.Framework.Services; +using System.Numerics; // For BigInteger + +namespace MyNeoContract.Example +{ + public class MyMethodContract : SmartContract + { + // This is a callable method + public static BigInteger Add(BigInteger a, BigInteger b) + { + return a + b; + } + + // This is also callable + [DisplayName("getUserBalance")] // Control the name in the manifest + public static BigInteger GetBalance(UInt160 user) + { + // ... logic to retrieve balance from storage ... + StorageMap balances = new StorageMap(Storage.CurrentContext, "BAL"); + return (BigInteger)(balances.Get(user) ?? BigInteger.Zero); + } + + // This method requires the caller to have signed the transaction + public static bool UpdateBalance(UInt160 user, BigInteger change) + { + if (!Runtime.CheckWitness(user)) + { + Runtime.Log("CheckWitness failed for balance update."); + return false; // Authorization failed + } + // ... logic to update balance ... + Runtime.Log("Balance updated successfully."); + return true; + } + + // Private methods are NOT callable from outside + private static void InternalCalculation() + { + // ... helper logic ... + } + + // Instance methods are NOT callable + public void InstanceMethod() + { + // Not allowed in static context of NeoVM + } + } +} +``` + +**Key Points:** + +* **`public static`:** Methods must be both `public` and `static` to be directly callable entry points. +* **`[DisplayName]` Attribute:** You can use this attribute to specify a different name for the method in the `.manifest.json` file than its C# name. This is useful for adhering to conventions (like camelCase) or providing clearer names. +* **Parameters & Return Types:** Methods can accept parameters and return values. These must be types compatible with the NeoVM (see [Data Types](./05-data-types.md)). +* **Private/Instance Methods:** Regular C# `private` or instance (non-static) methods are internal to the contract's implementation and cannot be called directly via a transaction. + +## Special Methods (Entry Points) + +As mentioned in [Contract Structure](./03-contract-structure.md), certain method names have special meaning and are triggered by specific events or phases: + +* **`_deploy(object data, bool update)`**: Called automatically by the system only once when the contract is deployed or updated. Cannot be called directly by users afterwards. +* **`OnNEP17Payment(UInt160 from, BigInteger amount, object data)`**: Called automatically by the system when the contract receives NEP-17 tokens via a `transfer` call where this contract is the recipient. +* **`verify()`**: If present and returns `true`, allows the contract's script hash to be used as a witness in a transaction's `Signers` array. This is crucial if the contract needs to authorize actions itself (e.g., sending assets it holds, approving certain operations). It's executed during the transaction **verification phase**, not the application execution phase. Its success allows the transaction to proceed to execution if other conditions are met. + +These special methods act as specific entry points triggered by the Neo protocol itself under defined circumstances. + +## Calling Methods + +Users or other contracts invoke your public static methods using tools, SDKs, or the `Contract.Call` method from within another smart contract. They need to know: + +1. Your contract's **Script Hash** (address). +2. The **Method Name** (as defined in the manifest, respecting `DisplayName` if used). +3. The required **Arguments** in the correct order and type. + +[Previous: Contract Structure](./03-contract-structure.md) | [Next: Data Types](./05-data-types.md) \ No newline at end of file diff --git a/docs/framework/03-core-concepts/05-data-types.md b/docs/framework/03-core-concepts/05-data-types.md new file mode 100644 index 000000000..ab830d97b --- /dev/null +++ b/docs/framework/03-core-concepts/05-data-types.md @@ -0,0 +1,67 @@ +# Data Types + +NeoVM has its own set of primitive and complex data types. The `Neo.SmartContract.Framework` provides C# types that map directly to these NeoVM types, allowing you to work with them in a familiar C# environment. + +## NeoVM StackItem Types + +The fundamental types within NeoVM are represented by `StackItemType`. Your C# code is compiled into operations that manipulate these types on the NeoVM stack. + +Key `StackItemType` values include: + +* `Boolean`: Represents `true` or `false`. +* `Integer`: Represents arbitrary-size integers. +* `ByteString`: Represents an immutable sequence of bytes (used for strings, script hashes, public keys, raw data). +* `Buffer`: Represents a mutable sequence of bytes. +* `Array`: Represents an array of `StackItem`s. +* `Struct`: Represents a struct (similar to an array, but often used for specific data structures) of `StackItem`s. +* `Map`: Represents a key-value map where keys and values are `StackItem`s. +* `Pointer`: Represents an instruction pointer (used internally by NeoVM). +* `InteropInterface`: Represents an object provided by the Interop Service Layer (e.g., `StorageContext`). +* `Any`: Represents null or can be implicitly converted from other types. + +## C# Mapping in `Neo.SmartContract.Framework` + +The framework provides C# types and implicit conversions for seamless integration: + +| C# Type in Framework | NeoVM Type (`StackItemType`) | Description | +| :------------------------------- | :--------------------------- | :-------------------------------------------------------------------------- | +| `bool` | `Boolean` | Standard boolean true/false. | +| `sbyte`, `byte`, `short`, `ushort`, `int`, `uint`, `long`, `ulong` | `Integer` | Standard C# integer types. Converted to NeoVM Integer. | +| `System.Numerics.BigInteger` | `Integer` | Represents arbitrarily large integers. | +| `char` | `Integer` | Represents a single character (stored as its integer value). | +| `string` | `ByteString` | UTF8 encoded immutable byte sequence. | +| `Neo.SmartContract.Framework.ByteString` | `ByteString` | Explicit representation of NeoVM's immutable byte sequence. Useful for hashes, keys, etc. | +| `byte[]` | `Buffer` | Mutable sequence of bytes. Use `Buffer` type for direct manipulation. | +| `Neo.SmartContract.Framework.ECPoint` | `ByteString` | Represents a public key on the elliptic curve (secp256r1). Stored as compressed bytes. | +| `Neo.SmartContract.Framework.UInt160` | `ByteString` | Represents a 160-bit unsigned integer, typically used for script hashes (addresses). | +| `Neo.SmartContract.Framework.UInt256` | `ByteString` | Represents a 256-bit unsigned integer, typically used for transaction or block hashes. | +| `object[]` / `System.Array` | `Array` / `Struct` | Can represent NeoVM Arrays or Structs. Usage determines the underlying type. | +| `Neo.SmartContract.Framework.List` | `Array` | Generic list, maps to NeoVM Array. | +| `Neo.SmartContract.Framework.Map` | `Map` | Generic map, maps to NeoVM Map. Keys and Values must be valid NeoVM types. | +| `object` | `Any` | Can represent `null` or be used for type casting. | +| Enums | `Integer` | Stored as their underlying integer value. | +| Structs (C# `struct`) | `Struct` | User-defined structs map to NeoVM Structs. Members must be valid types. | +| `Neo.SmartContract.Framework.Services.*` | `InteropInterface` | Types representing interop services (e.g., `StorageContext`, `Transaction`). | + +## Important Considerations + +* **Immutability:** `string` and `ByteString` are immutable. Operations that appear to modify them actually create new instances. +* **Mutability:** `byte[]` (Buffer) is mutable. +* **Type Safety:** While C# provides strong typing, interactions often involve `object` or `StackItem` conversions (especially with storage or complex types). Use explicit casting (`(type)value`) or helper methods (`Helper.AsString`, `Helper.AsBigInteger`, etc.) carefully. +* **Serialization:** When storing complex types (arrays, maps, structs) in storage, they are serialized. Be mindful of GAS costs associated with serialization/deserialization. +* **Storage Types:** `Storage.Put` expects `byte[]` or `ByteString` for keys and values. Use `Helper` methods or `StorageMap` extensions (`.Put(key, value)`) for automatic serialization of other types. + ```csharp + StorageMap balances = new StorageMap(Storage.CurrentContext, "BAL"); + UInt160 user = (UInt160)Runtime.ExecutingScriptHash; // Example user hash + BigInteger amount = 100; + + // StorageMap handles serialization for BigInteger value + balances.Put(user, amount); + + BigInteger retrievedAmount = (BigInteger)balances.Get(user); + ``` +* **Floating Point:** NeoVM does *not* natively support floating-point numbers (`float`, `double`, `decimal`). You must represent fractional values using `BigInteger` and a fixed decimal precision factor. + +Understanding this type mapping is essential for writing correct and efficient C# smart contracts. + +[Previous: Entry Points & Methods](./04-entry-points.md) | [Next: Deployment Artifacts (NEF & Manifest)](./06-deployment-files.md) \ No newline at end of file diff --git a/docs/framework/03-core-concepts/06-deployment-files.md b/docs/framework/03-core-concepts/06-deployment-files.md new file mode 100644 index 000000000..0ce329a99 --- /dev/null +++ b/docs/framework/03-core-concepts/06-deployment-files.md @@ -0,0 +1,109 @@ +# Deployment Artifacts: NEF and Manifest + +When you compile your C# smart contract project using `Neo.Compiler.CSharp` (typically via `dotnet build`), two essential files are generated. These files are required to deploy and interact with your contract on the Neo blockchain. + +## 1. NEF File (`.nef`) + +* **N**eo **E**xecutable **F**ormat. +* Contains the compiled **NeoVM bytecode** of your smart contract. +* This is the actual low-level instruction set that the Neo Virtual Machine executes. +* It includes: + * Compiler identification (e.g., "neo-compiler-csharp v3.x.x"). + * The raw NeoVM script (opcodes and data). + * Checksum for integrity verification. +* This file is **required** for deploying the contract's logic to the blockchain using the `ContractManagement` native contract's `deploy` method. + +```json +// Example structure (conceptual) +{ + "magic": 0x3346454E, // NEF3 + "compiler": "neo-compiler-csharp v3.6.2", + // other header fields... + "script": "0x...", // Hex-encoded NeoVM bytecode + "checksum": 0x... // Ensure file integrity +} +``` + +## 2. Manifest File (`.manifest.json`) + +* A JSON file describing the **metadata and interface** of your smart contract. +* It acts like an ABI (Application Binary Interface) for Neo contracts, telling users and applications how to interact with the contract. +* Does **not** contain executable code; it's purely descriptive. +* Generated based on your C# code, including class/method names, parameter types, return types, events, and attributes like `[DisplayName]`, `[SupportedStandards]`, `[ContractPermission]`, `[ManifestExtra]`, etc. +* This file is **required** alongside the `.nef` file when deploying the contract. +* Used by SDKs, wallets, and explorers to understand the contract's capabilities. + +**Key Sections of a Manifest:** + +```json +{ + "name": "MyContractDisplayName", // From [DisplayName] or + "groups": [], // Groups for permissioning (optional) + "features": { + "storage": true, // Indicates if the contract uses storage + "payable": true // Indicates if the contract accepts payments (e.g., has onNEP17Payment) + }, + "supportedstandards": ["NEP-17"], // From [SupportedStandards] + "abi": { + "methods": [ + { + "name": "getName", // Method name (respects [DisplayName]) + "parameters": [], + "returntype": "String", + "offset": 123, // Position in the NEF script + "safe": false // Indicates if method only reads data (no state changes) + }, + { + "name": "transfer", + "parameters": [ + {"name": "from", "type": "Hash160"}, + {"name": "to", "type": "Hash160"}, + {"name": "amount", "type": "Integer"} + ], + "returntype": "Boolean", + "offset": 210, + "safe": false + } + // ... other methods + ], + "events": [ + { + "name": "Transfer", // Event name (respects [DisplayName]) + "parameters": [ + {"name": "from", "type": "Hash160"}, + {"name": "to", "type": "Hash160"}, + {"name": "amount", "type": "Integer"} + ] + } + // ... other events + ] + }, + "permissions": [ + // Permissions declared using [ContractPermission] + {"contract": "*", "methods": "*"} + ], + "trusts": [], // Contracts that this contract trusts (usually * or specific hashes) + "extra": { + // Custom key-value pairs from [ManifestExtra] + "Author": "Your Name", + "Email": "your.email@example.com", + "Description": "This is my example contract." + } +} + +``` + +* **`name`**: User-friendly contract name. +* **`groups`**: For advanced permission settings. +* **`features`**: Indicates use of storage or payability. +* **`supportedstandards`**: Lists NEP standards complied with (e.g., "NEP-17", "NEP-11"). +* **`abi`**: Defines the Application Binary Interface. + * **`methods`**: Lists all public static methods, their parameters (name and type), return type, offset in the NEF script, and whether the method is `safe` (read-only). + * **`events`**: Lists declared events and their parameters. +* **`permissions`**: Specifies which contracts/methods this contract is allowed to call. Defined by `[ContractPermission]` attribute. +* **`trusts`**: Currently unused in N3 compiler, intended for future features. +* **`extra`**: Custom metadata defined by `[ManifestExtra]` attribute. + +Together, the `.nef` file (the code) and the `.manifest.json` file (the description) provide everything needed to deploy and interact with your smart contract on the Neo N3 network. + +[Previous: Data Types](./05-data-types.md) | [Next Section: Framework Features](../04-framework-features/README.md) \ No newline at end of file diff --git a/docs/framework/03-core-concepts/07-error-handling.md b/docs/framework/03-core-concepts/07-error-handling.md new file mode 100644 index 000000000..38116280c --- /dev/null +++ b/docs/framework/03-core-concepts/07-error-handling.md @@ -0,0 +1,127 @@ +# Error Handling in Neo N3 Contracts + +Handling errors correctly is crucial for smart contract security and reliability. Unlike traditional C# applications, standard `try-catch` blocks are generally **not recommended** in Neo N3 smart contracts due to their high and potentially unpredictable GAS costs and the way exceptions interact with the NeoVM's execution model. + +Instead, Neo contracts typically use two primary mechanisms: + +1. **`Helper.Assert`:** For validating critical conditions and invariants. Failure causes the *entire transaction* to fail and revert. +2. **Boolean Return Values (or specific error indicators):** For recoverable errors or conditions where the transaction should succeed but indicate a specific outcome. + +## `Helper.Assert(bool condition, string? message = null)` + +This is the most common way to enforce essential requirements. + +* **Purpose:** Checks if a `condition` is true. If the `condition` is **false**, the NeoVM throws a `FAULT` state, immediately halting execution. +* **Effect:** The entire transaction fails. All state changes made during the transaction (storage puts, token transfers, event emissions) are **reverted**. GAS fees paid for the transaction are still consumed. +* **Use Cases:** + * Validating critical input parameters (e.g., non-zero amount, valid addresses). + * Checking authorization (`Runtime.CheckWitness`). + * Ensuring state consistency (e.g., sufficient balance before transfer). + * Verifying return values from critical external calls. +* **`message` (Optional):** Provides a string message that might be included in the exception details visible in some tools or logs, aiding debugging. + +```csharp +using Neo.SmartContract.Framework; +using Neo.SmartContract.Framework.Services; +using System.Numerics; + +public class AssertDemo : SmartContract +{ + public static bool Transfer(UInt160 from, UInt160 to, BigInteger amount) + { + // CRITICAL CHECKS using Assert + Helper.Assert(from.IsValid && !from.IsZero, "Invalid 'from' address"); + Helper.Assert(to.IsValid && !to.IsZero, "Invalid 'to' address"); + Helper.Assert(amount > 0, "Transfer amount must be positive"); + Helper.Assert(Runtime.CheckWitness(from), "CheckWitness failed for 'from' address"); + + StorageMap balances = new StorageMap(Storage.CurrentContext, "BAL"); + BigInteger fromBalance = (BigInteger)balances.Get(from); + Helper.Assert(fromBalance >= amount, "Insufficient balance"); + + // If any Assert fails, execution stops here, transaction reverts. + + // Proceed with state changes only if all asserts pass + if (from != to) + { + balances.Put(from, fromBalance - amount); + balances.Put(to, (BigInteger)balances.Get(to) + amount); + } + Runtime.Notify("Transfer", from, to, amount); // Event only emitted if successful + return true; // Indicates success + } +} +``` + +## Boolean Return Values & Error Indicators + +For situations where a failure doesn't necessarily mean the entire transaction should revert, returning `false` (or another indicator like `null` or `-1`) is common. + +* **Purpose:** Signals to the caller (another contract or an off-chain application) that the requested operation could not be completed successfully under the given conditions, but the transaction itself might still be valid. +* **Effect:** The transaction completes successfully. State changes made *before* the point of failure are **not** reverted (unless an `Assert` or unhandled exception occurs later). +* **Use Cases:** + * Optional operations failing (e.g., trying to vote when voting has closed, but transaction shouldn't fail). + * Conditions that the caller might be able to handle or retry (e.g., checking if a name is available). + * Indicating specific non-critical outcomes. + +```csharp +using Neo.SmartContract.Framework; +using Neo.SmartContract.Framework.Services; +using System.Numerics; + +public class ReturnValueDemo : SmartContract +{ + private static readonly StorageMap Votes = new StorageMap(Storage.CurrentContext, "VOTE"); + private static readonly uint VotingEndBlock = 1000; + + public static bool CastVote(UInt160 voter, int option) + { + Helper.Assert(Runtime.CheckWitness(voter), "CheckWitness failed"); + + // Use boolean return for recoverable/expected conditions + if (LedgerContract.CurrentIndex >= VotingEndBlock) + { + Runtime.Log("Voting period ended"); + return false; // Indicate failure, but transaction can succeed + } + if (Votes.Get(voter) != null) + { + Runtime.Log("Already voted"); + return false; // Indicate failure (already voted) + } + + // Proceed if checks pass + Votes.Put(voter, option); + Runtime.Notify("Voted", voter, option); + return true; // Indicate success + } +} +``` + +## Why Avoid `try-catch`? + +While the Neo C# compiler *can* technically process standard C# `try-catch` blocks, their use in smart contracts is **strongly discouraged** for several reasons: + +1. **High & Unpredictable GAS Cost:** Implementing exception handling mechanisms requires significant NeoVM instruction overhead. This makes contracts much more expensive to execute, even if no exception is actually thrown during runtime. The exact GAS cost can be difficult to predict. +2. **Complexity:** Translating C# exception handling semantics (stack unwinding, filter blocks, finally blocks) into deterministic NeoVM bytecode is complex and can lead to subtle, hard-to-debug behaviors. +3. **State Reversion Conflict:** `Helper.Assert` provides clear, atomic transaction failure – the entire transaction's state changes are reverted. This is often the desired behavior for critical errors in smart contracts. Using `try-catch` can potentially allow parts of a transaction to succeed even if another part within the `try` block fails, which might lead to an inconsistent or unexpected final state if not managed with extreme care. +4. **Framework Design:** The `Neo.SmartContract.Framework` is designed around the `Assert` and boolean return value patterns for error handling, making them the idiomatic and expected approach. + +Stick to `Helper.Assert` for conditions that must hold true (invariants, authorization) and boolean/specific return values for recoverable or expected failure conditions. + +## Handling `Contract.Call` Failures + +Calls to other contracts (`Contract.Call`) can fail for various reasons (invalid method/contract, insufficient GAS, permission denied, assertion failure in the called contract). These calls often return `null` or throw an exception that might halt execution. + +Always check the return value of `Contract.Call` before using it, often using `Helper.Assert` if the call is critical or a simple conditional check if it's optional. + +```csharp +// Check result of external call +object result = Contract.Call(targetHash, "someMethod", CallFlags.ReadOnly); +Helper.Assert(result != null, "External call failed or returned null"); +BigInteger value = (BigInteger)result; // Proceed only if assert passes +``` + +By prioritizing `Helper.Assert` for invariants and boolean returns for recoverable conditions, you can write robust and predictable error-handling logic suitable for the Neo N3 blockchain environment. + +[Previous: Deployment Artifacts (NEF & Manifest)](./06-deployment-files.md) | [Next Section: Framework Features](../../04-framework-features/README.md) \ No newline at end of file diff --git a/docs/framework/03-core-concepts/README.md b/docs/framework/03-core-concepts/README.md new file mode 100644 index 000000000..f0d1278ec --- /dev/null +++ b/docs/framework/03-core-concepts/README.md @@ -0,0 +1,15 @@ +# Core Neo Smart Contract Concepts + +This section delves into the fundamental concepts you need to understand when developing smart contracts on the Neo N3 platform. + +## Topics + +* [NeoVM & GAS](./01-neovm-gas.md): Understanding the execution environment and the cost of operations. +* [Transactions](./02-transactions.md): How users interact with the blockchain and contracts. +* [Contract Structure](./03-contract-structure.md): The basic layout and components of a C# smart contract. +* [Entry Points & Methods](./04-entry-points.md): Defining how users and other contracts call your contract. +* [Data Types](./05-data-types.md): Overview of the data types available in the NeoVM and how they map to C#. +* [Deployment Artifacts (NEF & Manifest)](./06-deployment-files.md): Understanding the files generated by the compiler. +* [Error Handling](./07-error-handling.md): How to handle errors using Assert and return values. + +[Next: NeoVM & GAS](./01-neovm-gas.md) \ No newline at end of file diff --git a/docs/framework/04-framework-features/01-storage.md b/docs/framework/04-framework-features/01-storage.md new file mode 100644 index 000000000..2df20753c --- /dev/null +++ b/docs/framework/04-framework-features/01-storage.md @@ -0,0 +1,220 @@ +# Storage + +Smart contracts require persistent storage to maintain state between invocations. The `Neo.SmartContract.Framework` provides access to the contract's private key-value storage area, primarily through the static `Storage` class and the helpful `StorageMap` wrapper. + +## Storage Context (`StorageContext`) + +Every storage operation happens within a `StorageContext`. This context represents the specific storage space allocated to the contract. + +* **`Storage.CurrentContext`**: Provides **read-write** access to the storage area of the currently executing contract. Use this context for methods that need to read *and* modify state (`Storage.Put`, `Storage.Delete`). +* **`Storage.CurrentReadOnlyContext`**: Provides **read-only** access. Use this context for methods guaranteed not to modify storage (`Storage.Get`, `Storage.Find`). This is often used in methods marked with the `[Safe]` attribute. Using a read-only context where applicable can sometimes offer slight GAS optimizations and clearly signals intent. + +```csharp +// Read-write access +StorageContext rwContext = Storage.CurrentContext; +Storage.Put(rwContext, "key", "value"); + +// Read-only access +StorageContext roContext = Storage.CurrentReadOnlyContext; +ByteString value = Storage.Get(roContext, "key"); +``` + +## Low-Level Storage Operations (`Storage` class) + +The static `Storage` class provides direct access methods. These methods operate directly on byte arrays (`byte[]`) or `ByteString` for both keys and values. + +* **`Storage.Put(StorageContext, ByteString key, ByteString value)` / `(..., byte[] key, byte[] value)`**: Stores or overwrites a key-value pair. +* **`Storage.Get(StorageContext, ByteString key)` / `(..., byte[] key)`**: Retrieves the value (`ByteString`) for a key. Returns `null` if not found. +* **`Storage.Delete(StorageContext, ByteString key)` / `(..., byte[] key)`**: Removes a key-value pair. + +**Manual Serialization Required:** When using these low-level methods with types other than `byte[]` or `ByteString`, you *must* manually serialize your data before `Put` and deserialize it after `Get`, typically using `StdLib.Serialize` and `StdLib.Deserialize`. + +```csharp +using Neo.SmartContract.Framework; +using Neo.SmartContract.Framework.Native; // For StdLib +using Neo.SmartContract.Framework.Services; +using System.Numerics; + +public class LowLevelStorage : SmartContract +{ + public static void PutBigInt(byte[] key, BigInteger value) + { + // Manual serialization to ByteString + ByteString serializedValue = StdLib.Serialize(value); + Storage.Put(Storage.CurrentContext, key, serializedValue); + } + + public static BigInteger GetBigInt(byte[] key) + { + ByteString serializedValue = Storage.Get(Storage.CurrentReadOnlyContext, key); + if (serializedValue is null) return 0; // Or handle error + // Manual deserialization and casting + return (BigInteger)StdLib.Deserialize(serializedValue); + } +} +``` + +## `StorageMap` for Convenience and Structure + +Manually handling prefixes and serialization is error-prone. `StorageMap` is a wrapper that simplifies storage access significantly. + +* **Automatic Prefixing:** Each `StorageMap` is initialized with a unique prefix (string, byte, int). All keys managed by that map are automatically prepended with this prefix before interacting with the underlying storage. This prevents key collisions between different types of data within your contract (e.g., `Balances` map won't collide with `Allowances` map if they have different prefixes). +* **Automatic Serialization/Deserialization:** `StorageMap` provides extension methods (`Put`, `Get`, `GetString`, `GetBigInteger`, etc.) that handle serialization/deserialization for common types (`BigInteger`, `string`, `bool`, primitives, `UInt160/256`, `ECPoint`, enums, serializable structs, `List`, `Map`) automatically using `StdLib`. + +```csharp +using Neo.SmartContract.Framework; +using Neo.SmartContract.Framework.Native; +using Neo.SmartContract.Framework.Services; +using System.Numerics; + +public class StorageMapDemo : SmartContract +{ + // Use distinct prefixes for different data categories + private static readonly StorageMap Balances = new StorageMap(Storage.CurrentContext, "BAL"); // String prefix + private static readonly StorageMap UserInfo = new StorageMap(Storage.CurrentContext, 0x10); // Byte prefix + + // Define a serializable struct + public struct User + { + public string Name; + public BigInteger RegistrationTime; + public bool IsActive; + } + + public static void SetBalance(UInt160 user, BigInteger amount) + { + // `Put` handles key (UInt160) and value (BigInteger) serialization + Balances.Put(user, amount); + } + + [Safe] + public static BigInteger GetBalance(UInt160 user) + { + // `Get` retrieves serialized data, cast needed. Returns default (0) if not found. + return (BigInteger)Balances.Get(user); + // Or use specific helper: return Balances.GetBigInteger(user); // Might not exist, check framework version + } + + public static void StoreUserInfo(UInt160 user, string name, bool isActive) + { + User data = new User + { + Name = name, + RegistrationTime = Runtime.Time, // Store current block time + IsActive = isActive + }; + // `Put` serializes the User struct automatically + UserInfo.Put(user, data); + } + + [Safe] + public static User GetUserInfo(UInt160 user) + { + ByteString serializedData = UserInfo.Get(user); // Get raw bytes first + if (serializedData is null) return null; // Or default User + // Deserialize and cast + return (User)StdLib.Deserialize(serializedData); + } + + public static void DeleteBalance(UInt160 user) + { + // Automatically uses the "BAL" prefix + Balances.Delete(user); + } +} +``` + +## Iterating Over Storage (`Storage.Find`) + +`Storage.Find` allows iterating over key-value pairs within a `StorageContext`, optionally filtered by a key prefix. This is powerful but **potentially very GAS-intensive**. + +* **`Storage.Find(StorageContext context, ByteString prefix, FindFlags flags)`**: Returns an `Iterator`. +* **`FindFlags` Enum:** Controls behavior: + * `None`: Default. Iterator value is the `KeyValuePair`. + * `KeysOnly`: Iterator value is the key (`ByteString`). + * `ValuesOnly`: Iterator value is the value (`ByteString`). + * `RemovePrefix`: Removes the `prefix` used in the `Find` call from the yielded keys. **Crucial** when using with `StorageMap.Prefix` to get the original keys. + * `DeserializeValues`: If using generic `Find`, attempts to deserialize the value (`ByteString`) into type `T`. Iterator value becomes `KeyValuePair`. + * `PickField0` / `PickField1`: If values are structs/arrays, yields only the specified field (0 or 1). + +**Iteration Best Practices & Warnings:** + +1. **GAS Cost:** `Storage.Find` has a base cost plus a significant cost **per item iterated**. Avoid iterating over potentially large datasets in user-callable functions, as it can easily exceed GAS limits, making your contract unusable (DoS). +2. **Use Prefixes:** Always use a `prefix` with `Storage.Find` to limit the scope of iteration. Use `StorageMap.Prefix` when iterating over map data. +3. **Use `FindFlags.RemovePrefix`:** When iterating a `StorageMap`'s data, use `RemovePrefix` to get the keys as they were originally inserted. +4. **Bounded Iteration:** If iteration is necessary, implement bounds. Process only N items per transaction, potentially returning the last processed key so the next call can resume. This is complex pagination logic. +5. **Consider Alternatives:** Can you achieve the goal without iteration? Perhaps maintain a separate counter or index in storage instead of iterating to find items. + +```csharp +using Neo.SmartContract.Framework; +using Neo.SmartContract.Framework.Native; +using Neo.SmartContract.Framework.Services; +using System.Numerics; + +public class StorageFindDemo : SmartContract +{ + private static readonly StorageMap Balances = new StorageMap(Storage.CurrentContext, "BAL"); + + // ... SetBalance method ... + + // Example: Sum all balances (HIGH GAS RISK - Use only if dataset is small/controlled) + [Safe] + public static BigInteger GetTotalSupply() + { + // Iterate over keys starting with Balances.Prefix + // RemovePrefix gets the user address (key) without "BAL" + // DeserializeValues converts the stored balance (value) to BigInteger + Iterator<(ByteString Key, BigInteger Value)> iterator = + Storage.Find(Storage.CurrentReadOnlyContext, Balances.Prefix, FindFlags.RemovePrefix | FindFlags.DeserializeValues); + + BigInteger totalSupply = 0; + while (iterator.Next()) + { + var (key, value) = iterator.Value(); // Key is user addr, Value is balance + totalSupply += value; + } + return totalSupply; + } + + // Safer Pattern: Paginated Retrieval (Conceptual) + // Returns N balances starting after 'startKey' + public static Map GetPagedBalances(ByteString startKey, int count) + { + Iterator<(ByteString Key, BigInteger Value)> iterator = + Storage.Find(Storage.CurrentReadOnlyContext, Balances.Prefix, FindFlags.RemovePrefix | FindFlags.DeserializeValues); + + Map results = new Map(); + int found = 0; + bool pastStartKey = (startKey == null); // Start immediately if no startKey provided + + while (iterator.Next() && found < count) + { + var (key, value) = iterator.Value(); + if (!pastStartKey) + { + // Skip keys until we are past the startKey + if (key == startKey) // Or use MemoryCompare for proper ordering + { + pastStartKey = true; + } + continue; + } + // Add to results + results[(UInt160)key] = value; + found++; + } + return results; // Client needs to handle making subsequent calls with the last key + } +} +``` + +## Storage Design Patterns + +* **Combined Keys:** For mapping relationships (e.g., allowances `owner + spender`), concatenate keys to create a unique identifier. +* **Indexing:** If you need to look up data based on criteria other than the primary key, consider maintaining separate index maps (e.g., map `userName -> userAddress`). This adds complexity and write costs but can optimize read scenarios. +* **Counters:** Use dedicated storage keys for counters (`TotalSupplyKey`, `ProposalCounterKey`). +* **Versioning:** Store a version number with your data structures to facilitate future migrations (`struct MyDataV1 { int version; ... }`). + +Masting storage is key to building effective and efficient smart contracts. Always prioritize minimizing storage operations and carefully consider the GAS implications of `Storage.Find`. + +[Previous: Framework Overview](./README.md) | [Next: Runtime Services](./02-runtime.md) \ No newline at end of file diff --git a/docs/framework/04-framework-features/02-runtime.md b/docs/framework/04-framework-features/02-runtime.md new file mode 100644 index 000000000..61a9115b1 --- /dev/null +++ b/docs/framework/04-framework-features/02-runtime.md @@ -0,0 +1,179 @@ +# Runtime Services + +The static `Runtime` class in `Neo.SmartContract.Framework.Services` provides access to essential information and functionalities related to the execution environment of the smart contract. + +## Execution Context + +* **`Runtime.ExecutingScriptHash` (`UInt160`)**: Gets the script hash (address) of the contract currently being executed. +* **`Runtime.CallingScriptHash` (`UInt160`)**: Gets the script hash of the contract that called the current contract. Returns `null` if called directly by a transaction (the "entry point"). +* **`Runtime.EntryScriptHash` (`UInt160`)**: Gets the script hash of the contract that initiated the current execution chain (the first contract called in a transaction). +* **`Runtime.InvocationCounter` (`uint`)**: Gets how many contracts have been invoked in the current execution chain. Useful for preventing deep call stacks or certain reentrancy patterns. + +```csharp +using Neo.SmartContract.Framework; +using Neo.SmartContract.Framework.Services; + +public class RuntimeContextDemo : SmartContract +{ + public static UInt160 GetSelfHash() + { + return Runtime.ExecutingScriptHash; + } + + public static UInt160 GetCallerHash() + { + // This will be null if called directly from a transaction + return Runtime.CallingScriptHash; + } +} +``` + +## Blockchain Information + +* **`Runtime.Time` (`ulong`)**: Gets the timestamp (Unix epoch time in milliseconds) of the current block being processed. +* **`Runtime.Trigger` (`TriggerType`)**: Gets the trigger that caused the current execution (e.g., `Application`, `Verification`). +* **`Runtime.Network` (`uint`)**: Gets the magic number of the Neo network the contract is running on (e.g., MainNet, TestNet). +* **`Runtime.GetNetwork()` (`uint`)**: (Deprecated in newer versions, use `Runtime.Network`). +* **`Runtime.GasLeft` (`long`)**: Gets the amount of GAS remaining for the current execution context. Useful for controlling execution within GAS limits. +* **`Runtime.Platform` (`string`)**: Returns "Neo". +* **`Runtime.GetTrigger()` (`TriggerType`)**: (Deprecated, use `Runtime.Trigger`). +* **`Runtime.GetScriptContainer()` (`Transaction`)**: (Deprecated, use `Runtime.Transaction`). +* **`Runtime.Transaction` (`Transaction`)**: Gets the current transaction being executed. Note: Accessing transaction properties costs GAS. +* **`Runtime.CurrentSigners` (`Signer[]`)**: Gets the signers of the current transaction. + +```csharp +using Neo.SmartContract.Framework; +using Neo.SmartContract.Framework.Services; +using System.Numerics; + +public class BlockchainInfoDemo : SmartContract +{ + public static ulong GetCurrentBlockTime() + { + return Runtime.Time; + } + + public static TriggerType GetInvocationTrigger() + { + return Runtime.Trigger; + } + + public static BigInteger GetTransactionSystemFee() + { + Transaction tx = Runtime.Transaction; + // Accessing tx properties costs GAS + return tx?.SystemFee ?? -1; // Return -1 if tx is somehow null + } +} +``` + +## Logging & Notifications + +* **`Runtime.Log(string message)`**: Emits a log message. Logs are recorded on the blockchain (if the transaction succeeds) and can be viewed by explorers or node plugins. Useful for debugging and recording information. +* **`Runtime.Notify(string eventName, params object[] state)`**: Fires a notification event. Notifications include the executing contract hash and an array of `StackItem`s representing the `state`. Off-chain applications (dApps, explorers) typically subscribe to `Notify` events to react to contract state changes. This is preferred over `Log` for signalling important state transitions. +* **`Runtime.Notify(params object[] state)`**: (Deprecated, use version with eventName). Fires a notify event with a default event name. + +```csharp +using Neo.SmartContract.Framework; +using Neo.SmartContract.Framework.Services; +using System.Numerics; + +public class LogNotifyDemo : SmartContract +{ + // Using Notify with explicit event name (Recommended) + public static event System.Action OnValueUpdated; + + public static void UpdateValue(string key, BigInteger value) + { + Runtime.Log($"Attempting to update {key} to {value}"); + // ... store value ... + // Use Notify to signal the change to the outside world + Runtime.Notify("ValueUpdated", key, value); // Event name + parameters + + // Or use the C# event syntax, which compiles to Runtime.Notify + OnValueUpdated(key, value); + } +} +``` + +## Witness Verification (Authorization) + +One of the most critical runtime services is verifying signatures. + +* **`Runtime.CheckWitness(UInt160 hash)`**: Checks if the transaction was signed by the specified script hash (address). Returns `true` if the corresponding account is one of the transaction's signers and its signature covers the current execution context (based on Signer Scopes), `false` otherwise. +* **`Runtime.CheckWitness(ECPoint pubkey)`**: Checks if the transaction was signed by the account corresponding to the given public key. + +This is the standard way to implement access control in smart contracts – ensuring that only authorized accounts can perform sensitive actions. + +```csharp +using Neo.SmartContract.Framework; +using Neo.SmartContract.Framework.Attributes; +using Neo.SmartContract.Framework.Services; +using System.ComponentModel; + +namespace Neo.SmartContract.Examples +{ + public class CheckWitnessDemo : SmartContract + { + private static readonly StorageMap NameMap = new StorageMap(Storage.CurrentContext, "NAME"); + private static readonly byte[] OwnerKey = { 0x01 }; + + // Store owner on deploy + public static void _deploy(object data, bool update) + { + if (!update) + { + // Assume deployer's hash is passed in data or is the tx sender + // Example: Get owner from Transaction Signers (simplistic) + Signer[] signers = Runtime.Transaction.Signers; + if (signers.Length > 0) { + Storage.Put(Storage.CurrentContext, OwnerKey, signers[0].Account); + Runtime.Log("Owner set"); + } else { + Runtime.Log("Owner NOT set - no signer"); + } + } + } + + // Method only the owner can call + public static bool SetNameForOwner(string name) + { + ByteString owner = Storage.Get(Storage.CurrentContext, OwnerKey); + if (owner is null || owner.Length != 20) + { + Runtime.Log("Owner not set or invalid."); + return false; + } + + // Check if the stored owner signed the transaction + if (!Runtime.CheckWitness((UInt160)owner)) + { + Runtime.Log("CheckWitness failed for owner."); + return false; + } + + NameMap.Put("owner_name", name); + Runtime.Log("Name set successfully by owner."); + return true; + } + + // Method anyone can call if they sign + public static bool SetNameForSelf(UInt160 user, string name) + { + // Check if the 'user' parameter hash signed the transaction + if (!Runtime.CheckWitness(user)) + { + Runtime.Log($"CheckWitness failed for user {user}."); + return false; + } + NameMap.Put(user, name); // Use user hash as key + Runtime.Log($"Name set successfully for {user}."); + return true; + } + } +} +``` + +**Important:** `CheckWitness` relies on the `Signers` attached to the transaction and their specified `Scopes`. Ensure users sign transactions with appropriate scopes (`CalledByEntry` is common for simple calls) for `CheckWitness` to function as expected. + +[Previous: Storage](./01-storage.md) | [Next: Events](./03-events.md) \ No newline at end of file diff --git a/docs/framework/04-framework-features/03-events.md b/docs/framework/04-framework-features/03-events.md new file mode 100644 index 000000000..a1f7c81e8 --- /dev/null +++ b/docs/framework/04-framework-features/03-events.md @@ -0,0 +1,101 @@ +# Events (`Runtime.Notify`) + +Smart contract events are the standard way for contracts to broadcast information about state changes or significant occurrences to the outside world. Off-chain applications, explorers, and potentially other contracts can listen for these events to track activity and react accordingly. + +In the `Neo.SmartContract.Framework`, events are defined using standard C# delegate and event syntax, which the compiler translates into `Runtime.Notify` syscalls. + +## Defining Events + +1. **Declare a Delegate:** Define a `delegate` (typically derived from `System.Action<...>` or a custom delegate) specifying the signature of your event (the number and types of its parameters). Choose parameter types that are valid NeoVM types (e.g., `UInt160`, `BigInteger`, `string`, `ByteString`, `bool`). +2. **Declare an Event:** Declare a `public static event` using the delegate type. +3. **`[DisplayName]` Attribute:** **Crucially**, apply the `[DisplayName("MyEventName")]` attribute to the event declaration. This defines the `eventName` string that will be emitted in the `Runtime.Notify` call and recorded in the contract manifest's ABI section. This name is how external applications identify and subscribe to your event. Choose clear, descriptive names (camelCase is a common convention). + +```csharp +using Neo.SmartContract.Framework; +using Neo.SmartContract.Framework.Attributes; +using Neo.SmartContract.Framework.Services; +using System.Numerics; // For BigInteger +using System.ComponentModel; // For DisplayName + +namespace Neo.SmartContract.Examples +{ + public class EventDemo : SmartContract + { + // 1. Delegate Definitions (can reuse System.Action or define custom) + // public delegate void TransferredDelegate(UInt160 from, UInt160 to, BigInteger amount); + // public delegate void ApprovalDelegate(UInt160 owner, UInt160 spender, BigInteger amount); + + // 2. Event Declarations with DisplayName (Essential!) + [DisplayName("Transfer")] // Event name for off-chain listeners + public static event System.Action OnTransfer; + // Using System.Action<...> is often convenient + + [DisplayName("Approval")] + public static event System.Action OnApproval; + + [DisplayName("ItemAdded")] + public static event System.Action OnItemAdded; // ItemID, Description, Owner + + // ... rest of contract logic ... + } +} +``` + +## Firing Events + +To fire (emit) an event, simply call it like a static method from within your contract code, passing arguments matching the delegate signature. + +```csharp +// ... Inside a Transfer method ... +BigInteger amount = 100; +UInt160 from = GetSender(); +UInt160 to = GetRecipient(); + +// Update balances... +Balances.Put(from, currentFromBalance - amount); +Balances.Put(to, currentToBalance + amount); + +// 3. Fire the event - this compiles to Runtime.Notify("Transfer", from, to, amount) +OnTransfer(from, to, amount); +``` + +When `OnTransfer(...)` is executed: +1. The C# compiler translates this into `Runtime.Notify("Transfer", from, to, amount)`. +2. The `eventName` is "Transfer" (from `[DisplayName]`). +3. The `state` passed to `Runtime.Notify` is an array of `StackItem`s: `[from, to, amount]`. +4. This notification (containing the event name and state array) is recorded in the Application Log of the successful transaction. + +## Events vs. `Runtime.Log` + +* **`Runtime.Log`:** + * Intended primarily for **debugging** or simple informational messages during development. + * Emits only a single string message. + * Less structured, making reliable parsing by off-chain tools difficult. + * Consumes slightly less GAS than `Notify`. +* **Events (`Runtime.Notify`)**: + * The **standard mechanism** for signalling significant state changes or actions. + * Emits a structured notification: `(ExecutingContractHash, EventName, StateArray)`. + * The structure (`EventName` and parameter types) is defined in the manifest, allowing robust parsing and subscription by external tools. + * **Use events for all state changes or actions that external applications might need to track.** + +## Event Parameters and Indexing + +* **Parameter Types:** Can be any valid NeoVM data type, including primitives, `ByteString`, `UInt160/256`, `ECPoint`, `Map`, `List`, and serializable structs. +* **Limits:** There are limits on the size of the `state` array and the total size of the notification. Avoid overly large or deeply nested data in event parameters. +* **Indexing:** Off-chain applications often need to filter events based on specific parameters (e.g., find all `Transfer` events *to* a specific address). To facilitate this, consider making key identifiers (like addresses, token IDs) **primary parameters** in your event signature. While Neo nodes don't automatically index all parameters like some other chains, well-designed event structures and dedicated indexer services make filtering much easier. + * *Bad Example:* `OnDataStored(BigInteger recordId, Map allData)` - Hard to filter for events related to a specific user within `allData`. + * *Good Example:* `OnDataStored(UInt160 user, BigInteger recordId, string dataType)` - Easier to filter for events related to a specific `user`. + +## Listening to Events Off-Chain + +External applications (dApps, backend services, indexers) monitor the blockchain for `Runtime.Notify` calls. + +* **SDKs (Neon.js, neow3j, etc.):** Provide methods to: + * Fetch the Application Log for a specific transaction (`getapplicationlog` RPC call). This log contains all `Notify` calls (and `Log` messages) from that transaction. + * Subscribe to new blocks and process the Application Logs of included transactions. + * Filter logs based on the emitting contract hash and potentially the `eventName`. +* **Indexers/Middleware:** Services often run dedicated indexers that monitor the chain, parse Application Logs, decode event `state` arrays based on contract manifests (ABIs), and store the structured event data in a queryable database for dApps to consume easily. + +Designing clear, well-structured events with appropriate parameters and distinct names is crucial for building interactive and observable smart contracts and dApps on Neo. + +[Previous: Runtime Services](./02-runtime.md) | [Next: Contract Interaction](./04-contract-interaction.md) \ No newline at end of file diff --git a/docs/framework/04-framework-features/04-contract-interaction.md b/docs/framework/04-framework-features/04-contract-interaction.md new file mode 100644 index 000000000..6827c4f8c --- /dev/null +++ b/docs/framework/04-framework-features/04-contract-interaction.md @@ -0,0 +1,136 @@ +# Contract Interaction (`Contract.Call`) + +A core strength of smart contract platforms is **composability** – the ability for contracts to call and interact with each other. Neo N3 enables this primarily through the static `Contract.Call` method, allowing your contract to invoke methods on other deployed contracts, including native contracts. + +## `Contract.Call` Method Signature + +Located in `Neo.SmartContract.Framework.Services.Contract`. + +```csharp +// Standard method signature +public static extern object Call(UInt160 scriptHash, string method, CallFlags flags, params object[] args); +``` + +* **`scriptHash` (`UInt160`)**: The address (script hash) of the target contract to be called. This must be known by the calling contract (it can be hardcoded with `[InitialValue]`, stored, or passed as an argument). +* **`method` (`string`)**: The exact name of the **public static** method to invoke on the target contract. This name must match the one defined in the *target contract's manifest* (respecting any `[DisplayName]` attributes used there). Case sensitivity matters. +* **`flags` (`CallFlags`)**: A crucial enum controlling the permissions and state access granted during the execution of the called method. See details below. +* **`args` (`params object[]`)**: A variable number of arguments passed to the target method. These must match the expected parameter types and order defined in the target method's signature. Arguments must be valid NeoVM types. +* **Return Value (`object`)**: The value returned by the called method. This will be a `StackItem` representing the result, which often needs to be **checked for null** and then **cast** to the expected C# type (e.g., `(BigInteger)result`, `(bool)result`, `(UInt160)result`). + +## `CallFlags` Enum (Permissions) + +This enum dictates what the *called* contract is allowed to do during its execution initiated by this `Call`. Choosing the correct flags is vital for security and functionality. + +* **`None` (0)**: No permissions granted. The called method can only perform basic computations using its arguments. Very restrictive. +* **`ReadStates` (1)**: Allows reading state from the blockchain (e.g., its own storage via `Storage.Get`, other contract storage if permitted, `Runtime` info, `Ledger` info). Cannot write state or notify. +* **`WriteStates` (2)**: Allows modifying state (e.g., `Storage.Put`, `Storage.Delete`). Implicitly includes `ReadStates`. Cannot call other contracts or notify. +* **`AllowCall` (4)**: Allows the called contract to make further `Contract.Call` invocations to *other* contracts. +* **`AllowNotify` (8)**: Allows the called contract to emit events using `Runtime.Notify`. +* **Combined Flags (Bitwise OR):** + * **`States` (3)**: `ReadStates | WriteStates`. Allows reading and writing state. + * **`ReadOnly` (5)**: `ReadStates | AllowCall`. Allows reading state and making further *read-only* calls (useful for chaining view functions). + * **`All` (15)**: `ReadStates | WriteStates | AllowCall | AllowNotify`. Grants full permissions. Convenient but use cautiously – grants maximum capability to the called contract. + +**Best Practice:** Always use the **most restrictive `CallFlags` necessary** for the target method's intended operation. If a method only needs to return a value from its storage, use `CallFlags.ReadStates`. If it needs to update its state and call another contract, use `CallFlags.WriteStates | CallFlags.AllowCall` (or `CallFlags.All` if notifications are also needed). Using unnecessarily broad flags increases potential security risks if the target contract is malicious or buggy. + +## Dynamic Calls (`Contract.Call` with Variable Hash) + +While you often call known contracts (like native contracts or specific partner contracts), you can also call contracts whose script hashes are determined dynamically at runtime (e.g., passed as arguments, retrieved from storage). + +```csharp +// Example: Calling a dynamically provided token contract +public static bool ForwardTransfer(UInt160 tokenContractHash, UInt160 from, UInt160 to, BigInteger amount) +{ + Helper.Assert(Runtime.CheckWitness(from), "Sender must sign"); + Helper.Assert(tokenContractHash.IsValid && !tokenContractHash.IsZero, "Invalid token hash"); + + // Call the transfer method on the provided token contract hash + object result = Contract.Call(tokenContractHash, "transfer", CallFlags.All, from, to, amount, null); + + return result is bool && (bool)result; +} +``` +**Security Warning:** Be extremely careful when calling dynamically determined contracts. Ensure proper validation of the `tokenContractHash` and understand the potential risks of interacting with arbitrary, potentially malicious code. + +## Contract Permissions (`[ContractPermission]`) + +For a `Contract.Call` to succeed, the *calling* contract (your contract) must have permission declared in its *own* manifest to interact with the specified target contract and method. This acts as a firewall defined at deployment time. + +```csharp +// In MyCallingContract.cs + +// Grant permission to call 'balanceOf' on the GasToken native contract +[ContractPermission(nameof(GasToken), "balanceOf")] + +// Grant permission to call 'transfer' and 'approve' on a specific NEP-17 token hash +[ContractPermission("0x123abc...", "transfer", "approve")] + +// Grant permission to call *any* method ("*") on a specific Oracle contract +[ContractPermission("0x456def...", "*")] + +// Grant permission to call a specific method ("processData") on *any* contract ("*") +// Use wildcards carefully! +[ContractPermission("*", "processData")] + +// Grant permission to call any method on any contract (Least secure, generally avoid) +// [ContractPermission("*", "*")] + +public class MyCallingContract : SmartContract +{ + // ... methods using Contract.Call ... +} +``` +* If the required permission is missing from the calling contract's manifest, the `Contract.Call` attempt will **fail at runtime**, causing the transaction to FAULT. +* Specify the **minimum necessary permissions**. Avoid blanket `"*", "*"` permissions unless absolutely required and the implications are fully understood. + +## Error Handling & Return Values + +* `Contract.Call` returns an `object` which represents the `StackItem` returned by the target method. +* **Failure Modes:** The call can fail silently (return `null`) or throw an exception (causing the calling transaction to FAULT) if: + * The target `scriptHash` does not exist. + * The target `method` does not exist or has mismatching parameters. + * The provided `CallFlags` are insufficient for the operations performed by the target method. + * The calling contract lacks the necessary `[ContractPermission]` in its manifest. + * An `Assert` fails or an unhandled exception occurs within the *called* contract. + * Insufficient GAS is available for the call's execution. +* **Return Value Checking:** **ALWAYS** check the return value of `Contract.Call` before attempting to use or cast it. + * Check for `null` if the call might legitimately fail or return nothing. + * Use `Helper.Assert(result != null, "...")` if the call is expected to succeed and return a value. + * Safely cast the result: `if (result is BigInteger) { value = (BigInteger)result; } else { /* handle error */ }`. + +```csharp +// Example: Robustly handling Contract.Call return +[Safe] // Assume GetInfo is read-only +public static string GetTokenNameFromHash(UInt160 tokenHash) +{ + object result = Contract.Call(tokenHash, "symbol", CallFlags.ReadStates); // Read-only call + + // Check 1: Did the call itself fail/return null? + if (result == null) + { + Runtime.Log("Call to get symbol failed or returned null."); + return "Error: Call Failed"; + } + + // Check 2: Is the result the expected type? (Optional but safer) + if (result is ByteString || result is string) // Symbol could be ByteString or String + { + return Helper.AsString((ByteString)result); // Cast carefully + } + else + { + Runtime.Log("Call returned unexpected type for symbol."); + return "Error: Invalid Type"; + } +} +``` + +## GAS Considerations + +* `Contract.Call` has a significant base GAS cost. +* The execution of the *called* method also consumes GAS, which is charged to the *original transaction*. +* Ensure the initial transaction includes enough GAS to cover the costs of both the calling contract and any contracts it calls via `Contract.Call`. + +Contract interaction is fundamental to the Neo ecosystem, enabling complex dApps and protocols. However, it requires diligent attention to addresses, method names, arguments, `CallFlags`, permissions, return value checking, and GAS costs. + +[Previous: Events](./03-events.md) | [Next: Native Contracts](./05-native-contracts/README.md) \ No newline at end of file diff --git a/docs/framework/04-framework-features/05-native-contracts/ContractManagement.md b/docs/framework/04-framework-features/05-native-contracts/ContractManagement.md new file mode 100644 index 000000000..216835bc8 --- /dev/null +++ b/docs/framework/04-framework-features/05-native-contracts/ContractManagement.md @@ -0,0 +1,92 @@ +# Native Contract: ContractManagement + +Namespace: `Neo.SmartContract.Framework.Native` + +Provides functionalities for managing smart contracts on the blockchain: deployment, updates, and retrieval of contract information. + +## Key Methods + +* **`GetContract(UInt160 hash)` (`Contract`)**: Retrieves information about a deployed contract by its script hash. + * Returns a `Contract` object containing `Id`, `UpdateCounter`, `Hash`, `Nef`, and `Manifest`. + * Returns `null` if no contract with that hash exists. + * Requires read permissions (`CallFlags.ReadStates`). + +* **`HasMethod(UInt160 hash, string method, int pcount)` (`bool`)**: Checks if a deployed contract has a specific method with a given parameter count. + * Useful for verifying interface compatibility before calling. + * Requires read permissions (`CallFlags.ReadStates`). + +* **`Deploy(ByteString nefFile, string manifest)` (`Contract`)**: Deploys a new contract. + * `nefFile`: The compiled NeoVM bytecode (`.nef` file content). + * `manifest`: The contract's manifest (`.manifest.json` content). + * This method can *only* be called from the `_deploy` method of the contract being deployed. It automatically happens when a user sends a deployment transaction. + * You generally **do not call this directly** from your regular contract methods. + +* **`Deploy(ByteString nefFile, string manifest, object data)` (`Contract`)**: Deploys a new contract and passes `data` to its `_deploy` method. + * Same constraints as the version without `data`. + +* **`Update(ByteString nefFile, string manifest)`**: Updates the calling contract's code and manifest. + * Can only be called from within the contract itself. + * Requires the contract to have permission to call itself (`[ContractPermission(Runtime.ExecutingScriptHash, "update")]`). + * The logic for *who* can trigger an update must be implemented within the `update` method (e.g., checking `Runtime.CheckWitness` against an owner address). + +* **`Update(ByteString nefFile, string manifest, object data)`**: Updates the calling contract and passes `data` to its `_deploy` method (with `update` flag set to `true`). + +* **`Destroy()`**: Destroys the calling contract. + * Can only be called from within the contract itself. + * Requires the contract to have permission to call itself (`[ContractPermission(Runtime.ExecutingScriptHash, "destroy")]`). + * Logic for *who* can trigger destruction must be implemented (e.g., in a `destroy` method). + * Destroyed contracts cannot be called, and their storage becomes inaccessible. + +* **`GetMinimumDeploymentFee()` (`long`)**: Gets the minimum GAS fee required for contract deployment. + +## Example Usage + +```csharp +using Neo.SmartContract.Framework; +using Neo.SmartContract.Framework.Attributes; +using Neo.SmartContract.Framework.Native; +using Neo.SmartContract.Framework.Services; + +[ContractPermission("ContractManagement", "getContract", "hasMethod")] +[ContractPermission(nameof(ContractManagement), "update")] // Permission to update self +public class ContractMgmtDemo : SmartContract +{ + private static readonly byte[] OwnerKey = { 0x01 }; + + public static void _deploy(object data, bool update) + { + if (!update) + { + // Store owner on initial deployment (example) + Storage.Put(Storage.CurrentContext, OwnerKey, Runtime.Transaction.Sender); + } + } + + // Check if another contract exists + public static bool CheckContractExists(UInt160 hash) + { + Contract contract = ContractManagement.GetContract(hash); + return contract != null; + } + + // Check if another contract supports a specific method (e.g., NEP-17 transfer) + public static bool SupportsTransfer(UInt160 hash) + { + // NEP-17 transfer has 4 parameters: from, to, amount, data + return ContractManagement.HasMethod(hash, "transfer", 4); + } + + // Method to trigger an update (must be called by the owner) + public static void UpdateContract(ByteString nefFile, string manifest) + { + ByteString owner = Storage.Get(Storage.CurrentContext, OwnerKey); + if (owner == null || !Runtime.CheckWitness((UInt160)owner)) + { + throw new System.Exception("Unauthorized: Only owner can update."); + } + ContractManagement.Update(nefFile, manifest); + } +} +``` + +[Previous: Native Contracts Overview](./README.md) | [Next: CryptoLib](./CryptoLib.md) \ No newline at end of file diff --git a/docs/framework/04-framework-features/05-native-contracts/CryptoLib.md b/docs/framework/04-framework-features/05-native-contracts/CryptoLib.md new file mode 100644 index 000000000..ce8d48dc9 --- /dev/null +++ b/docs/framework/04-framework-features/05-native-contracts/CryptoLib.md @@ -0,0 +1,72 @@ +# Native Contract: CryptoLib + +Namespace: `Neo.SmartContract.Framework.Native` + +Provides common cryptographic functions useful within smart contracts. + +## Key Methods + +* **`Sha256(ByteString value)` (`ByteString`)**: Computes the SHA-256 hash of the input data. +* **`Sha256(byte[] value)` (`byte[]`)**: Computes the SHA-256 hash of the input data. + +* **`Ripemd160(ByteString value)` (`ByteString`)**: Computes the RIPEMD-160 hash of the input data. +* **`Ripemd160(byte[] value)` (`byte[]`)**: Computes the RIPEMD-160 hash of the input data. + +* **`Murmur32(ByteString value, uint seed)` (`ByteString`)**: Computes the Murmur32 hash. +* **`Murmur32(byte[] value, uint seed)` (`byte[]`)**: Computes the Murmur32 hash. + +* **`VerifyWithECDsaSecp256r1(ByteString message, ECPoint pubkey, ByteString signature)` (`bool`)**: Verifies a signature against a message and public key using the ECDSA algorithm with the secp256r1 curve (the standard curve used by Neo). +* **`VerifyWithECDsaSecp256r1(byte[] message, ECPoint pubkey, byte[] signature)` (`bool`)**: Verifies using `byte[]` inputs. + +* **`VerifyWithECDsaSecp256k1(ByteString message, ECPoint pubkey, ByteString signature)` (`bool`)**: Verifies a signature using the ECDSA algorithm with the secp256k1 curve (used by Bitcoin, Ethereum). +* **`VerifyWithECDsaSecp256k1(byte[] message, ECPoint pubkey, byte[] signature)` (`bool`)**: Verifies using `byte[]` inputs. + +* **`CheckMultisigWithECDsaSecp256r1(ByteString message, ECPoint[] pubkeys, ByteString[] signatures)` (`bool`)**: Verifies multiple signatures against a message for a multi-sig scenario using secp256r1. Requires a quorum (typically M-of-N) of valid signatures corresponding to the provided public keys. +* **`CheckMultisigWithECDsaSecp256r1(byte[] message, ECPoint[] pubkeys, byte[][] signatures)` (`bool`)**: Verifies using `byte[]` inputs. + +* **`CheckMultisigWithECDsaSecp256k1(ByteString message, ECPoint[] pubkeys, ByteString[] signatures)` (`bool`)**: Verifies multiple signatures using secp256k1. +* **`CheckMultisigWithECDsaSecp256k1(byte[] message, ECPoint[] pubkeys, byte[][] signatures)` (`bool`)**: Verifies using `byte[]` inputs. + +## Example Usage + +```csharp +using Neo.SmartContract.Framework; +using Neo.SmartContract.Framework.Attributes; +using Neo.SmartContract.Framework.Native; +using Neo.SmartContract.Framework.Services; + +[ContractPermission(nameof(CryptoLib), "*")] // Allow calling all CryptoLib methods +public class CryptoDemo : SmartContract +{ + public static ByteString HashDataSha256(string data) + { + ByteString byteData = (ByteString)data; + return CryptoLib.Sha256(byteData); + } + + public static ByteString HashDataRipemd160(string data) + { + ByteString byteData = (ByteString)data; + return CryptoLib.Ripemd160(byteData); + } + + // Example: Verify a signature provided externally + // Note: Typically Runtime.CheckWitness is used for transaction authorization. + // This method is for verifying signatures provided as arguments, e.g., for off-chain message signing. + public static bool VerifyExternalSignature( + ByteString message, + ECPoint publicKey, // Public key of the claimed signer + ByteString signature) // The signature provided + { + // Assumes secp256r1 curve (standard Neo) + return CryptoLib.VerifyWithECDsaSecp256r1(message, publicKey, signature); + } +} +``` + +**Note on Signature Verification:** + +* For standard transaction authorization (checking if a user signed the *current* transaction), always use `Runtime.CheckWitness(hashOrPubkey)`. It's more efficient and integrates directly with the transaction context. +* Use `CryptoLib.VerifyWithECDsa*` primarily for verifying signatures that were created *outside* the current transaction context, for example, verifying signed messages provided as arguments to your contract method. + +[Previous: ContractManagement](./ContractManagement.md) | [Next: GasToken (GAS)](./GasToken.md) \ No newline at end of file diff --git a/docs/framework/04-framework-features/05-native-contracts/GasToken.md b/docs/framework/04-framework-features/05-native-contracts/GasToken.md new file mode 100644 index 000000000..d38805968 --- /dev/null +++ b/docs/framework/04-framework-features/05-native-contracts/GasToken.md @@ -0,0 +1,88 @@ +# Native Contract: GasToken (GAS) + +Namespace: `Neo.SmartContract.Framework.Native` + +Represents the GAS token contract, which follows the NEP-17 fungible token standard. GAS is used to pay for network fees and computation. + +## NEP-17 Standard Methods + +Provides static methods corresponding to the NEP-17 standard: + +* **`Symbol` (`string`)**: Returns the token symbol ("GAS"). +* **`Decimals` (`byte`)**: Returns the number of decimals (8). +* **`TotalSupply()` (`System.Numerics.BigInteger`)**: Returns the total supply of GAS. +* **`BalanceOf(UInt160 account)` (`System.Numerics.BigInteger`)**: Gets the GAS balance of the specified account. +* **`Transfer(UInt160 from, UInt160 to, System.Numerics.BigInteger amount, object data)` (`bool`)**: Transfers GAS from the `from` account to the `to` account. + * Requires `Runtime.CheckWitness(from)` to be true, or the calling contract must have been approved by `from`. + * Returns `true` on success, `false` otherwise. + * The `data` parameter is passed to the `onNEP17Payment` method if `to` is a deployed contract. + +## Example Usage + +```csharp +using Neo.SmartContract.Framework; +using Neo.SmartContract.Framework.Attributes; +using Neo.SmartContract.Framework.Native; +using Neo.SmartContract.Framework.Services; +using System.Numerics; + +// Need permission to call GasToken methods +[ContractPermission(nameof(GasToken), "symbol", "decimals", "balanceOf", "transfer")] +public class GasDemo : SmartContract +{ + public static string GetGasSymbol() + { + return GasToken.Symbol; + } + + public static byte GetGasDecimals() + { + return GasToken.Decimals; + } + + public static BigInteger GetAccountGasBalance(UInt160 account) + { + return GasToken.BalanceOf(account); + } + + // Example: A contract method that requires a GAS payment + public static bool PayFeeWithGas(UInt160 payer, BigInteger requiredFee) + { + // Check if the payer signed the transaction + if (!Runtime.CheckWitness(payer)) return false; + + // Transfer the required fee from the payer to this contract + // Note: The contract itself needs GAS to execute this call + bool success = GasToken.Transfer(payer, Runtime.ExecutingScriptHash, requiredFee, null); + + if (success) + { + Runtime.Log("Fee paid successfully."); + // ... perform action that required the fee ... + } + else + { + Runtime.Log("Fee payment transfer failed."); + } + return success; + } + + // Method called when this contract receives GAS + public static void OnNEP17Payment(UInt160 from, BigInteger amount, object data) + { + // Check if the payment was GAS + if (Runtime.CallingScriptHash == GasToken.Hash) + { + Runtime.Log($"Received {amount * BigInteger.Pow(10, -GasToken.Decimals)} GAS from {from}"); + // Process the received GAS payment + } + } +} +``` + +**Important Considerations:** + +* **Fees:** Remember that calling any contract method, including `GasToken.Transfer`, requires GAS fees for the transaction itself. +* **Authorization:** `GasToken.Transfer` strictly enforces authorization. The `from` address must either sign the transaction (`Runtime.CheckWitness`) or have previously approved the calling contract via the standard NEP-17 `approve` mechanism (though `approve` is not directly exposed in the `GasToken` C# wrapper, it can be called via `Contract.Call`). + +[Previous: CryptoLib](./CryptoLib.md) | [Next: LedgerContract](./Ledger.md) \ No newline at end of file diff --git a/docs/framework/04-framework-features/05-native-contracts/Ledger.md b/docs/framework/04-framework-features/05-native-contracts/Ledger.md new file mode 100644 index 000000000..a0ff86d11 --- /dev/null +++ b/docs/framework/04-framework-features/05-native-contracts/Ledger.md @@ -0,0 +1,88 @@ +# Native Contract: LedgerContract + +Namespace: `Neo.SmartContract.Framework.Native` + +Provides access to historical ledger information, such as blocks and transactions. + +## Key Methods + +* **`CurrentHash` (`UInt256`)**: Gets the hash of the latest block. +* **`CurrentIndex` (`uint`)**: Gets the height/index of the latest block. +* **`GetBlock(uint index)` (`Block`)**: Retrieves block information by block height/index. + * Returns a `Block` object or `null` if the index is invalid. +* **`GetBlock(UInt256 hash)` (`Block`)**: Retrieves block information by block hash. + * Returns a `Block` object or `null` if the hash doesn't correspond to a block. +* **`GetTransaction(UInt256 hash)` (`Transaction`)**: Retrieves transaction information by transaction hash. + * Returns a `Transaction` object or `null` if the hash doesn't correspond to a transaction. +* **`GetTransactionFromBlock(UInt256 blockHash, int txIndex)` (`Transaction`)**: Retrieves a transaction by its index within a specific block (identified by hash). +* **`GetTransactionFromBlock(uint blockIndex, int txIndex)` (`Transaction`)**: Retrieves a transaction by its index within a specific block (identified by height). +* **`GetTransactionHeight(UInt256 hash)` (`uint`)**: Gets the block height in which a specific transaction was included. Returns `-1` (as a `uint`, so max value) if not found. + +## `Block` Object Properties + +The `Block` object returned by `GetBlock` has properties like: + +* `Hash` (`UInt256`) +* `Version` (`uint`) +* `PrevHash` (`UInt256`) +* `MerkleRoot` (`UInt256`) +* `Timestamp` (`ulong`) (milliseconds since Unix epoch) +* `Nonce` (`ulong`) +* `Index` (`uint`) (Block height) +* `NextConsensus` (`UInt160`) +* `Witness` (`Witness`) +* `TransactionsCount` (`int`) + +## `Transaction` Object Properties + +The `Transaction` object returned by `GetTransaction*` (and `Runtime.Transaction`) has properties like: + +* `Hash` (`UInt256`) +* `Version` (`byte`) +* `Nonce` (`uint`) +* `Sender` (`UInt160`) +* `SystemFee` (`long`) +* `NetworkFee` (`long`) +* `ValidUntilBlock` (`uint`) +* `Signers` (`Signer[]`) +* `Attributes` (`TransactionAttribute[]`) +* `Script` (`ByteString`) +* `Witnesses` (`Witness[]`) + +## Example Usage + +```csharp +using Neo.SmartContract.Framework; +using Neo.SmartContract.Framework.Attributes; +using Neo.SmartContract.Framework.Native; +using Neo.SmartContract.Framework.Services; +using System.Numerics; + +// Permission to call LedgerContract methods +[ContractPermission(nameof(LedgerContract), "currentIndex", "getBlock", "getTransactionHeight")] +public class LedgerDemo : SmartContract +{ + public static uint GetCurrentBlockHeight() + { + return LedgerContract.CurrentIndex; + } + + // Get timestamp of a specific block by height + public static ulong GetBlockTimestamp(uint index) + { + Block block = LedgerContract.GetBlock(index); + // Accessing block properties costs GAS + return block?.Timestamp ?? 0; // Return 0 if block not found + } + + // Get the block height a transaction was included in + public static uint GetTxHeight(UInt256 txHash) + { + return LedgerContract.GetTransactionHeight(txHash); + } +} +``` + +**GAS Costs:** Accessing properties of `Block` and `Transaction` objects retrieved from the `LedgerContract` incurs GAS costs. + +[Previous: GasToken (GAS)](./GasToken.md) | [Next: NameService (NNS)](./NameService.md) \ No newline at end of file diff --git a/docs/framework/04-framework-features/05-native-contracts/NameService.md b/docs/framework/04-framework-features/05-native-contracts/NameService.md new file mode 100644 index 000000000..ec66b45f6 --- /dev/null +++ b/docs/framework/04-framework-features/05-native-contracts/NameService.md @@ -0,0 +1,125 @@ +# Native Contract: NameService (NNS) + +Namespace: `Neo.SmartContract.Framework.Native` + +Provides interaction with the Neo Name Service (NNS), a decentralized system for mapping human-readable names (like `mywallet.neo`) to Neo addresses or other data. + +## Key Methods + +* **`AddRoot(string root)`**: Adds a new root domain (e.g., ".neo", ".gas"). Requires authorization from the NNS admins (usually the Neo Council). + +* **`Register(string name, UInt160 owner)` (`bool`)**: Registers a second-level domain name (e.g., "mywallet" under the ".neo" root). + * `name`: The full domain name (e.g., "mywallet.neo"). + * `owner`: The address that will own the registered name. + * Requires payment of a registration fee in GAS. + * Requires `Runtime.CheckWitness` of the account paying the fee. + * Returns `true` on success. + +* **`Renew(string name)` (`uint`)**: Renews the registration of a domain name, extending its validity period. + * Requires payment of a renewal fee in GAS. + * Returns the new expiration timestamp. + +* **`SetAdmin(string name, UInt160 admin)`**: Sets an administrative address for a domain. The admin can manage records but doesn't own the name. + * Requires `Runtime.CheckWitness` of the domain owner. + +* **`SetRecord(string name, RecordType type, string data)`**: Sets the data associated with a domain name record. + * `name`: The domain name (e.g., "mywallet.neo" or subdomains like "sub.mywallet.neo"). + * `type` (`RecordType` enum): The type of record (e.g., `A` for N3 address, `CNAME` for alias, `TXT` for text data). + * `data`: The content of the record. + * Requires `Runtime.CheckWitness` of the domain owner or admin. + +* **`GetRecord(string name, RecordType type)` (`string`)**: Retrieves the data for a specific record type associated with a domain name. + * Returns `null` if the name or record type doesn't exist. + +* **`DeleteRecord(string name, RecordType type)`**: Deletes a specific record. + * Requires `Runtime.CheckWitness` of the domain owner or admin. + +* **`GetAllRecords(string name)` (`Iterator`)**: Returns an iterator over all records (type and data) associated with a name. + +* **`Resolve(string name, RecordType type)` (`string`)**: Resolves a domain name to its corresponding record data, handling CNAME records recursively. + * This is the primary method for looking up addresses or other data associated with a name. + +* **`OwnerOf(string name)` (`UInt160`)**: Gets the owner address of a registered domain name. + +* **`Transfer(UInt160 to, string name)` (`bool`)**: Transfers ownership of a domain name. + * Requires `Runtime.CheckWitness` of the current owner. + * Requires payment of a transfer fee in GAS. + * Returns `true` on success. + +* **`IsAvailable(string name)` (`bool`)**: Checks if a domain name is currently available for registration. + +* **`GetPrice()` (`long`)**: Gets the current registration fee in GAS. + +## `RecordType` Enum + +* `A`: N3 Address (UInt160) +* `CNAME`: Canonical Name (alias to another NNS name) +* `TXT`: Text record +* `AAAA`: IPv6 Address + +## Example Usage + +```csharp +using Neo.SmartContract.Framework; +using Neo.SmartContract.Framework.Attributes; +using Neo.SmartContract.Framework.Native; +using Neo.SmartContract.Framework.Services; + +// Permissions required for the methods used +[ContractPermission(nameof(NameService), "resolve", "isAvailable", "register")] +[ContractPermission(nameof(GasToken), "transfer")] // Needed for registration fee +public class NnsDemo : SmartContract +{ + // Resolve a .neo address + public static string ResolveAddress(string name) + { + // name should be like "mywallet.neo" + return NameService.Resolve(name, RecordType.A); + } + + // Check if a name is available + public static bool CheckAvailability(string name) + { + // name should be like "newname.neo" + return NameService.IsAvailable(name); + } + + // Register a name for the caller (requires caller to pay GAS) + public static bool RegisterNameForCaller(string name) + { + UInt160 caller = Runtime.Transaction.Sender; // Or get from Signers + if (!Runtime.CheckWitness(caller)) return false; // Ensure caller signed + + if (!NameService.IsAvailable(name)) + { + Runtime.Log("Name not available."); + return false; + } + + long price = NameService.GetPrice(); + + // Transfer registration fee to NNS contract + if (!GasToken.Transfer(caller, NameService.Hash, price, null)) + { + Runtime.Log("Failed to pay registration fee."); + return false; + } + + // Attempt registration + bool success = NameService.Register(name, caller); + if (success) + { + Runtime.Log("Name registered successfully."); + } else { + // Important: If registration fails, the fee is NOT automatically refunded. + // Need to handle potential refunds if necessary. + Runtime.Log("Name registration failed after payment."); + } + return success; + } +} +``` + +**Important:** Interacting with NNS methods that require fees (`Register`, `Renew`, `Transfer`) involves transferring GAS to the `NameService.Hash` address within the same transaction. + +[Previous: LedgerContract](./Ledger.md) | [Next: NeoToken (NEO)](./NeoToken.md) \ No newline at end of file diff --git a/docs/framework/04-framework-features/05-native-contracts/NeoToken.md b/docs/framework/04-framework-features/05-native-contracts/NeoToken.md new file mode 100644 index 000000000..ec26072ca --- /dev/null +++ b/docs/framework/04-framework-features/05-native-contracts/NeoToken.md @@ -0,0 +1,98 @@ +# Native Contract: NeoToken (NEO) + +Namespace: `Neo.SmartContract.Framework.Native` + +Represents the NEO token contract. NEO is the governance token of the Neo blockchain. It follows the NEP-17 fungible token standard and also includes methods related to governance functions like voting and GAS distribution. + +## NEP-17 Standard Methods + +Provides static methods corresponding to the NEP-17 standard: + +* **`Symbol` (`string`)**: Returns the token symbol ("NEO"). +* **`Decimals` (`byte`)**: Returns the number of decimals (0, as NEO is indivisible). +* **`TotalSupply()` (`System.Numerics.BigInteger`)**: Returns the total supply of NEO (100,000,000). +* **`BalanceOf(UInt160 account)` (`System.Numerics.BigInteger`)**: Gets the NEO balance of the specified account. +* **`Transfer(UInt160 from, UInt160 to, System.Numerics.BigInteger amount, object data)` (`bool`)**: Transfers NEO from the `from` account to the `to` account. + * Requires `Runtime.CheckWitness(from)` or prior approval. + * NEO amount must be a whole number (no decimals). + * Returns `true` on success. + +## Governance Methods + +* **`UnclaimedGas(UInt160 account, uint end)` (`System.Numerics.BigInteger`)**: Calculates the amount of unclaimed GAS generated by the NEO held in the account up to (but not including) the specified block index `end`. +* **`RegisterCandidate(ECPoint pubkey)` (`bool`)**: Registers the account associated with the public key as a candidate for the Neo Council. + * Requires `Runtime.CheckWitness` of the account. + * Requires a registration fee in GAS. +* **`UnregisterCandidate(ECPoint pubkey)` (`bool`)**: Unregisters a candidate. + * Requires `Runtime.CheckWitness` of the account. +* **`Vote(UInt160 account, ECPoint voteTo)` (`bool`)**: Casts a vote from the `account` to a specific candidate public key (`voteTo`). + * `voteTo` can be `null` to cancel a previous vote. + * The voting weight is proportional to the NEO balance of the `account`. + * Requires `Runtime.CheckWitness(account)`. +* **`GetCandidates()` (`(ECPoint PublicKey, BigInteger Votes)[]`)**: Returns a list of all registered candidates and their current vote counts. +* **`GetNextBlockValidators()` (`ECPoint[]`)**: Returns the public keys of the validators selected for the next block (subset of the Neo Council). +* **`GetCommittee()` (`ECPoint[]`)**: Returns the public keys of the current Neo Council members. +* **`GetCommitteeAddress()` (`UInt160`)**: Gets the multi-sig address of the current Neo committee. +* **`GetAccountState(UInt160 account)` (`NeoAccountState`)**: Returns the state of an account related to voting, including balance and vote target. + * `NeoAccountState` has properties: `Balance` (`BigInteger`), `BalanceHeight` (`uint`), `VoteTo` (`ECPoint`). + +## Example Usage + +```csharp +using Neo.SmartContract.Framework; +using Neo.SmartContract.Framework.Attributes; +using Neo.SmartContract.Framework.Native; +using Neo.SmartContract.Framework.Services; +using System.Numerics; + +[ContractPermission(nameof(NeoToken), "balanceOf", "vote", "getCommittee", "unclaimedGas")] +[ContractPermission(nameof(GasToken), "transfer")] // Needed for candidate registration fee +public class NeoDemo : SmartContract +{ + public static BigInteger GetAccountNeoBalance(UInt160 account) + { + return NeoToken.BalanceOf(account); + } + + // Calculate unclaimed GAS for an account up to the current block + public static BigInteger CheckUnclaimedGas(UInt160 account) + { + uint currentBlock = LedgerContract.CurrentIndex; + return NeoToken.UnclaimedGas(account, currentBlock); + } + + // Vote for a specific candidate (requires caller to sign) + public static bool VoteForCandidate(ECPoint candidatePublicKey) + { + UInt160 voter = Runtime.Transaction.Sender; // Example: Get voter from sender + if (!Runtime.CheckWitness(voter)) return false; + + return NeoToken.Vote(voter, candidatePublicKey); + } + + // Get the list of current committee members' public keys + public static ECPoint[] GetCurrentCommittee() + { + return NeoToken.GetCommittee(); + } + + // Example: Registering as a candidate (Simplified - assumes caller has enough GAS) + // public static bool Register(ECPoint callerPublicKey) + // { + // UInt160 callerAccount = Contract.CreateStandardAccount(callerPublicKey); + // if (!Runtime.CheckWitness(callerAccount)) return false; + + // long regFee = PolicyContract.GetCandidateRegistrationFee(); + // if (!GasToken.Transfer(callerAccount, NeoToken.Hash, regFee, null)) return false; + + // return NeoToken.RegisterCandidate(callerPublicKey); + // } +} +``` + +**Important:** + +* NEO is indivisible (`Decimals = 0`). Transfers must use whole numbers. +* Governance actions like voting and candidate registration often require `Runtime.CheckWitness` and may involve GAS fees. + +[Previous: NameService (NNS)](./NameService.md) | [Next: OracleContract](./Oracle.md) \ No newline at end of file diff --git a/docs/framework/04-framework-features/05-native-contracts/Oracle.md b/docs/framework/04-framework-features/05-native-contracts/Oracle.md new file mode 100644 index 000000000..0fa01a71b --- /dev/null +++ b/docs/framework/04-framework-features/05-native-contracts/Oracle.md @@ -0,0 +1,134 @@ +# Native Contract: OracleContract + +Namespace: `Neo.SmartContract.Framework.Native` + +Provides the functionality for smart contracts to request data from external resources (URLs) off-chain. + +## Oracle Process + +1. **Request:** Your smart contract calls `OracleContract.Request` specifying the URL, filter (optional), callback method, user data (optional), and GAS for the response. +2. **Fee Payment:** Your contract transfers the required GAS fee to the `OracleContract.Hash`. +3. **Off-Chain Fetching:** Designated Oracle nodes monitor these requests, fetch data from the URL, optionally apply the filter, and reach consensus on the result. +4. **Callback:** An Oracle node submits a transaction that calls the specified callback method (`__callback`) in your contract, passing the original user data and the fetched result. + +## Key Methods + +* **`Request(string url, string filter, string callback, object userData, long gasForResponse)`**: Initiates an oracle request. + * `url`: The HTTP/HTTPS URL to fetch data from (must be HTTPS unless explicitly allowed by Oracle policy). + * `filter` (optional): A JSONPath filter string (e.g., `$.data.price`) to extract specific data from the JSON response. Use `null` or empty string for no filter. + * `callback`: The name of the *public static* method in your *calling* contract that will receive the response (e.g., `"__callback"`). + * `userData` (optional): Any data you want to be passed back to your callback method to help identify the request. + * `gasForResponse`: The amount of GAS provided to execute the callback method. This GAS is consumed by the Oracle transaction invoking your callback. + * **Requires payment:** Your contract must transfer GAS to `OracleContract.Hash` to cover the request fee (obtained via `GetPrice`). + +* **`GetPrice()` (`long`)**: Returns the minimum GAS fee required per oracle request (excluding `gasForResponse`). + +* **`Verify()` (`bool`)**: Used internally by Oracle nodes to verify their callback transaction. Returns `true` if the calling script hash belongs to an active Oracle node. You typically **do not call this** directly. + +## Implementing the Callback Method + +Your contract *must* implement the public static callback method specified in the `Request` call. + +```csharp +public static void __callback(string url, object userData, int code, ByteString result) +{ + // ... process the oracle response ... +} +``` + +* **`url` (`string`)**: The original URL requested. +* **`userData` (`object`)**: The user data you passed in the `Request` call. +* **`code` (`int`)**: The Oracle response code (enum `OracleResponseCode`). `Success` (0) indicates a successful fetch. +* **`result` (`ByteString`)**: The fetched data (after filtering, if applied), as raw bytes. You often need to convert this (`Helper.AsString(result)`) or parse it (e.g., using `StdLib.JsonDeserialize`). + +**Important:** The callback method is invoked in a *separate transaction* initiated by an Oracle node. It must contain logic to verify that the caller is a legitimate Oracle node (e.g., by checking `Runtime.CallingScriptHash == OracleContract.Hash` or using `OracleContract.Verify()`, although direct use is less common) before trusting the `result`. + +## Example Usage + +```csharp +using Neo.SmartContract.Framework; +using Neo.SmartContract.Framework.Attributes; +using Neo.SmartContract.Framework.Native; +using Neo.SmartContract.Framework.Services; +using System.Numerics; + +[ContractPermission(nameof(OracleContract), "request", "getPrice")] +[ContractPermission(nameof(GasToken), "transfer")] +[ContractPermission(nameof(StdLib), "jsonDeserialize", "atoi")] // For processing JSON result +public class OracleDemo : SmartContract +{ + // Store results associated with user data + private static readonly StorageMap PendingRequests = new StorageMap(Storage.CurrentContext, "PENDING"); + private static readonly StorageMap Results = new StorageMap(Storage.CurrentContext, "RESULT"); + + // Request the price of NEO from a hypothetical API + public static void RequestNeoPrice(string requestId) + { + string url = "https://api.example.com/prices/neo-usd"; // HTTPS is usually required + string filter = "$.data.price"; // JSONPath filter to extract the price field + string callbackMethod = "__callback"; // Must match the method name below + long gasForResponse = Oracle.MinimumResponseFee; // Use minimum defined by Oracle nodes + + // Check if caller signed + if (!Runtime.CheckWitness(Runtime.Transaction.Sender)) + throw new System.Exception("Unauthorized"); + + // Calculate total GAS needed + long requestFee = OracleContract.GetPrice(); + long totalGas = requestFee + gasForResponse; + + // Pay the fee + if (!GasToken.Transfer(Runtime.Transaction.Sender, OracleContract.Hash, totalGas, null)) + throw new System.Exception("Failed to pay Oracle fee"); + + // Make the request, passing our unique requestId as userData + OracleContract.Request(url, filter, callbackMethod, requestId, gasForResponse); + + // Mark request as pending (optional, for tracking) + PendingRequests.Put(requestId, true); + Runtime.Log("Oracle request sent."); + } + + // Callback method MUST be public static + public static void __callback(string url, object userData, int code, ByteString result) + { + // IMPORTANT: Verify the caller is the OracleContract native hash + if (Runtime.CallingScriptHash != OracleContract.Hash) + { + Runtime.Log("Callback verification failed!"); + throw new System.Exception("Unauthorized callback caller."); + } + + Runtime.Log($"Oracle callback received for URL: {url}"); + string requestId = (string)userData; // Retrieve our unique ID + + if (code != OracleResponseCode.Success) + { + Runtime.Log($"Oracle request failed with code: {code}"); + Results.Put(requestId, "Error: " + code); + } + else + { + Runtime.Log("Oracle request successful."); + // Process the result (example: assume price is a string number) + string priceString = Helper.AsString(result); + Results.Put(requestId, priceString); + // BigInteger price = StdLib.Atoi(priceString, 10); // Convert if integer + // Map json = (Map)StdLib.JsonDeserialize(result); // Parse if JSON + } + + // Mark request as completed + PendingRequests.Delete(requestId); + } + + // Method to retrieve the stored result + public static string GetResult(string requestId) + { + return Results.GetString(requestId); + } +} +``` + +**Security:** Always verify the caller in your callback method (`Runtime.CallingScriptHash == OracleContract.Hash`) before trusting the provided data. + +[Previous: NeoToken (NEO)](./NeoToken.md) | [Next: PolicyContract](./Policy.md) \ No newline at end of file diff --git a/docs/framework/04-framework-features/05-native-contracts/Policy.md b/docs/framework/04-framework-features/05-native-contracts/Policy.md new file mode 100644 index 000000000..b78082deb --- /dev/null +++ b/docs/framework/04-framework-features/05-native-contracts/Policy.md @@ -0,0 +1,78 @@ +# Native Contract: PolicyContract + +Namespace: `Neo.SmartContract.Framework.Native` + +Allows querying and (with authorization) setting various network-level policies and fees. + +## Key Methods + +**Read Methods (Generally accessible):** + +* **`GetMaxTransactionsPerBlock()` (`uint`)**: Gets the maximum number of transactions allowed in a block. +* **`GetMaxBlockSize()` (`uint`)**: Gets the maximum size (in bytes) allowed for a block. +* **`GetMaxBlockSystemFee()` (`long`)**: Gets the maximum system fee (in GAS) allowed for a block. +* **`GetFeePerByte()` (`long`)**: Gets the network fee charged per byte of transaction size. +* **`IsBlocked(UInt160 account)` (`bool`)**: Checks if the specified account is currently blocked from executing transactions on the network. +* **`GetExecFeeFactor()` (`uint`)**: Gets the execution fee factor used in GAS calculations for opcodes. +* **`GetStoragePrice()` (`uint`)**: Gets the price (in GAS) per byte for storing data. +* **`GetCandidateRegistrationFee()` (`long`)**: Gets the fee required to register as a Neo Council candidate. + +**Write Methods (Require Neo Council authorization):** + +*These methods can typically only be called successfully by the Neo Council multi-sig address.* Your contract usually won't call these directly, but they define the configurable parameters. + +* **`SetMaxTransactionsPerBlock(uint value)`**: Sets the maximum transactions per block. +* **`SetMaxBlockSize(uint value)`**: Sets the maximum block size. +* **`SetMaxBlockSystemFee(long value)`**: Sets the maximum block system fee. +* **`SetFeePerByte(long value)`**: Sets the network fee per byte. +* **`BlockAccount(UInt160 account)`**: Blocks an account. +* **`UnblockAccount(UInt160 account)`**: Unblocks an account. +* **`SetExecFeeFactor(uint value)`**: Sets the execution fee factor. +* **`SetStoragePrice(uint value)`**: Sets the storage price. +* **`SetCandidateRegistrationFee(long value)`**: Sets the candidate registration fee. + +## Example Usage (Reading Policies) + +```csharp +using Neo.SmartContract.Framework; +using Neo.SmartContract.Framework.Attributes; +using Neo.SmartContract.Framework.Native; +using Neo.SmartContract.Framework.Services; +using System.Numerics; + +// Permissions to read policy values +[ContractPermission(nameof(PolicyContract), "getFeePerByte", "getStoragePrice", "isBlocked")] +public class PolicyDemo : SmartContract +{ + // Get the current network fee per transaction byte + public static long GetNetworkFeePerByte() + { + return PolicyContract.GetFeePerByte(); + } + + // Get the current GAS price for storing 1 byte + public static uint GetCurrentStoragePrice() + { + return PolicyContract.GetStoragePrice(); + } + + // Check if a specific user account is blocked + public static bool CheckIfAccountBlocked(UInt160 account) + { + return PolicyContract.IsBlocked(account); + } + + // Estimate storage cost for a value (simplified) + public static BigInteger EstimateStorageCost(ByteString value) + { + // Note: Key size also matters in real cost + uint pricePerByte = PolicyContract.GetStoragePrice(); + int valueLength = value.Length; + return pricePerByte * valueLength; + } +} +``` + +Contracts typically interact with the `PolicyContract` to retrieve current fee settings or check account statuses rather than attempting to modify policies. + +[Previous: OracleContract](./Oracle.md) | [Next: RoleManagement](./RoleManagement.md) \ No newline at end of file diff --git a/docs/framework/04-framework-features/05-native-contracts/README.md b/docs/framework/04-framework-features/05-native-contracts/README.md new file mode 100644 index 000000000..6e7e7b41f --- /dev/null +++ b/docs/framework/04-framework-features/05-native-contracts/README.md @@ -0,0 +1,42 @@ +# Native Contracts + +Neo N3 includes several built-in "native" contracts that provide core blockchain functionalities. These contracts exist from the genesis block and have fixed script hashes. The `Neo.SmartContract.Framework` provides dedicated static classes within the `Neo.SmartContract.Framework.Native` namespace to interact with these contracts easily, acting as wrappers around `Contract.Call`. + +Using these native contract wrappers is generally preferred over calling them directly via `Contract.Call` as they provide type safety and convenience. + +## Available Native Contract Wrappers + +* **[`ContractManagement`](./ContractManagement.md):** Deploying, updating, and destroying contracts. +* **[`CryptoLib`](./CryptoLib.md):** Cryptographic functions (hashing, signature verification). +* **[`GasToken` (GAS)](./GasToken.md): NEP-17 methods for the GAS token. +* **[`LedgerContract`](./Ledger.md):** Accessing block and transaction data. +* **[`NameService` (NNS)](./NameService.md): Neo Name Service interactions (domain registration/resolution). +* **[`NeoToken` (NEO)](./NeoToken.md): NEP-17 methods and governance functions for the NEO token. +* **[`OracleContract`](./Oracle.md):** Requesting data from off-chain URLs. +* **[`PolicyContract`](./Policy.md):** Querying and (if authorized) setting network policies (fees, blocked accounts). +* **[`RoleManagement`](./RoleManagement.md):** Assigning and querying node roles. +* **[`StdLib`](./StdLib.md):** Standard library functions (serialization, data conversion). + +## Permissions + +Just like calling any other contract, your contract needs permission declared in its manifest (`[ContractPermission]`) to call methods on native contracts. You can reference native contracts by their well-known names: + +```csharp +using Neo.SmartContract.Framework.Attributes; + +// Example: Allow calling GasToken's balanceOf and Ledger's currentIndex +[ContractPermission("GasToken", "balanceOf")] +[ContractPermission("LedgerContract", "currentIndex")] + +// Allow calling any method on OracleContract +[ContractPermission("OracleContract", "*")] + +public class NativeCallDemo : SmartContract +{ + // ... +} +``` + +Refer to the specific pages for details on each native contract wrapper. + +[Next: ContractManagement](./ContractManagement.md) \ No newline at end of file diff --git a/docs/framework/04-framework-features/05-native-contracts/RoleManagement.md b/docs/framework/04-framework-features/05-native-contracts/RoleManagement.md new file mode 100644 index 000000000..88941577e --- /dev/null +++ b/docs/framework/04-framework-features/05-native-contracts/RoleManagement.md @@ -0,0 +1,55 @@ +# Native Contract: RoleManagement + +Namespace: `Neo.SmartContract.Framework.Native` + +Provides a method to designate node roles within the Neo network. + +## Node Roles (`Role` enum) + +Defines specific roles nodes can have: + +* `StateValidator`: Nodes responsible for validating the state root. +* `Oracle`: Nodes designated to fulfill oracle requests. +* `NeoFsAlphabet`: Nodes participating in NeoFS alphabet operations. +* `P2PNotary`: Nodes acting as notaries in P2P protocols. + +## Key Methods + +* **`DesignateAsRole(Role role, ECPoint[] nodes)`**: Designates a list of nodes (by public key) for a specific role. + * This method can **only be called by the Neo Council** multi-sig address. + * Your contract typically won't call this directly. + +* **`GetDesignatedByRole(Role role, uint index)` (`ECPoint[]`)**: Retrieves the list of nodes designated for a specific role at a given block index (height). + * This allows contracts to find nodes with specific capabilities (like Oracles). + +## Example Usage (Reading Roles) + +```csharp +using Neo.SmartContract.Framework; +using Neo.SmartContract.Framework.Attributes; +using Neo.SmartContract.Framework.Native; +using Neo.SmartContract.Framework.Services; + +// Permission to read designated Oracle nodes +[ContractPermission(nameof(RoleManagement), "getDesignatedByRole")] +[ContractPermission(nameof(LedgerContract), "currentIndex")] // To get current block height +public class RoleDemo : SmartContract +{ + // Get the list of designated Oracle nodes for the current block + public static ECPoint[] GetOracleNodes() + { + uint currentBlock = LedgerContract.CurrentIndex; + return RoleManagement.GetDesignatedByRole(Role.Oracle, currentBlock); + } + + // Get StateValidators for a specific past block + public static ECPoint[] GetStateValidatorsForBlock(uint blockIndex) + { + return RoleManagement.GetDesignatedByRole(Role.StateValidator, blockIndex); + } +} +``` + +Contracts mainly use `RoleManagement` to query the designated nodes for specific roles, particularly `Role.Oracle`, if they need to interact with or verify actions by those nodes. + +[Previous: PolicyContract](./Policy.md) | [Next: StdLib](./StdLib.md) \ No newline at end of file diff --git a/docs/framework/04-framework-features/05-native-contracts/StdLib.md b/docs/framework/04-framework-features/05-native-contracts/StdLib.md new file mode 100644 index 000000000..a47ea0cb7 --- /dev/null +++ b/docs/framework/04-framework-features/05-native-contracts/StdLib.md @@ -0,0 +1,96 @@ +# Native Contract: StdLib + +Namespace: `Neo.SmartContract.Framework.Native` + +Provides standard utility functions, primarily for data conversion and serialization. + +## Key Methods + +* **`Serialize(object source)` (`ByteString`)**: Serializes a supported C# object (primitive types, `BigInteger`, `string`, `ByteString`, `UInt160`, `UInt256`, `ECPoint`, arrays, structs, maps, `bool`) into its NeoVM binary representation. +* **`Deserialize(ByteString source)` (`object`)**: Deserializes NeoVM binary data back into its corresponding C# object representation. Returns a `StackItem` which may need casting. + +* **`JsonSerialize(object source)` (`string`)**: Serializes a supported C# object into a JSON string representation. +* **`JsonDeserialize(ByteString source)` (`object`)**: Deserializes a JSON ByteString (UTF8 encoded) into its corresponding C# object representation (often a `Map` for JSON objects or `List` for JSON arrays). Result needs casting. +* **`JsonDeserialize(string source)` (`object`)**: Deserializes a JSON string. + +* **`Atoi(string value, int @base)` (`System.Numerics.BigInteger`)**: Converts a string representation of a number in a specified base (e.g., 10 for decimal, 16 for hex) into a `BigInteger`. +* **`Atoi(ByteString value, int @base)` (`System.Numerics.BigInteger`)**: Converts a `ByteString` representation. + +* **`Itoa(System.Numerics.BigInteger value, int @base)` (`string`)**: Converts a `BigInteger` into its string representation in a specified base. + +* **`Base64Encode(ByteString value)` (`string`)**: Encodes binary data into a Base64 string. +* **`Base64Decode(string value)` (`ByteString`)**: Decodes a Base64 string back into binary data. + +* **`Base58Encode(ByteString value)` (`string`)**: Encodes binary data into a Base58 string (commonly used for addresses). +* **`Base58Decode(string value)` (`ByteString`)**: Decodes a Base58 string back into binary data. + +* **`MemoryCompare(ByteString str1, ByteString str2)` (`int`)**: Compares two `ByteString` values lexicographically. Returns < 0 if str1 < str2, 0 if str1 == str2, > 0 if str1 > str2. +* **`MemoryCompare(byte[] str1, byte[] str2)` (`int`)**: Compares two `byte[]` values. + +* **`MemorySearch(ByteString value, ByteString search)` (`int`)**: Finds the first occurrence of `search` within `value`. Returns the starting index or -1 if not found. +* **`MemorySearch(byte[] value, byte[] search)` (`int`)**: Searches within `byte[]`. +* **`MemorySearch(ByteString value, ByteString search, int start)` (`int`)**: Starts searching from a specific index. +* **`MemorySearch(byte[] value, byte[] search, int start)` (`int`)**: Searches within `byte[]` from a start index. + +## Example Usage + +```csharp +using Neo.SmartContract.Framework; +using Neo.SmartContract.Framework.Attributes; +using Neo.SmartContract.Framework.Native; +using Neo.SmartContract.Framework.Services; +using System.Numerics; + +// Allow calling all StdLib methods +[ContractPermission(nameof(StdLib), "*")] +public class StdLibDemo : SmartContract +{ + // Serialize/Deserialize a custom struct (ensure members are serializable) + public struct MyData + { + public string Name; + public BigInteger Value; + } + + private static readonly StorageMap DataStore = new StorageMap(Storage.CurrentContext, "DATA"); + + public static void StoreData(string key, string name, BigInteger value) + { + MyData data = new MyData { Name = name, Value = value }; + ByteString serializedData = StdLib.Serialize(data); + DataStore.Put(key, serializedData); + } + + public static MyData RetrieveData(string key) + { + ByteString serializedData = DataStore.Get(key); + if (serializedData == null) return default; // Or throw exception + + // Deserialize and cast to the specific struct type + return (MyData)StdLib.Deserialize(serializedData); + } + + // Parse a JSON string received from an Oracle, for example + public static BigInteger GetValueFromJson(string jsonString) + { + // Example JSON: { "user": "abc", "value": 123 } + Map jsonMap = (Map)StdLib.JsonDeserialize(jsonString); + if (jsonMap != null && jsonMap.ContainsKey("value")) + { + // Values within the map might need further casting + return (BigInteger)jsonMap["value"]; + } + return -1; // Indicate error or value not found + } + + // Convert hex string to integer + public static BigInteger HexToInt(string hex) + { + return StdLib.Atoi(hex, 16); + } +} +``` + +`StdLib` is essential for handling data serialization, especially for storage or when dealing with data from external sources like oracles. + +[Previous: RoleManagement](./RoleManagement.md) | [Back to Framework Overview](../README.md) \ No newline at end of file diff --git a/docs/framework/04-framework-features/06-attributes.md b/docs/framework/04-framework-features/06-attributes.md new file mode 100644 index 000000000..cdf77760e --- /dev/null +++ b/docs/framework/04-framework-features/06-attributes.md @@ -0,0 +1,167 @@ +# Attributes (`Neo.SmartContract.Framework.Attributes`) + +C# Attributes are metadata tags placed above classes, methods, events, or fields. In Neo C# development, they are essential tools used by the `Neo.Compiler.CSharp` compiler (`nccs`) to: + +1. **Generate the Contract Manifest (`.manifest.json`):** Provide descriptive information, declare supported standards, define permissions, and specify the contract's public interface (ABI). +2. **Influence Compiler Behavior:** Control optimizations, embed constant values, mark methods as read-only, and manage security features like reentrancy. +3. **Provide Type Hinting:** Clarify expected data types for parameters or fields, potentially aiding external tools or future compiler checks. + +Key attributes reside in the `Neo.SmartContract.Framework.Attributes` namespace, with some common ones (like `DisplayName`) coming from `System.ComponentModel`. + +## Manifest Generation Attributes + +These attributes directly influence the content of the `.manifest.json` file, which describes your contract to the outside world. Apply these primarily to your main contract class. + +* **`[DisplayName(string name)]`**: (From `System.ComponentModel`) + * **Purpose:** Specifies a user-friendly, public name for the contract class, method, or event in the manifest's ABI section. Overrides the C# name. + * **Usage:** `[DisplayName("MyToken")] public class ContractClass...`, `[DisplayName("transfer")] public static event Action<...> OnTransfer;` + +* **`[SupportedStandards(params string[] standards)]`**: + * **Purpose:** Declares which NEP standards (e.g., "NEP-17", "NEP-11") the contract implements. Populates the `supportedstandards` array in the manifest. + * **Usage:** `[SupportedStandards("NEP-17")] public class MyNep17Token...` + +* **`[ContractPermission(string contract, params string[] methods)]`**: + * **Purpose:** Defines which contracts and methods *this* contract is allowed to call using `Contract.Call`. Essential for interoperability. Populates the `permissions` array. + * **`contract`:** Target contract hash (hex string), native contract name (e.g., `"GasToken"`), or `"*"` (any contract). + * **`methods`:** Target method name(s) or `"*"` (any method). + * **Usage:** `[ContractPermission(nameof(GasToken), "transfer")]`, `[ContractPermission("0x123abc...", "*")]` + +* **`[ManifestExtra(string key, string value)]`**: + * **Purpose:** Adds custom key-value pairs to the `extra` section of the manifest for arbitrary metadata. Can be applied multiple times. + * **Usage:** `[ManifestExtra("Author", "Alice")] [ManifestExtra("Version", "1.2.0")] public class MyContract...` + +* **Shorthand `ManifestExtra` Attributes:** These provide convenient wrappers for common `ManifestExtra` keys: + * **`[ContractAuthor(string author)]`**: Equivalent to `[ManifestExtra("Author", author)]`. + * **`[ContractDescription(string description)]`**: Equivalent to `[ManifestExtra("Description", description)]`. + * **`[ContractEmail(string email)]`**: Equivalent to `[ManifestExtra("Email", email)]`. + * **`[ContractVersion(string version)]`**: Equivalent to `[ManifestExtra("Version", version)]`. + * **`[ContractSourceCode(string url)]`**: Equivalent to `[ManifestExtra("SourceCode", url)]` (intended for linking to source code repository). + +* **`[ContractTrust(string contract)]`**: + * **Purpose:** Intended to specify which contracts are trusted (e.g., for permissioning purposes). Populates the `trusts` array. + * **Status:** Currently (as of N3 framework v3.x) often noted as **unused** by the compiler/VM, but part of the manifest specification. + * **Usage:** `[ContractTrust("0xabc123...")]` or `[ContractTrust("*")]` + +* **`[ContractGroup(ECPoint pubkey, string signature)]`**: (Advanced) + * **Purpose:** Used to define designated groups of contracts (identified by public keys) for use in `[ContractPermission]` with group scopes. Requires signature verification. + * **Usage:** Primarily for defining shared permissions among related contracts. + +## Compiler Behavior & Optimization Attributes + +These attributes affect how `nccs` compiles your C# code into NeoVM bytecode or enable specific features. + +* **`[Safe]`**: + * **Purpose:** Marks a method as read-only. It guarantees the method does not modify blockchain state (no `Storage.Put/Delete`, no `Runtime.Notify`, no state-changing `Contract.Call`). + * **Effect:** Sets `"safe": true` in the method's ABI entry. Allows calls using restrictive `CallFlags` (like `ReadStates`). The compiler *may* perform checks to enforce safety. + * **Usage:** `[Safe] public static BigInteger GetBalance(UInt160 acc) {...}` + +* **`[InitialValue(string value, ContractParameterType type)]`**: + * **Purpose:** Embeds a constant value directly into the compiled `.nef` script for a `static readonly` field, avoiding the need for `_deploy` initialization or `Storage.Get` for constants. + * **Effect:** Replaces field loads with direct `PUSHDATA` opcodes. Saves GAS on deployment and runtime access for constants. + * **`value`:** The constant value as a string, parsable according to `type`. + * **`type`:** Specifies how to parse the `value` string (e.g., `Hash160`, `Integer`, `String`, `ByteArray`, `PublicKey`). + * **Usage:** + * Still necessary for types where direct C# constant assignment isn't possible or easily representable (e.g., complex byte arrays represented as hex strings). + * **Alternative (Often Preferred Now):** For many types like `UInt160`, `ECPoint`, `string`, `BigInteger`, and primitives, recent `nccs` versions allow **direct C# assignment** using compile-time constants or literals. The compiler often translates these direct assignments into the same efficient `PUSHDATA` opcodes as `[InitialValue]`. Direct assignment is generally more readable and type-safe. + * **Example:** + ```csharp + // Old way / Still valid / Needed for some hex byte arrays: + [InitialValue("Nd3uYA4nC5onLwQcfE6SkMcNVsTTs4T4oj", ContractParameterType.Hash160)] + private static readonly UInt160 OwnerAddress_Attr; + + [InitialValue("010203", ContractParameterType.ByteArray)] + private static readonly ByteString Prefix_Attr; + + // New way (often preferred for readability & type safety where possible): + private static readonly UInt160 OwnerAddress_Direct = "Nd3uYA4nC5onLwQcfE6SkMcNVsTTs4T4oj"; // Requires using Neo.SmartContract.Framework; + private static readonly ECPoint AdminPublicKey_Direct "03b209fd4f53a7170ea4444e0cb0a6bb6a53c2bd016926989cf85f9b0fba17a70c"; // Requires using Neo.SmartContract.Framework; + private static readonly string TokenName_Direct = "MyToken"; + private static readonly BigInteger MaxSupply_Direct = 1000000; + private static readonly byte[] Prefix_Direct = { 0x01, 0x02, 0x03 }; + + ``` + * **Recommendation:** **Strongly prefer direct C# assignment (`=`)** for `static readonly` constants whenever the value can be represented as a C# literal or via framework helper methods like `ToScriptHash()` or `ECPoint.FromString()`. It is more readable, type-safe, and idiomatic in modern Neo N3 development. Use `[InitialValue]` only as a fallback when direct assignment isn't feasible (e.g., initializing directly from a complex hex string for a `byte[]` without helper methods) or for specific compatibility reasons. + +* **`[OpCode(OpCode opcode, string operand = "")]`**: (Advanced / Use with Extreme Caution) + * **Purpose:** Directly injects a specific NeoVM `OpCode` (and optional operand) when the attributed method stub is called. Bypasses standard C# compilation for that call. + * **Effect:** Allows access to VM features not directly exposed by the framework or for micro-optimizations. Highly error-prone. + * **Usage:** Applied to `extern static` method declarations. `[OpCode(OpCode.NOP)] public static extern void MyNop();` + +* **`[Syscall(string method)]`**: (Advanced / Internal) + * **Purpose:** Maps an `extern static` method declaration directly to a NeoVM Interop `SYSCALL`. + * **Usage:** Primarily used **internally** by the `Neo.SmartContract.Framework` itself to define the wrappers around syscalls (e.g., how `Runtime.Log` maps to `System.Runtime.Log`). You generally **do not** use this directly in contract application code. + +* **`[CallingConvention(System.Runtime.InteropServices.CallingConvention convention)]`**: (Advanced / Internal) + * **Purpose:** Specifies the calling convention for P/Invoke or interop calls. + * **Usage:** Relevant mainly for low-level interop details, typically **not used** directly in standard contract code. + +## Security Attributes + +These attributes help enforce security patterns at compile time or runtime. + +* **`[NoReentrant]`**: + * **Purpose:** A compile-time check to prevent reentrancy vulnerabilities across the **entire contract**. The compiler analyzes methods and flags potential reentrancy issues if a method modifies state *after* making an external `Contract.Call`. + * **Effect:** If potential reentrancy is detected during compilation based on the Checks-Effects-Interactions pattern, the build will fail with an error (e.g., `NC4005`). Does not add runtime checks. + * **Usage:** Apply to the main contract class: `[NoReentrant] public class MySecureContract...`. This is a **highly recommended** attribute for most contracts. + +* **`[NoReentrantMethod]`**: + * **Purpose:** Similar to `[NoReentrant]`, but performs the compile-time reentrancy check only for the specific method it's applied to. + * **Effect:** Compiler checks for state modifications after external calls within this specific method. Build fails if potential reentrancy is found (`NC4005`). + * **Usage:** Apply to individual `public static` methods where reentrancy is a concern: `[NoReentrantMethod] public static bool RiskyWithdraw(...) {...}`. Can be used if applying `[NoReentrant]` to the whole class is too restrictive or causes issues with intended patterns. + +## Type Hinting / Validation Attributes + +These attributes primarily provide hints about the expected format or type of data, often for parameters or fields. They might be used by external tools, for documentation, or potentially by future compiler analyses. They don't typically alter the core NeoVM bytecode generation significantly compared to using the base C# types. + +* **`[Hash160]` / `[Hash160(isNullable: true)]`**: Hints the parameter/field should be a valid UInt160 script hash. +* **`[PublicKey]` / `[PublicKey(isNullable: true)]`**: Hints the parameter/field should be a valid ECPoint public key. +* **`[ByteArray]` / `[ByteArray(isNullable: true)]`**: Hints the parameter/field is raw byte array data. +* **`[String]` / `[String(isNullable: true)]`**: Hints the parameter/field is string data. +* **`[Integer(long min = long.MinValue, long max = long.MaxValue)]`**: Hints the parameter/field is an integer, optionally within a specific range (range check might not be enforced at runtime by default). +* **`[ContractHash(string hash)]`**: Associates a specific known contract hash with a parameter or field, potentially for documentation or tool usage. + +**Example Combining Attributes:** + +```csharp +using Neo.SmartContract.Framework; +using Neo.SmartContract.Framework.Attributes; +using Neo.SmartContract.Framework.Services; +using System.ComponentModel; +using System.Numerics; + +[DisplayName("FullSampleContract")] +[ContractAuthor("Alice")] +[ContractVersion("1.0")] +[SupportedStandards("NEP-17")] +[ContractPermission(nameof(GasToken), "*")] // Allow all GasToken calls +[NoReentrant] // Apply contract-wide reentrancy check +public class FullAttributeDemo : SmartContract +{ + // Use direct assignment where possible (preferred) + private static readonly UInt160 Owner = "Nd3uYA4nC5onLwQcfE6SkMcNVsTTs4T4oj"; + + public delegate void ValueSetDelegate(BigInteger value); + [DisplayName("ValueSet")] + public static event ValueSetDelegate OnValueSet; + + [Safe] // Read-only method + public static UInt160 GetOwner() + { + return Owner; + } + + // Parameter type hint + public static void SetValue([Integer(min: 0)] BigInteger newValue) + { + Helper.Assert(Runtime.CheckWitness(Owner), "Only Owner"); + Helper.Assert(newValue >= 0, "Value must be non-negative"); // Runtime check still needed + + // Store value... + OnValueSet(newValue); + } +} +``` + +Leveraging these attributes effectively is crucial for creating well-defined, secure, optimized, and maintainable Neo N3 smart contracts. The `[NoReentrant]` attribute, in particular, is a valuable addition for preventing common security flaws. + +[Previous: Native Contracts Overview](./05-native-contracts/README.md) | [Next: Helper Methods](./07-helper-methods.md) \ No newline at end of file diff --git a/docs/framework/04-framework-features/07-helper-methods.md b/docs/framework/04-framework-features/07-helper-methods.md new file mode 100644 index 000000000..02887b546 --- /dev/null +++ b/docs/framework/04-framework-features/07-helper-methods.md @@ -0,0 +1 @@ +# Helper Methods\n\nThe `Neo.SmartContract.Framework` namespace (and the base `SmartContract` class) provides several static helper methods for common tasks like type conversions, assertions, and byte manipulation.\n\n## `Helper` Class (`Neo.SmartContract.Framework.Helper`)\n\nProvides static utility methods:\n\n* **`Assert(bool condition, string message = null)`**: Throws an exception (terminating execution) if the `condition` is false. Optionally includes a `message` in the exception.\ Crucial for validating inputs and state.\n* **`ToBigInteger(ByteString value)` / `ToBigInteger(byte[] value)` / `ToBigInteger(string value)` / `ToBigInteger(bool value)`**: Converts various types to `BigInteger`.\n* **`ToByteArray(string value)` / `ToByteArray(BigInteger value)`**: Converts `string` (UTF8) or `BigInteger` to `byte[]`.\n* **`ToByteString(string value)` / `ToByteString(BigInteger value)`**: Converts `string` (UTF8) or `BigInteger` to `ByteString`.\n* **`AsString(byte[] value)` / `AsString(ByteString value)`**: Converts byte array/ByteString (assumed UTF8) to `string`.\n* **`ToUInt160(ByteString value)` / `ToUInt160(BigInteger value)`**: Converts to `UInt160` (requires correct length/value).\n* **`Concat(ByteString s1, ByteString s2)` / `Concat(byte[] s1, byte[] s2)`**: Concatenates two byte arrays/ByteStrings.\n* **`Last(ByteString value, int count)` / `Last(byte[] value, int count)`**: Returns the last `count` bytes.\n* **`Range(ByteString value, int index, int count)` / `Range(byte[] value, int index, int count)`**: Returns a sub-sequence of bytes.\n* **`Take(ByteString value, int count)` / `Take(byte[] value, int count)`**: Returns the first `count` bytes.\n* **`Reverse(byte[] source)`**: Reverses the bytes in an array (useful for endianness changes).\n\n## `SmartContract` Base Class (`Neo.SmartContract.Framework.SmartContract`)\n\nIf your contract inherits from `SmartContract`, you get access to some instance helpers (though most framework features are static). The primary benefit is slightly shorter access to common elements:\n\n* **`Context` (`StorageContext`)**: Shortcut for `Storage.CurrentContext`.\n* **`ReadOnlyContext` (`StorageContext`)**: Shortcut for `Storage.CurrentReadOnlyContext`.\n* **`ExecutingScriptHash` (`UInt160`)**: Shortcut for `Runtime.ExecutingScriptHash`.\n* **`CallingScriptHash` (`UInt160`)**: Shortcut for `Runtime.CallingScriptHash`.\n* **`Runtime`**: Direct access to the static `Runtime` class (less common).\n* **`Storage`**: Direct access to the static `Storage` class (less common).\n\n**Note:** Because contract methods must be `static`, you cannot directly use these instance helpers within them. Their primary use might be within instance helper methods if you were to structure your contract differently (which is uncommon) or if future framework versions utilize them more.\ Most developers rely on the static classes (`Runtime`, `Storage`, `Helper`, native contracts) directly within their static contract methods.\n\n## Example Usage (`Helper`)\n\n```csharp\nusing Neo.SmartContract.Framework;\nusing Neo.SmartContract.Framework.Attributes;\nusing Neo.SmartContract.Framework.Native;\nusing Neo.SmartContract.Framework.Services;\nusing System.Numerics;\n\n[ContractPermission(nameof(StdLib), \"* \")] // For atoi/itoa\npublic class HelperDemo : SmartContract\n{\n private static readonly StorageMap Balances = new StorageMap(Storage.CurrentContext, \"BAL\");\n\n public static void UpdateBalance(UInt160 user, BigInteger amount)\n {\n // Validate inputs using Assert\n Helper.Assert(amount > 0, \"Amount must be positive\"); \n Helper.Assert(user.IsValid && !user.IsZero, \"Invalid user address\");\n\n if (!Runtime.CheckWitness(user)) \n { \n Helper.Assert(false, \"CheckWitness failed\"); // Assert can take custom message\n }\n\n // ... Update logic ...\n BigInteger currentBalance = (BigInteger)Balances.Get(user);\n Balances.Put(user, currentBalance + amount);\n }\n\n // Concatenate strings using byte conversion\n public static string CombineStrings(string a, string b)\n {\n ByteString bytesA = Helper.ToByteString(a);\n ByteString bytesB = Helper.ToByteString(b);\n ByteString combinedBytes = Helper.Concat(bytesA, bytesB);\n return Helper.AsString(combinedBytes);\n }\n\n // Convert number to hex string\n public static string IntToHex(BigInteger value)\n {\n return StdLib.Itoa(value, 16); // Itoa is actually in StdLib\n }\n}\n```\n\nThe `Helper` class, especially `Assert`, is fundamental for writing robust and secure smart contracts by enforcing preconditions and validating state.\n\n[Previous: Attributes](./06-attributes.md) | [Next Section: Compilation](../05-compilation/README.md) \ No newline at end of file diff --git a/docs/framework/04-framework-features/README.md b/docs/framework/04-framework-features/README.md new file mode 100644 index 000000000..91e6acd6d --- /dev/null +++ b/docs/framework/04-framework-features/README.md @@ -0,0 +1,17 @@ +# Framework Features (`Neo.SmartContract.Framework`) + +The `Neo.SmartContract.Framework` NuGet package is the cornerstone of C# smart contract development on Neo. It provides the necessary classes, methods, attributes, and services to interact with the Neo blockchain environment from within your C# code. + +This section explores the key features provided by the framework. + +## Topics + +* [Storage](./01-storage.md): Reading from and writing to the contract's persistent storage. +* [Runtime Services](./02-runtime.md): Accessing blockchain state, context, logging, notifications, and checking witnesses. +* [Events](./03-events.md): Defining and emitting events for off-chain applications. +* [Contract Interaction](./04-contract-interaction.md): Calling other smart contracts. +* [Native Contracts](./05-native-contracts/README.md): Interacting with Neo's built-in system contracts (Ledger, NEO, GAS, Policy, Oracle, etc.). +* [Attributes](./06-attributes.md): Using attributes to control manifest generation and contract behavior. +* [Helper Methods](./07-helper-methods.md): Utility functions for common tasks like type conversions and assertions. + +[Next: Storage](./01-storage.md) \ No newline at end of file diff --git a/docs/framework/05-compilation/01-using-compiler.md b/docs/framework/05-compilation/01-using-compiler.md new file mode 100644 index 000000000..acb2c5083 --- /dev/null +++ b/docs/framework/05-compilation/01-using-compiler.md @@ -0,0 +1,56 @@ +# Using the Compiler (`nccs`) + +The `Neo.Compiler.CSharp` NuGet package provides MSBuild integration, meaning the Neo contract compilation process is automatically triggered when you build your .NET project. + +## Standard Build Process + +Assuming you have correctly set up your C# project (`.csproj` file) as described in [Framework & Compiler Setup](../02-getting-started/02-installation.md), compiling your smart contract is straightforward: + +1. **Navigate:** Open your terminal or command prompt in the directory containing your smart contract's `.csproj` file. +2. **Build:** Run the standard .NET build command: + ```bash + dotnet build + ``` + Or, for a release build (which might apply different optimizations if configured): + ```bash + dotnet build -c Release + ``` + +## What Happens During `dotnet build`? + +When `Neo.Compiler.CSharp` is referenced, the build process executes these steps: + +1. **Standard C# Compilation:** Your C# code is compiled into a standard .NET assembly (`.dll`). +2. **`nccs` Invocation:** The MSBuild tasks included in `Neo.Compiler.CSharp` invoke the Neo compiler (`nccs`). +3. **Analysis & Conversion:** `nccs` analyzes the compiled DLL, focusing on the classes and methods relevant to smart contracts (those using `Neo.SmartContract.Framework`). It converts the CIL (Common Intermediate Language) into NeoVM bytecode. +4. **NEF Generation:** The resulting NeoVM bytecode is packaged into the `.nef` file. +5. **Manifest Generation:** Metadata (methods, events, permissions, standards, extra info derived from attributes and code structure) is collected and written to the `.manifest.json` file. +6. **Debug Info Generation (Optional):** If `true` is set in the `.csproj` (typically for Debug configuration), a `.nefdbgnfo` file containing debug symbols is also generated. + +## Output Files + +After a successful build, the compiled artifacts will be placed in your project's output directory (e.g., `bin/Debug/net6.0/` or `bin/Release/net6.0/`). + +You should find: + +* `YourContractName.nef`: The NeoVM bytecode. +* `YourContractName.manifest.json`: The contract manifest. +* `YourContractName.nefdbgnfo` (Optional): Debug information. +* `YourProject.dll`: The standard .NET assembly (not used for deployment). + +These `.nef` and `.manifest.json` files are what you need to deploy your contract to a Neo network. + +## Compilation Errors + +If `nccs` encounters issues converting your C# code to valid NeoVM instructions, it will report errors during the `dotnet build` process. + +Common causes include: + +* Using C# features or .NET libraries not supported by the NeoVM or the compiler. +* Incorrectly using framework types or methods. +* Syntax errors that pass C# compilation but violate smart contract constraints. +* Missing necessary `[ContractPermission]` attributes for `Contract.Call` usage. + +The error messages usually indicate the problematic C# code section. + +[Previous: Compilation Overview](./README.md) | [Next: Compiler Options](./02-compiler-options.md) \ No newline at end of file diff --git a/docs/framework/05-compilation/02-compiler-options.md b/docs/framework/05-compilation/02-compiler-options.md new file mode 100644 index 000000000..59c6582b1 --- /dev/null +++ b/docs/framework/05-compilation/02-compiler-options.md @@ -0,0 +1,84 @@ +# Compiler Options (`.csproj` Properties) + +You can configure the behavior of the `Neo.Compiler.CSharp` (`nccs`) compiler by setting specific properties within your project's `.csproj` file, typically inside a ``. + +## Key Configuration Properties + +```xml + + + + net9.0 + disable + disable + + + MyAwesomeContract + Neo.Contracts.Examples + false + false + false + + + Latest + O1 + false + 1.0.0.0 + 4 + someScriptBytes + + + + + + true + + + + + + + + +``` + +**Explanation:** + +* **``**: + * Specifies the base filename for the output `.nef`, `.manifest.json`, and `.nefdbgnfo` files. + * If omitted, the project assembly name (usually the `.csproj` filename) is used. + +* **``**: + * Forces the use of a specific `nccs` version if multiple are available or for compatibility. + * Defaults to the version of the `Neo.Compiler.CSharp` package referenced. + * Use `Latest` to always try the newest installed version. + +* **``**: + * Controls the optimization level applied by `nccs`. + * `O0`: No optimization. + * `O1`: Standard optimizations (method inlining, basic peephole optimizations). This is usually the default and recommended setting. + * Higher optimization levels might exist in future versions. + +* **``**: + * Set to `true` to generate the `.nefdbgnfo` debug information file. + * Crucial for debugging contracts using tools like the Neo Blockchain Toolkit (Neo Express). + * Typically enabled only for the `Debug` configuration. + +* **``**: + * Embeds a version string into the compiled NEF file header. + * Useful for tracking deployed contract versions. + * If omitted, a default version might be used by the compiler. + +* **``**: + * Controls the level of warnings reported by `nccs` during compilation (0 = Off, 4 = Report all warnings). + * Default is usually 4. + +* **``**: (Advanced) + * Allows injecting raw script bytes. Use cases are very specific and rare. + +* **``, ``, ``**: + * Standard .NET properties recommended to be set to `false` for Neo contract projects to avoid potential path embedding issues or conflicts with the compiler. + +By adjusting these properties, you can fine-tune the compilation process to suit your needs, enabling debugging or applying specific optimizations. + +[Previous: Using the Compiler](./01-using-compiler.md) | [Next: Debugging Information](./03-debugging.md) \ No newline at end of file diff --git a/docs/framework/05-compilation/03-debugging.md b/docs/framework/05-compilation/03-debugging.md new file mode 100644 index 000000000..d8935a24c --- /dev/null +++ b/docs/framework/05-compilation/03-debugging.md @@ -0,0 +1,57 @@ +# Debugging Information (`.nefdbgnfo`) + +Debugging smart contracts directly on a public blockchain is impractical. However, the Neo C# compiler and associated tooling provide mechanisms for debugging contracts locally. + +A key component of this is the debug information file. + +## Generating Debug Info + +To enable debugging, you need the compiler (`nccs`) to generate a file containing debug symbols that map the compiled NeoVM bytecode back to your original C# source code. + +This is controlled by the `` property in your `.csproj` file. + +1. **Edit `.csproj`**: Ensure the property is set to `true`, typically within a conditional block for the `Debug` configuration: + ```xml + + true + + ``` + +2. **Build in Debug Configuration**: Compile your project specifically using the `Debug` configuration: + ```bash + dotnet build -c Debug + ``` + +3. **Output File**: If successful, alongside the `.nef` and `.manifest.json` files in your output directory (e.g., `bin/Debug/net6.0/`), you will find a file named: + `YourContractName.nefdbgnfo` + +This `.nefdbgnfo` file contains the mapping between NeoVM instruction offsets and your C# source code lines, variable names, and method boundaries. + +## Using Debug Info + +The `.nefdbgnfo` file is essential for tools that provide step-through debugging capabilities for Neo smart contracts. + +* **Neo Blockchain Toolkit (NBT):** This is the primary toolset for debugging Neo C# contracts. + * **Neo Express:** Allows you to run a private Neo blockchain instance locally. + * **Visual Studio Code Extension:** The NBT extension for VS Code integrates with Neo Express and uses the `.nefdbgnfo` file to allow you to: + * Set breakpoints in your C# code. + * Step through execution line by line. + * Inspect variable values. + * Examine the NeoVM stack and storage. + +**Typical Debugging Workflow with NBT:** + +1. Install Neo Express (`dotnet tool install Neo.Express -g`). +2. Create a Neo Express instance (`neox create ...`). +3. Compile your contract in `Debug` configuration (`dotnet build -c Debug`) to generate `.nef`, `.manifest.json`, and `.nefdbgnfo`. +4. Deploy your compiled contract to your local Neo Express instance (`neox contract deploy ...`). +5. Configure VS Code launch settings (`launch.json`) to attach the debugger to Neo Express. +6. Set breakpoints in your C# `.cs` files. +7. Invoke your contract method on Neo Express (e.g., using `neox contract invoke ...` or via a custom client application connected to Neo Express). +8. VS Code should hit the breakpoint, allowing you to step through the C# code as it executes within the simulated NeoVM environment. + +Refer to the [Neo Blockchain Toolkit documentation](https://github.com/neo-project/neo-blockchain-toolkit) for detailed instructions on setting up and using the debugger. + +**Note:** Never deploy contracts compiled with debug information (`.nefdbgnfo` present) to MainNet or public TestNets. Debug information increases the deployment size and is not needed for production execution. + +[Previous: Compiler Options](./02-compiler-options.md) | [Next Section: Advanced Topics](../06-advanced-topics/README.md) \ No newline at end of file diff --git a/docs/framework/05-compilation/README.md b/docs/framework/05-compilation/README.md new file mode 100644 index 000000000..c3295b7a2 --- /dev/null +++ b/docs/framework/05-compilation/README.md @@ -0,0 +1,13 @@ +# Compilation (`Neo.Compiler.CSharp`) + +Once you have written your smart contract code using the `Neo.SmartContract.Framework`, you need to compile it into the NeoVM bytecode (`.nef`) and manifest (`.manifest.json`) files required for deployment. + +This compilation process is handled by the `Neo.Compiler.CSharp` tool (often referred to as `nccs`). + +## Topics + +* [Using the Compiler](./01-using-compiler.md): How the compiler integrates with the `dotnet build` process. +* [Compiler Options](./02-compiler-options.md): Configuring the compiler via `.csproj` properties. +* [Debugging Information](./03-debugging.md): Generating debug symbols for use with tools like Neo Express. + +[Next: Using the Compiler](./01-using-compiler.md) \ No newline at end of file diff --git a/docs/framework/06-advanced-topics/01-contract-upgrade.md b/docs/framework/06-advanced-topics/01-contract-upgrade.md new file mode 100644 index 000000000..4bb690bde --- /dev/null +++ b/docs/framework/06-advanced-topics/01-contract-upgrade.md @@ -0,0 +1,147 @@ +# Contract Upgrade and Migration + +Smart contracts deployed on the blockchain are typically immutable. However, bugs may be found, or new features may be required. Neo N3 provides a mechanism to update the code of a deployed contract using the `ContractManagement.Update` native method. However, updating requires careful planning, especially regarding storage. + +## The `Update` Mechanism + +1. **Compile New Version:** Compile the new version of your contract code (`.nef`, `.manifest.json`). +2. **Implement `update` Method:** Your *original* contract must have a method (conventionally named `update`) that calls `ContractManagement.Update`. This method should include authorization logic (e.g., `Runtime.CheckWitness` against an owner or admin address) to control who can trigger the update. +3. **Permissions:** The original contract's manifest must grant permission for the contract to call its own `update` method (`[ContractPermission(Runtime.ExecutingScriptHash, "update")]`). +4. **Invoke `update`:** An authorized user sends a transaction calling the `update` method, passing the new `.nef` file content and `.manifest.json` content as arguments. +5. **`ContractManagement.Update` Call:** Inside your `update` method, `ContractManagement.Update(nefFile, manifest, optionalData)` is called. +6. **Execution:** The NeoVM replaces the contract's code (NEF) and manifest with the new versions. +7. **`_deploy` Execution:** The `_deploy(object data, bool update)` method of the *new* contract code is executed with the `update` flag set to `true`. The `optionalData` from the `Update` call is passed as the `data` argument. + +```csharp +// In YourContract.cs (Version 1) +using Neo.SmartContract.Framework; +using Neo.SmartContract.Framework.Attributes; +using Neo.SmartContract.Framework.Native; +using Neo.SmartContract.Framework.Services; + +// Grant permission to call self.update +[ContractPermission(nameof(ContractManagement), "update")] +public class YourContractV1 : SmartContract +{ + private static readonly byte[] OwnerKey = { 0x01 }; + + public static void _deploy(object data, bool update) + { + if (!update) + { + // Set owner on initial deployment + Storage.Put(Storage.CurrentContext, OwnerKey, Runtime.Transaction.Sender); + } + Runtime.Log("V1 Deployed/Updated"); + } + + // Method to trigger the update + public static void update(ByteString nefFile, string manifest, object data) + { + ByteString owner = Storage.Get(Storage.CurrentContext, OwnerKey); + if (owner is null || !Runtime.CheckWitness((UInt160)owner)) + throw new System.Exception("Unauthorized update"); + + ContractManagement.Update(nefFile, manifest, data); + Runtime.Log("Contract update initiated."); + } + + // ... other V1 methods ... +} +``` + +```csharp +// In YourContract.cs (Version 2) +using Neo.SmartContract.Framework; +using Neo.SmartContract.Framework.Attributes; +using Neo.SmartContract.Framework.Native; +using Neo.SmartContract.Framework.Services; + +public class YourContractV2 : SmartContract +{ + // Storage keys should ideally remain consistent or be migrated + private static readonly byte[] OwnerKey = { 0x01 }; + private static readonly StorageMap UserData = new StorageMap(Storage.CurrentContext, "UDTA"); + + public static void _deploy(object data, bool update) + { + Runtime.Log("V2 Deployed/Updated"); + if (update) + { + // Perform migration tasks only needed during an update + Runtime.Log("Running V2 update logic."); + // Example: Migrate data passed via 'data' argument + if (data != null) { /* process migration data */ } + // Example: Migrate existing storage if format changed + MigrateStorageFromV1(); + } + else + { + // Set owner only on initial V2 deployment (if V2 is deployed fresh) + Storage.Put(Storage.CurrentContext, OwnerKey, Runtime.Transaction.Sender); + } + } + + // Update method should also exist in V2 for future updates + public static void update(ByteString nefFile, string manifest, object data) + { + ByteString owner = Storage.Get(Storage.CurrentContext, OwnerKey); + if (owner is null || !Runtime.CheckWitness((UInt160)owner)) + throw new System.Exception("Unauthorized update"); + ContractManagement.Update(nefFile, manifest, data); + } + + private static void MigrateStorageFromV1() + { + // Add logic here if V1 storage needs transformation for V2 + Runtime.Log("Performing storage migration..."); + // e.g., Read old storage format, transform, write to new format + } + + // ... other V2 methods ... +} +``` + +## Storage Compatibility & Migration + +The biggest challenge with updates is **storage**. When you update a contract, its storage area persists. + +* **Compatible Changes:** If the new contract version uses the same storage keys and data structures (serialization format) as the old version, no migration is needed. +* **Incompatible Changes:** If you change storage keys, add/remove fields in stored structs, or change serialization methods, the new code might not be able to read the old data correctly. + +**Strategies for Handling Storage Changes:** + +1. **Design for Upgradability:** + * Use `StorageMap` with consistent prefixes. + * Avoid complex nested structures in storage if possible. + * Use version numbers within your stored data. + * Try to make changes additive rather than breaking. + +2. **Migration in `_deploy`:** + * Place migration logic inside the `_deploy` method of the *new* contract version, within the `if (update)` block. + * This logic reads data in the old format (using old keys/deserialization), transforms it, and writes it back in the new format. + * This can be GAS-intensive if migrating large amounts of data. Consider migrating data lazily (see below). + * You might pass necessary migration parameters via the `data` argument of `ContractManagement.Update`. + +3. **Lazy Migration:** + * Instead of migrating all data at once in `_deploy`, migrate individual data items only when they are accessed for the first time by the new contract version. + * Add version checks when reading data. If old version data is detected, migrate it on the fly before returning/using it. + * Requires careful implementation in all methods that read potentially old data. + +4. **Proxy Pattern (Advanced):** + * Deploy a minimal, non-updatable Proxy contract that users interact with. + * The Proxy contract holds the address of the current *implementation* contract. + * All calls to the Proxy are forwarded (using `Contract.Call`) to the implementation contract. + * Storage is usually kept in the Proxy contract (or a separate dedicated storage contract) to persist across implementation changes. + * To upgrade, deploy a new implementation contract and update the implementation address stored in the Proxy contract. + * This pattern separates logic and state, simplifying updates but adding complexity and GAS overhead (due to the extra `Contract.Call`). + +5. **Data Export/Import (Off-Chain):** + * For major breaking changes, provide a mechanism in the old contract to export data (e.g., emit events, allow reading state). + * Deploy a completely new contract. + * Users (or an admin) interact with the new contract to import their data, potentially providing proof of ownership from the old contract. + * This is essentially a manual migration, not an in-place update. + +**Choosing the right strategy depends on the complexity of the changes, the amount of data, and the desired user experience.** Always test upgrade scenarios thoroughly on a testnet or using local simulation tools like Neo Express. + +[Previous: Advanced Topics Overview](./README.md) | [Next: Security Best Practices](./02-security.md) \ No newline at end of file diff --git a/docs/framework/06-advanced-topics/02-security.md b/docs/framework/06-advanced-topics/02-security.md new file mode 100644 index 000000000..d3c6e3da3 --- /dev/null +++ b/docs/framework/06-advanced-topics/02-security.md @@ -0,0 +1,146 @@ +# Security Best Practices + +Writing secure smart contracts is paramount, as vulnerabilities can lead to significant financial loss or unexpected behavior. This section outlines common pitfalls and best practices for Neo C# smart contract development. + +## 1. Access Control (`Runtime.CheckWitness`) + +* **Problem:** Methods that modify critical state or transfer assets must be protected. +* **Solution:** **Always** use `Runtime.CheckWitness(authorizedAccount)` to verify that the intended user/admin/contract has signed the transaction before performing sensitive operations. +* **Pitfall:** Forgetting `CheckWitness` or checking against the wrong account. +* **Pitfall:** Relying solely on `Runtime.Transaction.Sender`. While often the sender *is* the signer you want, `CheckWitness` is the explicit way to check transaction signatures. + +```csharp +// Bad: Anyone can call this and drain funds +public static bool UnsafeWithdraw(UInt160 to, BigInteger amount) +{ + return GasToken.Transfer(Runtime.ExecutingScriptHash, to, amount, null); +} + +// Good: Only the owner can withdraw +private static readonly byte[] OwnerKey = { 0x01 }; +public static bool SafeWithdraw(UInt160 to, BigInteger amount) +{ + ByteString owner = Storage.Get(Storage.CurrentContext, OwnerKey); + Helper.Assert(owner != null, "Owner not set"); + if (!Runtime.CheckWitness((UInt160)owner)) return false; // Check owner signature + return GasToken.Transfer(Runtime.ExecutingScriptHash, to, amount, null); +} +``` + +## 2. Reentrancy Attacks + +* **Problem:** When a contract calls an external contract (`Contract.Call`), the external contract might call back into the original contract *before* the original call finishes. If state updates haven't completed, the reentrant call might exploit an inconsistent state. +* **Mitigation:** + * **Checks-Effects-Interactions Pattern:** Perform checks first, then update internal state (effects), and *only then* interact with external contracts. + * **Reentrancy Guards:** Use a storage flag (e.g., `isLocked`) to prevent reentrant calls to sensitive functions while one is already in progress. + * **Limit External Calls:** Minimize interactions with untrusted external contracts, especially within functions that modify critical state. + * **Use `CallFlags.ReadOnly`:** When calling external contracts just to read data, use restrictive flags like `CallFlags.ReadStates` or `CallFlags.ReadOnly` to prevent the external contract from writing state or calling back unexpectedly (if it respects the flags). + +```csharp +// Vulnerable to Reentrancy +private static readonly StorageMap Balances = new StorageMap(Storage.CurrentContext, "BAL"); +public static bool VulnerableWithdraw(UInt160 recipient, BigInteger amount) +{ + if (!Runtime.CheckWitness(Runtime.ExecutingScriptHash)) return false; // Assume caller is contract owner + BigInteger balance = (BigInteger)Balances.Get(Runtime.ExecutingScriptHash); + Helper.Assert(balance >= amount, "Insufficient balance"); + + // Interaction BEFORE effect + bool success = (bool)Contract.Call(recipient, "receivePayment", CallFlags.All, amount); + Helper.Assert(success, "External call failed"); + + // Effect: Update balance AFTER external call + Balances.Put(Runtime.ExecutingScriptHash, balance - amount); + return true; +} + +// Safer: Checks-Effects-Interactions +private static bool locked = false; // Simple reentrancy guard (in-memory, better to use storage) +public static bool SaferWithdraw(UInt160 recipient, BigInteger amount) +{ + // Check + if (!Runtime.CheckWitness(Runtime.ExecutingScriptHash)) return false; + Helper.Assert(!locked, "Reentrancy detected"); // Check guard + BigInteger balance = (BigInteger)Balances.Get(Runtime.ExecutingScriptHash); + Helper.Assert(balance >= amount, "Insufficient balance"); + + // Effects + Balances.Put(Runtime.ExecutingScriptHash, balance - amount); // Update balance first + locked = true; // Set guard + + // Interaction + bool success = false; + try { + success = (bool)Contract.Call(recipient, "receivePayment", CallFlags.All, amount); + } finally { + locked = false; // Unset guard even if call fails + } + + Helper.Assert(success, "External call failed"); // Check result AFTER state change + return true; +} +``` +*(Note: Simple boolean guard shown; persistent storage guard is more robust)* + +## 3. Integer Overflow/Underflow + +* **Problem:** Arithmetic operations on bounded integer types can wrap around (overflow/underflow) leading to incorrect calculations (e.g., large balances becoming small). +* **Solution:** `BigInteger` (which represents arbitrarily large integers) is the standard for balances and critical arithmetic in Neo contracts and **does not overflow/underflow** in the traditional sense. Always use `BigInteger` for token amounts and sensitive calculations. +* **Pitfall:** Using standard C# types like `int` or `long` for values that could exceed their bounds. + +## 4. Input Validation (`Helper.Assert`) + +* **Problem:** Incorrect or malicious inputs can lead to unexpected behavior or state corruption. +* **Solution:** Use `Helper.Assert` liberally to validate: + * Input arguments (e.g., amounts > 0, addresses are valid and not zero). + * State preconditions (e.g., balance >= amount). + * Return values from external calls. +* **Pitfall:** Assuming inputs are always valid. + +## 5. Incorrect Calculation / Logic Errors + +* **Problem:** Standard programming bugs in calculations or state transitions. +* **Solution:** Rigorous testing (unit tests, simulation testing), code reviews, and clear, simple logic. + +## 6. Timestamp Dependence + +* **Problem:** Relying on `Runtime.Time` for critical logic can be manipulated by consensus nodes to some extent. +* **Solution:** Avoid using `Runtime.Time` as the *sole* factor for critical decisions like unlocking funds or determining winners. Use block height (`LedgerContract.CurrentIndex`) or external oracles for more reliable time-based logic if needed. + +## 7. GAS Issues & Denial of Service (DoS) + +* **Problem:** Methods consuming excessive GAS can become unusable. +* **Problem:** Malicious users might exploit operations that loop over user-provided data, causing excessive GAS usage (DoS). +* **Solution:** + * Be mindful of GAS costs ([GAS Optimization](./03-optimization.md)). + * Avoid unbounded loops. If looping over data, ensure there are limits (e.g., process only N items per call). + * Consider if operations need to be pausable or require specific roles to execute. + * Ensure users provide sufficient GAS for operations. + +## 8. Unhandled Exceptions + +* **Problem:** An unhandled exception (e.g., from `Helper.Assert`, failed casts, division by zero) will cause the entire transaction to fail, rolling back all state changes made within that transaction. +* **Solution:** Use assertions (`Helper.Assert`) for conditions that *must* be true. Handle expected error conditions gracefully (e.g., return `false` from a method) rather than throwing exceptions unless failure should revert the entire transaction. + +## 9. Oracle Security + +* **Problem:** Relying on data from Oracles requires trusting the Oracle nodes and the data source. +* **Solution:** + * **Verify Callback Caller:** Crucially, always check `Runtime.CallingScriptHash == OracleContract.Hash` in your `__callback` method. + * **Use Multiple Oracles/Sources:** For critical data, consider requesting from multiple sources and aggregating/validating results. + * **Validate Data:** Sanitize and validate data received from oracles before using it. + +## 10. Upgrade Security + +* **Problem:** Flaws in the `update` method logic can allow unauthorized contract updates. +* **Solution:** Ensure the `update` method robustly checks the authorization (e.g., `Runtime.CheckWitness` against a secure owner/admin address or multi-sig). + +**General Principles:** + +* **Keep it Simple:** Complexity breeds bugs. +* **Least Privilege:** Grant minimal permissions (`[ContractPermission]`, `CallFlags`). +* **Test Thoroughly:** Cover edge cases, failures, and security scenarios. +* **Code Reviews:** Have others review your contract logic. +* **Stay Updated:** Be aware of newly discovered vulnerabilities and best practices in the Neo ecosystem. + +[Previous: Contract Upgrade & Migration](./01-contract-upgrade.md) | [Next: GAS Optimization](./03-optimization.md) \ No newline at end of file diff --git a/docs/framework/06-advanced-topics/03-optimization.md b/docs/framework/06-advanced-topics/03-optimization.md new file mode 100644 index 000000000..7ad8bcfb5 --- /dev/null +++ b/docs/framework/06-advanced-topics/03-optimization.md @@ -0,0 +1,57 @@ +# GAS Optimization + +GAS is the fuel of the Neo network, and every operation within a smart contract consumes GAS. Optimizing your contract's GAS usage makes it cheaper for users to interact with, prevents hitting block/transaction GAS limits, and reduces overall network load. + +## Understanding GAS Costs + +* **OpCode Costs:** Every NeoVM opcode has a base GAS cost. +* **Interop Service Costs:** Calls to the framework services (`Runtime.*`, `Storage.*`, `Contract.*`, native contracts) translate to specific syscalls (interop opcodes), each with its own cost. These are generally more expensive than basic opcodes. +* **Storage Costs:** `Storage.Put` costs GAS based on key length + value length. `Storage.Get` costs based on value length. `Storage.Find` costs per item iterated. +* **Execution Fee Factor:** Costs are multiplied by a factor (`PolicyContract.GetExecFeeFactor()`) which can be adjusted by the Neo Council. + +**(Important: Exact GAS costs can change. Always refer to the official Neo N3 documentation for the most current cost tables for opcodes and syscalls relevant to the network you are targeting).** + +## Optimization Techniques + +1. **Minimize Storage Operations:** Storage is expensive. + * **Read Less:** Avoid reading from storage if the value isn't needed. + * **Write Less:** Only write to storage when state actually changes. + * **Cache Reads:** If a value from storage is needed multiple times within the *same execution*, read it once into a local variable instead of calling `Storage.Get` repeatedly. + * **Combine Writes:** If possible, structure logic to minimize the number of separate `Storage.Put` calls. + +2. **Optimize Storage Structure:** + * **Shorter Keys:** Use shorter keys or prefixes for `StorageMap` where possible. + * **Pack Data:** Instead of storing multiple boolean flags or small numbers as separate keys, consider packing them into a single `BigInteger` or `ByteString` using bit manipulation or careful serialization. This reduces the number of reads/writes but increases complexity. + * **Avoid Large Values:** Storing very large strings or byte arrays costs more. + +3. **Efficient Data Types & Operations:** + * **`BigInteger` vs. Primitives:** While `BigInteger` is necessary for balances, use standard C# integer types (`int`, `long`) for counters or values known to fit within their bounds, as operations might be slightly cheaper than `BigInteger` arithmetic. + * **`ByteString` vs `byte[]`:** Operations on immutable `ByteString` might sometimes be cheaper than creating/modifying mutable `byte[]` (Buffer). + * **Prefer `Helper` Methods:** Use `Helper.Concat`, `Helper.Range` etc., over manual byte manipulation in loops, as the helper methods often map to efficient opcodes. + +4. **Loop Optimization:** + * **Avoid Unbounded Loops:** Loops iterating based on user input or unbounded storage reads (`Storage.Find`) are dangerous and costly. Impose limits or redesign. + * **Minimize Work Inside Loops:** Perform calculations or storage access outside the loop whenever possible. + +5. **Contract Call Optimization:** + * **`Contract.Call` is Expensive:** Each call incurs significant overhead. + * **Restrict `CallFlags`:** Use the minimum necessary `CallFlags` (`ReadStates` is cheaper than `WriteStates` or `All`). + * **Batch Operations:** If calling another contract multiple times, see if the target contract offers batch methods to reduce call overhead. + +6. **Code Structure & Compiler (`nccs`):** + * **Use `O1` Optimization:** Ensure `O1` (usually default) is set in your `.csproj` for compiler optimizations like inlining. + * **Static Calls:** The compiler can often optimize direct static method calls within the same contract more effectively than dynamic calls. + * **Avoid Unnecessary Abstraction:** While good for readability, excessive layers of method calls *can* sometimes add overhead compared to more direct logic (measure if concerned). + +7. **Algorithmic Efficiency:** + * Choose efficient algorithms. A O(n^2) approach will cost significantly more GAS than a O(n log n) or O(n) approach for large inputs. + +## Measuring GAS Usage + +* **Neo Express:** Tools like Neo Express often report the GAS consumed by invoked transactions in the local environment. +* **TestNet/MainNet Explorers:** Block explorers show the GAS consumed by executed transactions. +* **`Runtime.GasLeft`:** You can programmatically check `Runtime.GasLeft` at different points in your code during testing to understand the cost of specific sections (though this adds a small cost itself). + +**Trade-offs:** Optimization often involves trade-offs between GAS cost, code complexity, and readability. Focus on optimizing the most frequently executed or inherently expensive operations (storage, complex loops, external calls). Premature optimization of minor details can sometimes obscure logic without significant GAS savings. + +[Previous: Security Best Practices](./02-security.md) | [Next: Interop Layer](./04-interop.md) \ No newline at end of file diff --git a/docs/framework/06-advanced-topics/04-interop.md b/docs/framework/06-advanced-topics/04-interop.md new file mode 100644 index 000000000..8df9867a7 --- /dev/null +++ b/docs/framework/06-advanced-topics/04-interop.md @@ -0,0 +1,52 @@ +# Interop Layer + +Neo smart contracts written in high-level languages like C# do not run directly on the bare metal or OS of the nodes. They execute within the Neo Virtual Machine (NeoVM), a sandboxed environment. + +To interact with the blockchain state (storage, blocks, transactions) or perform actions beyond simple computation (logging, notifications, calling other contracts), the NeoVM relies on an **Interop Service Layer**. + +The `Neo.SmartContract.Framework` acts as a C# bridge to this Interop Layer. + +## NeoVM and Syscalls + +* **NeoVM:** Executes basic stack operations, control flow, arithmetic, etc., defined by standard NeoVM opcodes. +* **Interop Layer:** Provides access to blockchain-specific functionality. +* **Syscalls:** The NeoVM uses special `SYSCALL` opcodes to invoke functions within the Interop Layer. Each syscall corresponds to a specific service (e.g., `System.Storage.Put`, `System.Runtime.CheckWitness`, `System.Contract.Call`). +* **Syscall Hashes:** Each syscall function is identified by a unique hash (a 4-byte integer). + +## Framework Role + +When you write C# code using the framework, for example: + +```csharp +Storage.Put(Storage.CurrentContext, "mykey", "myvalue"); +bool isOwner = Runtime.CheckWitness(ownerAddress); +Contract.Call(targetContract, "someMethod", CallFlags.All); +``` + +The `Neo.Compiler.CSharp` (`nccs`) translates these high-level C# method calls into a sequence of NeoVM opcodes that culminate in a `SYSCALL` instruction with the appropriate syscall hash corresponding to the underlying interop service (`System.Storage.Put`, `System.Runtime.CheckWitness`, `System.Contract.Call`, respectively). + +**Essentially, the framework provides type-safe C# wrappers around the NeoVM syscalls.** + +## Key Interop Service Categories (Conceptual) + +The syscalls, and thus the framework methods, generally fall into these categories: + +* **Runtime Services (`System.Runtime.*`)**: Getting execution context (`ExecutingScriptHash`, `CallingScriptHash`), time (`GetTime`), trigger (`GetTrigger`), checking witnesses (`CheckWitness`), logging (`Log`), notifications (`Notify`), platform info. +* **Storage Services (`System.Storage.*`)**: Putting, getting, deleting, and finding data in the contract's storage context (`Put`, `Get`, `Delete`, `Find`). Also includes context management (`GetContext`, `GetReadOnlyContext`). +* **Contract Services (`System.Contract.*`)**: Calling other contracts (`Call`), managing contracts (`Create`, `Update`, `Destroy` - these map to `ContractManagement` native contract calls), checking methods (`GetCallFlags`). +* **Iterator Services (`System.Iterator.*`)**: Creating and traversing iterators (used by `Storage.Find`). +* **Callback Services (`System.Callback.*`)**: Creating callbacks (used internally). +* **Native Contract Wrappers**: Classes like `GasToken`, `NeoToken`, `LedgerContract`, etc., wrap `System.Contract.Call` syscalls directed at the specific native contract hashes. +* **Crypto Services (`Neo.Crypto.*`)**: Hashing (`Sha256`, `Ripemd160`) and signature verification (`CheckSig`, `CheckMultiSig` - these map to `CryptoLib` native contract calls). +* **Serialization Services (`System.Binary.*`)**: Serialization/deserialization (`Serialize`, `Deserialize` - mapping to `StdLib` native contract calls). + +## Implications + +* **Performance:** Syscalls generally cost more GAS than basic NeoVM opcodes due to the overhead of interacting with the underlying node state. +* **Limitations:** You can only perform actions explicitly exposed via syscalls. You cannot directly access the node's filesystem, network (except via Oracles), or arbitrary memory. +* **Framework Dependency:** Your C# contract code is tightly coupled to the `Neo.SmartContract.Framework` because it relies on these wrappers to generate the necessary syscalls. +* **Abstraction:** The framework hides the complexity of managing the NeoVM stack, arguments, and syscall hashes directly, allowing developers to focus on application logic. + +Understanding the Interop Layer helps clarify *why* certain operations are possible and others are not, and why framework methods translate into specific, costed blockchain interactions. + +[Previous: GAS Optimization](./03-optimization.md) | [Next Section: Tutorials](../07-tutorials/README.md) \ No newline at end of file diff --git a/docs/framework/06-advanced-topics/README.md b/docs/framework/06-advanced-topics/README.md new file mode 100644 index 000000000..cc7b3117d --- /dev/null +++ b/docs/framework/06-advanced-topics/README.md @@ -0,0 +1,12 @@ +# Advanced Topics + +This section covers more advanced concepts and techniques for experienced Neo C# smart contract developers. + +## Topics + +* [Contract Upgrade & Migration](./01-contract-upgrade.md): Strategies for updating deployed contracts and handling storage changes. +* [Security Best Practices](./02-security.md): Common pitfalls and recommendations for writing secure contracts. +* [GAS Optimization](./03-optimization.md): Techniques for reducing the GAS consumption of your contract. +* [Interop Layer](./04-interop.md): A closer look at how the framework interacts with the NeoVM. + +[Next: Contract Upgrade & Migration](./01-contract-upgrade.md) \ No newline at end of file diff --git a/docs/framework/07-tutorials/01-nep17-token.md b/docs/framework/07-tutorials/01-nep17-token.md new file mode 100644 index 000000000..ea991cc7f --- /dev/null +++ b/docs/framework/07-tutorials/01-nep17-token.md @@ -0,0 +1,283 @@ +# Tutorial: Building a NEP-17 Token + +NEP-17 is the standard for fungible tokens on the Neo N3 blockchain (similar to ERC-20 on Ethereum). This tutorial guides you through creating a basic NEP-17 token contract in C#. + +## 1. Project Setup + +1. Create a new C# class library project (`dotnet new classlib -n MyNep17Token`). +2. Add the necessary NuGet packages: + ```bash + dotnet add package Neo.SmartContract.Framework + dotnet add package Neo.Compiler.CSharp + ``` +3. Configure your `.csproj` file for Neo contract compilation (set `NeoContractName`, disable unwanted properties, add package references - see [Getting Started](../02-getting-started/02-installation.md)). Ensure you enable `` for Debug builds. + +## 2. Contract Code (`MyNep17Token.cs`) + +Replace the default `Class1.cs` with `MyNep17Token.cs`: + +```csharp +using Neo.SmartContract.Framework; +using Neo.SmartContract.Framework.Attributes; +using Neo.SmartContract.Framework.Native; +using Neo.SmartContract.Framework.Services; +using System.ComponentModel; +using System.Numerics; + +namespace Neo.SmartContract.Examples.NEP17 +{ + [DisplayName("MySampleToken")] // Choose a display name + [ManifestExtra("Author", "Your Name")] + [ManifestExtra("Description", "A sample NEP-17 Token")] + [SupportedStandards("NEP-17")] // Declare NEP-17 compliance + [ContractPermission("*")] // Allow calling any contract (needed for onNEP17Payment callbacks) + public class MyNep17Token : SmartContract + { + #region NEP-17 Settings + + // Use InitialValue for constants embedded in the NEF + [InitialValue("MST", ContractParameterType.String)] + private static readonly string _symbol; + + [InitialValue("8", ContractParameterType.Integer)] // Decimals (e.g., 8) + private static readonly byte _decimals; + + // Use a variable for total supply if it can change (e.g., minting/burning) + // Otherwise, use InitialValue for fixed supply + // [InitialValue("1000000000000000", ContractParameterType.Integer)] // 10 million tokens with 8 decimals + // private static readonly BigInteger _totalSupply; + private static readonly byte[] TotalSupplyKey = { 0x11 }; + + #endregion + + #region Storage Prefixes + + // Storage prefixes are crucial to prevent key collisions + private static readonly StorageMap Balances = new StorageMap(Storage.CurrentContext, 0x01); + private static readonly StorageMap Allowances = new StorageMap(Storage.CurrentContext, 0x02); + + #endregion + + #region NEP-17 Events + + // Define the Transfer event as required by NEP-17 + public delegate void TransferDelegate(UInt160 from, UInt160 to, BigInteger amount); + [DisplayName("Transfer")] + public static event TransferDelegate OnTransfer; + + #endregion + + #region NEP-17 Methods + + [Safe] // This method doesn't change state + public static string Symbol() + { + return _symbol; + } + + [Safe] + public static byte Decimals() + { + return _decimals; + } + + [Safe] + public static BigInteger TotalSupply() + { + // If fixed supply, return _totalSupply + // If variable, read from storage + return (BigInteger)Storage.Get(Storage.CurrentContext, TotalSupplyKey); + } + + [Safe] + public static BigInteger BalanceOf(UInt160 account) + { + Helper.Assert(account.IsValid && !account.IsZero, "Invalid Account"); + return (BigInteger)Balances.Get(account); + } + + // Primary Transfer method + public static bool Transfer(UInt160 from, UInt160 to, BigInteger amount, object data) + { + // Validate arguments + Helper.Assert(from.IsValid && !from.IsZero, "Invalid From Address"); + Helper.Assert(to.IsValid && !to.IsZero, "Invalid To Address"); + Helper.Assert(amount > 0, "Amount must be positive"); + + // Check authorization (caller must be 'from' or approved) + if (!Runtime.CheckWitness(from) && !IsApproved(from, Runtime.ExecutingScriptHash, amount)) + return false; + + // Check sender balance + BigInteger fromBalance = BalanceOf(from); + Helper.Assert(fromBalance >= amount, "Insufficient Balance"); + + // Perform the transfer + if (from == to) return true; // No change if transferring to self + + Balances.Put(from, fromBalance - amount); + Balances.Put(to, BalanceOf(to) + amount); + + // Emit NEP-17 Transfer event + PostTransfer(from, to, amount, data); + return true; + } + + #endregion + + #region Approval Methods (Optional but Recommended) + + // Although not strictly required by base NEP-17, approve/allowance is standard practice + + public static bool Approve(UInt160 owner, UInt160 spender, BigInteger amount) + { + Helper.Assert(owner.IsValid && !owner.IsZero, "Invalid Owner Address"); + Helper.Assert(spender.IsValid && !spender.IsZero, "Invalid Spender Address"); + Helper.Assert(amount >= 0, "Amount cannot be negative"); // Allow 0 to reset approval + + // Check authorization + if (!Runtime.CheckWitness(owner)) return false; + + Allowances.Put(owner + spender, amount); // Combine owner+spender for key + // Consider firing an Approval event here + return true; + } + + [Safe] + public static BigInteger Allowance(UInt160 owner, UInt160 spender) + { + Helper.Assert(owner.IsValid && !owner.IsZero, "Invalid Owner Address"); + Helper.Assert(spender.IsValid && !spender.IsZero, "Invalid Spender Address"); + return (BigInteger)Allowances.Get(owner + spender); + } + + // Helper to check if an allowance exists for Transfer + private static bool IsApproved(UInt160 owner, UInt160 spender, BigInteger amount) + { + BigInteger allowance = Allowance(owner, spender); + if (allowance < amount) return false; + + // Decrease allowance after use (important!) + // Only decrease if allowance is not max value (effectively infinite) + // NEP-17 doesn't mandate allowance decrease, but it's safer practice + // if (allowance != BigInteger.MinusOne) // Assuming -1 means infinite + // { + Allowances.Put(owner + spender, allowance - amount); + // } + return true; + } + + #endregion + + #region Mint & Burn (Optional Example) + // Add mint/burn functions if your token supply is variable + + private static readonly byte[] OwnerKey = { 0xFF }; // Example key for owner + + public static bool Mint(UInt160 account, BigInteger amount) + { + Helper.Assert(account.IsValid && !account.IsZero, "Invalid Account"); + Helper.Assert(amount > 0, "Amount must be positive"); + + // Authorization: Only contract owner can mint + ByteString owner = Storage.Get(Storage.CurrentContext, OwnerKey); + Helper.Assert(owner != null, "Owner not set"); + if (!Runtime.CheckWitness((UInt160)owner)) return false; + + // Increase balance and total supply + Balances.Put(account, BalanceOf(account) + amount); + BigInteger supply = TotalSupply(); + Storage.Put(Storage.CurrentContext, TotalSupplyKey, supply + amount); + + // Emit Transfer event for minting (from null) + PostTransfer(null, account, amount, null); + return true; + } + + public static bool Burn(UInt160 account, BigInteger amount) + { + Helper.Assert(account.IsValid && !account.IsZero, "Invalid Account"); + Helper.Assert(amount > 0, "Amount must be positive"); + + // Authorization: Check if the burner signed + if (!Runtime.CheckWitness(account)) return false; + + // Check balance + BigInteger balance = BalanceOf(account); + Helper.Assert(balance >= amount, "Insufficient balance to burn"); + + // Decrease balance and total supply + Balances.Put(account, balance - amount); + BigInteger supply = TotalSupply(); + Storage.Put(Storage.CurrentContext, TotalSupplyKey, supply - amount); + + // Emit Transfer event for burning (to null) + PostTransfer(account, null, amount, null); + return true; + } + + #endregion + + #region Deployment + + // _deploy is called on contract deployment and updates + public static void _deploy(object data, bool update) + { + if (!update) + { + // Initialize on first deployment + BigInteger initialSupply = 1000000_00000000; // 10 million with 8 decimals + UInt160 owner = Runtime.Transaction.Sender; // Or get from deploy data + + Balances.Put(owner, initialSupply); + Storage.Put(Storage.CurrentContext, TotalSupplyKey, initialSupply); + Storage.Put(Storage.CurrentContext, OwnerKey, owner); + + // Emit initial Transfer event + PostTransfer(null, owner, initialSupply, null); + } + } + + #endregion + + #region Helper Methods + + // Centralized place to emit Transfer event and call onNEP17Payment + private static void PostTransfer(UInt160 from, UInt160 to, BigInteger amount, object data) + { + OnTransfer(from, to, amount); + + // Call onNEP17Payment if the recipient is a deployed contract + if (to != null && ContractManagement.GetContract(to) != null) + { + Contract.Call(to, "onNEP17Payment", CallFlags.All, from, amount, data); + } + } + + #endregion + } +} +``` + +## 3. Key Elements Explained + +* **Attributes:** `[DisplayName]`, `[ManifestExtra]`, `[SupportedStandards("NEP-17")]`, `[ContractPermission]` are crucial for defining the contract's identity and capabilities. +* **Settings:** Define `Symbol`, `Decimals`. Total supply can be constant (`[InitialValue]`) or variable (stored). +* **Storage:** Use `StorageMap` with distinct prefixes (`Balances`, `Allowances`) to avoid collisions. +* **`OnTransfer` Event:** Mandatory NEP-17 event. +* **Core Methods:** Implement `symbol`, `decimals`, `totalSupply`, `balanceOf`, `transfer` exactly as specified by NEP-17. +* **Authorization:** Use `Runtime.CheckWitness` in `transfer` (and `mint`/`burn`/`approve`). +* **Validation:** Use `Helper.Assert` to validate inputs and preconditions. +* **`_deploy`:** Initialize supply, owner, and balances on first deployment. +* **`PostTransfer` Helper:** Centralizes emitting the `OnTransfer` event and calling `onNEP17Payment` on recipient contracts. +* **Approvals:** Include `approve` and `allowance` for standard token interaction patterns. +* **Mint/Burn:** Add if your tokenomics require variable supply, ensuring proper authorization. + +## 4. Compile and Deploy + +1. Compile the contract: `dotnet build` +2. Deploy the generated `.nef` and `.manifest.json` files to Neo Express, TestNet, or MainNet using appropriate tools (e.g., `neox contract deploy ...`). + +This provides a solid foundation for a NEP-17 token. You can extend it with more features like pausing, blacklisting, etc., based on your requirements. + +[Previous: Tutorials Overview](./README.md) | [Next: Creating a Voting Contract](./02-voting-contract.md) \ No newline at end of file diff --git a/docs/framework/07-tutorials/02-voting-contract.md b/docs/framework/07-tutorials/02-voting-contract.md new file mode 100644 index 000000000..f93c272ef --- /dev/null +++ b/docs/framework/07-tutorials/02-voting-contract.md @@ -0,0 +1,193 @@ +# Tutorial: Creating a Voting Contract + +This tutorial demonstrates how to build a simple on-chain voting contract where users can vote on predefined proposals. + +## 1. Project Setup + +1. Create a new C# class library project (`dotnet new classlib -n SimpleVoting`). +2. Add NuGet packages: `Neo.SmartContract.Framework`, `Neo.Compiler.CSharp`. +3. Configure the `.csproj` file for Neo compilation. + +## 2. Contract Code (`SimpleVoting.cs`) + +```csharp +using Neo.SmartContract.Framework; +using Neo.SmartContract.Framework.Attributes; +using Neo.SmartContract.Framework.Native; +using Neo.SmartContract.Framework.Services; +using System.ComponentModel; +using System.Numerics; + +namespace Neo.SmartContract.Examples.Voting +{ + [DisplayName("SimpleVotingContract")] + [ManifestExtra("Author", "Your Name")] + [ContractPermission("*")] // Example: Allow necessary calls + public class SimpleVoting : SmartContract + { + // Storage Prefixes + private static readonly StorageMap Proposals = new StorageMap(Storage.CurrentContext, "PROP"); // Proposal ID -> Proposal Info (Serialized) + private static readonly StorageMap Votes = new StorageMap(Storage.CurrentContext, "VOTE"); // Voter + Proposal ID -> Voted Option + private static readonly byte[] ProposalCounterKey = { 0x11 }; + private static readonly byte[] OwnerKey = { 0xFF }; + + // Event for new proposals + public delegate void ProposalAddedDelegate(BigInteger proposalId, string description, UInt160 proposer); + [DisplayName("ProposalAdded")] + public static event ProposalAddedDelegate OnProposalAdded; + + // Event for votes cast + public delegate void VotedDelegate(UInt160 voter, BigInteger proposalId, int optionIndex); + [DisplayName("Voted")] + public static event VotedDelegate OnVoted; + + // Structure to hold proposal information (simple example) + public class ProposalInfo + { + public string Description; + public List Options; // Voting options + public Map VoteCounts; // Option Index -> Count + public uint EndBlock; // Block height when voting ends + public UInt160 Proposer; + } + + // Deploy: Set Owner + public static void _deploy(object data, bool update) + { + if (!update) + { + Storage.Put(Storage.CurrentContext, OwnerKey, Runtime.Transaction.Sender); + Storage.Put(Storage.CurrentContext, ProposalCounterKey, 0); + } + } + + // --- Proposal Management --- + + // Only Owner can add proposals + public static BigInteger AddProposal(string description, List options, uint votingDurationBlocks) + { + ByteString owner = Storage.Get(Storage.CurrentContext, OwnerKey); + Helper.Assert(owner != null && Runtime.CheckWitness((UInt160)owner), "Unauthorized: Only owner can add proposals"); + Helper.Assert(options.Count > 1, "Must have at least two options"); + + BigInteger currentCounter = (BigInteger)Storage.Get(Storage.CurrentContext, ProposalCounterKey); + BigInteger newProposalId = currentCounter + 1; + Storage.Put(Storage.CurrentContext, ProposalCounterKey, newProposalId); + + ProposalInfo info = new ProposalInfo(); + info.Description = description; + info.Options = options; + info.VoteCounts = new Map(); + // Initialize vote counts to zero for each option + for (int i = 0; i < options.Count; i++) + { + info.VoteCounts[i] = 0; + } + info.EndBlock = LedgerContract.CurrentIndex + votingDurationBlocks; + info.Proposer = (UInt160)owner; + + // Store the serialized proposal info + Proposals.Put(newProposalId, StdLib.Serialize(info)); + + OnProposalAdded(newProposalId, description, (UInt160)owner); + return newProposalId; + } + + [Safe] + public static ProposalInfo GetProposal(BigInteger proposalId) + { + ByteString data = Proposals.Get(proposalId); + if (data == null) return null; + return (ProposalInfo)StdLib.Deserialize(data); + } + + [Safe] + public static BigInteger GetProposalCount() + { + return (BigInteger)Storage.Get(Storage.CurrentContext, ProposalCounterKey); + } + + // --- Voting --- + + public static bool Vote(BigInteger proposalId, int optionIndex) + { + UInt160 voter = Runtime.Transaction.Sender; // Use Sender as voter ID + Helper.Assert(Runtime.CheckWitness(voter), "Unauthorized: CheckWitness failed for voter"); + + ProposalInfo info = GetProposal(proposalId); + Helper.Assert(info != null, "Proposal not found"); + Helper.Assert(LedgerContract.CurrentIndex < info.EndBlock, "Voting period has ended"); + Helper.Assert(optionIndex >= 0 && optionIndex < info.Options.Count, "Invalid option index"); + + // Check if already voted + ByteString voteKey = voter + proposalId; // Combine voter and proposal ID for unique key + if (Votes.Get(voteKey) != null) + { + Runtime.Log("Voter has already voted on this proposal."); + return false; // Already voted + } + + // Record vote and increment count + Votes.Put(voteKey, optionIndex); + info.VoteCounts[optionIndex]++; + + // Save updated proposal info + Proposals.Put(proposalId, StdLib.Serialize(info)); + + OnVoted(voter, proposalId, optionIndex); + return true; + } + + [Safe] + public static int GetVote(BigInteger proposalId, UInt160 voter) + { + ByteString voteKey = voter + proposalId; + ByteString voteData = Votes.Get(voteKey); + if (voteData == null) return -1; // Not voted + return (int)(BigInteger)voteData; // Cast stored option index + } + } +} + +``` + +## 3. Key Elements Explained + +* **Owner:** Set during deployment, only the owner can add new proposals. +* **Proposal ID:** A simple counter ensures unique IDs for each proposal. +* **`ProposalInfo` Struct:** Holds all relevant data for a proposal (description, options, vote counts, end block). C# structs are implicitly serialized/deserialized by `StdLib.Serialize`/`Deserialize`. +* **Storage:** + * `Proposals`: Maps Proposal ID to serialized `ProposalInfo`. + * `Votes`: Maps a combined key (`voter + proposalId`) to the option index voted for. This prevents a user from voting multiple times on the same proposal. + * `ProposalCounterKey`, `OwnerKey`: Simple keys for global values. +* **`AddProposal`:** + * Checks owner authorization. + * Increments proposal counter. + * Initializes `ProposalInfo` (including vote counts to 0). + * Serializes and stores the info. + * Emits `OnProposalAdded` event. +* **`GetProposal`:** Retrieves and deserializes proposal info. +* **`Vote`:** + * Checks voter authorization (`CheckWitness`). + * Validates proposal existence, voting period, and option index. + * **Prevents double voting** by checking if an entry exists in the `Votes` map for the `voter + proposalId` key. + * Records the vote in the `Votes` map. + * Increments the vote count in the `ProposalInfo` struct. + * **Re-serializes and saves** the updated `ProposalInfo`. + * Emits `Voted` event. +* **`GetVote`:** Retrieves the option a specific voter chose for a proposal. +* **Serialization:** `StdLib.Serialize` and `StdLib.Deserialize` are used to store and retrieve the `ProposalInfo` struct in storage. + +## 4. Compile and Deploy + +1. Compile: `dotnet build` +2. Deploy the `.nef` and `.manifest.json` files. + +## Potential Enhancements + +* **Weighted Voting:** Use token balances (e.g., NEO or a custom NEP-17) instead of 1-person-1-vote. +* **Gas Sponsorship:** Allow the owner or proposal creators to sponsor GAS fees for voters. +* **More Complex Proposal Structures:** Allow different voting types (ranked choice, etc.). +* **UI Integration:** Build a frontend application to interact with the contract. + +[Previous: Building a NEP-17 Token](./01-nep17-token.md) | [Next: Using Oracles](./03-oracle-usage.md) \ No newline at end of file diff --git a/docs/framework/07-tutorials/03-oracle-usage.md b/docs/framework/07-tutorials/03-oracle-usage.md new file mode 100644 index 000000000..a2631e94d --- /dev/null +++ b/docs/framework/07-tutorials/03-oracle-usage.md @@ -0,0 +1,210 @@ +# Tutorial: Using Oracles + +This tutorial shows how to use the native `OracleContract` to fetch external data (e.g., a cryptocurrency price) from a URL and store it within your smart contract. + +## 1. Project Setup + +1. Create a new C# class library project (`dotnet new classlib -n OraclePriceFeed`). +2. Add NuGet packages: `Neo.SmartContract.Framework`, `Neo.Compiler.CSharp`. +3. Configure the `.csproj` file, including necessary permissions. + +## 2. Contract Code (`OraclePriceFeed.cs`) + +```csharp +using Neo.SmartContract.Framework; +using Neo.SmartContract.Framework.Attributes; +using Neo.SmartContract.Framework.Native; +using Neo.SmartContract.Framework.Services; +using System.ComponentModel; +using System.Numerics; + +namespace Neo.SmartContract.Examples.Oracle +{ + [DisplayName("OraclePriceFeed")] + [ManifestExtra("Author", "Your Name")] + // Permissions needed: + [ContractPermission(nameof(OracleContract), "request", "getPrice")] // To make requests + [ContractPermission(nameof(GasToken), "transfer")] // To pay Oracle fees + [ContractPermission(nameof(StdLib), "atoi")] // To parse the result + [ContractPermission(nameof(Runtime), "log")] // For logging + public class OraclePriceFeed : SmartContract + { + // Storage for the latest price + private static readonly byte[] LatestPriceKey = { 0x01 }; + private static readonly byte[] RequestorKey = { 0x02 }; // Who can request updates + + // Event for price updates + public delegate void PriceUpdatedDelegate(BigInteger newPrice, ulong timestamp); + [DisplayName("PriceUpdated")] + public static event PriceUpdatedDelegate OnPriceUpdated; + + // Deploy: Set who can request updates + public static void _deploy(object data, bool update) + { + if (!update) + { + // Set initial requestor (e.g., deployer) + Storage.Put(Storage.CurrentContext, RequestorKey, Runtime.Transaction.Sender); + } + } + + // --- Price Request --- + + // Only the authorized requestor can trigger an update + public static void RequestNeoUsdPriceUpdate() + { + ByteString requestor = Storage.Get(Storage.CurrentContext, RequestorKey); + Helper.Assert(requestor != null && Runtime.CheckWitness((UInt160)requestor), "Unauthorized: Only designated requestor can update price"); + + // Define request parameters + // Note: Use a reliable, HTTPS supporting API endpoint + string url = "https://api.coingecko.com/api/v3/simple/price?ids=neo&vs_currencies=usd"; // Example API + string filter = "$.neo.usd"; // JSONPath filter for USD price + string callbackMethod = "__oracleCallback"; // Must match method below + object userData = "NEO_USD_PRICE"; // Custom data to identify this request type + long gasForResponse = Oracle.MinimumResponseFee; // Minimum GAS for callback execution + + // Calculate and pay fee + long requestFee = OracleContract.GetPrice(); + long totalGasFee = requestFee + gasForResponse; + if (!GasToken.Transfer((UInt160)requestor, OracleContract.Hash, totalGasFee, null)) + { + throw new System.Exception("Failed to pay Oracle GAS fee"); + } + + // Send the request + OracleContract.Request(url, filter, callbackMethod, userData, gasForResponse); + Runtime.Log("Oracle price request sent."); + } + + // --- Oracle Callback --- + + // This method MUST be public static and match the name in Request() + public static void __oracleCallback(string url, object userData, int code, ByteString result) + { + // CRITICAL: Verify the caller is the Oracle Contract + if (Runtime.CallingScriptHash != OracleContract.Hash) + { + Runtime.Log("Callback verification failed! Caller: " + Runtime.CallingScriptHash); + throw new System.Exception("Unauthorized callback caller."); + } + + // Check if the request identifier matches what we expect + if ((string)userData != "NEO_USD_PRICE") + { + Runtime.Log("Callback received for unexpected userData: " + (string)userData); + return; // Ignore if it's not the request we care about + } + + // Check the response code + if (code != OracleResponseCode.Success) + { + Runtime.Log($"Oracle request failed for URL {url} with code: {code}"); + // Optionally store error state + return; + } + + // Process the successful result + Runtime.Log("Oracle price request successful."); + try + { + // Assuming the API returns a number (potentially with decimals) + // CoinGecko example returns a simple number like 9.87 + // We need to handle potential decimals - let's store price * 100 + string priceString = Helper.AsString(result); // e.g., "9.87" + BigInteger price = ParseDecimalString(priceString, 2); // Helper to parse "9.87" to 987 + + // Store the latest price + Storage.Put(Storage.CurrentContext, LatestPriceKey, price); + + // Emit event + OnPriceUpdated(price, Runtime.Time); + Runtime.Log("Stored price: " + price); + } + catch (System.Exception e) + { + Runtime.Log("Failed to parse Oracle result: " + e.Message); + // Optionally store error state + } + } + + // --- Price Retrieval --- + + [Safe] + public static BigInteger GetLatestPrice() + { + // Returns price * 100 (or chosen precision) + return (BigInteger)Storage.Get(Storage.CurrentContext, LatestPriceKey); + } + + // --- Helper Function --- + + // Basic helper to parse a decimal string like "12.34" into an integer + // representing the value multiplied by 10^decimals + private static BigInteger ParseDecimalString(string value, int decimals) + { + string integerPart = value; + string fractionalPart = ""; + int decimalPoint = value.IndexOf('.'); + + if (decimalPoint != -1) { + integerPart = value.Substring(0, decimalPoint); + fractionalPart = value.Substring(decimalPoint + 1); + } + + BigInteger intValue = integerPart.Length > 0 ? StdLib.Atoi(integerPart, 10) : 0; + BigInteger fracValue = 0; + + if (fractionalPart.Length > 0) { + // Trim or pad fractional part to match desired decimals + if (fractionalPart.Length > decimals) { + fractionalPart = fractionalPart.Substring(0, decimals); + } else if (fractionalPart.Length < decimals) { + fractionalPart = fractionalPart.PadRight(decimals, '0'); + } + fracValue = StdLib.Atoi(fractionalPart, 10); + } + + BigInteger multiplier = BigInteger.Pow(10, decimals); + return intValue * multiplier + fracValue; + } + } +} +``` + +## 3. Key Elements Explained + +* **Permissions:** Crucially requires permissions for `OracleContract`, `GasToken`, and potentially `StdLib`. +* **Request Trigger (`RequestNeoUsdPriceUpdate`):** + * Protected by `CheckWitness` to ensure only authorized accounts can initiate requests. + * Defines the URL, filter, callback function name (`__oracleCallback`), and user data. + * Calculates the total GAS fee (request fee + callback execution fee). + * Transfers the fee to the `OracleContract.Hash`. + * Calls `OracleContract.Request`. +* **Callback Method (`__oracleCallback`):** + * Must be `public static` and match the name provided in the request. + * **MUST** verify `Runtime.CallingScriptHash == OracleContract.Hash` to prevent fake callbacks. + * Checks the `userData` to identify which request this callback corresponds to. + * Checks the `code` for success or errors. + * Parses the `result` (ByteString). This often involves converting to a string (`Helper.AsString`) and potentially parsing JSON (`StdLib.JsonDeserialize`) or numbers (`StdLib.Atoi`). + * Handles potential decimal values appropriately (e.g., multiplying by a power of 10 to store as `BigInteger`). + * Stores the processed result. + * Emits an event (`OnPriceUpdated`). +* **Storage:** Stores the latest processed price. +* **Retrieval (`GetLatestPrice`):** A simple `[Safe]` method to read the stored price. +* **Decimal Parsing:** Includes a helper function (`ParseDecimalString`) to demonstrate handling potential decimal numbers returned by APIs, storing them as scaled integers. + +## 4. Compile and Deploy + +1. Compile: `dotnet build` +2. Deploy the `.nef` and `.manifest.json` files. + +## Important Considerations + +* **API Reliability & Trust:** The security and accuracy of your contract depend heavily on the reliability and trustworthiness of the external API and the Neo Oracle nodes. +* **HTTPS:** Most Oracles require HTTPS URLs. +* **GAS:** Ensure enough GAS is paid for both the request and the callback execution. +* **Callback Security:** The callback verification step is non-negotiable. +* **Data Parsing:** Robustly handle potential errors during data parsing in the callback. + +[Previous: Creating a Voting Contract](./02-voting-contract.md) | [Next Section: Testing & Deployment](../08-testing-deployment/README.md) \ No newline at end of file diff --git a/docs/framework/07-tutorials/README.md b/docs/framework/07-tutorials/README.md new file mode 100644 index 000000000..e1681b208 --- /dev/null +++ b/docs/framework/07-tutorials/README.md @@ -0,0 +1,13 @@ +# Tutorials + +This section provides step-by-step guides for building common types of smart contracts on Neo N3 using C#. + +## Topics + +* [Building a NEP-17 Token](./01-nep17-token.md): Create a standard fungible token. +* [Creating a Voting Contract](./02-voting-contract.md): Implement a basic on-chain voting system. +* [Using Oracles](./03-oracle-usage.md): Fetch external data using the Oracle service. + +These tutorials assume familiarity with the concepts covered in previous sections. + +[Next: Building a NEP-17 Token](./01-nep17-token.md) \ No newline at end of file diff --git a/docs/framework/08-testing-deployment/01-unit-testing.md b/docs/framework/08-testing-deployment/01-unit-testing.md new file mode 100644 index 000000000..ea7b61ffb --- /dev/null +++ b/docs/framework/08-testing-deployment/01-unit-testing.md @@ -0,0 +1,142 @@ +# Unit Testing Smart Contracts + +Unit testing focuses on testing individual components or methods of your smart contract in isolation, without requiring a running blockchain. This is crucial for quickly verifying logic, edge cases, and preventing regressions. + +While you can't directly execute NeoVM bytecode in a standard C# test runner, you can test the C# logic *before* it gets compiled, with some caveats regarding blockchain interactions. + +## Approach: Testing Logic via Mocking/Faking + +While you can't directly execute NeoVM bytecode in a standard C# test runner, you *can* test the C# logic *before* compilation by simulating the blockchain environment. This is a common and valuable practice within the Neo ecosystem. + +The standard approach involves: + +1. **Isolate Logic:** Structure your contract methods to separate core business logic from direct blockchain interactions where feasible. +2. **Mocking/Faking Dependencies:** Create mock or fake implementations of the `Neo.SmartContract.Framework` classes and methods your contract depends on (`Runtime`, `Storage`, `Contract`, native contracts, `Helper`). This allows you to control their behavior during tests (e.g., simulate `Storage.Get` returning a specific value, `Runtime.CheckWitness` returning true/false, `Runtime.Time` returning a set time). + * **Manual Fakes:** Write simple static classes that mimic the framework methods' signatures and provide controllable return values or record calls. + * **Mocking Libraries (Advanced):** Libraries like Moq *might* be usable with specific techniques (interfaces, wrappers) but mocking the static methods used extensively by the framework can be challenging. + * **Neo Test Harnesses:** Look for community or official testing libraries specifically designed for Neo N3 contracts (e.g., `Neo.Test.Runner`, parts of NGD Enterprise toolchains, or other emerging frameworks) which often provide pre-built fakes or a more integrated simulation. +3. **Test Logic:** Write standard C# unit tests using MSTest, NUnit, or xUnit. In your tests, set up the state of your mocks/fakes, call your contract's static methods, and then assert the expected outcomes based on return values or the final state of your mocks (e.g., checking what was "stored" in your fake storage). + +## Limitations Remain, But Value is High + +* **Incomplete Simulation:** Mocks are approximations. They won't perfectly replicate exact GAS costs, transaction context details, complex `WitnessScope` behaviors, or low-level NeoVM quirks. +* **Framework Stubs:** Remember that many framework methods are `extern` stubs mapping to syscalls. Your mocks provide the *behavior* for these during testing. +* **Compiler vs. Runtime:** There's always a small chance the C# code tested runs differently once compiled to NeoVM bytecode, especially if using obscure C# features. + +**Despite limitations, unit testing with mocked dependencies is highly valuable:** + +* **Fast Feedback:** Allows rapid verification of logic without slow deployment cycles. +* **Isolates Bugs:** Helps pinpoint errors in specific methods or calculations. +* **Edge Cases:** Makes it easier to test numerous edge cases and input variations. +* **Regression Prevention:** Ensures existing logic doesn't break as you add features. + +**Crucially, unit tests complement, but DO NOT replace, integration testing on Neo Express or TestNet.** + +## Example (Conceptual using Manual Fake Storage) + +Let's rethink testing the simple counter contract using a slightly more structured fake. + +**Contract Code (`Counter.cs`)** + +```csharp +// Same as before... +``` + +**Fake Storage Implementation (`Testing/FakeStorage.cs`)** + +```csharp +// VERY basic manual fake storage for demonstration +namespace Testing.Fakes +{ + public static class FakeStorage + { + // Simulate contract storage + private static Dictionary _store = new(); + + // Simulate StorageMap prefixing (basic) + public static void Put(byte prefix, ByteString key, BigInteger value) + { + _store[new byte[] { prefix }.Concat(key)] = (ByteString)value; + } + public static void Put(byte prefix, byte[] key, BigInteger value) + { + Put(prefix, (ByteString)key, value); + } + + public static ByteString Get(byte prefix, ByteString key) + { + _store.TryGetValue(new byte[] { prefix }.Concat(key), out var value); + return value ?? (ByteString)BigInteger.Zero; // Return 0 if not found + } + public static ByteString Get(byte prefix, byte[] key) + { + return Get(prefix, (ByteString)key); + } + + public static void Reset() + { + _store.Clear(); + } + } + + // Similarly, create FakeRuntime with methods for Log, CheckWitness etc. + // You need a way to INJECT these fakes or have the contract use them during tests. + // This might involve conditional compilation (#if DEBUG_TEST) or passing + // delegates/interfaces, though the static nature makes this harder. + // Test harnesses often solve this injection problem. +} +``` + +**Test Class (`CounterTests.cs`)** + +```csharp +using Microsoft.VisualStudio.TestTools.UnitTesting; +using Neo.SmartContract.Framework; +using System.Numerics; +// Assuming a mechanism exists to make Counter use FakeStorage during tests: +// using Testing.Fakes; + +[TestClass] +public class CounterTests +{ + // Define keys corresponding to contract + private static readonly byte CountPrefix = (byte)'C'; // Example prefix + private static readonly byte[] CountKey = { 0x01 }; + + [TestInitialize] + public void Setup() + { + // Testing.Fakes.FakeStorage.Reset(); + // Requires test runner setup to initialize/reset state and inject fakes + } + + [TestMethod] + public void TestIncrementFromZero() + { + // --- ARRANGE --- + // Manually set initial state in fake storage (if Reset doesn't do it) + // Testing.Fakes.FakeStorage.Put(CountPrefix, CountKey, 0); + // Assume FakeRuntime.CheckWitness is mocked to return true if needed + + // --- ACT --- + // Counter.Increment(); // Call the contract method + + // --- ASSERT --- + // BigInteger finalCount = (BigInteger)Testing.Fakes.FakeStorage.Get(CountPrefix, CountKey); + // Assert.AreEqual(BigInteger.One, finalCount); + + Assert.Inconclusive("Test requires a proper test harness or mocking infrastructure to inject FakeStorage/FakeRuntime."); + } +} +``` + +## Neo Test Frameworks + +Given the challenges of mocking static framework methods, using dedicated Neo testing frameworks is often more practical: + +* **Neo Test Harnesses:** Explore libraries like `Neo.Test.Runner` or tools provided within NGD Enterprise developments (if applicable). These often provide pre-built mocks, state management, and helper assertions for testing N3 contracts in a more simulated environment. +* **Neo Blockchain Toolkit (NBT) Scripting:** Neo Express commands (`neox batch ...`) can be scripted, allowing automated sequences of deployment and invocations on a local private network. This is closer to integration testing but can automate checks. + +**Conclusion:** Unit testing Neo N3 contracts via mocking/faking is a valuable and recommended practice demonstrated in official examples. While manual setup can be complex, dedicated Neo testing harnesses simplify the process. Remember to always combine unit tests with comprehensive integration testing on Neo Express and TestNet. + +[Previous: Testing & Deployment Overview](./README.md) | [Next: Testing on Neo Networks](./02-blockchain-testing.md) \ No newline at end of file diff --git a/docs/framework/08-testing-deployment/02-blockchain-testing.md b/docs/framework/08-testing-deployment/02-blockchain-testing.md new file mode 100644 index 000000000..c1ea437c7 --- /dev/null +++ b/docs/framework/08-testing-deployment/02-blockchain-testing.md @@ -0,0 +1,56 @@ +# Testing on Neo Networks (Neo Express & TestNet) + +Since pure unit testing has limitations, testing your smart contract's behavior on a running Neo blockchain environment is crucial. Neo provides two primary environments for this before deploying to MainNet: Neo Express (local) and the public TestNet. + +## 1. Neo Express (Local Private Network) + +Neo Express is part of the [Neo Blockchain Toolkit (NBT)](https://github.com/neo-project/neo-blockchain-toolkit) and is the recommended tool for local development and testing. + +* **Private Instance:** Runs a private, single-node or multi-node Neo N3 blockchain entirely on your local machine. +* **Fast & Free:** Transactions are processed instantly, and you can mint yourself unlimited NEO and GAS for testing. +* **Debugging Support:** Integrates with the VS Code NBT extension for step-through debugging of your C# contract code (requires compiling with debug info). +* **State Control:** Easily reset the chain, create checkpoints, and manage accounts. + +**Typical Workflow:** + +1. **Install:** `dotnet tool install Neo.Express -g` +2. **Create Instance:** `neox create mychain.neo-express` (Creates a configuration file) +3. **Run Instance:** `neox run mychain.neo-express` (Starts the local node) +4. **Create Wallet:** `neox wallet create mywallet` +5. **Mint Assets:** `neox transfer 10000 NEO genesis mywallet` (Transfer initial assets from the instance's genesis wallet) +6. **Compile Contract:** `dotnet build -c Debug` (Ensure debug info is generated) +7. **Deploy Contract:** `neox contract deploy ./bin/Debug/netX.Y/MyContract.nef mywallet --force` (`--force` allows overwriting during development) +8. **Invoke Methods:** `neox contract invoke MyContract.manifest.json someMethod arg1 arg2 mywallet` +9. **Inspect Storage/Events:** Use `neox contract storage ...`, `neox contract get ...`, or check Neo Express logs/output. +10. **Debug (VS Code):** Configure `launch.json` to attach to Neo Express, set breakpoints in C#, and invoke methods to trigger debugging sessions. + +Neo Express is ideal for rapid iteration, functional testing, GAS usage estimation, and debugging during the development cycle. + +## 2. Neo TestNet + +The Neo TestNet (currently N3 TestNet T5) is a public blockchain that mirrors the MainNet's protocol and features but uses valueless test tokens (NEO/GAS). + +* **Realistic Environment:** Operates with multiple consensus nodes run by the community, providing a more realistic test of consensus behavior, network latency, and interaction with other deployed contracts. +* **Public Access:** Anyone can connect and deploy contracts. +* **Free Test Assets:** Obtain free TestNet NEO/GAS from a faucet (search for "Neo N3 TestNet Faucet"). +* **Final Pre-MainNet Check:** It's the last stage of testing before deploying to MainNet. + +**Workflow:** + +1. **Get TestNet Assets:** Use a faucet to fund a TestNet wallet. +2. **Configure Wallet/Tool:** Point your wallet (e.g., Neon Wallet, NeoLine) or SDK to the N3 TestNet RPC endpoints. +3. **Compile for Release:** `dotnet build -c Release` (Do not include debug info for TestNet/MainNet). +4. **Deploy Contract:** Use a wallet or SDK connected to TestNet to perform the deployment transaction, paying fees with TestNet GAS. +5. **Interact:** Use the wallet/SDK to invoke methods, check balances, and monitor events on a TestNet explorer (like Dora, NeoTube). + +Testing on TestNet helps ensure your contract behaves correctly in a multi-node environment and interacts properly with the live network conditions and potentially other contracts before risking real assets on MainNet. + +## Testing Strategy + +1. **Unit Tests:** Verify core logic and algorithms in isolation. +2. **Neo Express:** Perform functional testing, integration testing (contract-to-contract calls), debugging, and initial GAS analysis. +3. **TestNet:** Conduct final testing in a realistic public environment, focusing on deployment, interaction flows, and behavior under real network conditions. + +This multi-layered approach provides the highest confidence in your smart contract's correctness and security. + +[Previous: Unit Testing](./01-unit-testing.md) | [Next: Deployment Process](./03-deployment.md) \ No newline at end of file diff --git a/docs/framework/08-testing-deployment/03-deployment.md b/docs/framework/08-testing-deployment/03-deployment.md new file mode 100644 index 000000000..204fdb4d6 --- /dev/null +++ b/docs/framework/08-testing-deployment/03-deployment.md @@ -0,0 +1,64 @@ +# Deployment Process + +Deploying a smart contract makes its code and manifest available on the Neo blockchain, allowing users and other contracts to interact with it. Deployment is achieved by sending a specific transaction that invokes the `deploy` method of the native `ContractManagement` contract. + +## Prerequisites + +1. **Compiled Artifacts:** You need the compiled `.nef` file (bytecode) and `.manifest.json` file (metadata) for your contract. Compile using `dotnet build` (use `-c Release` for TestNet/MainNet). +2. **Neo Wallet:** A Neo N3 compatible wallet (e.g., Neon Wallet, NeoLine, O3 Wallet) holding the address you want to use for deployment. +3. **GAS:** Sufficient GAS in the deploying wallet to cover: + * The base deployment fee (`ContractManagement.GetMinimumDeploymentFee()`). + * Transaction network fees (based on transaction size, `PolicyContract.GetFeePerByte()`). + * GAS required by your contract's `_deploy` method, if any. + +## Deployment Steps (Conceptual) + +The exact steps vary depending on the tool (Wallet UI, SDK, Neo Express), but the underlying process involves constructing and sending a deployment transaction: + +1. **Read Files:** Read the content of the `.nef` file (as raw bytes) and the `.manifest.json` file (as a UTF8 string). +2. **Construct Script:** Create a NeoVM script that calls the `ContractManagement.deploy` method, passing the NEF bytes and manifest string as arguments. + ```csharp + // Pseudo-code for script generation + scriptBuilder.EmitDynamicCall(ContractManagement.Hash, "deploy", nefFileBytes, manifestJsonString); + script = scriptBuilder.ToArray(); + ``` +3. **Create Transaction:** Build a transaction that includes: + * The generated script. + * **Signer:** The deploying account (your wallet address) with appropriate scopes (usually `CalledByEntry`). + * **Fees:** Sufficient System Fee and Network Fee (calculated based on script size, execution cost, and network policy). + * Nonce, ValidUntilBlock, etc. +4. **Sign Transaction:** Sign the transaction using the private key corresponding to the deploying account. +5. **Broadcast Transaction:** Send the signed transaction to a Neo node (connected to the target network - Neo Express, TestNet, or MainNet). +6. **Confirmation:** Wait for the transaction to be included in a block and confirmed by the network. +7. **`_deploy` Execution:** Upon successful deployment confirmation, the `_deploy(object data, bool update)` method within your newly deployed contract code is automatically executed by the NeoVM with `update` set to `false`. + +## Deployment Using Tools + +* **Neo Express (Local):** + ```bash + # Ensure Neo Express instance is running + # Deploy using your neox wallet + neox contract deploy ./path/to/MyContract.nef YourWalletName --force + # --force allows overwriting if already deployed locally + ``` + Neo Express handles reading the manifest (assumes it's alongside the NEF), calculating fees (using local settings), creating, signing, and broadcasting the transaction to the local instance. + +* **Wallets (Neon, NeoLine, etc.):** + * Most graphical wallets provide a UI for contract deployment. + * You typically need to select/upload the `.nef` and `.manifest.json` files. + * The wallet calculates estimated fees. + * You approve the transaction, and the wallet signs and broadcasts it to the selected network (TestNet/MainNet). + +* **SDKs (Neon.js, neow3j, NGD SDK, etc.):** + * SDKs provide APIs to programmatically perform the deployment steps: reading files, building the script and transaction, calculating fees, signing, and sending. + * This offers more flexibility for automated deployment pipelines. + +## Post-Deployment + +* **Record Script Hash:** Once deployed, your contract is identified by its unique **Script Hash**. Note this address down – it's needed to interact with the contract. +* **Verification (Optional):** Some block explorers allow developers to verify their contract source code, linking the deployed script hash to the original C# code for transparency. +* **Interaction:** Use wallets, explorers, or SDKs to call the methods defined in your contract's manifest, using its script hash. + +Deploying to MainNet is irreversible and involves real costs (GAS). **Always test extensively on Neo Express and TestNet before deploying to MainNet.** + +[Previous: Testing on Neo Networks](./02-blockchain-testing.md) | [Next: Interacting with Deployed Contracts](./04-interaction.md) \ No newline at end of file diff --git a/docs/framework/08-testing-deployment/04-interaction.md b/docs/framework/08-testing-deployment/04-interaction.md new file mode 100644 index 000000000..cdef9262d --- /dev/null +++ b/docs/framework/08-testing-deployment/04-interaction.md @@ -0,0 +1,63 @@ +# Interacting with Deployed Contracts + +Once your smart contract is deployed on a Neo network (Neo Express, TestNet, or MainNet), users and applications need ways to interact with it by calling its public methods. + +Interaction typically involves constructing and sending a transaction that invokes a specific method on your contract. + +## Required Information + +To interact with a deployed contract, you need: + +1. **Contract Script Hash:** The unique address (`UInt160`) of the deployed contract. +2. **Method Name:** The name of the public static method you want to call (as defined in the manifest, respecting `[DisplayName]`). +3. **Arguments:** Any arguments required by the method, in the correct order and type. +4. **Signer Account:** A wallet/account to sign the transaction and potentially pay GAS fees. +5. **Network Connection:** Access to a Neo node (RPC endpoint) for the target network. + +## Interaction Methods + +* **Wallets (Neon, NeoLine, O3, etc.):** + * Many wallets provide a user interface for invoking contract methods. + * You typically enter the script hash, select the method from a list (often populated by fetching the manifest), input arguments, and choose the signing account. + * The wallet handles constructing the script (`Contract.Call`), building the transaction, calculating fees, signing, and broadcasting. + * This is the most common way for end-users to interact. + +* **Block Explorers (Dora, NeoTube, etc.):** + * Some explorers offer interfaces to read data from contracts (calling `[Safe]` methods) and sometimes even invoke methods (which would usually redirect to a connected wallet for signing). + * Primarily used for viewing contract state, transactions, and events. + +* **Neo Express (Local):** + * Provides the `neox contract invoke` command for command-line interaction with contracts deployed on a local Neo Express instance. + * You provide the manifest file (or contract name if known), method name, arguments, and the signing wallet name. + * ```bash + neox contract invoke ./path/to/MyContract.manifest.json myMethod arg1 "arg 2" MyWalletName -- optional-args + ``` + * Useful for testing and scripting interactions locally. + +* **SDKs (Neon.js, neow3j, NGD SDK - C#, Python, Go):** + * Software Development Kits provide libraries to interact with Neo nodes and contracts programmatically. + * This is how backend services, dApps, and automated scripts typically interact with contracts. + * **Steps (Conceptual using an SDK):** + 1. Connect to a Neo RPC node for the target network. + 2. Define contract script hash, method name, and arguments. + 3. Use the SDK's "contract invocation" functions to build the NeoVM script (often abstracted away). + 4. Create a transaction including the script, signer(s), and calculated fees. + 5. Sign the transaction using the private key of the signer account (managed securely). + 6. Send the transaction to the node. + 7. Optionally, wait for confirmation and retrieve the execution result (return value, events, logs). + +## Reading vs. Writing State + +* **Reading (`[Safe]` Methods):** Calling methods marked as `[Safe]` in the manifest only reads data. This can often be done via RPC calls (`invokefunction` or `invokescript`) without sending an actual transaction, meaning no GAS fee is required (though the RPC node might have its own limits). +* **Writing (Non-`[Safe]` Methods):** Calling methods that modify state (write to storage, transfer tokens, emit events) **always** requires sending a transaction, which must be signed and include GAS fees. + +## Monitoring Events + +Off-chain applications often need to react to events emitted by smart contracts (`Runtime.Notify`). + +* **SDKs:** Provide mechanisms to subscribe to event notifications from the blockchain or query past transaction logs for specific events emitted by your contract. +* **Explorers:** Display events associated with transactions involving your contract. + +Effective interaction involves choosing the right tool for the job – wallets for users, Neo Express for local testing, SDKs for dApps and automation, and explorers for monitoring. + +[Previous: Deployment Process](./03-deployment.md) | [Next Section: Reference](../09-reference/README.md) \ No newline at end of file diff --git a/docs/framework/08-testing-deployment/README.md b/docs/framework/08-testing-deployment/README.md new file mode 100644 index 000000000..a78ec85fd --- /dev/null +++ b/docs/framework/08-testing-deployment/README.md @@ -0,0 +1,12 @@ +# Testing & Deployment + +After writing and compiling your smart contract, thorough testing and a clear understanding of the deployment process are essential before launching on the Neo MainNet. + +## Topics + +* [Unit Testing](./01-unit-testing.md): Testing contract logic in isolation using C# testing frameworks. +* [Testing on Neo Networks](./02-blockchain-testing.md): Using Neo Express (local private net) and public TestNets. +* [Deployment Process](./03-deployment.md): Steps involved in deploying your contract. +* [Interacting with Deployed Contracts](./04-interaction.md): Using SDKs and wallets. + +[Next: Unit Testing](./01-unit-testing.md) \ No newline at end of file diff --git a/docs/framework/09-reference/01-opcodes.md b/docs/framework/09-reference/01-opcodes.md new file mode 100644 index 000000000..ca9ba3a9a --- /dev/null +++ b/docs/framework/09-reference/01-opcodes.md @@ -0,0 +1,15 @@ +# NeoVM Opcodes + +Neo smart contracts, when compiled, result in NeoVM bytecode, which is a series of low-level instructions (opcodes) executed by the Neo Virtual Machine. + +Understanding these opcodes can be helpful for advanced debugging, optimization, and comprehending the underlying execution model, although it's not typically required for standard C# development using the framework. + +## Official Reference + +The definitive list and description of Neo N3 VM opcodes can be found in the official Neo developer documentation: + +* **[Neo N3 VM Opcode Reference](https://developers.neo.org/docs/n3/reference/neovm/opcodes/stack)** + +This reference details each opcode, its function, stack behavior, and GAS cost. + +[Previous: Reference Overview](./README.md) | [Next: System Limits & Costs](./02-limits.md) \ No newline at end of file diff --git a/docs/framework/09-reference/02-limits.md b/docs/framework/09-reference/02-limits.md new file mode 100644 index 000000000..e69904a57 --- /dev/null +++ b/docs/framework/09-reference/02-limits.md @@ -0,0 +1,60 @@ +# System Limits & Costs + +Developing Neo smart contracts requires awareness of various system limits and associated GAS costs to ensure contracts are executable and economical. + +**Note:** These values can be changed by the Neo Council via the `PolicyContract`. Always verify current values on the target network (MainNet/TestNet) if precision is critical. + +## GAS Costs + +* **Execution Fees:** Every NeoVM opcode and Interop Syscall has an associated GAS cost. Complex operations, storage access, and contract calls are generally more expensive. + * Costs are calculated as `BaseCost * ExecFeeFactor / DefaultFactor` where `ExecFeeFactor` is set by policy (`PolicyContract.GetExecFeeFactor()`). + * Refer to the official [Neo N3 Opcode reference](https://developers.neo.org/docs/n3/reference/neovm/opcodes/stack) and Syscall documentation for base costs. +* **Storage Fees (`PolicyContract.GetStoragePrice()`):** + * A fee (in GAS) is charged per byte of data stored (`key_length + value_length`). + * Paid when `Storage.Put` is called. +* **Network Fees (`PolicyContract.GetFeePerByte()`):** + * Fee paid based on the byte size of the transaction itself. + * Compensates consensus nodes for including the transaction. +* **System Fees:** + * Collected by the system for certain operations (like contract deployment, execution exceeding free limits) and are burned. + +## Execution Limits + +* **Max GAS per Transaction:** Transactions have a GAS limit. If execution exceeds this, the transaction fails and state changes are reverted (though fees are still paid). + * There's a base amount of free GAS (~10 GAS) per transaction for basic operations. +* **Max GAS per Block (`PolicyContract.GetMaxBlockSystemFee()`):** The total system fees collected by transactions within a single block cannot exceed this limit. +* **Max Invocation Stack Depth:** NeoVM limits how deeply contracts can call each other (typically around 1024 levels) to prevent stack overflows. +* **Max Item Size:** Limits on the size of individual items pushed onto the stack or stored (e.g., `ByteString` size). +* **Max Stack Size:** Limits on the number of items on the NeoVM execution stack. + +## Transaction & Block Limits + +* **Max Transactions per Block (`PolicyContract.GetMaxTransactionsPerBlock()`):** Limits the number of transactions a block can contain. +* **Max Block Size (`PolicyContract.GetMaxBlockSize()`):** Limits the total byte size of a block. +* **Transaction Size:** Individual transactions also have size limits. + +## Other Limits + +* **Manifest Size:** The `.manifest.json` file has a size limit. +* **NEF Size:** The `.nef` file has a size limit. +* **Parameter Count:** Limits on the number of parameters a method can accept. + +## Querying Limits On-Chain + +You can use the `PolicyContract` native contract wrapper to query some of these limits directly within a smart contract or via RPC calls: + +```csharp +long feePerByte = PolicyContract.GetFeePerByte(); +uint storagePrice = PolicyContract.GetStoragePrice(); +uint maxTxPerBlock = PolicyContract.GetMaxTransactionsPerBlock(); +// etc. +``` + +**Implications:** + +* Design contracts to be GAS-efficient. +* Be aware of potential limits when designing complex interactions or storing large amounts of data. +* Ensure users attach sufficient GAS to cover execution and network fees. +* Test GAS consumption using Neo Express or TestNet. + +[Previous: Reference Overview](./README.md) | [Back to Main README](../README.md) \ No newline at end of file diff --git a/docs/framework/09-reference/README.md b/docs/framework/09-reference/README.md new file mode 100644 index 000000000..95a4574e9 --- /dev/null +++ b/docs/framework/09-reference/README.md @@ -0,0 +1,13 @@ +# Reference + +This section provides links to external references and summaries of key system limits. + +## Topics + +* [NeoVM Opcodes](./01-opcodes.md): The low-level instruction set. +* [System Limits & Costs](./02-limits.md): Overview of key constraints like GAS limits, transaction size, and storage costs. +* [NEP Standards (Neo Enhancement Proposals)](https://github.com/neo-project/proposals/tree/master/nep) + * [NEP-17: Fungible Token Standard](https://github.com/neo-project/proposals/blob/master/nep-17.mediawiki) + * [NEP-11: Non-Fungible Token Standard](https://github.com/neo-project/proposals/blob/master/nep-11.mediawiki) + +[Next: NeoVM Opcodes](./01-opcodes.md) \ No newline at end of file diff --git a/docs/framework/README.md b/docs/framework/README.md new file mode 100644 index 000000000..91fecc975 --- /dev/null +++ b/docs/framework/README.md @@ -0,0 +1,78 @@ +# Neo N3 Smart Contract Development with C# + +Welcome to the comprehensive guide for developing Neo N3 smart contracts using C#. This documentation provides everything you need to get started, from basic concepts to advanced techniques, leveraging the `Neo.SmartContract.Framework` and `Neo.Compiler.CSharp`. + +This guide is structured to help you build robust, efficient, and secure smart contracts on the Neo blockchain. + +## Table of Contents + +**I. Foundational Concepts** + +* [Introduction](./01-introduction/README.md) + * [What is Neo?](./01-introduction/01-what-is-neo.md) + * [What are Smart Contracts?](./01-introduction/02-smart-contracts.md) + * [Why C# for Neo?](./01-introduction/03-why-csharp.md) +* [Getting Started](./02-getting-started/README.md) + * [Environment Setup](./02-getting-started/01-setup.md) + * [Framework & Compiler](./02-getting-started/02-installation.md) + * [Your First Contract](./02-getting-started/03-first-contract.md) +* [Core Concepts](./03-core-concepts/README.md) + * [NeoVM & GAS](./03-core-concepts/01-neovm-gas.md) + * [Transactions](./03-core-concepts/02-transactions.md) + * [Contract Structure](./03-core-concepts/03-contract-structure.md) + * [Entry Points & Methods](./03-core-concepts/04-entry-points.md) + * [Data Types](./03-core-concepts/05-data-types.md) + * [Deployment Artifacts (NEF & Manifest)](./03-core-concepts/06-deployment-files.md) + * [Error Handling](./03-core-concepts/07-error-handling.md) + +**II. The Smart Contract Framework (`Neo.SmartContract.Framework`)** + +* [Framework Features Overview](./04-framework-features/README.md) + * [Storage](./04-framework-features/01-storage.md) + * [Runtime Services](./04-framework-features/02-runtime.md) + * [Events](./04-framework-features/03-events.md) + * [Contract Interaction](./04-framework-features/04-contract-interaction.md) + * [Native Contracts](./04-framework-features/05-native-contracts/README.md) + * [LedgerContract](./04-framework-features/05-native-contracts/Ledger.md) + * [NeoToken (NEO)](./04-framework-features/05-native-contracts/NeoToken.md) + * [GasToken (GAS)](./04-framework-features/05-native-contracts/GasToken.md) + * [PolicyContract](./04-framework-features/05-native-contracts/Policy.md) + * [RoleManagement](./04-framework-features/05-native-contracts/RoleManagement.md) + * [OracleContract](./04-framework-features/05-native-contracts/Oracle.md) + * [NameService (NNS)](./04-framework-features/05-native-contracts/NameService.md) + * [StdLib](./04-framework-features/05-native-contracts/StdLib.md) + * [CryptoLib](./04-framework-features/05-native-contracts/CryptoLib.md) + * [ContractManagement](./04-framework-features/05-native-contracts/ContractManagement.md) + * [Attributes](./04-framework-features/06-attributes.md) + * [Helper Methods](./04-framework-features/07-helper-methods.md) + +**III. The C# Compiler (`Neo.Compiler.CSharp`)** + +* [Compiler Documentation](./compiler/README.md) + +**IV. Advanced Development** + +* [Advanced Topics](./06-advanced-topics/README.md) + * [Contract Upgrade & Migration](./06-advanced-topics/01-contract-upgrade.md) + * [Security Best Practices](./06-advanced-topics/02-security.md) + * [GAS Optimization](./06-advanced-topics/03-optimization.md) + * [Interop Layer](./06-advanced-topics/04-interop.md) + +**V. Practical Guides** + +* [Tutorials](./07-tutorials/README.md) + * [Building a NEP-17 Token](./07-tutorials/01-nep17-token.md) + * [Creating a Voting Contract](./07-tutorials/02-voting-contract.md) + * [Using Oracles](./07-tutorials/03-oracle-usage.md) +* [Testing & Deployment](./08-testing-deployment/README.md) + * [Unit Testing](./08-testing-deployment/01-unit-testing.md) + * [Testing on Neo Networks](./08-testing-deployment/02-blockchain-testing.md) + * [Deployment Process](./08-testing-deployment/03-deployment.md) + * [Interacting with Deployed Contracts](./08-testing-deployment/04-interaction.md) + +**VI. Reference** + +* [Reference Materials](./09-reference/README.md) + * [NeoVM Opcodes](./09-reference/01-opcodes.md) + * [System Limits & Costs](./09-reference/02-limits.md) + * [NEP Standards](https://github.com/neo-project/proposals/tree/master/nep) \ No newline at end of file