diff --git a/docs/Architecture/Smart Contract/architecture.md b/docs/Architecture/Smart Contract/architecture.md new file mode 100644 index 00000000..a39747ea --- /dev/null +++ b/docs/Architecture/Smart Contract/architecture.md @@ -0,0 +1,62 @@ +# Smart Contract Architecture + +A blockchain platform works like a distributed database that stores all smart contracts. Each contract has a unique address used for state queries and updates. Methods in the contract code handle permission checks and logic. + +## Smart Contract Parts in aelf + +1. **Interface**: + - Supports multiple languages. + - Uses Protobuf format for cross-language definitions. + +2. **State**: + - Language-specific SDK provides state prototypes. + - Developers can query and update the state directly through these prototypes. + +3. **Business Logic**: + - Protobuf plugins generate the smart contract skeleton. + - Developers fill in the logic by overriding methods. + +Smart contracts in aelf are divided across the Kernel, the runtime, and the SDK. The Kernel handles core components and execution abstractions. Contracts rely on runtime modules and the SDK. + +A smart contract consists of methods that interact with state variables. Transactions trigger these methods to modify the blockchain state. + +## Architecture Overview + +aelf defines Smart Contracts as micro-services, making them language-independent. For example, the Consensus Protocol is a service defined by a smart contract. + +![Smart Contract Architecture](../../_images/sc-as-service.png) + +### Chain Interactions + +Smart contracts interact with the chain and access contextual information via a bridge and a bridge host. The SDKs implement features to communicate through the bridge. + +Key contextual information provided by the bridge includes: +- `Self`: Address of the current contract. +- `Sender`: Address that sent the transaction. +- `Origin`: Address that signed the transaction. +- `OriginTransactionId` and `TransactionId`: IDs of the transactions involved. + +The bridge also allows: +- Firing events (similar to logging). +- Calling methods on other contracts in a read-only manner. +- Sending inline transactions, which can persist state changes. + +## State + +Smart contracts read and/or modify state. The language SDKs provide state helpers and access through the bridge’s `StateProvider`. + +## Runtime and Execution + +When a block’s transactions are executed, each transaction generates a trace containing: +- Return value of the called method. +- Error outputs, if any. +- Results from inner calls in `InlineTraces`. +- Events launched in `Logs`. + +## SDK + +aelf has a native C# SDK for developing smart contracts in C#. It includes: +- Helpers to communicate with the bridge. +- Type infrastructure like `ContractState`, `MappedState`, and `SingletonState`. + +Any developer or company can create an SDK and runtime for a specific language by adapting it to communicate with the bridge through gRPC. \ No newline at end of file diff --git a/docs/Architecture/Smart Contract/event.md b/docs/Architecture/Smart Contract/event.md new file mode 100644 index 00000000..58613182 --- /dev/null +++ b/docs/Architecture/Smart Contract/event.md @@ -0,0 +1,31 @@ +# Smart Contract Events + +## Event Option + +Events in aelf smart contracts are used to represent occurrences during execution. These events are logged in the transaction traces. + +Example of an event definition: + +```cs +message Transferred { + option (aelf.is_event) = true; + aelf.Address from = 1 [(aelf.is_indexed) = true]; + aelf.Address to = 2 [(aelf.is_indexed) = true]; + string symbol = 3 [(aelf.is_indexed) = true]; + sint64 amount = 4; + string memo = 5; +} +``` +- option (aelf.is_event) = true; indicates that Transferred is an event. + +To trigger this event in a contract: +```cs +Context.Fire(new Transferred() +{ + From = from, + To = to, + ... +}); +``` + +External code can monitor this event after the transaction execution. \ No newline at end of file diff --git a/docs/Architecture/Smart Contract/messages.md b/docs/Architecture/Smart Contract/messages.md new file mode 100644 index 00000000..ddc8f2a7 --- /dev/null +++ b/docs/Architecture/Smart Contract/messages.md @@ -0,0 +1,20 @@ +# Smart Contract Messages + +In aelf, we use protobuf messages to call smart contracts and serialize their state. Here's a simple example of a message definition: + +```cs +message CreateInput { + string symbol = 1; + sint64 totalSupply = 2; + sint32 decimals = 3; +} +``` + +This message has three fields: +- symbol (string) +- totalSupply (sint64) +- decimals (sint32). + +You can use any protobuf-supported types, including composite messages (messages containing other messages). + +We use the proto3 version of protobuf for message and service definitions. You can refer to the full protobuf language reference for more details. \ No newline at end of file diff --git a/docs/Architecture/Smart Contract/requirements-and-restrictions/namespace-and-type-restrictions.md b/docs/Architecture/Smart Contract/requirements-and-restrictions/namespace-and-type-restrictions.md new file mode 100644 index 00000000..b525a45f --- /dev/null +++ b/docs/Architecture/Smart Contract/requirements-and-restrictions/namespace-and-type-restrictions.md @@ -0,0 +1,101 @@ +# Type and Namespace Restrictions + +When deploying new contract code, Nodes perform checks against a whitelist. If any type used in the code is not listed in the whitelist, or if the method access or type name is denied, the deployment will fail. This ensures that only approved types and namespaces can be utilized within the contract code. + +## Assembly Dependencies + +| Assembly | Trust | +|------------------------------|---------| +| netstandard.dll | Partial | +| System.Runtime.dll | Partial | +| System.Runtime.Extensions.dll| Partial | +| System.Private.CoreLib.dll | Partial | +| System.ObjectModel.dll | Partial | +| System.Linq.dll | Full | +| System.Collections | Full | +| Google.Protobuf.dll | Full | +| AElf.Sdk.CSharp.dll | Full | +| AElf.Types.dll | Full | +| AElf.CSharp.Core.dll | Full | +| AElf.Cryptography.dll | Full | + +## Types and Members Whitelist in System Namespace + +| Type | Member (Field / Method) | Allowed | +|-------------------------------|-------------------------|-----------| +| Array | AsReadOnly | Allowed | +| Func | ALL | Allowed | +| Func | ALL | Allowed | +| Func | ALL | Allowed | +| Nullable | ALL | Allowed | +| Environment | CurrentManagedThreadId | Allowed | +| BitConverter | GetBytes | Allowed | +| NotImplementedException | ALL | Allowed | +| NotSupportedException | ALL | Allowed | +| ArgumentOutOfRangeException | ALL | Allowed | +| DateTime | Partially | Allowed | +| DateTime | Now, UtcNow, Today | Denied | +| Uri | TryCreate | Allowed | +| Uri | Scheme | Allowed | +| Uri | UriSchemeHttp | Allowed | +| Uri | UriSchemeHttps | Allowed | +| void | ALL | Allowed | +| object | ALL | Allowed | +| Type | ALL | Allowed | +| IDisposable | ALL | Allowed | +| Convert | ALL | Allowed | +| Math | ALL | Allowed | +| bool | ALL | Allowed | +| byte | ALL | Allowed | +| sbyte | ALL | Allowed | +| char | ALL | Allowed | +| int | ALL | Allowed | +| uint | ALL | Allowed | +| long | ALL | Allowed | +| ulong | ALL | Allowed | +| decimal | ALL | Allowed | +| string | ALL | Allowed | +| string | Constructor | Denied | +| Byte[] | ALL | Allowed | + +## Types and Members Whitelist in System.Reflection Namespace + +| Type | Member (Field / Method) | Allowed | +|-------------------------------------|---------------------------------|-----------| +| AssemblyCompanyAttribute | ALL | Allowed | +| AssemblyConfigurationAttribute | ALL | Allowed | +| AssemblyFileVersionAttribute | ALL | Allowed | +| AssemblyInformationalVersionAttribute | ALL | Allowed | +| AssemblyProductAttribute | ALL | Allowed | +| AssemblyTitleAttribute | ALL | Allowed | + +## Other Whitelisted Namespaces + +| Namespace | Type | Member | Allowed | +|---------------------------------------|--------------|-----------------------|-----------| +| System.Linq | ALL | ALL | Allowed | +| System.Collections | ALL | ALL | Allowed | +| System.Collections.Generic | ALL | ALL | Allowed | +| System.Collections.ObjectModel | ALL | ALL | Allowed | +| System.Globalization | CultureInfo | InvariantCulture | Allowed | +| System.Runtime.CompilerServices | RuntimeHelpers | InitializeArray | Allowed | +| System.Text | Encoding | UTF8, GetByteCount | Allowed | + +## Allowed Types for Arrays + +| Type | Array Size Limit | +|--------------------|------------------| +| byte | 40960 | +| short | 20480 | +| int | 10240 | +| long | 5120 | +| ushort | 20480 | +| uint | 10240 | +| ulong | 5120 | +| decimal | 2560 | +| char | 20480 | +| string | 320 | +| Type | 5 | +| Object | 5 | +| FileDescriptor | 10 | +| GeneratedClrTypeInfo | 100 | \ No newline at end of file diff --git a/docs/Architecture/Smart Contract/requirements-and-restrictions/other-restrictions.md b/docs/Architecture/Smart Contract/requirements-and-restrictions/other-restrictions.md new file mode 100644 index 00000000..3abf5e8f --- /dev/null +++ b/docs/Architecture/Smart Contract/requirements-and-restrictions/other-restrictions.md @@ -0,0 +1,37 @@ +# Other Restrictions + +## GetHashCode Usage +The GetHashCode method can only be called within other GetHashCode methods. Calling GetHashCode from other methods is not permitted. This restriction allows developers to implement custom GetHashCode methods for their own types and also supports protobuf-generated message types. + +It is prohibited to modify any field within GetHashCode methods. + +## Execution Observer +aelf's contract patcher automatically includes a method call count observer for your contract. This feature prevents infinite method calls, such as recursion. During transaction execution, the observer counts the number of methods called. If this count exceeds 15,000, transaction execution pauses. Adjustments to this limit are managed by Parliament. + +Additionally, aelf's contract patcher includes a method branch count observer for your contract. This prevents infinite loop scenarios by counting control transfers within your contract code during transaction execution. If the number of control transfers exceeds 15,000, transaction execution pauses. The control transfer opcodes in C# contracts are listed below. + +| Opcode | Description | +|------------------|-----------------| +| OpCodes.Beq | Branch if equal | +| OpCodes.Beq_S | Branch if equal (short form) | +| OpCodes.Bge | Branch if greater than or equal | +| OpCodes.Bge_S | Branch if greater than or equal (short form) | +| OpCodes.Bge_Un | Branch if greater than or equal (unsigned) | +| OpCodes.Bge_Un_S | Branch if greater than or equal (unsigned, short form) | +| OpCodes.Bgt | Branch if greater than | +| OpCodes.Bgt_S | Branch if greater than (short form) | +| OpCodes.Ble | Branch if less than or equal | +| OpCodes.Ble_S | Branch if less than or equal (short form) | +| OpCodes.Ble_Un | Branch if less than or equal (unsigned) | +| OpCodes.Blt | Branch if less than | +| OpCodes.Bne_Un | Branch if not equal (unsigned) | +| OpCodes.Bne_Un_S | Branch if not equal (unsigned, short form) | +| OpCodes.Br | Branch unconditional | +| OpCodes.Brfalse | Branch if false | +| OpCodes.Brfalse_S| Branch if false (short form) | +| OpCodes.Brtrue | Branch if true | +| OpCodes.Brtrue_S | Branch if true (short form) | +| OpCodes.Br_S | Branch unconditional (short form) | + +## State Size Limit +Data written to State is subject to a size limit enforced by aelf's contract patcher, set at 128 KB by default. This ensures contracts cannot store excessively large data. Any adjustments to this limit are decided by Parliament. \ No newline at end of file diff --git a/docs/Architecture/Smart Contract/requirements-and-restrictions/project-requirements.md b/docs/Architecture/Smart Contract/requirements-and-restrictions/project-requirements.md new file mode 100644 index 00000000..da90201d --- /dev/null +++ b/docs/Architecture/Smart Contract/requirements-and-restrictions/project-requirements.md @@ -0,0 +1,28 @@ +# Contract Project Requirements + +## Project Properties + +### Add Contract Proto File +Ensure to add a contract proto file in the contract directory of your project. This allows aelf’s contract patcher to process the DLL, enabling necessary injections for code checks during deployment. Without this, deployment will fail. + +```tree +src +├── Protobuf +│ └── contract +│ └── hello_world_contract.proto +``` + +### Enable Overflow Checks +Enable `CheckForOverflowUnderflow` for both Release and Debug modes. This ensures arithmetic operations that overflow will throw an `OverflowException`, preventing unpredictable results. + +```xml + + true + + + + true + +``` + +If your contract has unchecked arithmetic operations, deployment will fail. \ No newline at end of file diff --git a/docs/Architecture/Smart Contract/requirements-and-restrictions/structure-restrictions-and-requirements.md b/docs/Architecture/Smart Contract/requirements-and-restrictions/structure-restrictions-and-requirements.md new file mode 100644 index 00000000..283ed035 --- /dev/null +++ b/docs/Architecture/Smart Contract/requirements-and-restrictions/structure-restrictions-and-requirements.md @@ -0,0 +1,180 @@ +# Contract Class Structure + +## Restrictions for Simplified Code Checks + +### Inheritance Rules +- Only one inheritance is allowed from `ContractBase`, generated by the contract plugin as a nested type in `ContractContainer`. +- Only one inheritance is allowed from `CSharpSmartContract`. +- Only one inheritance is allowed from `ContractState`. +- The type inherited from `ContractState` should be the element type of the `CSharpSmartContract` generic instance type. + +If these rules are not followed, code deployment will fail. + +![Contract Class Structure](../../_images/contract-class-structure.png) + +## Field Usage Limitations + +### In Contract Implementation Class +- Initial values for non-readonly, non-constant fields are not allowed (for both static and non-static fields). This is because their value will reset to 0 or null after the first execution, losing the initial value. + +#### Allowed: +```cs +class MyContract : MyContractBase +{ + int test; + static const int test = 2; +} +``` + +#### Not Allowed: +```cs +class MyContract : MyContractBase +{ +! int test = 2; +} +``` + +```cs +class MyContract : MyContractBase +{ + int test; + + public MyContract + { +! test = 2; + } +} +``` + +- Only primitive types or the following types are allowed for readonly/constant fields: + - `Marshaller` + - `Method` + - `MessageParser` + - `FieldCodec` + - `MapField` + - `ReadonlyCollection` + - `ReadonlyDictionary` +Note: T can only be a primitive type. + +### In Non-Contract Classes (Classes not inheriting from `ContractBase`) +- Initial values for non-readonly, non-constant static fields are not allowed. They reset to 0 or null after the first execution, losing the initial value. + +#### Allowed: +```cs +class AnyClass +{ + static int test; +} +``` + +#### Not Allowed: +```cs +class AnyClass +{ +! static int test = 2; +} +``` + +```cs +class AnyClass +{ + static int test; + + public AnyClass + { +! test = 2; + } +} +``` + +- Exception: Fields with FileDescriptor types are allowed due to protobuf-generated code. These fields don’t have a readonly modifier and write access to them is allowed only from the constructor of the declaring type. + +#### Allowed: +```cs +public class TestType +{ + private static FileDescriptor test; + + public class TestType + { + test = ... + } +} +``` + +#### Not Allowed: +```cs +public class TestType +{ + private static FileDescriptor test; + + public TestType + { + test = ... + } + +! public void SetFromSomeWhereElse(FileDescriptor input) +! { +! test = input; +! } +} +``` + +- Accessing test fields is restricted to the declaring type’s constructor only. + +- Only the following types are allowed for readonly/constant static fields: + - `Marshaller` + - `Method` + - `MessageParser` + - `FieldCodec` + - `MapField` + - `ReadonlyCollection` + - `ReadonlyDictionary` + +Note: T can only be a primitive type. + +- Exception: If a type has a readonly field of the same type as itself, it is only allowed if the type has no instance fields (to support LINQ-related generated types). + +#### Allowed: +```cs +public class TestType +{ + private static readonly TestType test; + + private static int i; +} +``` + +#### Not Allowed: +```cs +public class TestType +{ + private static readonly TestType test; + +! private int i; +} +``` + +### In Contract State +In contract state, only the following types are allowed: + +#### Primitive Types +- `BoolState` +- `Int32State` +- `UInt32State` +- `Int64State` +- `UInt64State` +- `StringState` +- `BytesState` +- `Complex Types` + +#### Complex Types +- `SingletonState` +- `ReadonlyState` +- `MappedState` +- `MappedState` +- `MappedState` +- `MappedState` +- `MethodReference` +- `ProtobufState` +- `ContractReferenceState` \ No newline at end of file diff --git a/docs/Architecture/Smart Contract/service.md b/docs/Architecture/Smart Contract/service.md new file mode 100644 index 00000000..914b55f6 --- /dev/null +++ b/docs/Architecture/Smart Contract/service.md @@ -0,0 +1,55 @@ +# Smart Contract Service + +When writing a smart contract in aelf, the first step is to define it so our tools can generate it. aelf contracts are defined and generated using gRPC and protobuf. + +## Example: Multi-Token Contract + +Here is a simplified part of our multi-token contract definition: + +```cs +syntax = "proto3"; + +package token; +option csharp_namespace = "AElf.Contracts.MultiToken.Messages"; + +service TokenContract { + option (aelf.csharp_state) = "AElf.Contracts.MultiToken.TokenContractState"; + + // Actions + rpc Create (CreateInput) returns (google.protobuf.Empty) { } + rpc Transfer (TransferInput) returns (google.protobuf.Empty) { } + + // Views + rpc GetBalance (GetBalanceInput) returns (GetBalanceOutput) { + option (aelf.is_view) = true; + } +} +``` + +## Service Methods +There are two types of methods in a service: + +### Actions +- These methods take input and output protobuf messages. +- They usually modify the state of the chain. + +Example: +```cs +rpc Create (CreateInput) returns (google.protobuf.Empty) { } +``` + +- Takes a protobuf message as input and returns a protobuf message. +- google.protobuf.Empty signifies returning nothing. +- Convention: append Input to protobuf types used as parameters. + +### Views +- These methods do not modify the state of the chain. +- They are used to query the state. + +Example: +```cs +rpc GetBalance (GetBalanceInput) returns (GetBalanceOutput) { + option (aelf.is_view) = true; +} +``` +- Annotated with a view option to indicate it's read-only. \ No newline at end of file