diff --git a/docs/Reference/ACS Introduction/ACS0 - Contract Deployment Standard.md b/docs/Reference/ACS Introduction/ACS0 - Contract Deployment Standard.md new file mode 100644 index 0000000..cba403d --- /dev/null +++ b/docs/Reference/ACS Introduction/ACS0 - Contract Deployment Standard.md @@ -0,0 +1,298 @@ +# ACS0 - Contract Deployment Standard + +ACS0 manages contract deployment and updates. + +## Interface + +Contracts using ACS0 need to implement these methods: + +### Methods + +| Method Name | Request | Response | Description | +|------------------------------------|-------------------------------------|-----------------------------------|--------------------------------------------------| +| DeploySystemSmartContract | `acs0.SystemContractDeploymentInput`| `aelf.Address` | Deploys a system smart contract and returns its address. | +| DeploySmartContract | `acs0.ContractDeploymentInput` | `aelf.Address` | Deploys a smart contract and returns its address. | +| UpdateSmartContract | `acs0.ContractUpdateInput` | `aelf.Address` | Updates a smart contract. | +| ProposeNewContract | `acs0.ContractDeploymentInput` | `aelf.Hash` | Creates a proposal to deploy a new contract and returns its ID. | +| ProposeContractCodeCheck | `acs0.ContractCodeCheckInput` | `aelf.Hash` | Creates a proposal to check contract code and returns its ID. | +| ProposeUpdateContract | `acs0.ContractUpdateInput` | `aelf.Hash` | Creates a proposal to update a contract and returns its ID. | +| ReleaseApprovedContract | `acs0.ReleaseContractInput` | `google.protobuf.Empty` | Releases an approved contract proposal. | +| ReleaseCodeCheckedContract | `acs0.ReleaseContractInput` | `google.protobuf.Empty` | Releases a proposal that passed the code check. | +| ValidateSystemContractAddress | `acs0.ValidateSystemContractAddressInput` | `google.protobuf.Empty` | Validates if the input system contract exists. | +| SetContractProposerRequiredState | `google.protobuf.BoolValue` | `google.protobuf.Empty` | Sets contract deployment authority. | +| CurrentContractSerialNumber | `google.protobuf.Empty` | `google.protobuf.Int64Value` | Gets the current serial number of the genesis contract. | +| GetContractInfo | `aelf.Address` | `acs0.ContractInfo` | Gets information about a contract. | +| GetContractAuthor | `aelf.Address` | `aelf.Address` | Gets the author of a contract. | +| GetContractHash | `aelf.Address` | `aelf.Hash` | Gets the code hash of a contract by address. | +| GetContractAddressByName | `aelf.Hash` | `aelf.Address` | Gets the address of a system contract by name. | +| GetSmartContractRegistrationByAddress | `aelf.Address` | `aelf.SmartContractRegistration` | Gets the registration of a smart contract by address. | +| GetSmartContractRegistrationByCodeHash | `aelf.Hash` | `aelf.SmartContractRegistration` | Gets the registration of a smart contract by code hash. | +| DeployUserSmartContract | `acs0.ContractDeploymentInput` | `acs0.DeployUserSmartContractOutput` | Deploys a user smart contract and returns the contract code hash. | +| UpdateUserSmartContract | `acs0.ContractUpdateInput` | `google.protobuf.Empty` | Updates a user smart contract. | +| ReleaseApprovedUserSmartContract | `acs0.ReleaseContractInput` | `google.protobuf.Empty` | Releases a proposal that passed the code check. | +| PerformDeployUserSmartContract | `acs0.ContractDeploymentInput` | `aelf.Address` | Deploys a user contract. | +| PerformUpdateUserSmartContract | `acs0.ContractUpdateInput` | `google.protobuf.Empty` | Updates a user contract. | +| SetContractAuthor | `acs0.SetContractAuthorInput` | `google.protobuf.Empty` | Sets the author of a contract. | + +### Types + +#### acs0.AuthorUpdated + +| Field | Type | Description | +|--------------|--------------|----------------------------------| +| `address` | `aelf.address` | Contract code byte array. | +| `old_author` | `aelf.address` | Contract code category (0: C#). | + +#### acs0.CodeCheckRequired + +| Field | Type | Description | +|-------------------------------|--------------|--------------------------------| +| `code` | `bytes` | Contract code byte array. | +| `proposed_contract_input_hash`| `aelf.Hash` | Proposed contract ID. | +| `category` | `sint32` | Contract code category (0: C#).| +| `is_system_contract` | `bool` | Is it a system contract? | +| `is_user_contract` | `bool` | Is it a user contract? | + +#### acs0.CodeUpdated + +| Field | Type | Description | +|-----------------|--------------|--------------------------------| +| `address` | `aelf.Address` | Contract address. | +| `old_code_hash` | `aelf.Hash` | Old contract code byte array. | +| `new_code_hash` | `aelf.Hash` | New contract code byte array. | +| `version` | `int32` | Contract version. | + +### acs0.ContractCodeCheckInput# + +| Field | Type | Description | +|-------------------------------|--------------|-------------------------------------------------------| +| `contract_input` | `bytes` | Contract code byte array. | +| `is_contract_deployment` | `bool` | Is the contract being deployed or updated? | +| `code_check_release_method` | `string` | Method to call after code check (DeploySmartContract or UpdateSmartContract). | +| `proposed_contract_input_hash`| `aelf.Hash` | Proposed contract ID. | +| `category` | `sint32` | Contract code category (0: C#). | +| `is_system_contract` | `bool` | Is it a system contract? | + +#### acs0.ContractDeployed + +| Field | Type | Description | +|---------------------|--------------|----------------------------------| +| `author` | `aelf.Address` | Contract author. | +| `code_hash` | `aelf.Hash` | Contract code hash. | +| `address` | `aelf.Address` | Contract address. | +| `version` | `int32` | Contract version. | +| `name` | `aelf.Hash` | Contract name (must be unique). | +| `contract_version` | `string` | Contract version. | + +#### acs0.ContractDeploymentInput + +| Field | Type | Description | +|-----------|---------|------------------------------------| +| `category`| `sint32`| Contract code category (0: C#). | +| `code` | `bytes` | Contract code byte array. | + +#### acs0.DeployUserSmartContractOutput + +| Field | Type | Description | +|------------|------------|---------------------------------| +| `code_hash`| `aelf.Hash`| Deployed/updated contract code hash. | + +#### acs0.ContractInfo + +| Field | Type | Description | +|--------------------|--------------|----------------------------------| +| `serial_number` | `int64` | Contract serial number. | +| `author` | `aelf.Address` | Contract author. | +| `category` | `sint32` | Contract code category (0: C#). | +| `code_hash` | `aelf.Hash` | Contract code hash. | +| `is_system_contract`| `bool` | Is it a system contract? | +| `version` | `int32` | Contract version. | +| `contract_version` | `string` | Contract version. | +| `contract_type` | `string` | Contract type. | +| `deployed_by` | `aelf.Address` | Deployer address. | +| `deployed_on` | `google.protobuf.Timestamp` | Deployment time. | + +#### acs0.ContractUpdateInput + +| Field | Type | Description | +|---------------------|--------------|----------------------------------| +| `address` | `aelf.Address` | Contract address. | +| `code` | `bytes` | Contract code byte array. | +| `category` | `sint32` | Contract code category (0: C#). | +| `is_system_contract`| `bool` | Is it a system contract? | +| `is_user_contract` | `bool` | Is it a user contract? | + +#### acs0.ReleaseContractInput + +| Field | Type | Description | +|-------------|------------|--------------------------------| +| `proposal_id` | `aelf.Hash` | Proposal ID to be released. | + +#### acs0.SetContractAuthorInput + +| Field | Type | Description | +|-----------------|--------------|----------------------------------| +| `address` | `aelf.Address` | Contract address. | +| `author` | `aelf.Address` | New author address. | + +#### acs0.SystemContractDeploymentInput + +| Field | Type | Description | +|-----------------|--------------|----------------------------------| +| `category` | `sint32` | Contract code category (0: C#). | +| `code` | `bytes` | Contract code byte array. | +| `name` | `aelf.Hash` | Contract name (must be unique). | + +#### acs0.ValidateSystemContractAddressInput + +| Field | Type | Description | +|------------------------|--------------|----------------------------------| +| `system_contract_hash_name` | `aelf.Hash` | Contract name hash. | +| `address` | `aelf.Address` | Contract address. | + +#### aelf.Address + +| Field | Type | Description | +|-----------|---------|----------------------------| +| `value` | `bytes` | Address value in bytes. | + +#### aelf.BinaryMerkleTree + +| Field | Type | Description | +|-------------|----------|----------------------------------| +| `nodes` | `Hash` | Leaf nodes. | +| `root` | `Hash` | Root node hash. | +| `leaf_count`| `int32` | Number of leaf nodes. | + +#### aelf.Hash + +| Field | Type | Description | +|-----------|---------|----------------------------| +| `value` | `bytes` | Hash value in bytes. | + +#### aelf.LogEvent + +| Field | Type | Description | +|-------------|----------|----------------------------------| +| `address` | `Address`| Contract address. | +| `name` | `string` | Log event name. | +| `indexed` | `bytes` | Indexed data for bloom filter. | +| `non_indexed`| `bytes` | Non-indexed data. | + +#### aelf.MerklePath + +| Field | Type | Description | +|----------------------|-----------------|----------------------------------| +| `merkle_path_nodes` | `MerklePathNode`| Merkle path nodes. | + +#### aelf.MerklePathNode + +| Field | Type | Description | +|--------------------|----------|--------------------------------| +| `hash` | `Hash` | Node hash. | +| `is_left_child_node`| `bool` | Is it a left child node? | + +#### aelf.SInt32Value + +| Field | Type | Description | +|---------|---------|------------------------------| +| `value` | `sint32`| Signed 32-bit integer value. | + +#### aelf.SInt64Value + +| Field | Type | Description | +|---------|---------|------------------------------| +| `value` | `sint64`| Signed 64-bit integer value. | + +#### aelf.ScopedStatePath + +| Field | Type | Description | +|-----------|---------|------------------------------------| +| `address` | `Address` | Scope address (contract address). | +| `path` | `StatePath` | Contract state path. | + +#### aelf.SmartContractRegistration + +| Field | Type | Description | +|--------------------|----------|----------------------------------| +| `category` | `sint32` | Contract code category (0: C#). | +| `code` | `bytes` | Contract code byte array. | +| `code_hash` | `Hash` | Contract code hash. | +| `is_system_contract`| `bool` | Is it a system contract? | +| `version` | `int32` | Contract version. | + +#### aelf.StatePath + +| Field | Type | Description | +|---------|---------|--------------------------------------| +| `parts` | `string`| State path parts. | + +#### aelf.Transaction + +| Field | Type | Description | +|------------------|--------------|------------------------------------| +| `from` | `Address` | Sender address. | +| `to` | `Address` | Contract address. | +| `ref_block_number`| `int64` | Referenced block height. | +| `ref_block_prefix`| `bytes` | Referenced block hash prefix. | +| `method_name` | `string` | Smart contract method name. | +| `params` | `bytes` | Smart contract method parameters. | +| `signature` | `bytes` | Transaction signature. | + +#### aelf.TransactionExecutingStateSet + +| Field | Type | Description | +|---------|----------------------------------|---------------------------------| +| `writes`| `TransactionExecutingStateSet.WritesEntry`| Changed states. | +| `reads` | `TransactionExecutingStateSet.ReadsEntry` | Read states. | +| `deletes`| `TransactionExecutingStateSet.DeletesEntry` | Deleted states. | + +#### aelf.TransactionExecutingStateSet.DeletesEntry + +| Field | Type | Description | +|---------|---------|---------------------------| +| `key` | `string`| State key. | +| `value` | `bool` | Deletion state (true/false).| + +#### aelf.TransactionExecutingStateSet.ReadsEntry + +| Field | Type | Description | +|---------|---------|---------------------------| +| `key` | `string`| State key. | +| `value` | `bool` | Read state (true/false). | + +#### aelf.TransactionExecutingStateSet.WritesEntry + +| Field | Type | Description | +|---------|---------|---------------------------| +| `key` | `string`| State key. | +| `value` | `bytes` | Written state value. | + +#### aelf.TransactionResult + +| Field | Type | Description | +|-----------------|--------------|------------------------------------| +| `transaction_id`| `Hash` | Transaction ID. | +| `status` | `TransactionResultStatus` | Transaction result status. | +| `logs` | `LogEvent` | Log events. | +| `bloom` | `bytes` | Bloom filter for logs. | +| `return_value` | `bytes` | Transaction execution return value.| +| `block_number` | `int64` | Block height. | +| `block_hash` | `Hash` | Block hash. | +| `error` | `string` | Error message. | + +#### aelf.TransactionResultStatus + +| Name | Value | Description | +|------------------------|-------|---------------------------------------------------------| +| `NOT_EXISTED` | 0 | Transaction result does not exist. | +| `PENDING` | 1 | Transaction in pool waiting to be packaged. | +| `FAILED` | 2 | Transaction execution failed. | +| `MINED` | 3 | Transaction successfully executed and packaged. | +| `CONFLICT` | 4 | Conflicts with other transactions during parallel execution.| +| `PENDING_VALIDATION` | 5 | Waiting for validation. | +| `NODE_VALIDATION_FAILED`| 6 | Validation failed. | + +## Example + +ACS0 defines methods for contract deployment and updates. AElf provides the Genesis Contract as an implementation of ACS0. \ No newline at end of file diff --git a/docs/Reference/ACS Introduction/ACS1 - Transaction Fee Standard.md b/docs/Reference/ACS Introduction/ACS1 - Transaction Fee Standard.md new file mode 100644 index 0000000..f4b7f76 --- /dev/null +++ b/docs/Reference/ACS Introduction/ACS1 - Transaction Fee Standard.md @@ -0,0 +1,270 @@ +# ACS1 - Transaction Fee Standard +ACS1 handles transaction fees. + +## Interface +Contracts using ACS1 must implement these methods: + +### Methods +| Method Name | Request Type | Response Type | Description | +|--------------------------|--------------------------------|-------------------------|-----------------------------------------------------| +| SetMethodFee | `acs1.MethodFees` | `google.protobuf.Empty` | Sets the method fees for a method, overriding all fees. | +| ChangeMethodFeeController| `AuthorityInfo` | `google.protobuf.Empty` | Changes the method fee controller. Default is parliament. | +| GetMethodFee | `google.protobuf.StringValue` | `acs1.MethodFees` | Queries the fee for a method by name. | +| GetMethodFeeController | `google.protobuf.Empty` | `AuthorityInfo` | Queries the method fee controller. | + +### Types +#### acs1.MethodFee +| Field | Type | Description | +|------------|--------|-----------------------------------| +| symbol | string | The token symbol for the fee. | +| basic_fee | int64 | The fee amount. | + +#### acs1.MethodFees +| Field | Type | Description | +|-----------------|------------|----------------------------------| +| method_name | string | The name of the method. | +| fees | MethodFee | List of fees. | +| is_size_fee_free| bool | Optional based on implementation.| + +#### AuthorityInfo +| Field | Type | Description | +|------------------|---------------|-----------------------------------| +| contract_address | aelf.Address | The controller's contract address.| +| owner_address | aelf.Address | The owner's address. | + +**Note:** Only system contracts on the main chain can implement ACS1. + +## Usage +A pre-transaction, generated by FeeChargePreExecutionPlugin, charges the transaction fee before main processing. +```cs +/// +/// Related transactions will be generated by acs1 pre-plugin service, +/// and will be executed before the origin transaction. +/// +/// +/// +public override BoolValue ChargeTransactionFees(ChargeTransactionFeesInput input) +{ + // ... + // Record tx fee bill during current charging process. + var bill = new TransactionFeeBill(); + var fromAddress = Context.Sender; + var methodFees = Context.Call(input.ContractAddress, nameof(GetMethodFee), + new StringValue {Value = input.MethodName}); + var successToChargeBaseFee = true; + if (methodFees != null && methodFees.Fees.Any()) + { + successToChargeBaseFee = ChargeBaseFee(GetBaseFeeDictionary(methodFees), ref bill); + } + var successToChargeSizeFee = true; + if (!IsMethodFeeSetToZero(methodFees)) + { + // Then also do not charge size fee. + successToChargeSizeFee = ChargeSizeFee(input, ref bill); + } + // Update balances. + foreach (var tokenToAmount in bill.FeesMap) + { + ModifyBalance(fromAddress, tokenToAmount.Key, -tokenToAmount.Value); + Context.Fire(new TransactionFeeCharged + { + Symbol = tokenToAmount.Key, + Amount = tokenToAmount.Value + }); + if (tokenToAmount.Value == 0) + { + //Context.LogDebug(() => $"Maybe incorrect charged tx fee of {tokenToAmount.Key}: it's 0."); + } + } + return new BoolValue {Value = successToChargeBaseFee && successToChargeSizeFee}; +} +``` + +### Steps: +1. System calls `GetMethodFee` to determine the fee. +2. Checks if the balance is sufficient: + - If yes, the fee is billed. + - If no, the transaction is rejected. +3. If the method fee is not zero, the system charges a size fee based on the parameter's size. +4. After charging, an `TransactionFeeCharged` event is thrown, modifying the sender's balance. +5. The event is processed to calculate the total transaction fees in the block. +6. In the next block: + - 10% of the fees are destroyed. + - 90% goes to the dividend pool on the main chain and to the FeeReceiver on the side chain. +```cs +/// +/// Burn 10% of tx fees. +/// If Side Chain didn't set FeeReceiver, burn all. +/// +/// +/// +private void TransferTransactionFeesToFeeReceiver(string symbol, long totalAmount) +{ + Context.LogDebug(() => "Transfer transaction fee to receiver."); + if (totalAmount <= 0) return; + var burnAmount = totalAmount.Div(10); + if (burnAmount > 0) + Context.SendInline(Context.Self, nameof(Burn), new BurnInput + { + Symbol = symbol, + Amount = burnAmount + }); + var transferAmount = totalAmount.Sub(burnAmount); + if (transferAmount == 0) + return; + var treasuryContractAddress = + Context.GetContractAddressByName(SmartContractConstants.TreasuryContractSystemName); + if ( treasuryContractAddress!= null) + { + // Main chain would donate tx fees to dividend pool. + if (State.DividendPoolContract.Value == null) + State.DividendPoolContract.Value = treasuryContractAddress; + State.DividendPoolContract.Donate.Send(new DonateInput + { + Symbol = symbol, + Amount = transferAmount + }); + } + else + { + if (State.FeeReceiver.Value != null) + { + Context.SendInline(Context.Self, nameof(Transfer), new TransferInput + { + To = State.FeeReceiver.Value, + Symbol = symbol, + Amount = transferAmount, + }); + } + else + { + // Burn all! + Context.SendInline(Context.Self, nameof(Burn), new BurnInput + { + Symbol = symbol, + Amount = transferAmount + }); + } + } +} +``` + +## Implementation +### Simple Implementation +Implement only `GetMethodFee` to set fixed fees for methods. +```cs +public override MethodFees GetMethodFee(StringValue input) +{ + if (input.Value == nameof(Foo1) || input.Value == nameof(Foo2)) + { + return new MethodFees + { + MethodName = input.Value, + Fees = + { + new MethodFee + { + BasicFee = 1_00000000, + Symbol = Context.Variables.NativeSymbol + } + } + }; + } + if (input.Value == nameof(Bar1) || input.Value == nameof(Bar2)) + { + return new MethodFees + { + MethodName = input.Value, + Fees = + { + new MethodFee + { + BasicFee = 2_00000000, + Symbol = Context.Variables.NativeSymbol + } + } + }; + } + return new MethodFees(); +} +``` + + +### Recommended Implementation +1. Define a `MappedState` in the contract's State file for transaction fees. +```cs +public MappedState TransactionFees { get; set; } +``` +2. Modify `TransactionFees` in `SetMethodFee` and return the value in `GetMethodFee`. +```cs +public override MethodFees GetMethodFee(StringValue input) { + return State.TransactionFees[input.Value]; +} +``` +3. Add permission management to `SetMethodFee` to prevent arbitrary fee changes. +```cs +public SingletonState MethodFeeController { get; set; } +``` + +```cs +public override Empty SetMethodFee(MethodFees input) +{ + foreach (var symbolToAmount in input.Fees) + { + AssertValidToken(symbolToAmount.Symbol, symbolToAmount.BasicFee); + } + RequiredMethodFeeControllerSet(); + Assert(Context.Sender == State.MethodFeeController.Value.OwnerAddress, "Unauthorized to set method fee."); + State.TransactionFees[input.MethodName] = input; + return new Empty(); +} +``` +### Permission Management +1. Define a `SingletonState` with type `AuthorityInfo`. +```cs +private void RequiredMethodFeeControllerSet() +{ + if (State.MethodFeeController.Value != null) return; + if (State.ParliamentContract.Value == null) + { + State.ParliamentContract.Value = Context.GetContractAddressByName(SmartContractConstants.ParliamentContractSystemName); + } + var defaultAuthority = new AuthorityInfo(); + // Parliament Auth Contract maybe not deployed. + if (State.ParliamentContract.Value != null) + { + defaultAuthority.OwnerAddress = State.ParliamentContract.GetDefaultOrganizationAddress.Call(new Empty()); + defaultAuthority.ContractAddress = State.ParliamentContract.Value; + } + State.MethodFeeController.Value = defaultAuthority; +} +``` +2. Check the sender’s right by comparing its address with the owner’s address. +3. Implement permission checks to ensure only authorized changes. + +### Changing Authority +The authority for `SetMethodFee` can be changed through a transaction from the default parliament address. +```cs +public override Empty ChangeMethodFeeController(AuthorityInfo input) +{ + RequiredMethodFeeControllerSet(); + AssertSenderAddressWith(State.MethodFeeController.Value.OwnerAddress); + var organizationExist = CheckOrganizationExist(input); + Assert(organizationExist, "Invalid authority input."); + State.MethodFeeController.Value = input; + return new Empty(); +} +``` +```cs +public override AuthorityInfo GetMethodFeeController(Empty input) +{ + RequiredMethodFeeControllerSet(); + return State.MethodFeeController.Value; +} +``` + +## Testing +Create ACS1’s Stub and call `GetMethodFee` and `GetMethodFeeController` to check the return values. + +## Example +All AElf system contracts implement ACS1 and can be used as references. \ No newline at end of file diff --git a/docs/Reference/ACS Introduction/ACS10 - Dividend Pool Standard.md b/docs/Reference/ACS Introduction/ACS10 - Dividend Pool Standard.md new file mode 100644 index 0000000..a09b12d --- /dev/null +++ b/docs/Reference/ACS Introduction/ACS10 - Dividend Pool Standard.md @@ -0,0 +1,421 @@ +# ACS10 - Dividend Pool Standard +ACS10 facilitates the creation and management of dividend pools within a contract. + +## Interface +To create a dividend pool, implement these optional interfaces: + +### Methods +| Method Name | Request Type | Response Type | Description | +|--------------------------|-----------------------|-------------------------|---------------------------------------------------------------------------------------------------| +| Donate | `acs10.DonateInput` | `google.protobuf.Empty` | Transfers tokens from the caller to the dividend pool. Converts non-native tokens to native tokens if required. | +| Release | `acs10.ReleaseInput` | `google.protobuf.Empty` | Releases dividends based on the specified period number. | +| SetSymbolList | `acs10.SymbolList` | `google.protobuf.Empty` | Sets the list of token symbols supported by the dividend pool. | +| GetSymbolList | `google.protobuf.Empty` | `acs10.SymbolList` | Retrieves the list of token symbols supported by the dividend pool. | +| GetUndistributedDividends| `google.protobuf.Empty` | `acs10.Dividends` | Queries the balance of undistributed tokens according to the symbol list. | +| GetDividends | `google.protobuf.Int64Value` | `acs10.Dividends` | Queries dividend information based on the specified height. | + + +### Types + +#### `acs10.Dividends` +| Field | Type | Description | Label | +|--------|--------------------|----------------------------------------|--------| +| `value`| `Dividends.ValueEntry` | The dividends, symbol -> amount. | repeated | + +#### `acs10.Dividends.ValueEntry` +| Field | Type | Description | Label | +|--------|----------|---------------|-------| +| `key` | `string` | | | +| `value`| `int64` | | | + +#### `acs10.DonateInput` +| Field | Type | Description | Label | +|----------|----------|-----------------------------|-------| +| `symbol` | `string` | The token symbol to donate. | | +| `amount` | `int64` | The amount to donate. | | + +#### `acs10.DonationReceived` +| Field | Type | Description | Label | +|-----------------|-----------------|-------------------------------|--------| +| `from` | `aelf.Address` | The address of donors. | | +| `pool_contract` | `aelf.Address` | The address of dividend pool. | | +| `symbol` | `string` | The token symbol Donated. | | +| `amount` | `int64` | The amount Donated. | | + +#### `acs10.ReleaseInput` +| Field | Type | Description | Label | +|-----------------|---------|--------------------------------|--------| +| `period_number` | `int64` | The period number to release. | | + +#### `acs10.SymbolList` +| Field | Type | Description | Label | +|--------|----------|--------------------------------|--------| +| `value`| `string` | The token symbol list. | repeated | + +#### `aelf.Address` +| Field | Type | Description | Label | +|--------|--------|---------------------|--------| +| `value`| `bytes`| | | + +#### `aelf.BinaryMerkleTree` +| Field | Type | Description | Label | +|--------|-------|---------------------|--------| +| `nodes`| `Hash`| The leaf nodes. | | +| `root` | `Hash`| The root node hash. | repeated | +| `leaf_count` | `int32` | The count of leaf node. | | + +#### `aelf.Hash` +| Field | Type | Description | Label | +|--------|--------|---------------------|--------| +| `value`| `bytes`| | | + +#### `aelf.LogEvent` +| Field | Type | Description | Label | +|-------------|--------------|---------------------------------|--------| +| `address` | `Address` | The contract address. | | +| `name` | `string` | The name of the log event. | | +| `indexed` | `bytes` | The indexed data, used to calculate bloom. | repeated | +| `non_indexed` | `bytes` | The non indexed data. | repeated | + +#### `aelf.MerklePath` +| Field | Type | Description | Label | +|-------------------|---------------------|----------------------------|--------| +| `merkle_path_nodes` | `MerklePathNode` | The merkle path nodes. | repeated | + +#### `aelf.MerklePathNode` +| Field | Type | Description | Label | +|--------|--------|----------------------|--------| +| `hash` | `Hash` | The node hash. | | +| `is_left_child_node` | `bool` | Whether it is a left child node. | | + +#### `aelf.SInt32Value` +| Field | Type | Description | Label | +|--------|--------|---------------------|--------| +| `value`| `sint32`| | | + +#### `aelf.SInt64Value` +| Field | Type | Description | Label | +|--------|--------|---------------------|--------| +| `value`| `sint64`| | | + +#### `aelf.ScopedStatePath` +| Field | Type | Description | Label | +|----------|-----------|---------------------|--------| +| `address`| `Address` | The scope address. | | +| `path` | `StatePath`| The path of contract state. | | + +#### `aelf.SmartContractRegistration` +| Field | Type | Description | Label | +|-------------------|--------|-------------------------------|--------| +| `category` | `sint32`| The category of contract code (0: C#). | | +| `code` | `bytes`| The byte array of the contract code. | | +| `code_hash` | `Hash` | The hash of the contract code. | | +| `is_system_contract` | `bool`| Whether it is a system contract. | | +| `version` | `int32`| The version of the current contract. | | + +#### `aelf.StatePath` +| Field | Type | Description | Label | +|----------|--------|-------------------------------|--------| +| `parts` | `string`| The partial path of the state path. | repeated | + +#### `aelf.Transaction` +| Field | Type | Description | Label | +|-------------------|-----------|--------------------------------|--------| +| `from` | `Address` | The address of the sender of the transaction. | | +| `to` | `Address` | The address of the contract when calling a contract. | | +| `ref_block_number`| `int64` | The height of the referenced block hash. | | +| `ref_block_prefix`| `bytes` | The first four bytes of the referenced block hash. | | +| `method_name` | `string` | The name of a method in the smart contract at the To address. | | +| `params` | `bytes` | The parameters to pass to the smart contract method. | | +| `signature` | `bytes` | When signing a transaction, subset of fields: from/to, target method, parameter, reference block number, prefix. | | + +#### `aelf.TransactionExecutingStateSet` +| Field | Type | Description | Label | +|-----------------|---------------------|------------------------------|--------| +| `writes` | `TransactionExecutingStateSet.WritesEntry` | The changed states. | repeated | +| `reads` | `TransactionExecutingStateSet.ReadsEntry` | The read states. | repeated | +| `deletes` | `TransactionExecutingStateSet.DeletesEntry` | The deleted states. | repeated | + +#### `aelf.TransactionExecutingStateSet.DeletesEntry` +| Field | Type | Description | Label | +|--------|--------|---------------------|--------| +| `key` | `string`| | | +| `value`| `bool` | | | + +#### `aelf.TransactionExecutingStateSet.ReadsEntry` +| Field | Type | Description | Label | +|--------|--------|---------------------|--------| +| `key` | `string`| | | +| `value`| `bool` | | | + +#### `aelf.TransactionExecutingStateSet.WritesEntry` +| Field | Type | Description | Label | +|--------|--------|---------------------|--------| +| `key` | `string`| | | +| `value`| `bytes` | | | + +#### `aelf.TransactionResult` +| Field | Type | Description | Label | +|-------------------|-----------|--------------------------------|--------| +| `transaction_id` | `Hash` | The transaction id. | | +| `status` | `TransactionResultStatus` | The transaction result status. | | +| `logs` | `LogEvent`| The log events. | repeated | +| `bloom` | `bytes` | Bloom filter for transaction logs. | repeated | +| `return_value` | `bytes` | The return value of the transaction execution. | | +| `block_number` | `int64` | The height of the block that packages the transaction. | | +| `block_hash` | `Hash` | The hash of the block that packages the transaction. | | +| `error` | `string` | Failed execution error message. | | + +#### `aelf.TransactionResultStatus` +| Name | Number | Description | +|---------------------|--------|-------------------------------------------------------| +| `NOT_EXISTED` | `0` | The execution result of the transaction does not exist. | +| `PENDING` | `1` | The transaction is in the transaction pool waiting to be packaged. | +| `FAILED` | `2` | Transaction execution failed. | +| `MINED` | `3` | The transaction was successfully executed and packaged into a block. | +| `CONFLICT` | `4` | When executed in parallel, there are conflicts with other transactions. | +| `PENDING_VALIDATION`| `5` | The transaction is waiting for validation. | +| `NODE_VALIDATION_FAILED` | `6`| Transaction validation failed. | + +## Usage +ACS10 provides a standardized interface for dividend pools, independent of AElf chain interactions. + +### Implementation + +- Using the Profit Contract +```cs +State.ProfitContract.Value = + Context.GetContractAddressByName(SmartContractConstants.ProfitContractSystemName); +var schemeToken = HashHelper.ComputeFrom(Context.Self); +State.ProfitContract.CreateScheme.Send(new CreateSchemeInput +{ + Manager = Context.Self, + CanRemoveBeneficiaryDirectly = true, + IsReleaseAllBalanceEveryTimeByDefault = true, + Token = schemeToken +}); +State.ProfitSchemeId.Value = Context.GenerateId(State.ProfitContract.Value, schemeToken); +``` + +- Using the TokenHolder Contract +```cs +State.TokenHolderContract.Value = + Context.GetContractAddressByName(SmartContractConstants.TokenHolderContractSystemName); +State.TokenHolderContract.CreateScheme.Send(new CreateTokenHolderProfitSchemeInput +{ + Symbol = Context.Variables.NativeSymbol, + MinimumLockMinutes = input.MinimumLockMinutes +}); +return new Empty(); +``` + +- Donate can be implemented as: +```cs +public override Empty Donate(DonateInput input) +{ + State.TokenContract.TransferFrom.Send(new TransferFromInput + { + From = Context.Sender, + Symbol = input.Symbol, + Amount = input.Amount, + To = Context.Self + }); + State.TokenContract.Approve.Send(new ApproveInput + { + Symbol = input.Symbol, + Amount = input.Amount, + Spender = State.TokenHolderContract.Value + }); + State.TokenHolderContract.ContributeProfits.Send(new ContributeProfitsInput + { + SchemeManager = Context.Self, + Symbol = input.Symbol, + Amount = input.Amount + }); + Context.Fire(new DonationReceived + { + From = Context.Sender, + Symbol = input.Symbol, + Amount = input.Amount, + PoolContract = Context.Self + }); + var currentReceivedDividends = State.ReceivedDividends[Context.CurrentHeight]; + if (currentReceivedDividends != null && currentReceivedDividends.Value.ContainsKey(input.Symbol)) + { + currentReceivedDividends.Value[input.Symbol] = + currentReceivedDividends.Value[input.Symbol].Add(input.Amount); + } + else + { + currentReceivedDividends = new Dividends + { + Value = + { + { + input.Symbol, input.Amount + } + } + }; + } + State.ReceivedDividends[Context.CurrentHeight] = currentReceivedDividends; + Context.LogDebug(() => string.Format("Contributed {0} {1}s to side chain dividends pool.", input.Amount, input.Symbol)); + return new Empty(); +} +``` + +- The method Release directly sends the TokenHolder’s method DistributeProfits transaction: +```cs +public override Empty Release(ReleaseInput input) +{ + State.TokenHolderContract.DistributeProfits.Send(new DistributeProfitsInput + { + SchemeManager = Context.Self + }); + return new Empty(); +} +``` + +- GetSymbolList returns the symbol list recorded in dividend scheme: +```cs +public override SymbolList GetSymbolList(Empty input) +{ + return new SymbolList + { + Value = + { + GetDividendPoolScheme().ReceivedTokenSymbols + } + }; +} +private Scheme GetDividendPoolScheme() +{ + if (State.DividendPoolSchemeId.Value == null) + { + var tokenHolderScheme = State.TokenHolderContract.GetScheme.Call(Context.Self); + State.DividendPoolSchemeId.Value = tokenHolderScheme.SchemeId; + } + return Context.Call( + Context.GetContractAddressByName(SmartContractConstants.ProfitContractSystemName), + nameof(ProfitContractContainer.ProfitContractReferenceState.GetScheme), + State.DividendPoolSchemeId.Value); +} +``` +- Implementation of GetUndistributedDividends returns the balance (same as previous section): +```cs +public override Dividends GetUndistributedDividends(Empty input) +{ + var scheme = GetDividendPoolScheme(); + return new Dividends + { + Value = + { + scheme.ReceivedTokenSymbols.Select(s => State.TokenContract.GetBalance.Call(new GetBalanceInput + { + Owner = scheme.VirtualAddress, + Symbol = s + })).ToDictionary(b => b.Symbol, b => b.Balance) + } + }; +} +``` + +### Test +Testing includes sending Donate, Release transactions, and querying operations. Example: + +- Define the required Stubs: +```cs +const long amount = 10_00000000; +var keyPair = SampleECKeyPairs.KeyPairs[0]; +var address = Address.FromPublicKey(keyPair.PublicKey); +var acs10DemoContractStub = + GetTester(DAppContractAddress, keyPair); +var tokenContractStub = + GetTester(TokenContractAddress, keyPair); +var tokenHolderContractStub = + GetTester(TokenHolderContractAddress, + keyPair); +``` + +- Approve the TokenHolder contract and the dividend pool contract +```cs +await tokenContractStub.Approve.SendAsync(new ApproveInput +{ + Spender = TokenHolderContractAddress, + Symbol = "ELF", + Amount = long.MaxValue +}); +await tokenContractStub.Approve.SendAsync(new ApproveInput +{ + Spender = DAppContractAddress, + Symbol = "ELF", + Amount = long.MaxValue +}); +``` + +- Lock the position to reducethe account balance by 10 ELF: +```cs +await tokenHolderContractStub.RegisterForProfits.SendAsync(new RegisterForProfitsInput +{ + SchemeManager = DAppContractAddress, + Amount = amount +}); +``` + +- Implement Donate to reduce the account balance by another 10 ELF: +await acs10DemoContractStub.Donate.SendAsync(new DonateInput +{ + Symbol = "ELF", + Amount = amount +}); + +- Test the GetUndistributedDividends and GetDividends: +```cs +// Check undistributed dividends before releasing. +{ + var undistributedDividends = + await acs10DemoContractStub.GetUndistributedDividends.CallAsync(new Empty()); + undistributedDividends.Value["ELF"].ShouldBe(amount); +} +var blockchainService = Application.ServiceProvider.GetRequiredService(); +var currentBlockHeight = (await blockchainService.GetChainAsync()).BestChainHeight; +var dividends = + await acs10DemoContractStub.GetDividends.CallAsync(new Int64Value {Value = currentBlockHeight}); +dividends.Value["ELF"].ShouldBe(amount); +``` + +- Release bonus, and test GetUndistributedDividends again: +```cs +await acs10DemoContractStub.Release.SendAsync(new ReleaseInput +{ + PeriodNumber = 1 +}); +// Check undistributed dividends after releasing. +{ + var undistributedDividends = + await acs10DemoContractStub.GetUndistributedDividends.CallAsync(new Empty()); + undistributedDividends.Value["ELF"].ShouldBe(0); +} +``` + +- Account will receive the dividend and will change the balance: +```cs +var balanceBeforeClaimForProfits = await tokenContractStub.GetBalance.CallAsync(new GetBalanceInput +{ + Owner = address, + Symbol = "ELF" +}); +await tokenHolderContractStub.ClaimProfits.SendAsync(new ClaimProfitsInput +{ + SchemeManager = DAppContractAddress, + Beneficiary = address +}); +var balanceAfterClaimForProfits = await tokenContractStub.GetBalance.CallAsync(new GetBalanceInput +{ + Owner = address, + Symbol = "ELF" +}); +balanceAfterClaimForProfits.Balance.ShouldBe(balanceBeforeClaimForProfits.Balance + amount); +``` + +### Example +Implementing ACS10 facilitates building dividend pools on main and side chains. \ No newline at end of file diff --git a/docs/Reference/ACS Introduction/ACS11 - Cross Chain Consensus Standard.md b/docs/Reference/ACS Introduction/ACS11 - Cross Chain Consensus Standard.md new file mode 100644 index 0000000..a0d5dd9 --- /dev/null +++ b/docs/Reference/ACS Introduction/ACS11 - Cross Chain Consensus Standard.md @@ -0,0 +1,142 @@ +# ACS11 - Cross Chain Consensus Standard +ACS11 is a standard used to customize consensus mechanisms for cross-chain operations. + +## Interface +Contracts inheriting from ACS11 must implement the following interfaces: + +### Methods +| Method Name | Request Type | Response Type | Description | +|-----------------------------------|----------------------------------|----------------------|-------------------------------------------------| +| `UpdateInformationFromCrossChain` | `google.protobuf.BytesValue` | `google.protobuf.Empty` | Update the consensus information of the side chain. | +| `GetChainInitializationInformation` | `google.protobuf.BytesValue` | `google.protobuf.BytesValue` | Get the current miner list and consensus round information. | +| `CheckCrossChainIndexingPermission` | `aelf.Address` | `google.protobuf.BoolValue` | Verify that the input address is the current miner. | + +### Types + +#### `aelf.Address` +| Field | Type | Description | Label | +|--------|--------|------------------------|--------| +| `value`| `bytes`| | | + +#### `aelf.BinaryMerkleTree` +| Field | Type | Description | Label | +|--------|-------|---------------------|--------| +| `nodes`| `Hash`| The leaf nodes. | | +| `root` | `Hash`| The root node hash. | repeated | +| `leaf_count` | `int32` | The count of leaf node. | | + +#### `aelf.Hash` +| Field | Type | Description | Label | +|--------|--------|---------------------|--------| +| `value`| `bytes`| | | + +#### `aelf.LogEvent` +| Field | Type | Description | Label | +|-------------|--------------|---------------------------------|--------| +| `address` | `Address` | The contract address. | | +| `name` | `string` | The name of the log event. | | +| `indexed` | `bytes` | The indexed data, used to calculate bloom. | repeated | +| `non_indexed` | `bytes` | The non-indexed data. | repeated | + +#### `aelf.MerklePath` +| Field | Type | Description | Label | +|-------------------|---------------------|----------------------------|--------| +| `merkle_path_nodes` | `MerklePathNode` | The merkle path nodes. | repeated | + +#### `aelf.MerklePathNode` +| Field | Type | Description | Label | +|--------|--------|----------------------|--------| +| `hash` | `Hash` | The node hash. | | +| `is_left_child_node` | `bool` | Whether it is a left child node. | | + +#### `aelf.SInt32Value` +| Field | Type | Description | Label | +|--------|--------|---------------------|--------| +| `value`| `sint32`| | | + +#### `aelf.SInt64Value` +| Field | Type | Description | Label | +|--------|--------|---------------------|--------| +| `value`| `sint64`| | | + +#### `aelf.ScopedStatePath` +| Field | Type | Description | Label | +|----------|-----------|---------------------|--------| +| `address`| `Address` | The scope address. | | +| `path` | `StatePath`| The path of contract state. | | + +#### `aelf.SmartContractRegistration` +| Field | Type | Description | Label | +|-------------------|--------|-------------------------------|--------| +| `category` | `sint32`| The category of contract code (0: C#). | | +| `code` | `bytes`| The byte array of the contract code. | | +| `code_hash` | `Hash` | The hash of the contract code. | | +| `is_system_contract` | `bool`| Whether it is a system contract. | | +| `version` | `int32`| The version of the current contract. | | + +#### `aelf.StatePath` +| Field | Type | Description | Label | +|----------|--------|-------------------------------|--------| +| `parts` | `string`| The partial path of the state path. | repeated | + +#### `aelf.Transaction` +| Field | Type | Description | Label | +|-------------------|-----------|--------------------------------|--------| +| `from` | `Address` | The address of the sender of the transaction. | | +| `to` | `Address` | The address of the contract when calling a contract. | | +| `ref_block_number`| `int64` | The height of the referenced block hash. | | +| `ref_block_prefix`| `bytes` | The first four bytes of the referenced block hash. | | +| `method_name` | `string` | The name of a method in the smart contract at the To address. | | +| `params` | `bytes` | The parameters to pass to the smart contract method. | | +| `signature` | `bytes` | Subset of fields: from/to, target method, parameter, reference block number, prefix. | | + +#### `aelf.TransactionExecutingStateSet` +| Field | Type | Description | Label | +|-----------------|---------------------|------------------------------|--------| +| `writes` | `TransactionExecutingStateSet.WritesEntry` | The changed states. | repeated | +| `reads` | `TransactionExecutingStateSet.ReadsEntry` | The read states. | repeated | +| `deletes` | `TransactionExecutingStateSet.DeletesEntry` | The deleted states. | repeated | + +##### `aelf.TransactionExecutingStateSet.DeletesEntry` +| Field | Type | Description | Label | +|--------|--------|---------------------|--------| +| `key` | `string`| | | +| `value`| `bool` | | | + +##### `aelf.TransactionExecutingStateSet.ReadsEntry` +| Field | Type | Description | Label | +|--------|--------|---------------------|--------| +| `key` | `string`| | | +| `value`| `bool` | | | + +##### `aelf.TransactionExecutingStateSet.WritesEntry` +| Field | Type | Description | Label | +|--------|--------|---------------------|--------| +| `key` | `string`| | | +| `value`| `bytes` | | | + +#### `aelf.TransactionResult` +| Field | Type | Description | Label | +|-------------------|-----------|--------------------------------|--------| +| `transaction_id` | `Hash` | The transaction id. | | +| `status` | `TransactionResultStatus` | The transaction result status. | | +| `logs` | `LogEvent`| The log events. | repeated | +| `bloom` | `bytes` | Bloom filter for transaction logs. | repeated | +| `return_value` | `bytes` | The return value of the transaction execution. | | +| `block_number` | `int64` | The height of the block that packages the transaction. | | +| `block_hash` | `Hash` | The hash of the block that packages the transaction. | | +| `error` | `string` | Failed execution error message. | | + +#### `aelf.TransactionResultStatus` +| Name | Number | Description | +|---------------------|--------|-------------------------------------------------------| +| `NOT_EXISTED` | `0` | The execution result of the transaction does not exist. | +| `PENDING` | `1` | The transaction is in the transaction pool waiting to be packaged. | +| `FAILED` | `2` | Transaction execution failed. | +| `MINED` | `3` | The transaction was successfully executed and packaged into a block. | +| `CONFLICT` | `4` | When executed in parallel, there are conflicts with other transactions. | +| `PENDING_VALIDATION`| `5` | The transaction is waiting for validation. | +| `NODE_VALIDATION_FAILED` | `6`| Transaction validation failed. | + +### Example +ACS11 defines methods for customizing consensus mechanisms for cross-chain scenarios. AElf provides an implementation of ACS11 through the AEDPoS contract. Developers can refer to the AEDPoS contract API for implementation details. \ No newline at end of file diff --git a/docs/Reference/ACS Introduction/ACS12 - User Contract Standard.md b/docs/Reference/ACS Introduction/ACS12 - User Contract Standard.md new file mode 100644 index 0000000..927d5d4 --- /dev/null +++ b/docs/Reference/ACS Introduction/ACS12 - User Contract Standard.md @@ -0,0 +1,16 @@ +# ACS12 - User Contract Standard +ACS12 is a standard used to manage user contracts. + +## Types + +### `acs12.UserContractMethodFees` +| Field | Type | Label | Description | +|--------|-------------------------|-----------|----------------------------------------| +| `fees` | `acs12.UserContractMethodFee` | repeated | List of fees to be charged. | +| `is_size_fee_free` | `bool` | | Optional based on the implementation of `SetConfiguration` method. | + +### `acs12.UserContractMethodFee` +| Field | Type | Label | Description | +|-------------|---------|-----------|----------------------------------------| +| `symbol` | `string`| | The token symbol of the method fee. | +| `basic_fee` | `int64` | | The amount of fees to be charged. | \ No newline at end of file diff --git a/docs/Reference/ACS Introduction/ACS2 - Parallel Execution Standard.md b/docs/Reference/ACS Introduction/ACS2 - Parallel Execution Standard.md new file mode 100644 index 0000000..a0bea4d --- /dev/null +++ b/docs/Reference/ACS Introduction/ACS2 - Parallel Execution Standard.md @@ -0,0 +1,389 @@ +# ACS2 - Parallel Execution Standard +ACS2 helps with parallel transaction execution. + +## Interface +Contracts using ACS2 need to implement one method: + +### Methods +| Method Name | Request Type | Response Type | Description | +|------------------|-----------------------|----------------------|-----------------------------------------------------| +| GetResourceInfo | `aelf.Transaction` | `acs2.ResourceInfo` | Gets the resource info that the transaction execution depends on. | + +### Types +#### acs2.ResourceInfo +| Field | Type | Description | +|------------------|-------------------------|----------------------------------| +| write_paths | aelf.ScopedStatePath | State paths for writing. | +| read_paths | aelf.ScopedStatePath | State paths for reading. | +| non_parallelizable| bool | If the transaction isn't parallel.| + +#### aelf.Address +| Field | Type | Description | +|-------|-------|--------------------------| +| value | bytes | | + +#### aelf.BinaryMerkleTree +| Field | Type | Description | +|-------------|--------|--------------------| +| nodes | Hash | Leaf nodes. | +| root | Hash | Root node hash. | +| leaf_count | int32 | Count of leaf nodes.| + +#### aelf.Hash +| Field | Type | Description | +|-------|-------|--------------------------| +| value | bytes | | + +#### aelf.LogEvent +| Field | Type | Description | +|--------------|----------|---------------------| +| address | Address | Contract address. | +| name | string | Log event name. | +| indexed | bytes | Indexed data. | +| non_indexed | bytes | Non-indexed data. | + +#### aelf.MerklePath +| Field | Type | Description | +|-------------------|------------------|-------------------------| +| merkle_path_nodes | MerklePathNode | Merkle path nodes. | + +#### aelf.MerklePathNode +| Field | Type | Description | +|-------------------|-------|-------------------------| +| hash | Hash | Node hash. | +| is_left_child_node| bool | If it's a left child node.| + +#### aelf.SInt32Value +| Field | Type | Description | +|-------|-------|------------------------| +| value | sint32| | + +#### aelf.SInt64Value + +| Field | Type | Description | +|-------|-------|------------------------| +| value | sint64| | + +#### aelf.ScopedStatePath +| Field | Type | Description | +|----------|-------------|--------------------------------------| +| address | Address | Contract address. | +| path | StatePath | Path of contract state. | + +#### aelf.SmartContractRegistration +| Field | Type | Description | +|-------------------|---------|----------------------------------| +| category | sint32 | Contract code category (0: C#). | +| code | bytes | Contract code byte array. | +| code_hash | Hash | Contract code hash. | +| is_system_contract| bool | If it's a system contract. | +| version | int32 | Current contract version. | + +#### aelf.StatePath +| Field | Type | Description | +|-------|--------|--------------------------| +| parts | string | State path parts. | + +#### aelf.Transaction +| Field | Type | Description | +|------------------|--------|-----------------------------------| +| from | Address| Sender address. | +| to | Address| Contract address. | +| ref_block_number | int64 | Referenced block height. | +| ref_block_prefix | bytes | First 4 bytes of referenced block hash. | +| method_name | string | Method name in the contract. | +| params | bytes | Method parameters. | +| signature | bytes | Signature of the transaction. | + +#### aelf.TransactionExecutingStateSet +| Field | Type | Description | +|----------|-------------------------------------------|---------------------| +| writes | TransactionExecutingStateSet.WritesEntry | Changed states. | +| reads | TransactionExecutingStateSet.ReadsEntry | Read states. | +| deletes | TransactionExecutingStateSet.DeletesEntry | Deleted states. | + +#### aelf.TransactionExecutingStateSet.DeletesEntry +| Field | Type | Description | +|-------|--------|-------------------| +| key | string | | +| value | bool | | + +#### aelf.TransactionExecutingStateSet.ReadsEntry +| Field | Type | Description | +|-------|--------|-------------------| +| key | string | | +| value | bool | | + +#### aelf.TransactionExecutingStateSet.WritesEntry +| Field | Type | Description | +|-------|--------|-------------------| +| key | string | | +| value | bytes | | + +#### aelf.TransactionResult +| Field | Type | Description | +|----------------|-----------------------|-----------------------------------------------| +| transaction_id | Hash | Transaction ID. | +| status | TransactionResultStatus| Transaction result status. | +| logs | LogEvent | Log events. | +| bloom | bytes | Bloom filter for transaction logs. | +| return_value | bytes | Return value of the transaction execution. | +| block_number | int64 | Block height that packages the transaction. | +| block_hash | Hash | Block hash that packages the transaction. | +| error | string | Failed execution error message. | + +#### aelf.TransactionResultStatus + +| Name | Number | Description | +|-----------------------|--------|-----------------------------------------------------------| +| NOT_EXISTED | 0 | Transaction result does not exist. | +| PENDING | 1 | Transaction is waiting to be packaged. | +| FAILED | 2 | Transaction execution failed. | +| MINED | 3 | Transaction was successfully executed and packaged. | +| CONFLICT | 4 | Transaction has conflicts with other transactions. | +| PENDING_VALIDATION | 5 | Transaction is waiting for validation. | +| NODE_VALIDATION_FAILED| 6 | Transaction validation failed. | + +## Usage + +AElf uses a key-value database to store data. State Path determines the key for contract execution data. + +For example, a Token contract defines a balance property: +```cs +public MappedState Balances { get; set; } +``` + +To access the balance of an address (`2EM5uV6bSJh6xJfZTUa1pZpYsYcCUAdPvZvFUJzMDJEx3rbioz`) for a `Token contract` address (`Nmjj7noTpMqZ522j76SDsFLhiKkThv1u3d4TxqJMD8v89tWmE`), use its key in the database: +```cs +Nmjj7noTpMqZ522j76SDsFLhiKkThv1u3d4TxqJMD8v89tWmE/Balances/2EM5uV6bSJh6xJfZTUa1pZpYsYcCUAdPvZvFUJzMDJEx3rbioz/ELF +``` + +Parallel execution groups transactions by their State Paths. If two methods don’t access the same `StatePath`, they can be executed in parallel. + +If State Paths mismatch, the transaction is canceled and labeled as “cannot be grouped”. + +For more details, check the `ITransactionGrouper` and `IParallelTransactionExecutingService` code. + +## Implementation +Example: Token Contract +For the Transfer method, notify ITransactionGrouper via GetResourceInfo about the ELF balances of address A and B: +```cs +var args = TransferInput.Parser.ParseFrom(txn.Params); +var resourceInfo = new ResourceInfo +{ + Paths = + { + GetPath(nameof(TokenContractState.Balances), txn.From.ToString(), args.Symbol), + GetPath(nameof(TokenContractState.Balances), args.To.ToString(), args.Symbol), + } +}; +return resourceInfo; +``` + +The `GetPath` method forms a `ScopedStatePath` from key data: +```cs +private ScopedStatePath GetPath(params string[] parts) +{ + return new ScopedStatePath + { + Address = Context.Self, + Path = new StatePath + { + Parts = + { + parts + } + } + } +} +``` + +## Testing +Construct two transactions and pass them to `ITransactionGrouper` to test if they can run in parallel using `GroupAsync`. + +Prepare two stubs with different addresses: +```cs +var keyPair1 = SampleECKeyPairs.KeyPairs[0]; +var acs2DemoContractStub1 = GetACS2DemoContractStub(keyPair1); +var keyPair2 = SampleECKeyPairs.KeyPairs[1]; +var acs2DemoContractStub2 = GetACS2DemoContractStub(keyPair2); +``` + +```cs +var transactionGrouper = Application.ServiceProvider.GetRequiredService(); +var blockchainService = Application.ServiceProvider.GetRequiredService(); +var chain = await blockchainService.GetChainAsync(); +``` + +Check with transactionGrouper: +```cs +// Situation can be parallel executed. +{ + var groupedTransactions = await transactionGrouper.GroupAsync(new ChainContext + { + BlockHash = chain.BestChainHash, + BlockHeight = chain.BestChainHeight + }, new List + { + acs2DemoContractStub1.TransferCredits.GetTransaction(new TransferCreditsInput + { + To = Address.FromPublicKey(SampleECKeyPairs.KeyPairs[2].PublicKey), + Symbol = "ELF", + Amount = 1 + }), + acs2DemoContractStub2.TransferCredits.GetTransaction(new TransferCreditsInput + { + To = Address.FromPublicKey(SampleECKeyPairs.KeyPairs[3].PublicKey), + Symbol = "ELF", + Amount = 1 + }), + }); + groupedTransactions.Parallelizables.Count.ShouldBe(2); +} +// Situation cannot. +{ + var groupedTransactions = await transactionGrouper.GroupAsync(new ChainContext + { + BlockHash = chain.BestChainHash, + BlockHeight = chain.BestChainHeight + }, new List + { + acs2DemoContractStub1.TransferCredits.GetTransaction(new TransferCreditsInput + { + To = Address.FromPublicKey(SampleECKeyPairs.KeyPairs[2].PublicKey), + Symbol = "ELF", + Amount = 1 + }), + acs2DemoContractStub2.TransferCredits.GetTransaction(new TransferCreditsInput + { + To = Address.FromPublicKey(SampleECKeyPairs.KeyPairs[2].PublicKey), + Symbol = "ELF", + Amount = 1 + }), + }); + groupedTransactions.Parallelizables.Count.ShouldBe(1); +} +``` + +## Example +Refer to the `MultiToken contract` implementation for `GetResourceInfo`. Note that `Transfer` method needs to handle transaction fees along with keys. + + + + + + +ACS2 - Parallel Execution Standard + +ACS2 enables parallel execution of transactions by providing necessary resource information. + +### Interface + +A contract inheriting ACS2 must implement: + +#### Methods + +| Method Name | Request Type | Response Type | Description | +|------------------|---------------------|---------------------|-------------------------------------------------------| +| GetResourceInfo | aelf.Transaction | acs2.ResourceInfo | Retrieves resource dependencies for transaction exec. | + +### Types + +#### acs2.ResourceInfo + +| Field | Type | Description | Label | +|-------------------|----------------------|------------------------------------|-----------| +| write_paths | aelf.ScopedStatePath | State paths written during execution| repeated | +| read_paths | aelf.ScopedStatePath | State paths read during execution | repeated | +| non_parallelizable| bool | Indicates if transaction is non-parallelizable.| | + +#### Other Types (Omitted for brevity) + +Several other types like `aelf.Address`, `aelf.BinaryMerkleTree`, `aelf.LogEvent`, etc., are used within `acs2.ResourceInfo`. + +### Usage + +aelf uses State Paths to manage data storage, ensuring transaction grouping based on accessed paths for efficient parallel execution. + +### Implementation + +Token contract, for example, modifies balances through method Transfer. GetResourceInfo must notify ITransactionGrouper of accessed state paths. + +```cs +var args = TransferInput.Parser.ParseFrom(txn.Params); +var resourceInfo = new ResourceInfo +{ + Paths = + { + GetPath(nameof(TokenContractState.Balances), txn.From.ToString(), args.Symbol), + GetPath(nameof(TokenContractState.Balances), args.To.ToString(), args.Symbol), + } +}; +return resourceInfo; +``` + +### Test +Test transaction parallelizability using ITransactionGrouper's GroupAsync method with sample transactions. + + ```cs + var keyPair1 = SampleECKeyPairs.KeyPairs[0]; + var acs2DemoContractStub1 = GetACS2DemoContractStub(keyPair1); + var keyPair2 = SampleECKeyPairs.KeyPairs[1]; + var acs2DemoContractStub2 = GetACS2DemoContractStub(keyPair2); + + var transactionGrouper = Application.ServiceProvider.GetRequiredService(); + var blockchainService = Application.ServiceProvider.GetRequiredService(); + var chain = await blockchainService.GetChainAsync(); + + // Test parallel execution scenario + { + var groupedTransactions = await transactionGrouper.GroupAsync(new ChainContext + { + BlockHash = chain.BestChainHash, + BlockHeight = chain.BestChainHeight + }, new List + { + acs2DemoContractStub1.TransferCredits.GetTransaction(new TransferCreditsInput + { + To = Address.FromPublicKey(SampleECKeyPairs.KeyPairs[2].PublicKey), + Symbol = "ELF", + Amount = 1 + }), + acs2DemoContractStub2.TransferCredits.GetTransaction(new TransferCreditsInput + { + To = Address.FromPublicKey(SampleECKeyPairs.KeyPairs[3].PublicKey), + Symbol = "ELF", + Amount = 1 + }), + }); + groupedTransactions.Parallelizables.Count.ShouldBe(2); + } + + // Test non-parallel execution scenario + { + var groupedTransactions = await transactionGrouper.GroupAsync(new ChainContext + { + BlockHash = chain.BestChainHash, + BlockHeight = chain.BestChainHeight + }, new List + { + acs2DemoContractStub1.TransferCredits.GetTransaction(new TransferCreditsInput + { + To = Address.FromPublicKey(SampleECKeyPairs.KeyPairs[2].PublicKey), + Symbol = "ELF", + Amount = 1 + }), + acs2DemoContractStub2.TransferCredits.GetTransaction(new TransferCreditsInput + { + To = Address.FromPublicKey(SampleECKeyPairs.KeyPairs[2].PublicKey), + Symbol = "ELF", + Amount = 1 + }), + }); + groupedTransactions.Parallelizables.Count.ShouldBe(1); + } + ``` + +### Example + +For an example of implementing GetResourceInfo, refer to the MultiToken contract, ensuring transaction fees are considered for the keys involved. diff --git a/docs/Reference/ACS Introduction/ACS3 - Contract Proposal Standard.md b/docs/Reference/ACS Introduction/ACS3 - Contract Proposal Standard.md new file mode 100644 index 0000000..79b5f66 --- /dev/null +++ b/docs/Reference/ACS Introduction/ACS3 - Contract Proposal Standard.md @@ -0,0 +1,357 @@ +# ACS3 - Contract Proposal Standard + +ACS3 is used when a method needs multiple approvals. Implement these methods for voting and approval: + +## Interface + +### Methods + +| Method Name | Request Type | Response Type | Description | +| ---------------------------------- | ---------------------------------------------- | ---------------------------- | ------------------------------------------------------------------------------------------------ | +| **CreateProposal** | acs3.CreateProposalInput | aelf.Hash | Creates a proposal for voting and returns the proposal ID. | +| **Approve** | aelf.Hash | google.protobuf.Empty | Approves a proposal by its ID. | +| **Reject** | aelf.Hash | google.protobuf.Empty | Rejects a proposal by its ID. | +| **Abstain** | aelf.Hash | google.protobuf.Empty | Abstains from voting on a proposal by its ID. | +| **Release** | aelf.Hash | google.protobuf.Empty | Releases a proposal by its ID, triggering the specified contract call. | +| **ChangeOrganizationThreshold** | acs3.ProposalReleaseThreshold | google.protobuf.Empty | Changes the proposal thresholds, affecting all current proposals. | +| **ChangeOrganizationProposerWhiteList** | acs3.ProposerWhiteList | google.protobuf.Empty | Changes the proposer whitelist for the organization. | +| **CreateProposalBySystemContract** | acs3.CreateProposalBySystemContractInput | aelf.Hash | Creates a proposal by system contracts and returns the proposal ID. | +| **ClearProposal** | aelf.Hash | google.protobuf.Empty | Removes a specified proposal. If the proposal is active, removal fails. | +| **GetProposal** | aelf.Hash | acs3.ProposalOutput | Retrieves a proposal by its ID. | +| **ValidateOrganizationExist** | aelf.Address | google.protobuf.BoolValue | Checks if an organization exists. | +| **ValidateProposerInWhiteList** | acs3.ValidateProposerInWhiteListInput | google.protobuf.BoolValue | Checks if the proposer is in the whitelist. | + +### Types + +#### acs3.CreateProposalBySystemContractInput +| Field | Type | Description | Label | +| --------------- | ---------------- | --------------------------------- | ------- | +| proposal_input | CreateProposalInput | Parameters for creating the proposal | | +| origin_proposer | aelf.Address | Address of the proposer | | + +#### acs3.CreateProposalInput +| Field | Type | Description | Label | +| -------------------- | -------------------------- | ------------------------------------- | ----- | +| contract_method_name | string | Method name to call after release | | +| to_address | aelf.Address | Contract address to call after release| | +| params | bytes | Parameters for the method call | | +| expired_time | google.protobuf.Timestamp | Proposal expiration time | | +| organization_address | aelf.Address | Organization address | | +| proposal_description_url | string | URL for proposal description | | +| token | aelf.Hash | Token for proposal ID generation | | + +#### acs3.OrganizationCreated +| Field | Type | Description | Label | +| ------------------- | ------------- | ------------------------ | ----- | +| organization_address| aelf.Address | Created organization address | | + +#### acs3.OrganizationHashAddressPair +| Field | Type | Description | Label | +| ------------------- | ------------- | ------------------------ | ----- | +| organization_hash | aelf.Hash | Organization ID | | +| organization_address| aelf.Address | Organization address | | + +#### acs3.OrganizationThresholdChanged +| Field | Type | Description | Label | +| ------------------------- | ------------------------ | ------------------------ | ----- | +| organization_address | aelf.Address | Organization address | | +| proposer_release_threshold| ProposalReleaseThreshold | New release threshold | | + +#### acs3.OrganizationWhiteListChanged +| Field | Type | Description | Label | +| ------------------- | ------------- | ------------------------ | ----- | +| organization_address| aelf.Address | Organization address | | +| proposer_white_list | ProposerWhiteList | New proposer whitelist | | + +#### acs3.ProposalCreated +| Field | Type | Description | Label | +| ------------------- | ------------- | ------------------------ | ----- | +| proposal_id | aelf.Hash | Created proposal ID | | +| organization_address| aelf.Address | Organization address | | + +#### acs3.ProposalOutput +| Field | Type | Description | Label | +| -------------------- | -------------------------- | ------------------------------------- | ----- | +| proposal_id | aelf.Hash | Proposal ID | | +| contract_method_name | string | Method name for release | | +| to_address | aelf.Address | Target contract address | | +| params | bytes | Release transaction parameters | | +| expired_time | google.protobuf.Timestamp | Proposal expiration date | | +| organization_address | aelf.Address | Organization address | | +| proposer | aelf.Address | Proposer address | | +| to_be_released | bool | Indicates if releasable | | +| approval_count | int64 | Approval count | | +| rejection_count | int64 | Rejection count | | +| abstention_count | int64 | Abstention count | | + +#### acs3.ProposalReleaseThreshold +| Field | Type | Description | Label | +| -------------------------- | ----- | ------------------------------- | ----- | +| minimal_approval_threshold | int64 | Minimum approval threshold | | +| maximal_rejection_threshold| int64 | Maximum rejection threshold | | +| maximal_abstention_threshold | int64 | Maximum abstention threshold | | +| minimal_vote_threshold | int64 | Minimum vote threshold | | + +#### acs3.ProposalReleased +| Field | Type | Description | Label | +| ------------------- | ------------- | ------------------------ | ----- | +| proposal_id | aelf.Hash | Released proposal ID | | +| organization_address| aelf.Address | Organization address | | + +#### acs3.ProposerWhiteList +| Field | Type | Description | Label | +| ---------- | ------------- | ----------------- | -------- | +| proposers | aelf.Address | Proposer addresses| repeated | + +#### acs3.ReceiptCreated +| Field | Type | Description | Label | +| ------------------- | ------------------------- | ------------------------ | ----- | +| proposal_id | aelf.Hash | Proposal ID | | +| address | aelf.Address | Sender address | | +| receipt_type | string | Receipt type (Approve, Reject, Abstain) | | +| time | google.protobuf.Timestamp | Timestamp | | +| organization_address| aelf.Address | Organization address | | + +#### acs3.ValidateProposerInWhiteListInput +| Field | Type | Description | Label | +| ------------------- | ------------- | ------------------------ | ----- | +| proposer | aelf.Address | Proposer address | | +| organization_address| aelf.Address | Organization address | | + +## Implementation + +Assume there's only one organization in a contract, so no need to define the Organization type. Voters must use a token to vote. We'll focus on the core methods: CreateProposal, Approve, Reject, Abstain, and Release. + +### State Attributes +```cs +public MappedState Proposals { get; set; } +public SingletonState ProposalReleaseThreshold { get; set; } +``` + +- `Proposals` stores all proposal info. +- `ProposalReleaseThreshold` saves the requirements to release a proposal. + +### Initialization +Set the proposal release requirements when the contract initializes: +```cs +public override Empty Initialize(Empty input) +{ + State.TokenContract.Value = + Context.GetContractAddressByName(SmartContractConstants.TokenContractSystemName); + State.ProposalReleaseThreshold.Value = new ProposalReleaseThreshold + { + MinimalApprovalThreshold = 1, + MinimalVoteThreshold = 1 + }; + return new Empty(); +} +``` + +Requires at least one vote and one approval. + +### Create Proposal +Creates a proposal and stores it with its details. +```cs +public override Hash CreateProposal(CreateProposalInput input) +{ + var proposalId = Context.GenerateId(Context.Self, input.Token); + Assert(State.Proposals[proposalId] == null, "Proposal with same token already exists."); + State.Proposals[proposalId] = new ProposalInfo + { + ProposalId = proposalId, + Proposer = Context.Sender, + ContractMethodName = input.ContractMethodName, + Params = input.Params, + ExpiredTime = input.ExpiredTime, + ToAddress = input.ToAddress, + ProposalDescriptionUrl = input.ProposalDescriptionUrl + }; + return proposalId; +} +``` + +### Voting Methods + +#### Abstain +```cs +public override Empty Abstain(Hash input) +{ + Charge(); + var proposal = State.Proposals[input]; + if (proposal == null) + { + throw new AssertionException("Proposal not found."); + } + proposal.Abstentions.Add(Context.Sender); + State.Proposals[input] = proposal; + return new Empty(); +} +``` + +#### Approve +```cs +public override Empty Approve(Hash input) +{ + Charge(); + var proposal = State.Proposals[input]; + if (proposal == null) + { + throw new AssertionException("Proposal not found."); + } + proposal.Approvals.Add(Context.Sender); + State.Proposals[input] = proposal; + return new Empty(); +} +``` + +#### Reject +```cs +public override Empty Reject(Hash input) +{ + Charge(); + var proposal = State.Proposals[input]; + if (proposal == null) + { + throw new AssertionException("Proposal not found."); + } + proposal.Rejections.Add(Context.Sender); + State.Proposals[input] = proposal; + return new Empty(); +} +``` + +#### Charge +```cs +private void Charge() +{ + State.TokenContract.TransferFrom.Send(new TransferFromInput + { + From = Context.Sender, + To = Context.Self, + Symbol = Context.Variables.NativeSymbol, + Amount = 1_00000000 + }); +} +``` + +### Release Proposal +Releases a proposal if the vote count meets the threshold: +```cs +public override Empty Release(Hash input) +{ + var proposal = State.Proposals[input]; + if (proposal == null) + { + throw new AssertionException("Proposal not found."); + } + Assert(IsReleaseThresholdReached(proposal), "Didn't reach release threshold."); + Context.SendInline(proposal.ToAddress, proposal.ContractMethodName, proposal.Params); + return new Empty(); +} +private bool IsReleaseThresholdReached(ProposalInfo proposal) +{ + var isRejected = IsProposalRejected(proposal); + if (isRejected) + return false; + var isAbstained = IsProposalAbstained(proposal); + return !isAbstained && CheckEnoughVoteAndApprovals(proposal); +} +private bool IsProposalRejected(ProposalInfo proposal) +{ + var rejectionMemberCount = proposal.Rejections.Count; + return rejectionMemberCount > State.ProposalReleaseThreshold.Value.MaximalRejectionThreshold; +} +private bool IsProposalAbstained(ProposalInfo proposal) +{ + var abstentionMemberCount = proposal.Abstentions.Count; + return abstentionMemberCount > State.ProposalReleaseThreshold.Value.MaximalAbstentionThreshold; +} +private bool CheckEnoughVoteAndApprovals(ProposalInfo proposal) +{ + var approvedMemberCount = proposal.Approvals.Count; + var isApprovalEnough = + approvedMemberCount >= State.ProposalReleaseThreshold.Value.MinimalApprovalThreshold; + if (!isApprovalEnough) + return false; + var isVoteThresholdReached = + proposal.Abstentions.Concat(proposal.Approvals).Concat(proposal.Rejections).Count() >= + State.ProposalReleaseThreshold.Value.MinimalVoteThreshold; + return isVoteThresholdReached; +} +``` + +## Test +Add methods to a Dapp contract and test the proposal with these methods. + +### State Class +```cs +public StringState Slogan { get; set; } +public SingletonState
Organization { get; set; } +``` + +#### Set/Get Methods +```cs +public override StringValue GetSlogan(Empty input) +{ + return State.Slogan.Value == null ? new StringValue() : new StringValue {Value = State.Slogan.Value}; +} + +public override Empty SetSlogan(StringValue input) +{ + Assert(Context.Sender == State.Organization.Value, "No permission."); + State.Slogan.Value = input.Value; + return new Empty(); +} +``` + +#### Prepare a Stub +```cs +var keyPair = SampleECKeyPairs.KeyPairs[0]; +var acs3DemoContractStub = + GetTester(DAppContractAddress, keyPair); +``` + +#### Approve Token Transaction +```cs +var tokenContractStub = + GetTester( + GetAddress(TokenSmartContractAddressNameProvider.StringName), keyPair); +await tokenContractStub.Approve.SendAsync(new ApproveInput +{ + Spender = DAppContractAddress, + Symbol = "ELF", + Amount = long.MaxValue +}); +``` + +#### Create and Test Proposal +Create a proposal to change the Slogan to "AElf": +```cs +var proposalId = (await acs3DemoContractStub.CreateProposal.SendAsync(new CreateProposalInput +{ + OrganizationAddress = OrganizationAddress + ContractMethodName = nameof(acs3DemoContractStub.SetSlogan), + ToAddress = DAppContractAddress, + ExpiredTime = TimestampHelper.GetUtcNow().AddHours(1), + Params = new StringValue {Value = "AElf"}.ToByteString(), + Token = HashHelper.ComputeFrom("AElf") +})).Output; +``` + +#### Check that Slogan is empty, vote, and release: +```cs +// Check slogan +{ + var slogan = await acs3DemoContractStub.GetSlogan.CallAsync(new Empty()); + slogan.Value.ShouldBeEmpty(); +} +await acs3DemoContractStub.Approve.SendAsync(proposalId); +``` +```cs +await acs3DemoContractStub.Release.SendAsync(proposalId); +// Check slogan +{ + var slogan = await acs3DemoContractStub.GetSlogan.CallAsync(new Empty()); + slogan.Value.ShouldBe("AElf"); +} +``` diff --git a/docs/Reference/ACS Introduction/ACS4 - Consensus Standard.md b/docs/Reference/ACS Introduction/ACS4 - Consensus Standard.md new file mode 100644 index 0000000..806f0d0 --- /dev/null +++ b/docs/Reference/ACS Introduction/ACS4 - Consensus Standard.md @@ -0,0 +1,175 @@ +# ACS4 - Consensus Standard +ACS4 customizes consensus mechanisms. + +## Interface +To customize, implement these five interfaces: + +### Methods +| Method Name | Request Type | Response Type | Description | +|--------------------------------|-------------------------------|-----------------------------|------------------------------------------------------------------------------------------------| +| GetConsensusCommand | google.protobuf.BytesValue | acs4.ConsensusCommand | Generate a consensus command based on contract state and the input public key. | +| GetConsensusExtraData | google.protobuf.BytesValue | google.protobuf.BytesValue | Generate extra data when a block is generated. | +| GenerateConsensusTransactions | google.protobuf.BytesValue | acs4.TransactionList | Generate system transactions when a block is generated. Each block has one consensus transaction. | +| ValidateConsensusBeforeExecution | google.protobuf.BytesValue | acs4.ValidationResult | Verify consensus info in the block header before execution. | +| ValidateConsensusAfterExecution | google.protobuf.BytesValue | acs4.ValidationResult | Verify the state info written to consensus after execution. | + +## Types + +### acs4.ConsensusCommand +| Field | Type | Description | +|-------------------------------|--------------------------|------------------------------------------------------------------| +| limit_milliseconds_of_mining_block | int32 | Time limit for mining the next block. | +| hint | bytes | Diverse context according to the consensus protocol. | +| arranged_mining_time | google.protobuf.Timestamp | The arranged mining time. | +| mining_due_time | google.protobuf.Timestamp | The expiration time for mining. | + +### acs4.TransactionList +| Field | Type | Description | +|--------------|----------------|-----------------------------| +| transactions | aelf.Transaction | Consensus system transactions. | + +### acs4.ValidationResult +| Field | Type | Description | +|--------------|--------|------------------------| +| success | bool | Is successful. | +| message | string | Error message. | +| is_re_trigger| bool | Whether to re-trigger mining. | + +### aelf.Address +| Field | Type | Description | +|-------|-------|-------------| +| value | bytes | | + +### aelf.BinaryMerkleTree +| Field | Type | Description | +|------------|-------|-------------------| +| nodes | Hash | Leaf nodes. | +| root | Hash | Root node hash. | +| leaf_count | int32 | Leaf node count. | + +### aelf.Hash +| Field | Type | Description | +|-------|-------|-------------| +| value | bytes | | + +### aelf.LogEvent +| Field | Type | Description | +|--------------|---------|--------------------------| +| address | Address | Contract address. | +| name | string | Name of the log event. | +| indexed | bytes | Indexed data for bloom. | +| non_indexed | bytes | Non-indexed data. | + +### aelf.MerklePath +| Field | Type | Description | +|--------------------|------------------|---------------------| +| merkle_path_nodes | MerklePathNode | Merkle path nodes. | + +### aelf.MerklePathNode +| Field | Type | Description | +|--------------------|-------|--------------------------| +| hash | Hash | Node hash. | +| is_left_child_node | bool | Is it a left child node? | + +### aelf.SInt32Value +| Field | Type | Description | +|-------|-------|-------------| +| value | sint32 | | + +### aelf.SInt64Value +| Field | Type | Description | +|-------|-------|-------------| +| value | sint64 | | + +### aelf.ScopedStatePath +| Field | Type | Description | +|---------|---------|----------------------------------| +| address | Address | Scope address (contract address) | +| path | StatePath | Path of contract state | + +### aelf.SmartContractRegistration +| Field | Type | Description | +|------------------|-------|-----------------------------------| +| category | sint32| Contract code category (0: C#). | +| code | bytes | Byte array of the contract code. | +| code_hash | Hash | Hash of the contract code. | +| is_system_contract | bool | Is it a system contract? | +| version | int32 | Current contract version. | + +### aelf.StatePath +| Field | Type | Description | +|-------|--------|----------------------| +| parts | string | Partial state path. | + +### aelf.Transaction +| Field | Type | Description | +|-------------------|----------|----------------------------------------------------------------| +| from | Address | Sender's address. | +| to | Address | Contract address being called. | +| ref_block_number | int64 | Referenced block number. | +| ref_block_prefix | bytes | First four bytes of the referenced block hash. | +| method_name | string | Method name in the smart contract. | +| params | bytes | Parameters for the smart contract method. | +| signature | bytes | Signature including sender, target method, parameters, and block reference. | + +### aelf.TransactionExecutingStateSet +| Field | Type | Description | +|---------|------------------------------------|-----------------| +| writes | TransactionExecutingStateSet.WritesEntry | Changed states. | +| reads | TransactionExecutingStateSet.ReadsEntry | Read states. | +| deletes | TransactionExecutingStateSet.DeletesEntry | Deleted states. | + +### aelf.TransactionExecutingStateSet.DeletesEntry +| Field | Type | Description | +|-------|-------|-------------| +| key | string| | +| value | bool | | + +### aelf.TransactionExecutingStateSet.ReadsEntry +| Field | Type | Description | +|-------|-------|-------------| +| key | string| | +| value | bool | | + +### aelf.TransactionExecutingStateSet.WritesEntry +| Field | Type | Description | +|-------|-------|-------------| +| key | string| | +| value | bytes | | + +### aelf.TransactionResult +| Field | Type | Description | +|----------------|-------------------------|---------------------------------------------------------------------------| +| transaction_id | Hash | Transaction ID. | +| status | TransactionResultStatus | Transaction result status. | +| logs | LogEvent | Log events. | +| bloom | bytes | Bloom filter for transaction logs. | +| return_value | bytes | Return value of the transaction execution. | +| block_number | int64 | Block height that packages the transaction. | +| block_hash | Hash | Block hash that packages the transaction. | +| error | string | Failed execution error message. | + +### aelf.TransactionResultStatus +| Name | Number | Description | +|----------------------|--------|-------------------------------------------------------------------| +| NOT_EXISTED | 0 | Transaction result does not exist. | +| PENDING | 1 | Transaction is in the pool waiting to be packaged. | +| FAILED | 2 | Transaction execution failed. | +| MINED | 3 | Transaction executed successfully and packaged into a block. | +| CONFLICT | 4 | Conflicts with other transactions when executed in parallel. | +| PENDING_VALIDATION | 5 | Transaction is waiting for validation. | +| NODE_VALIDATION_FAILED | 6 | Transaction validation failed. | + +## Usage +ACS4 methods correspond to the IConsensusService interface in the AElf.Kernel.Consensus project: + +| ACS4 Method | IConsensusService Method | Methodology | Timing | +|------------------------------------|-----------------------------------|--------------------------------------------------------------------------------------------------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| GetConsensusCommand | Task TriggerConsensusAsync | When TriggerConsensusAsync is called, it will use the node's configured account to call the GetConsensusCommand method to obtain block information (ConsensusCommand) | When the node starts; When the BestChainFoundEventData event is thrown; When consensus data validation fails and needs to be triggered again (IsReTrigger field is true). | +| GetConsensusExtraData | Task GetConsensusExtraDataAsync | When a node produces a block, it generates block header info via IBlockExtraDataService, which calls GetConsensusExtraData in the consensus contract | When a node produces a new block. | +| GenerateConsensusTransactions | Task> GenerateConsensusTransactionsAsync | In the process of generating new blocks, a consensus transaction needs to be generated as one of the system transactions | When a node produces a new block. | +| ValidateConsensusBeforeExecution | Task ValidateConsensusBeforeExecutionAsync | The IBlockValidationProvider interface allows adding a new block validator. The consensus validator, ConsensusValidationProvider, calls ValidateConsensusBeforeExecution | When a node produces a new block. | +| ValidateConsensusAfterExecution | Task ValidateConsensusAfterExecutionAsync | The implementation of ValidateBlockAfterExecuteAsync in ConsensusValidationProvider calls ValidateConsensusAfterExecution in the consensus contract | When a node produces a new block. | + +### Example +Refer to the AEDPoS contract implementation. \ No newline at end of file diff --git a/docs/Reference/ACS Introduction/ACS5 - Contract Threshold Standard.md b/docs/Reference/ACS Introduction/ACS5 - Contract Threshold Standard.md new file mode 100644 index 0000000..40abe0f --- /dev/null +++ b/docs/Reference/ACS Introduction/ACS5 - Contract Threshold Standard.md @@ -0,0 +1,192 @@ +# ACS5 - Contract Threshold Standard +To raise the threshold for using a contract, consider implementing ACS5. + +## Interface +To limit calling a method in a contract, implement these interfaces: + +### Methods +| Method Name | Request Type | Response Type | Description | +|----------------------------|---------------------------------------|-------------------------------|-----------------------------------| +| SetMethodCallingThreshold | acs5.SetMethodCallingThresholdInput | google.protobuf.Empty | Set the threshold for method calling. | +| GetMethodCallingThreshold | google.protobuf.StringValue | acs5.MethodCallingThreshold | Get the threshold for method calling. | + +### Types + +#### acs5.MethodCallingThreshold +| Field | Type | Description | Label | +|-----------------------|-----------------------------------------|-----------------------------------|------------| +| symbol_to_amount | MethodCallingThreshold.SymbolToAmountEntry | The threshold for method calling, token symbol -> amount. | repeated | +| threshold_check_type | ThresholdCheckType | The type of threshold check. | | + +#### acs5.MethodCallingThreshold.SymbolToAmountEntry +| Field | Type | Description | Label | +|-------|--------|-------------|-------| +| key | string | | | +| value | int64 | | | + +#### acs5.SetMethodCallingThresholdInput +| Field | Type | Description | Label | +|----------------------|---------------------------------------------|-------------|-------| +| method | string | The method name to check. | | +| symbol_to_amount | SetMethodCallingThresholdInput.SymbolToAmountEntry | The threshold for method calling, token symbol -> amount. | repeated | +| threshold_check_type | ThresholdCheckType | The type of threshold check. | | + +#### acs5.SetMethodCallingThresholdInput.SymbolToAmountEntry +| Field | Type | Description | Label | +|-------|--------|-------------|-------| +| key | string | | | +| value | int64 | | | + +#### acs5.ThresholdCheckType +| Name | Number | Description | +|----------|--------|--------------------------------------| +| BALANCE | 0 | Check balance only. | +| ALLOWANCE| 1 | Check balance and allowance at the same time. | + +## Usage +ACS5 works similarly to ACS1, which uses a pre-plugin transaction called `ChargeTransactionFees` to charge a transaction fee. ACS5 uses a pre-plugin transaction called `CheckThreshold` to ensure the account sending the transaction can invoke the method. + +### Implementation of `CheckThreshold`: + +```cs +public override Empty CheckThreshold(CheckThresholdInput input) +{ + var meetThreshold = false; + var meetBalanceSymbolList = new List(); + foreach (var symbolToThreshold in input.SymbolToThreshold) + { + if (GetBalance(input.Sender, symbolToThreshold.Key) < symbolToThreshold.Value) + continue; + meetBalanceSymbolList.Add(symbolToThreshold.Key); + } + if (meetBalanceSymbolList.Count > 0) + { + if (input.IsCheckAllowance) + { + foreach (var symbol in meetBalanceSymbolList) + { + if (State.Allowances[input.Sender][Context.Sender][symbol] < + input.SymbolToThreshold[symbol]) continue; + meetThreshold = true; + break; + } + } + else + { + meetThreshold = true; + } + } + if (input.SymbolToThreshold.Count == 0) + { + meetThreshold = true; + } + Assert(meetThreshold, "Cannot meet the calling threshold."); + return new Empty(); +} +``` + +If the sender's token balance or the authorized amount for the target contract doesn't meet the set limit, the pre-plugin transaction throws an exception and prevents the original transaction from executing. + +## Implementation +Implement a single `GetMethodCallingThreshold` method like `GetMethodFee` in ACS1. Use `MappedState` in the State class: +```cs +public MappedState MethodCallingThresholds { get; set; } +``` + +Configure the call permission of SetMethodCallingThreshold, requiring an Admin in the State: +```cs +public SingletonState
Admin { get; set; } +``` +```cs +public override Empty SetMethodCallingThreshold(SetMethodCallingThresholdInput input) +{ + Assert(State.Admin.Value == Context.Sender, "No permission."); + State.MethodCallingThresholds[input.Method] = new MethodCallingThreshold + { + SymbolToAmount = {input.SymbolToAmount} + }; + return new Empty(); +} + +public override MethodCallingThreshold GetMethodCallingThreshold(StringValue input) +{ + return State.MethodCallingThresholds[input.Value]; +} + +public override Empty Foo(Empty input) +{ + return new Empty(); +} + +message SetMethodCallingThresholdInput { + string method = 1; + map symbol_to_amount = 2;// The order matters. + ThresholdCheckType threshold_check_type = 3; +} +``` + +## Test +Test the Foo method: + +1. Make a Stub +```cs +var keyPair = SampleECKeyPairs.KeyPairs[0]; +var acs5DemoContractStub = + GetTester(DAppContractAddress, keyPair); +``` + +2. Check the current threshold (should be 0): +```cs +var methodResult = await acs5DemoContractStub.GetMethodCallingThreshold.CallAsync( + new StringValue + { + Value = nameof(acs5DemoContractStub.Foo) + }); +methodResult.SymbolToAmount.Count.ShouldBe(0); +``` + +3. Ensure the caller's ELF balance is greater than 1 ELF: +```cs +await acs5DemoContractStub.SetMethodCallingThreshold.SendAsync( + new SetMethodCallingThresholdInput + { + Method = nameof(acs5DemoContractStub.Foo), + SymbolToAmount = + { + {"ELF", 1_0000_0000} + }, + ThresholdCheckType = ThresholdCheckType.Balance + }); +``` + +4. Check the threshold again: +```cs +methodResult = await acs5DemoContractStub.GetMethodCallingThreshold.CallAsync( + new StringValue + { + Value = nameof(acs5DemoContractStub.Foo) + }); +methodResult.SymbolToAmount.Count.ShouldBe(1); +methodResult.ThresholdCheckType.ShouldBe(ThresholdCheckType.Balance); +``` + +5. Send the Foo transaction with an account that has enough balance: +```cs +// Call with enough balance. +{ + var executionResult = await acs5DemoContractStub.Foo.SendAsync(new Empty()); + executionResult.TransactionResult.Status.ShouldBe(TransactionResultStatus.Mined); +} +``` + +6. Send the Foo transaction with an account without ELF: +```cs +// Call without enough balance. +{ + var poorStub = + GetTester(DAppContractAddress, + SampleECKeyPairs.KeyPairs[1]); + var executionResult = await poorStub.Foo.SendWithExceptionAsync(new Empty()); + executionResult.TransactionResult.Error.ShouldContain("Cannot meet the calling threshold."); +} +``` diff --git a/docs/Reference/ACS Introduction/ACS6 - Random Number Generation.md b/docs/Reference/ACS Introduction/ACS6 - Random Number Generation.md new file mode 100644 index 0000000..54d7f4b --- /dev/null +++ b/docs/Reference/ACS Introduction/ACS6 - Random Number Generation.md @@ -0,0 +1,34 @@ +# ACS6 - Random Number Provider Standard +To generate a random number in your contract, use ACS6. + +## Interface +To provide a random number based on input, implement this interface: + +### Methods +| Method Name | Request Type | Response Type | Description | +|-----------------|----------------------------|----------------------------|-------------------------------------------| +| GetRandomBytes | google.protobuf.BytesValue | google.protobuf.BytesValue | Get the random number provided by the contract. | + +## Usage +Override the `GetRandomBytes` method to return a random number based on the given input. The logic for generating the random number is up to you. Ensure you return a `BytesValue` type so the caller can deserialize the output. + +## Implementation +The simplest implementation: + +```cs +public override BytesValue GetRandomBytes(BytesValue input) +{ + var serializedInput = new GetRandomBytesInput(); + serializedInput.MergeFrom(input.Value); + var value = new Hash(); + value.MergeFrom(serializedInput.Value); + var randomHashFromContext = Context.GetRandomHash(value); + + return new BytesValue + { + Value = serializedInput.Kind == 1 + ? new BytesValue {Value = randomHashFromContext.Value}.ToByteString() + : new Int64Value {Value = Context.ConvertHashToInt64(randomHashFromContext, 1, 10000)}.ToByteString() + }; +} +``` \ No newline at end of file diff --git a/docs/Reference/ACS Introduction/ACS7 - Cross Chain Standard.md b/docs/Reference/ACS Introduction/ACS7 - Cross Chain Standard.md new file mode 100644 index 0000000..03f669f --- /dev/null +++ b/docs/Reference/ACS Introduction/ACS7 - Cross Chain Standard.md @@ -0,0 +1,95 @@ +# ACS7 - Contract CrossChain Standard + +ACS7 is for implementing cross-chain contracts. + +## Interface + +This involves methods for chain creation and indexing: + +### Methods + +| Method Name | Request Type | Response Type | Description | +|-------------------------------------|---------------------------------------------|--------------------------------|---------------------------------------------------------------------| +| ProposeCrossChainIndexing | acs7.CrossChainBlockData | google.protobuf.Empty | Propose a cross-chain indexing. | +| ReleaseCrossChainIndexingProposal | acs7.ReleaseCrossChainIndexingProposalInput | google.protobuf.Empty | Release the proposed indexing if approved. | +| RequestSideChainCreation | acs7.SideChainCreationRequest | google.protobuf.Empty | Request side chain creation. | +| ReleaseSideChainCreation | acs7.ReleaseSideChainCreationInput | google.protobuf.Empty | Release the side chain creation request if approved. | +| CreateSideChain | acs7.CreateSideChainInput | google.protobuf.Int32Value | Create the side chain and return its ID. Only authorized users. | +| Recharge | acs7.RechargeInput | google.protobuf.Empty | Recharge a specified side chain. | +| DisposeSideChain | google.protobuf.Int32Value | google.protobuf.Int32Value | Dispose a side chain by ID. Only authorized users. | +| AdjustIndexingFeePrice | acs7.AdjustIndexingFeeInput | google.protobuf.Empty | Adjust side chain indexing fee. Only authorized users. | +| VerifyTransaction | acs7.VerifyTransactionInput | google.protobuf.BoolValue | Verify a cross-chain transaction. | +| GetSideChainIdAndHeight | google.protobuf.Empty | acs7.ChainIdAndHeightDict | Get all side chain IDs and heights. | +| GetSideChainIndexingInformationList | google.protobuf.Empty | acs7.SideChainIndexingInformationList | Get indexing information of side chains. | +| GetAllChainsIdAndHeight | google.protobuf.Empty | acs7.ChainIdAndHeightDict | Get IDs and heights of all chains. | +| GetIndexedSideChainBlockDataByHeight| google.protobuf.Int64Value | acs7.IndexedSideChainBlockData | Get block data of indexed side chain by height. | +| GetBoundParentChainHeightAndMerklePathByHeight | google.protobuf.Int64Value | acs7.CrossChainMerkleProofContext | Get Merkle path bound to side chain by height. | +| GetChainInitializationData | google.protobuf.Int32Value | acs7.ChainInitializationData | Get initialization data for a specified side chain. | + +### Types + +#### acs7.AdjustIndexingFeeInput +| Field | Description | Label | +|--------------------|-------------------------|----------| +| `side_chain_id` (int32) | The side chain ID | | +| `indexing_fee` (int64) | The new indexing fee | | + +#### acs7.ChainIdAndHeightDict +| Field | Description | Label | +|--------------------------|-------------------------|-----------| +| `id_height_dict` (map) | Chain IDs and heights | repeated | + +#### acs7.ChainInitializationData +| Field | Description | Label | +|----------------------------------------|------------------------------------|---------| +| `chain_id` (int32) | Side chain ID | | +| `creator` (aelf.Address) | Creator's address | | +| `creation_timestamp` (google.protobuf.Timestamp) | Creation timestamp | | +| `creation_height_on_parent_chain` (int64) | Height on parent chain | | +| `chain_creator_privilege_preserved` (bool) | If privilege is preserved | | +| `parent_chain_token_contract_address` (aelf.Address) | Token contract address | | +| `chain_initialization_consensus_info` (ChainInitializationConsensusInfo) | Initial consensus info | | +| `native_token_info_data` (bytes) | Native token info | | +| `resource_token_info` (ResourceTokenInfo) | Resource token info | | +| `chain_primary_token_info` (ChainPrimaryTokenInfo) | Primary token info | | + +#### acs7.CreateSideChainInput +| Field | Description | Label | +|------------------------------------|---------------------------------|----------| +| `side_chain_creation_request` (SideChainCreationRequest) | Creation request info | | +| `proposer` (aelf.Address) | Proposer's address | | + +#### acs7.CrossChainBlockData +| Field | Description | Label | +|-------------------------------------|---------------------------------|-----------| +| `side_chain_block_data_list` (repeated SideChainBlockData) | List of side chain block data | repeated | +| `parent_chain_block_data_list` (repeated ParentChainBlockData) | List of parent chain block data | repeated | + +#### acs7.RechargeInput +| Field | Description | Label | +|--------------------|-------------------------|----------| +| `chain_id` (int32) | Side chain ID | | +| `amount` (int64) | Amount to recharge | | + +#### acs7.ReleaseCrossChainIndexingProposalInput +| Field | Description | Label | +|----------------------------|-----------------------------|-----------| +| `chain_id_list` (repeated int32) | List of chain IDs to release | repeated | + +#### acs7.ReleaseSideChainCreationInput +| Field | Description | Label | +|--------------------|-------------------------|----------| +| `proposal_id` (aelf.Hash) | Proposal ID | | + +#### acs7.SideChainCreationRequest +| Field | Description | Label | +|------------------------------------------|------------------------------------|-----------| +| `indexing_price` (int64) | Cross-chain indexing price | | +| `locked_token_amount` (int64) | Initial locked balance | | +| `is_privilege_preserved` (bool) | If privilege is preserved | | +| `side_chain_token_creation_request` (SideChainTokenCreationRequest) | Token creation request | | +| `side_chain_token_initial_issue_list` (repeated SideChainTokenInitialIssue) | Initial token issues list | repeated | +| `initial_resource_amount` (repeated SideChainCreationRequest.InitialResourceAmountEntry) | Initial resource amounts | repeated | + +### Example +ACS7 defines methods for cross-chain scenarios. AElf provides an implementation for ACS7 called `CrossChainContract`. Refer to this implementation for more details. \ No newline at end of file diff --git a/docs/Reference/ACS Introduction/ACS8 - Transaction Resource Token Fee Standard.md b/docs/Reference/ACS Introduction/ACS8 - Transaction Resource Token Fee Standard.md new file mode 100644 index 0000000..ff99d54 --- /dev/null +++ b/docs/Reference/ACS Introduction/ACS8 - Transaction Resource Token Fee Standard.md @@ -0,0 +1,74 @@ +# ACS8 - Transaction Resource Token Fee Standard +ACS8 is a transaction fee standard similar to ACS1, but it charges the called contract rather than the user. The fee charged includes four specified tokens: WRITE, READ, NET, and TRAFFIC. + +When a contract inherits from ACS8, each transaction within this contract incurs charges in these four resource tokens. + +## Interface +The acs8.proto file defines the following method: + +### Methods +| Method Name | Request Type | Response Type | Description | +|-------------------|-----------------------------|-------------------|--------------------------------------------------| +| BuyResourceToken | acs8.BuyResourceTokenInput | google.protobuf.Empty | Buys one of the four resource tokens. Consumes ELF balance in the contract account. | + +### Types + +#### acs8.BuyResourceTokenInput +| Field | Type | Description | Label | +|------------|--------|-----------------------------------------------|-----------| +| symbol | string | The symbol of the token to buy. | | +| amount | int64 | The amount of token to buy. | | +| pay_limit | int64 | Limit of cost; buying is abandoned if exceeded. 0 means no limit. | | + +## Usage +Contracts inheriting ACS1 use a pre-plugin transaction called ChargeTransactionFees for transaction fee charging. ACS8 introduces a similar post-plugin transaction called ChargeResourceToken, which charges resource tokens based on actual transaction consumption. + +The ChargeResourceToken implementation involves calculating token amounts using polynomial coefficients stored in CalculateFeeCoefficients defined in token_contract.proto. Each resource token has a polynomial for fee calculation, which determines the cost based on transaction consumption. +```cs +public override Empty ChargeResourceToken(ChargeResourceTokenInput input) +{ + Context.LogDebug(() => string.Format("Start executing ChargeResourceToken.{0}", input)); + if (input.Equals(new ChargeResourceTokenInput())) + { + return new Empty(); + } + var bill = new TransactionFeeBill(); + foreach (var pair in input.CostDic) + { + Context.LogDebug(() => string.Format("Charging {0} {1} tokens.", pair.Value, pair.Key)); + var existingBalance = GetBalance(Context.Sender, pair.Key); + Assert(existingBalance >= pair.Value, + string.Format("Insufficient resource of {0}. Need balance: {1}; Current balance: {2}.", pair.Key, pair.Value, existingBalance)); + bill.FeesMap.Add(pair.Key, pair.Value); + } + foreach (var pair in bill.FeesMap) + { + Context.Fire(new ResourceTokenCharged + { + Symbol = pair.Key, + Amount = pair.Value, + ContractAddress = Context.Sender + }); + if (pair.Value == 0) + { + Context.LogDebug(() => string.Format("Maybe incorrect charged resource fee of {0}: it's 0.", pair.Key)); + } + } + return new Empty(); +} +``` + +Additionally, contracts cannot execute methods if they lack sufficient resource token balance. To enforce this, a pre-plugin transaction CheckResourceToken, similar to ACS5, verifies the contract's resource token balance before method execution. +```cs +public override Empty CheckResourceToken(Empty input) +{ + foreach (var symbol in Context.Variables.GetStringArray(TokenContractConstants.PayTxFeeSymbolListName)) + { + var balance = GetBalance(Context.Sender, symbol); + var owningBalance = State.OwningResourceToken[Context.Sender][symbol]; + Assert(balance > owningBalance, + string.Format("Contract balance of {0} token is not enough. Owning {1}.", symbol, owningBalance)); + } + return new Empty(); +} +``` \ No newline at end of file diff --git a/docs/Reference/ACS Introduction/ACS9 - Contract Profit Dividend Standard.md b/docs/Reference/ACS Introduction/ACS9 - Contract Profit Dividend Standard.md new file mode 100644 index 0000000..08293b0 --- /dev/null +++ b/docs/Reference/ACS Introduction/ACS9 - Contract Profit Dividend Standard.md @@ -0,0 +1,483 @@ +# ACS9 - Contract Profit Dividend Standard +ACS9 defines a standard for distributing profits on AElf's side chain contracts. + +## Interface +ACS9 includes several methods given below: + +### Methods +| Method Name | Request Type | Response Type | Description | +|----------------------|-------------------------------|----------------------------|---------------------------------------------| +| TakeContractProfits | acs9.TakeContractProfitsInput | google.protobuf.Empty | Allows developers to collect contract profits. | +| GetProfitConfig | google.protobuf.Empty | acs9.ProfitConfig | Retrieves profit distribution configuration. | +| GetProfitsAmount | google.protobuf.Empty | acs9.ProfitsMap | Queries total profits accumulated by the contract. | + +### Types + +#### acs9.ProfitConfig +| Field | Type | Description | Label | +|--------------------------------|----------|-----------------------------------------------|------------| +| donation_parts_per_hundred | int32 | Percentage of profit donated to dividend pool. | | +| profits_token_symbol_list | string | List of profit token symbols. | repeated | +| staking_token_symbol | string | Token symbol users can lock to claim profits. | | + +#### acs9.ProfitsMap +| Field | Type | Description | Label | +|---------------|------------------------------------|------------------------------------------|-----------| +| value | map | Profits accumulated, symbol -> amount. | repeated | + +#### acs9.TakeContractProfitsInput +| Field | Type | Description | Label | +|---------------|--------|-----------------------------------|-----------| +| symbol | string | Token symbol to withdraw profits. | | +| amount | int64 | Amount of token to withdraw. | | + +## Implementation +The contract initializes by creating a token called APP and establishing a profit distribution scheme using the TokenHolder contract. Users receive 10 APP tokens upon signing up and can deposit ELF to receive APP tokens. The Use method consumes APP tokens. + +Upon initialization, ACS9 sets the profit configuration and enables profit distribution to the profit receiver and dividend pool. + +The contract allows users to interact by signing up, depositing and withdrawing tokens, and using APP tokens for transactions. Developers can configure profit distribution settings and monitor accumulated profits. + +1. Implementation of Initialize +```cs +public override Empty Initialize(InitializeInput input) +{ + State.TokenHolderContract.Value = + Context.GetContractAddressByName(SmartContractConstants.TokenHolderContractSystemName); + State.TokenContract.Value = + Context.GetContractAddressByName(SmartContractConstants.TokenContractSystemName); + State.DividendPoolContract.Value = + Context.GetContractAddressByName(input.DividendPoolContractName.Value.ToBase64()); + State.Symbol.Value = input.Symbol == string.Empty ? "APP" : input.Symbol; + State.ProfitReceiver.Value = input.ProfitReceiver; + CreateToken(input.ProfitReceiver); + // To test TokenHolder Contract. + CreateTokenHolderProfitScheme(); + // To test ACS9 workflow. + SetProfitConfig(); + State.ProfitReceiver.Value = input.ProfitReceiver; + return new Empty(); +} +private void CreateToken(Address profitReceiver, bool isLockWhiteListIncludingSelf = false) +{ + var lockWhiteList = new List
+ {Context.GetContractAddressByName(SmartContractConstants.TokenHolderContractSystemName)}; + if (isLockWhiteListIncludingSelf) + lockWhiteList.Add(Context.Self); + State.TokenContract.Create.Send(new CreateInput + { + Symbol = State.Symbol.Value, + TokenName = "DApp Token", + Decimals = ACS9DemoContractConstants.Decimal, + Issuer = Context.Self, + IsBurnable = true, + IsProfitable = true, + TotalSupply = ACS9DemoContractConstants.TotalSupply, + LockWhiteList = + { + lockWhiteList + } + }); + State.TokenContract.Issue.Send(new IssueInput + { + To = profitReceiver, + Amount = ACS9DemoContractConstants.TotalSupply / 5, + Symbol = State.Symbol.Value, + Memo = "Issue token for profit receiver" + }); +} +private void CreateTokenHolderProfitScheme() +{ + State.TokenHolderContract.CreateScheme.Send(new CreateTokenHolderProfitSchemeInput + { + Symbol = State.Symbol.Value + }); +} +private void SetProfitConfig() +{ + State.ProfitConfig.Value = new ProfitConfig + { + DonationPartsPerHundred = 1, + StakingTokenSymbol = "APP", + ProfitsTokenSymbolList = {"ELF"} + }; +} +``` + +2. The user can use the SighUp method to register and get the bonus +```cs +/// +/// When user sign up, give him 10 APP tokens, then initialize his profile. +/// +/// +/// +public override Empty SignUp(Empty input) +{ + Assert(State.Profiles[Context.Sender] == null, "Already registered."); + var profile = new Profile + { + UserAddress = Context.Sender + }; + State.TokenContract.Issue.Send(new IssueInput + { + Symbol = State.Symbol.Value, + Amount = ACS9DemoContractConstants.ForNewUser, + To = Context.Sender + }); + // Update profile. + profile.Records.Add(new Record + { + Type = RecordType.SignUp, + Timestamp = Context.CurrentBlockTime, + Description = string.Format("{0} +{1}",State.Symbol.Value, ACS9DemoContractConstants.ForNewUser) + }); + State.Profiles[Context.Sender] = profile; + return new Empty(); +} +``` + +3. Recharge and redemption: +```cs +public override Empty Deposit(DepositInput input) +{ + // User Address -> DApp Contract. + State.TokenContract.TransferFrom.Send(new TransferFromInput + { + From = Context.Sender, + To = Context.Self, + Symbol = "ELF", + Amount = input.Amount + }); + State.TokenContract.Issue.Send(new IssueInput + { + Symbol = State.Symbol.Value, + Amount = input.Amount, + To = Context.Sender + }); + // Update profile. + var profile = State.Profiles[Context.Sender]; + profile.Records.Add(new Record + { + Type = RecordType.Deposit, + Timestamp = Context.CurrentBlockTime, + Description = string.Format("{0} +{1}", State.Symbol.Value, input.Amount) + }); + State.Profiles[Context.Sender] = profile; + return new Empty(); +} +public override Empty Withdraw(WithdrawInput input) +{ + State.TokenContract.TransferFrom.Send(new TransferFromInput + { + From = Context.Sender, + To = Context.Self, + Symbol = State.Symbol.Value, + Amount = input.Amount + }); + State.TokenContract.Transfer.Send(new TransferInput + { + To = Context.Sender, + Symbol = input.Symbol, + Amount = input.Amount + }); + State.TokenHolderContract.RemoveBeneficiary.Send(new RemoveTokenHolderBeneficiaryInput + { + Beneficiary = Context.Sender, + Amount = input.Amount + }); + // Update profile. + var profile = State.Profiles[Context.Sender]; + profile.Records.Add(new Record + { + Type = RecordType.Withdraw, + Timestamp = Context.CurrentBlockTime, + Description = string.Format("{0} -{1}", State.Symbol.Value, input.Amount) + }); + State.Profiles[Context.Sender] = profile; + return new Empty(); +} +``` + +4. Implementation of Use directly transfers 1/3 profits into the token holder dividend scheme: +```cs +public override Empty Use(Record input) +{ + State.TokenContract.TransferFrom.Send(new TransferFromInput + { + From = Context.Sender, + To = Context.Self, + Symbol = State.Symbol.Value, + Amount = ACS9DemoContractConstants.UseFee + }); + if (input.Symbol == string.Empty) + input.Symbol = State.TokenContract.GetPrimaryTokenSymbol.Call(new Empty()).Value; + var contributeAmount = ACS9DemoContractConstants.UseFee.Div(3); + State.TokenContract.Approve.Send(new ApproveInput + { + Spender = State.TokenHolderContract.Value, + Symbol = input.Symbol, + Amount = contributeAmount + }); + // Contribute 1/3 profits (ELF) to profit scheme. + State.TokenHolderContract.ContributeProfits.Send(new ContributeProfitsInput + { + SchemeManager = Context.Self, + Amount = contributeAmount, + Symbol = input.Symbol + }); + // Update profile. + var profile = State.Profiles[Context.Sender]; + profile.Records.Add(new Record + { + Type = RecordType.Withdraw, + Timestamp = Context.CurrentBlockTime, + Description = string.Format("{0} -{1}", State.Symbol.Value, ACS9DemoContractConstants.UseFee), + Symbol = input.Symbol + }); + State.Profiles[Context.Sender] = profile; + return new Empty(); +} +``` +5. Implement ACS9 for the perfect profit distribution: +```cs +public override Empty TakeContractProfits(TakeContractProfitsInput input) +{ + var config = State.ProfitConfig.Value; + // For Side Chain Dividends Pool. + var amountForSideChainDividendsPool = input.Amount.Mul(config.DonationPartsPerHundred).Div(100); + State.TokenContract.Approve.Send(new ApproveInput + { + Symbol = input.Symbol, + Amount = amountForSideChainDividendsPool, + Spender = State.DividendPoolContract.Value + }); + State.DividendPoolContract.Donate.Send(new DonateInput + { + Symbol = input.Symbol, + Amount = amountForSideChainDividendsPool + }); + // For receiver. + var amountForReceiver = input.Amount.Sub(amountForSideChainDividendsPool); + State.TokenContract.Transfer.Send(new TransferInput + { + To = State.ProfitReceiver.Value, + Amount = amountForReceiver, + Symbol = input.Symbol + }); + // For Token Holder Profit Scheme. (To distribute.) + State.TokenHolderContract.DistributeProfits.Send(new DistributeProfitsInput + { + SchemeManager = Context.Self + }); + return new Empty(); +} +public override ProfitConfig GetProfitConfig(Empty input) +{ + return State.ProfitConfig.Value; +} +public override ProfitsMap GetProfitsAmount(Empty input) +{ + var profitsMap = new ProfitsMap(); + foreach (var symbol in State.ProfitConfig.Value.ProfitsTokenSymbolList) + { + var balance = State.TokenContract.GetBalance.Call(new GetBalanceInput + { + Owner = Context.Self, + Symbol = symbol + }).Balance; + profitsMap.Value[symbol] = balance; + } + return profitsMap; +} +``` + +## Test + +Testing involves deploying contracts implementing ACS9 or ACS10, initializing the ACS9 contract using IContractInitializationProvider, and verifying profit distribution among stakeholders. + +- Before the testing begins, the contract implementing ACS9 can be initialized by interface IContractInitializationProvider +```cs +public class ACS9DemoContractInitializationProvider : IContractInitializationProvider +{ + public List GetInitializeMethodList(byte[] contractCode) + { + return new List + { + new InitializeMethod + { + MethodName = nameof(ACS9DemoContract.Initialize), + Params = new InitializeInput + { + ProfitReceiver = Address.FromPublicKey(SampleECKeyPairs.KeyPairs.Skip(3).First().PublicKey), + DividendPoolContractName = ACS10DemoSmartContractNameProvider.Name + }.ToByteString() + } + }; + } + public Hash SystemSmartContractName { get; } = ACS9DemoSmartContractNameProvider.Name; + public string ContractCodeName { get; } = "AElf.Contracts.ACS9DemoContract"; +} +``` + +- Prepare a user account: +```cs +protected List UserKeyPairs => SampleECKeyPairs.KeyPairs.Skip(2).Take(3).ToList(); +``` + +- Prepare some Stubs: +```cs +var keyPair = UserKeyPairs[0]; +var address = Address.FromPublicKey(keyPair.PublicKey); +// Prepare stubs. +var acs9DemoContractStub = GetACS9DemoContractStub(keyPair); +var acs10DemoContractStub = GetACS10DemoContractStub(keyPair); +var userTokenStub = + GetTester(TokenContractAddress, UserKeyPairs[0]); +var userTokenHolderStub = + GetTester(TokenHolderContractAddress, + UserKeyPairs[0]); +``` + +- Then, transfer ELF to the user (TokenContractStub is the Stub of the initial bp who has much ELF) : +```cs +// Transfer some ELFs to user. +await TokenContractStub.Transfer.SendAsync(new TransferInput +{ + To = address, + Symbol = "ELF", + Amount = 1000_00000000 +}); +``` + +- User have to call SignUp to check if they got 10 APP tokens: +```cs +await acs9DemoContractStub.SignUp.SendAsync(new Empty()); +// User has 10 APP tokens because of signing up. +(await GetFirstUserBalance("APP")).ShouldBe(10_00000000); +``` + +- Test the recharge method of the contract itself: +```cs +var elfBalanceBefore = await GetFirstUserBalance("ELF"); +// User has to Approve an amount of ELF tokens before deposit to the DApp. +await userTokenStub.Approve.SendAsync(new ApproveInput +{ + Amount = 1000_00000000, + Spender = ACS9DemoContractAddress, + Symbol = "ELF" +}); +await acs9DemoContractStub.Deposit.SendAsync(new DepositInput +{ + Amount = 100_00000000 +}); +// Check the change of balance of ELF. +var elfBalanceAfter = await GetFirstUserBalance("ELF"); +elfBalanceAfter.ShouldBe(elfBalanceBefore - 100_00000000); +// Now user has 110 APP tokens. +(await GetFirstUserBalance("APP")).ShouldBe(110_00000000); +``` + +- The user locks up 57 APP via the TokenHolder contract in order to obtain profits from the contract: +```cs +// User lock some APP tokens for getting profits. (APP -57) +await userTokenHolderStub.RegisterForProfits.SendAsync(new RegisterForProfitsInput +{ + SchemeManager = ACS9DemoContractAddress, + Amount = 57_00000000 +}); +``` + +- The Use method is invoked 10 times and 0.3 APP is consumed each time, and finally the user have 50 APP left: +```cs +await userTokenStub.Approve.SendAsync(new ApproveInput +{ + Amount = long.MaxValue, + Spender = ACS9DemoContractAddress, + Symbol = "APP" +}); +// User uses 10 times of this DApp. (APP -3) +for (var i = 0; i < 10; i++) +{ + await acs9DemoContractStub.Use.SendAsync(new Record()); +} +// Now user has 50 APP tokens. +(await GetFirstUserBalance("APP")).ShouldBe(50_00000000); +``` + +- Using the TakeContractProfits method, the developer attempts to withdraw 10 ELF as profits. The 10 ELF will be transferred to the developer in this method: +```cs +const long baseBalance = 0; +{ + var balance = await TokenContractStub.GetBalance.CallAsync(new GetBalanceInput + { + Owner = UserAddresses[1], Symbol = "ELF" + }); + balance.Balance.ShouldBe(baseBalance); +} +// Profits receiver claim 10 ELF profits. +await acs9DemoContractStub.TakeContractProfits.SendAsync(new TakeContractProfitsInput +{ + Symbol = "ELF", + Amount = 10_0000_0000 +}); +// Then profits receiver should have 9.9 ELF tokens. +{ + var balance = await TokenContractStub.GetBalance.CallAsync(new GetBalanceInput + { + Owner = UserAddresses[1], Symbol = "ELF" + }); + balance.Balance.ShouldBe(baseBalance + 9_9000_0000); +} +``` + +- Next check the profit distribution results. The dividend pool should be allocated 0.1 ELF: +```cs +// And Side Chain Dividends Pool should have 0.1 ELF tokens. +{ + var scheme = await TokenHolderContractStub.GetScheme.CallAsync(ACS10DemoContractAddress); + var virtualAddress = await ProfitContractStub.GetSchemeAddress.CallAsync(new SchemePeriod + { + SchemeId = scheme.SchemeId, + Period = 0 + }); + var balance = await TokenContractStub.GetBalance.CallAsync(new GetBalanceInput + { + Owner = virtualAddress, + Symbol = "ELF" + }); + balance.Balance.ShouldBe(1000_0000); +} +``` + +- The user receives 1 ELF from the token holder dividend scheme: +```cs +// Help user to claim profits from token holder profit scheme. +await TokenHolderContractStub.ClaimProfits.SendAsync(new ClaimProfitsInput +{ + Beneficiary = UserAddresses[0], + SchemeManager = ACS9DemoContractAddress, +}); +// Profits should be 1 ELF. +(await GetFirstUserBalance("ELF")).ShouldBe(elfBalanceAfter + 1_0000_0000); +``` + +- Finally, let’s test the Withdraw method. +```cs +// Withdraw +var beforeBalance = + await userTokenStub.GetBalance.CallAsync(new GetBalanceInput + { + Symbol = "APP", + Owner = UserAddresses[0] + }); +var withDrawResult = await userTokenHolderStub.Withdraw.SendAsync(ACS9DemoContractAddress); +withDrawResult.TransactionResult.Status.ShouldBe(TransactionResultStatus.Mined); +var resultBalance = await userTokenStub.GetBalance.CallAsync(new GetBalanceInput +{ + Symbol = "APP", + Owner = UserAddresses[0] +}); +resultBalance.Balance.ShouldBe(beforeBalance.Balance + 57_00000000); +``` + +This documentation provides a framework for implementing profit distribution and verifying its functionality through various test scenarios. \ No newline at end of file