diff --git a/docs/quick-start/lottery-game/index.md b/docs/quick-start/lottery-game/index.md new file mode 100644 index 0000000..ba5b7c6 --- /dev/null +++ b/docs/quick-start/lottery-game/index.md @@ -0,0 +1,1497 @@ +--- +sidebar_position: 4 +title: Lottery Game Contract +--- + +# Lottery Game Contract + +This guide provides step-by-step instructions to set up your local development environment to get started developing and deploying aelf smart contracts. + +## 1. Setup Environment + +### Prerequisites + + + + +- Basic knowledge of terminal commands +- **IDE** - Install [VS Code](https://code.visualstudio.com/) + +**Install Required Packages** + +- [Install dotnet](https://dotnet.microsoft.com/en-us/download/dotnet/6.0) +- Install aelf contract templates + +```bash +dotnet new --install AELF.ContractTemplates +``` + +AELF.ContractTemplates contains various predefined templates for the ease of developing smart contracts on the aelf blockchain. + +- Install aelf deploy tool + +```bash +dotnet tool install --global aelf.deploy +``` + +aelf.deploy is a utility tool for deploying smart contracts on the aelf blockchain. +Please remember to export PATH after installing aelf.deploy. + +**Install Node.js and Yarn** + +- [Install Node.js](https://nodejs.org/en) +- Install aelf-command + +```bash +sudo npm i -g aelf-command +``` + +aelf-command is a CLI tool for interacting with the aelf blockchain, enabling tasks like creating wallets and managing transactions. +Provide required permissions while installing aelf-command globally. + + + + +1. **Go to the Repository Template**: + Visit [aelf-devcontainer-template](https://github.com/AElfProject/aelf-devcontainer-template). + +2. **Create a New Repository**: + Click the **"Use this template"** button. + Choose **"Create a new repository"**. + +3. **Name Your Repository**: + Enter a suitable repository name. + Click **"Create repository"**. + +4. **Access Codespaces**: + Within the GitHub interface of your new repository, click on **"Code"**. + Select **"Codespaces"**. + +5. **Create a New Codespace**: + Click on the **"+"** sign to create a new Codespace. + +6. **Wait for Your Workspace to Load**: + After some time, your workspace will load with the contents of the repository. + You can now continue your development using GitHub Codespaces. + + + + +## 2. Develop Smart Contract + +### Start Your Smart Contract Project + +Open your `Terminal`. + +Enter the following command to generate a new project: + +```bash +mkdir lottery-game +cd lottery-game +dotnet new aelf -n LotteryGame +``` + +### Install ACS12.proto + +```bash +mkdir Protobuf +cd Protobuf +mkdir references +cd .. +export ACS_DIR=Protobuf/references +curl -O --output-dir $ACS_DIR https://raw.githubusercontent.com/AElfProject/AElf/dev/protobuf/acs12.proto +``` + +### Adding Your Smart Contract Code + +Now that we have a template lottery game project, we can customise the template to incorporate our own contract logic. +Lets start by implementing methods to provide basic functionality for updating and reading a message stored persistently in the contract state. + +```bash +cd src +``` + +#### Defining Methods and Messages + +The implementation of file `src/Protobuf/contract/lottery_game_contract.proto` is as follows: + +```csharp +syntax = "proto3"; + +import "aelf/core.proto"; +import "aelf/options.proto"; +import "google/protobuf/empty.proto"; +import "google/protobuf/wrappers.proto"; +import "acs12.proto"; +// The namespace of this class +option csharp_namespace = "AElf.Contracts.LotteryGame"; + +service LotteryGame { + // The name of the state class the smart contract is going to use to access blockchain state + option (aelf.csharp_state) = "AElf.Contracts.LotteryGame.LotteryGameState"; + option (aelf.base) = "acs12.proto"; + + rpc Initialize (google.protobuf.Empty) returns (google.protobuf.Empty) { + } + + rpc Play (google.protobuf.Int64Value) returns (google.protobuf.Empty) { + } + + rpc Withdraw (google.protobuf.Int64Value) returns (google.protobuf.Empty) { + } + + rpc Deposit (google.protobuf.Int64Value) returns (google.protobuf.Empty) { + } + + rpc TransferOwnership (aelf.Address) returns (google.protobuf.Empty) { + } + + rpc GetPlayAmountLimit (google.protobuf.Empty) returns (PlayAmountLimitMessage) { + option (aelf.is_view) = true; + } + + rpc GetContractBalance (google.protobuf.Empty) returns (google.protobuf.Int64Value) { + option (aelf.is_view) = true; + } + + rpc GetOwner (google.protobuf.Empty) returns (google.protobuf.StringValue) { + option (aelf.is_view) = true; + } +} + +// An event that will be emitted from contract method call when Play is called. +message PlayOutcomeEvent { + option (aelf.is_event) = true; + int64 amount = 1; + int64 won = 2; +} + +// An event that will be emitted from contract method call when Withdraw is called. +message WithdrawEvent { + option (aelf.is_event) = true; + int64 amount = 1; + aelf.Address from = 2; + aelf.Address to = 3; +} + +// An event that will be emitted from contract method call when Deposit is called. +message DepositEvent { + option (aelf.is_event) = true; + int64 amount = 1; + aelf.Address from = 2; + aelf.Address to = 3; +} + +// The message containing the play amount limits +message PlayAmountLimitMessage { + int64 minimumAmount = 1; + int64 maximumAmount = 2; +} + + +``` + +#### Define Contract States + +The implementation of file `src/LotteryGameState.cs` is as follows: + +```csharp +using AElf.Sdk.CSharp.State; +using AElf.Types; + +namespace AElf.Contracts.LotteryGame +{ + // The state class is access the blockchain state + public partial class LotteryGameState : ContractState + { + // A state to check if contract is initialized + public BoolState Initialized { get; set; } + // A state to store the owner address + public SingletonState
Owner { get; set; } + } +} +``` + +#### Contract Reference State + +Create a new folder **reference** under `src/Protobuf/` + +```bash +cd /src/Protobuf +mkdir reference +``` + +Create a new file **token_contract.proto** under `src/Protobuf/reference/` +The implementation of file `token_contract.protot` + +```csharp + +/** + * MultiToken contract. + */ +syntax = "proto3"; + +package token; + +import "aelf/core.proto"; +import "aelf/options.proto"; +import "google/protobuf/empty.proto"; +import "google/protobuf/wrappers.proto"; + +option csharp_namespace = "AElf.Contracts.MultiToken"; + +service TokenContract { + // Create a new token. + rpc Create (CreateInput) returns (google.protobuf.Empty) { + } + + // Issuing some amount of tokens to an address is the action of increasing that addresses balance + // for the given token. The total amount of issued tokens must not exceed the total supply of the token + // and only the issuer (creator) of the token can issue tokens. + // Issuing tokens effectively increases the circulating supply. + rpc Issue (IssueInput) returns (google.protobuf.Empty) { + } + + // Transferring tokens simply is the action of transferring a given amount of tokens from one address to another. + // The origin or source address is the signer of the transaction. + // The balance of the sender must be higher than the amount that is transferred. + rpc Transfer (TransferInput) returns (google.protobuf.Empty) { + } + + // The TransferFrom action will transfer a specified amount of tokens from one address to another. + // For this operation to succeed the from address needs to have approved (see allowances) enough tokens + // to Sender of this transaction. If successful the amount will be removed from the allowance. + rpc TransferFrom (TransferFromInput) returns (google.protobuf.Empty) { + } + + // The approve action increases the allowance from the Sender to the Spender address, + // enabling the Spender to call TransferFrom. + rpc Approve (ApproveInput) returns (google.protobuf.Empty) { + } + + rpc BatchApprove (BatchApproveInput) returns (google.protobuf.Empty) { + } + + // This is the reverse operation for Approve, it will decrease the allowance. + rpc UnApprove (UnApproveInput) returns (google.protobuf.Empty) { + } + + // This method can be used to lock tokens. + rpc Lock (LockInput) returns (google.protobuf.Empty) { + } + + // This is the reverse operation of locking, it un-locks some previously locked tokens. + rpc Unlock (UnlockInput) returns (google.protobuf.Empty) { + } + + // This action will burn the specified amount of tokens, removing them from the token’s Supply. + rpc Burn (BurnInput) returns (google.protobuf.Empty) { + } + + // Set the primary token of side chain. + rpc SetPrimaryTokenSymbol (SetPrimaryTokenSymbolInput) returns (google.protobuf.Empty) { + } + + // This interface is used for cross-chain transfer. + rpc CrossChainTransfer (CrossChainTransferInput) returns (google.protobuf.Empty) { + } + + // This method is used to receive cross-chain transfers. + rpc CrossChainReceiveToken (CrossChainReceiveTokenInput) returns (google.protobuf.Empty) { + } + + // The side chain creates tokens. + rpc CrossChainCreateToken(CrossChainCreateTokenInput) returns (google.protobuf.Empty) { + } + + // When the side chain is started, the side chain is initialized with the parent chain information. + rpc InitializeFromParentChain (InitializeFromParentChainInput) returns (google.protobuf.Empty) { + } + + // Handle the transaction fees charged by ChargeTransactionFees. + rpc ClaimTransactionFees (TotalTransactionFeesMap) returns (google.protobuf.Empty) { + } + + // Used to collect transaction fees. + rpc ChargeTransactionFees (ChargeTransactionFeesInput) returns (ChargeTransactionFeesOutput) { + } + + rpc ChargeUserContractTransactionFees(ChargeTransactionFeesInput) returns(ChargeTransactionFeesOutput){ + + } + + // Check the token threshold. + rpc CheckThreshold (CheckThresholdInput) returns (google.protobuf.Empty) { + } + + // Initialize coefficients of every type of tokens supporting charging fee. + rpc InitialCoefficients (google.protobuf.Empty) returns (google.protobuf.Empty){ + } + + // Processing resource token received. + rpc DonateResourceToken (TotalResourceTokensMaps) returns (google.protobuf.Empty) { + } + + // A transaction resource fee is charged to implement the ACS8 standards. + rpc ChargeResourceToken (ChargeResourceTokenInput) returns (google.protobuf.Empty) { + } + + // Verify that the resource token are sufficient. + rpc CheckResourceToken (google.protobuf.Empty) returns (google.protobuf.Empty) { + } + + // Set the list of tokens to pay transaction fees. + rpc SetSymbolsToPayTxSizeFee (SymbolListToPayTxSizeFee) returns (google.protobuf.Empty){ + } + + // Update the coefficient of the transaction fee calculation formula. + rpc UpdateCoefficientsForSender (UpdateCoefficientsInput) returns (google.protobuf.Empty) { + } + + // Update the coefficient of the transaction fee calculation formula. + rpc UpdateCoefficientsForContract (UpdateCoefficientsInput) returns (google.protobuf.Empty) { + } + + // This method is used to initialize the governance organization for some functions, + // including: the coefficient of the user transaction fee calculation formula, + // the coefficient of the contract developer resource fee calculation formula, and the side chain rental fee. + rpc InitializeAuthorizedController (google.protobuf.Empty) returns (google.protobuf.Empty){ + } + + rpc AddAddressToCreateTokenWhiteList (aelf.Address) returns (google.protobuf.Empty) { + } + rpc RemoveAddressFromCreateTokenWhiteList (aelf.Address) returns (google.protobuf.Empty) { + } + + rpc SetTransactionFeeDelegations (SetTransactionFeeDelegationsInput) returns (SetTransactionFeeDelegationsOutput){ + } + + rpc RemoveTransactionFeeDelegator (RemoveTransactionFeeDelegatorInput) returns (google.protobuf.Empty){ + } + + rpc RemoveTransactionFeeDelegatee (RemoveTransactionFeeDelegateeInput) returns (google.protobuf.Empty){ + } + + rpc SetSymbolAlias (SetSymbolAliasInput) returns (google.protobuf.Empty){ + } + + // Get all delegatees' address of delegator from input + rpc GetTransactionFeeDelegatees (GetTransactionFeeDelegateesInput) returns (GetTransactionFeeDelegateesOutput) { + option (aelf.is_view) = true; + } + + // Query token information. + rpc GetTokenInfo (GetTokenInfoInput) returns (TokenInfo) { + option (aelf.is_view) = true; + } + + // Query native token information. + rpc GetNativeTokenInfo (google.protobuf.Empty) returns (TokenInfo) { + option (aelf.is_view) = true; + } + + // Query resource token information. + rpc GetResourceTokenInfo (google.protobuf.Empty) returns (TokenInfoList) { + option (aelf.is_view) = true; + } + + // Query the balance at the specified address. + rpc GetBalance (GetBalanceInput) returns (GetBalanceOutput) { + option (aelf.is_view) = true; + } + + // Query the account's allowance for other addresses + rpc GetAllowance (GetAllowanceInput) returns (GetAllowanceOutput) { + option (aelf.is_view) = true; + } + + // Query the account's available allowance for other addresses + rpc GetAvailableAllowance (GetAllowanceInput) returns (GetAllowanceOutput) { + option (aelf.is_view) = true; + } + + // Check whether the token is in the whitelist of an address, + // which can be called TransferFrom to transfer the token under the condition of not being credited. + rpc IsInWhiteList (IsInWhiteListInput) returns (google.protobuf.BoolValue) { + option (aelf.is_view) = true; + } + + // Query the information for a lock. + rpc GetLockedAmount (GetLockedAmountInput) returns (GetLockedAmountOutput) { + option (aelf.is_view) = true; + } + + // Query the address of receiving token in cross-chain transfer. + rpc GetCrossChainTransferTokenContractAddress (GetCrossChainTransferTokenContractAddressInput) returns (aelf.Address) { + option (aelf.is_view) = true; + } + + // Query the name of the primary Token. + rpc GetPrimaryTokenSymbol (google.protobuf.Empty) returns (google.protobuf.StringValue) { + option (aelf.is_view) = true; + } + + // Query the coefficient of the transaction fee calculation formula. + rpc GetCalculateFeeCoefficientsForContract (google.protobuf.Int32Value) returns (CalculateFeeCoefficients) { + option (aelf.is_view) = true; + } + + // Query the coefficient of the transaction fee calculation formula. + rpc GetCalculateFeeCoefficientsForSender (google.protobuf.Empty) returns (CalculateFeeCoefficients) { + option (aelf.is_view) = true; + } + + // Query tokens that can pay transaction fees. + rpc GetSymbolsToPayTxSizeFee (google.protobuf.Empty) returns (SymbolListToPayTxSizeFee){ + option (aelf.is_view) = true; + } + + // Query the hash of the last input of ClaimTransactionFees. + rpc GetLatestTotalTransactionFeesMapHash (google.protobuf.Empty) returns (aelf.Hash){ + option (aelf.is_view) = true; + } + + // Query the hash of the last input of DonateResourceToken. + rpc GetLatestTotalResourceTokensMapsHash (google.protobuf.Empty) returns (aelf.Hash){ + option (aelf.is_view) = true; + } + rpc IsTokenAvailableForMethodFee (google.protobuf.StringValue) returns (google.protobuf.BoolValue) { + option (aelf.is_view) = true; + } + rpc GetReservedExternalInfoKeyList (google.protobuf.Empty) returns (StringList) { + option (aelf.is_view) = true; + } + + rpc GetTransactionFeeDelegationsOfADelegatee(GetTransactionFeeDelegationsOfADelegateeInput) returns(TransactionFeeDelegations){ + option (aelf.is_view) = true; + } + + rpc GetTokenAlias (google.protobuf.StringValue) returns (google.protobuf.StringValue) { + option (aelf.is_view) = true; + } + + rpc GetSymbolByAlias (google.protobuf.StringValue) returns (google.protobuf.StringValue) { + option (aelf.is_view) = true; + } +} + +message TokenInfo { + // The symbol of the token.f + string symbol = 1; + // The full name of the token. + string token_name = 2; + // The current supply of the token. + int64 supply = 3; + // The total supply of the token. + int64 total_supply = 4; + // The precision of the token. + int32 decimals = 5; + // The address that has permission to issue the token. + aelf.Address issuer = 6; + // A flag indicating if this token is burnable. + bool is_burnable = 7; + // The chain id of the token. + int32 issue_chain_id = 8; + // The amount of issued tokens. + int64 issued = 9; + // The external information of the token. + ExternalInfo external_info = 10; + // The address that owns the token. + aelf.Address owner = 11; +} + +message ExternalInfo { + map value = 1; +} + +message CreateInput { + // The symbol of the token. + string symbol = 1; + // The full name of the token. + string token_name = 2; + // The total supply of the token. + int64 total_supply = 3; + // The precision of the token + int32 decimals = 4; + // The address that has permission to issue the token. + aelf.Address issuer = 5; + // A flag indicating if this token is burnable. + bool is_burnable = 6; + // A whitelist address list used to lock tokens. + repeated aelf.Address lock_white_list = 7; + // The chain id of the token. + int32 issue_chain_id = 8; + // The external information of the token. + ExternalInfo external_info = 9; + // The address that owns the token. + aelf.Address owner = 10; +} + +message SetPrimaryTokenSymbolInput { + // The symbol of the token. + string symbol = 1; +} + +message IssueInput { + // The token symbol to issue. + string symbol = 1; + // The token amount to issue. + int64 amount = 2; + // The memo. + string memo = 3; + // The target address to issue. + aelf.Address to = 4; +} + +message TransferInput { + // The receiver of the token. + aelf.Address to = 1; + // The token symbol to transfer. + string symbol = 2; + // The amount to to transfer. + int64 amount = 3; + // The memo. + string memo = 4; +} + +message LockInput { + // The one want to lock his token. + aelf.Address address = 1; + // Id of the lock. + aelf.Hash lock_id = 2; + // The symbol of the token to lock. + string symbol = 3; + // a memo. + string usage = 4; + // The amount of tokens to lock. + int64 amount = 5; +} + +message UnlockInput { + // The one want to un-lock his token. + aelf.Address address = 1; + // Id of the lock. + aelf.Hash lock_id = 2; + // The symbol of the token to un-lock. + string symbol = 3; + // a memo. + string usage = 4; + // The amount of tokens to un-lock. + int64 amount = 5; +} + +message TransferFromInput { + // The source address of the token. + aelf.Address from = 1; + // The destination address of the token. + aelf.Address to = 2; + // The symbol of the token to transfer. + string symbol = 3; + // The amount to transfer. + int64 amount = 4; + // The memo. + string memo = 5; +} + +message ApproveInput { + // The address that allowance will be increased. + aelf.Address spender = 1; + // The symbol of token to approve. + string symbol = 2; + // The amount of token to approve. + int64 amount = 3; +} +message BatchApproveInput { + repeated ApproveInput value = 1; +} + +message UnApproveInput { + // The address that allowance will be decreased. + aelf.Address spender = 1; + // The symbol of token to un-approve. + string symbol = 2; + // The amount of token to un-approve. + int64 amount = 3; +} + +message BurnInput { + // The symbol of token to burn. + string symbol = 1; + // The amount of token to burn. + int64 amount = 2; +} + +message ChargeResourceTokenInput { + // Collection of charge resource token, Symbol->Amount. + map cost_dic = 1; + // The sender of the transaction. + aelf.Address caller = 2; +} + +message TransactionFeeBill { + // The transaction fee dictionary, Symbol->fee. + map fees_map = 1; +} + +message TransactionFreeFeeAllowanceBill { + // The transaction free fee allowance dictionary, Symbol->fee. + map free_fee_allowances_map = 1; +} + +message CheckThresholdInput { + // The sender of the transaction. + aelf.Address sender = 1; + // The threshold to set, Symbol->Threshold. + map symbol_to_threshold = 2; + // Whether to check the allowance. + bool is_check_allowance = 3; +} + +message GetTokenInfoInput { + // The symbol of token. + string symbol = 1; +} + +message GetBalanceInput { + // The symbol of token. + string symbol = 1; + // The target address of the query. + aelf.Address owner = 2; +} + +message GetBalanceOutput { + // The symbol of token. + string symbol = 1; + // The target address of the query. + aelf.Address owner = 2; + // The balance of the owner. + int64 balance = 3; +} + +message GetAllowanceInput { + // The symbol of token. + string symbol = 1; + // The address of the token owner. + aelf.Address owner = 2; + // The address of the spender. + aelf.Address spender = 3; +} + +message GetAllowanceOutput { + // The symbol of token. + string symbol = 1; + // The address of the token owner. + aelf.Address owner = 2; + // The address of the spender. + aelf.Address spender = 3; + // The amount of allowance. + int64 allowance = 4; +} + +message CrossChainTransferInput { + // The receiver of transfer. + aelf.Address to = 1; + // The symbol of token. + string symbol = 2; + // The amount of token to transfer. + int64 amount = 3; + // The memo. + string memo = 4; + // The destination chain id. + int32 to_chain_id = 5; + // The chain id of the token. + int32 issue_chain_id = 6; +} + +message CrossChainReceiveTokenInput { + // The source chain id. + int32 from_chain_id = 1; + // The height of the transfer transaction. + int64 parent_chain_height = 2; + // The raw bytes of the transfer transaction. + bytes transfer_transaction_bytes = 3; + // The merkle path created from the transfer transaction. + aelf.MerklePath merkle_path = 4; +} + +message IsInWhiteListInput { + // The symbol of token. + string symbol = 1; + // The address to check. + aelf.Address address = 2; +} + +message SymbolToPayTxSizeFee{ + // The symbol of token. + string token_symbol = 1; + // The charge weight of primary token. + int32 base_token_weight = 2; + // The new added token charge weight. For example, the charge weight of primary Token is set to 1. + // The newly added token charge weight is set to 10. If the transaction requires 1 unit of primary token, + // the user can also pay for 10 newly added tokens. + int32 added_token_weight = 3; +} + +message SymbolListToPayTxSizeFee{ + // Transaction fee token information. + repeated SymbolToPayTxSizeFee symbols_to_pay_tx_size_fee = 1; +} + +message ChargeTransactionFeesInput { + // The method name of transaction. + string method_name = 1; + // The contract address of transaction. + aelf.Address contract_address = 2; + // The amount of transaction size fee. + int64 transaction_size_fee = 3; + // Transaction fee token information. + repeated SymbolToPayTxSizeFee symbols_to_pay_tx_size_fee = 4; +} + +message ChargeTransactionFeesOutput { + // Whether the charge was successful. + bool success = 1; + // The charging information. + string charging_information = 2; +} + +message CallbackInfo { + aelf.Address contract_address = 1; + string method_name = 2; +} + +message ExtraTokenListModified { + option (aelf.is_event) = true; + // Transaction fee token information. + SymbolListToPayTxSizeFee symbol_list_to_pay_tx_size_fee = 1; +} + +message GetLockedAmountInput { + // The address of the lock. + aelf.Address address = 1; + // The token symbol. + string symbol = 2; + // The id of the lock. + aelf.Hash lock_id = 3; +} + +message GetLockedAmountOutput { + // The address of the lock. + aelf.Address address = 1; + // The token symbol. + string symbol = 2; + // The id of the lock. + aelf.Hash lock_id = 3; + // The locked amount. + int64 amount = 4; +} + +message TokenInfoList { + // List of token information. + repeated TokenInfo value = 1; +} + +message GetCrossChainTransferTokenContractAddressInput { + // The chain id. + int32 chainId = 1; +} + +message CrossChainCreateTokenInput { + // The chain id of the chain on which the token was created. + int32 from_chain_id = 1; + // The height of the transaction that created the token. + int64 parent_chain_height = 2; + // The transaction that created the token. + bytes transaction_bytes = 3; + // The merkle path created from the transaction that created the transaction. + aelf.MerklePath merkle_path = 4; +} + +message InitializeFromParentChainInput { + // The amount of resource. + map resource_amount = 1; + // The token contract addresses. + map registered_other_token_contract_addresses = 2; + // The creator the side chain. + aelf.Address creator = 3; +} + +message UpdateCoefficientsInput { + // The specify pieces gonna update. + repeated int32 piece_numbers = 1; + // Coefficients of one single type. + CalculateFeeCoefficients coefficients = 2; +} + +enum FeeTypeEnum { + READ = 0; + STORAGE = 1; + WRITE = 2; + TRAFFIC = 3; + TX = 4; +} + +message CalculateFeePieceCoefficients { + // Coefficients of one single piece. + // The first char is its type: liner / power. + // The second char is its piece upper bound. + repeated int32 value = 1; +} + +message CalculateFeeCoefficients { + // The resource fee type, like READ, WRITE, etc. + int32 fee_token_type = 1; + // Coefficients of one single piece. + repeated CalculateFeePieceCoefficients piece_coefficients_list = 2; +} + +message AllCalculateFeeCoefficients { + // The coefficients of fee Calculation. + repeated CalculateFeeCoefficients value = 1; +} + +message TotalTransactionFeesMap +{ + // Token dictionary that charge transaction fee, Symbol->Amount. + map value = 1; + // The hash of the block processing the transaction. + aelf.Hash block_hash = 2; + // The height of the block processing the transaction. + int64 block_height = 3; +} + +message TotalResourceTokensMaps { + // Resource tokens to charge. + repeated ContractTotalResourceTokens value = 1; + // The hash of the block processing the transaction. + aelf.Hash block_hash = 2; + // The height of the block processing the transaction. + int64 block_height = 3; +} + +message ContractTotalResourceTokens { + // The contract address. + aelf.Address contract_address = 1; + // Resource tokens to charge. + TotalResourceTokensMap tokens_map = 2; +} + +message TotalResourceTokensMap +{ + // Resource token dictionary, Symbol->Amount. + map value = 1; +} + +message StringList { + repeated string value = 1; +} + +message TransactionFeeDelegations{ + // delegation, symbols and its' amount + map delegations = 1; + // height when added + int64 block_height = 2; + //Whether to pay transaction fee continuously + bool isUnlimitedDelegate = 3; +} + +message TransactionFeeDelegatees{ + map delegatees = 1; +} + +message SetTransactionFeeDelegationsInput { + // the delegator address + aelf.Address delegator_address = 1; + // delegation, symbols and its' amount + map delegations = 2; +} + +message SetTransactionFeeDelegationsOutput { + bool success = 1; +} + +message RemoveTransactionFeeDelegatorInput{ + // the delegator address + aelf.Address delegator_address = 1; +} + +message RemoveTransactionFeeDelegateeInput { + // the delegatee address + aelf.Address delegatee_address = 1; +} + +message GetTransactionFeeDelegationsOfADelegateeInput { + aelf.Address delegatee_address = 1; + aelf.Address delegator_address = 2; +} + +message GetTransactionFeeDelegateesInput { + aelf.Address delegator_address = 1; +} + +message GetTransactionFeeDelegateesOutput { + repeated aelf.Address delegatee_addresses = 1; +} + +message SetSymbolAliasInput { + string symbol = 1; + string alias = 2; +} + +// Events + +message Transferred { + option (aelf.is_event) = true; + // The source address of the transferred token. + aelf.Address from = 1 [(aelf.is_indexed) = true]; + // The destination address of the transferred token. + aelf.Address to = 2 [(aelf.is_indexed) = true]; + // The symbol of the transferred token. + string symbol = 3 [(aelf.is_indexed) = true]; + // The amount of the transferred token. + int64 amount = 4; + // The memo. + string memo = 5; +} + +message Approved { + option (aelf.is_event) = true; + // The address of the token owner. + aelf.Address owner = 1 [(aelf.is_indexed) = true]; + // The address that allowance be increased. + aelf.Address spender = 2 [(aelf.is_indexed) = true]; + // The symbol of approved token. + string symbol = 3 [(aelf.is_indexed) = true]; + // The amount of approved token. + int64 amount = 4; +} + +message UnApproved { + option (aelf.is_event) = true; + // The address of the token owner. + aelf.Address owner = 1 [(aelf.is_indexed) = true]; + // The address that allowance be decreased. + aelf.Address spender = 2 [(aelf.is_indexed) = true]; + // The symbol of un-approved token. + string symbol = 3 [(aelf.is_indexed) = true]; + // The amount of un-approved token. + int64 amount = 4; +} + +message Burned +{ + option (aelf.is_event) = true; + // The address who wants to burn token. + aelf.Address burner = 1 [(aelf.is_indexed) = true]; + // The symbol of burned token. + string symbol = 2 [(aelf.is_indexed) = true]; + // The amount of burned token. + int64 amount = 3; +} + +message ChainPrimaryTokenSymbolSet { + option (aelf.is_event) = true; + // The symbol of token. + string token_symbol = 1; +} + +message CalculateFeeAlgorithmUpdated { + option (aelf.is_event) = true; + // All calculate fee coefficients after modification. + AllCalculateFeeCoefficients all_type_fee_coefficients = 1; +} + +message RentalCharged { + option (aelf.is_event) = true; + // The symbol of rental fee charged. + string symbol = 1; + // The amount of rental fee charged. + int64 amount = 2; + // The payer of rental fee. + aelf.Address payer = 3; + // The receiver of rental fee. + aelf.Address receiver = 4; +} + +message RentalAccountBalanceInsufficient { + option (aelf.is_event) = true; + // The symbol of insufficient rental account balance. + string symbol = 1; + // The balance of the account. + int64 amount = 2; +} + +message TokenCreated { + option (aelf.is_event) = true; + // The symbol of the token. + string symbol = 1; + // The full name of the token. + string token_name = 2; + // The total supply of the token. + int64 total_supply = 3; + // The precision of the token. + int32 decimals = 4; + // The address that has permission to issue the token. + aelf.Address issuer = 5; + // A flag indicating if this token is burnable. + bool is_burnable = 6; + // The chain id of the token. + int32 issue_chain_id = 7; + // The external information of the token. + ExternalInfo external_info = 8; + // The address that owns the token. + aelf.Address owner = 9; +} + +message Issued { + option (aelf.is_event) = true; + // The symbol of issued token. + string symbol = 1; + // The amount of issued token. + int64 amount = 2; + // The memo. + string memo = 3; + // The issued target address. + aelf.Address to = 4; +} + +message CrossChainTransferred { + option (aelf.is_event) = true; + // The source address of the transferred token. + aelf.Address from = 1; + // The destination address of the transferred token. + aelf.Address to = 2; + // The symbol of the transferred token. + string symbol = 3; + // The amount of the transferred token. + int64 amount = 4; + // The memo. + string memo = 5; + // The destination chain id. + int32 to_chain_id = 6; + // The chain id of the token. + int32 issue_chain_id = 7; +} + +message CrossChainReceived { + option (aelf.is_event) = true; + // The source address of the transferred token. + aelf.Address from = 1; + // The destination address of the transferred token. + aelf.Address to = 2; + // The symbol of the received token. + string symbol = 3; + // The amount of the received token. + int64 amount = 4; + // The memo. + string memo = 5; + // The destination chain id. + int32 from_chain_id = 6; + // The chain id of the token. + int32 issue_chain_id = 7; + // The parent chain height of the transfer transaction. + int64 parent_chain_height = 8; + // The id of transfer transaction. + aelf.Hash transfer_transaction_id =9; +} + +message TransactionFeeDelegationAdded { + option (aelf.is_event) = true; + aelf.Address delegator = 1 [(aelf.is_indexed) = true]; + aelf.Address delegatee = 2 [(aelf.is_indexed) = true]; + aelf.Address caller = 3 [(aelf.is_indexed) = true]; +} + +message TransactionFeeDelegationCancelled { + option (aelf.is_event) = true; + aelf.Address delegator = 1 [(aelf.is_indexed) = true]; + aelf.Address delegatee = 2 [(aelf.is_indexed) = true]; + aelf.Address caller = 3 [(aelf.is_indexed) = true]; +} + +message SymbolAliasAdded { + option (aelf.is_event) = true; + string symbol = 1 [(aelf.is_indexed) = true]; + string alias = 2 [(aelf.is_indexed) = true]; +} + +message SymbolAliasDeleted { + option (aelf.is_event) = true; + string symbol = 1 [(aelf.is_indexed) = true]; + string alias = 2 [(aelf.is_indexed) = true]; +} +``` + +#### Contract Reference State + +Navigate to `src/` create a **new file** `ContractReferences.cs` +The implementation of file `src/ContractRefefrence.cs` is as follows: + +```csharp +using AElf.Contracts.MultiToken; + +namespace AElf.Contracts.LotteryGame +{ + public partial class LotteryGameState + { + internal TokenContractContainer.TokenContractReferenceState TokenContract { get; set; } + } +} +``` + +#### Implement Lottery Game Smart Contract + +Navigate to `src/LotteryGame.cs` + +```csharp +using AElf.Contracts.MultiToken; +using AElf.Sdk.CSharp; +using AElf.Types; +using Google.Protobuf.WellKnownTypes; + +namespace AElf.Contracts.LotteryGame +{ + // Contract class must inherit the base class generated from the proto file + public class LotteryGame : LotteryGameContainer.LotteryGameBase + { + private const string TokenContractAddress = "ASh2Wt7nSEmYqnGxPPzp4pnVDU4uhj1XW9Se5VeZcX2UDdyjx"; // tDVW token contract address + private const string TokenSymbol = "ELF"; + private const long MinimumPlayAmount = 1_000_000; // 0.01 ELF + private const long MaximumPlayAmount = 1_000_000_000; // 10 ELF + + // Initializes the contract + public override Empty Initialize(Empty input) + { + // Check if the contract is already initialized + Assert(State.Initialized.Value == false, "Already initialized."); + // Set the contract state + State.Initialized.Value = true; + // Set the owner address + State.Owner.Value = Context.Sender; + + // Initialize the token contract + State.TokenContract.Value = Address.FromBase58(TokenContractAddress); + + return new Empty(); + } + + // Plays the lottery game with a specified amount of tokens. + // The method checks if the play amount is within the limit. + // If the player wins, tokens are transferred from the contract to the sender and a PlayOutcomeEvent is fired with the won amount. + // If the player loses, tokens are transferred from the sender to the contract and a PlayOutcomeEvent is fired with the lost amount. + public override Empty Play(Int64Value input) + { + var playAmount = input.Value; + + // Check if input amount is within the limit + Assert(playAmount is >= MinimumPlayAmount and <= MaximumPlayAmount, "Invalid play amount."); + + // Check if the sender has enough tokens + var balance = State.TokenContract.GetBalance.Call(new GetBalanceInput + { + Owner = Context.Sender, + Symbol = TokenSymbol + }).Balance; + Assert(balance >= playAmount, "Insufficient balance."); + + // Check if the contract has enough tokens + var contractBalance = State.TokenContract.GetBalance.Call(new GetBalanceInput + { + Owner = Context.Self, + Symbol = TokenSymbol + }).Balance; + Assert(contractBalance >= playAmount, "Insufficient contract balance."); + + if(IsWinner()) + { + // Transfer the token from the contract to the sender + State.TokenContract.Transfer.Send(new TransferInput + { + To = Context.Sender, + Symbol = TokenSymbol, + Amount = playAmount + }); + + // Emit an event to notify listeners about the outcome + Context.Fire(new PlayOutcomeEvent + { + Amount = input.Value, + Won = playAmount + }); + } + else + { + // Transfer the token from the sender to the contract + State.TokenContract.TransferFrom.Send(new TransferFromInput + { + From = Context.Sender, + To = Context.Self, + Symbol = TokenSymbol, + Amount = playAmount + }); + + // Emit an event to notify listeners about the outcome + Context.Fire(new PlayOutcomeEvent + { + Amount = input.Value, + Won = -playAmount + }); + } + + return new Empty(); + } + + // Withdraws a specified amount of tokens from the contract. + // This method can only be called by the owner of the contract. + // After the tokens are transferred, a WithdrawEvent is fired to notify any listeners about the withdrawal. + public override Empty Withdraw(Int64Value input) + { + AssertIsOwner(); + + // Transfer the token from the contract to the sender + State.TokenContract.Transfer.Send(new TransferInput + { + To = Context.Sender, + Symbol = TokenSymbol, + Amount = input.Value + }); + + // Emit an event to notify listeners about the withdrawal + Context.Fire(new WithdrawEvent + { + Amount = input.Value, + From = Context.Self, + To = State.Owner.Value + }); + + return new Empty(); + } + + // Deposits a specified amount of tokens into the contract. + // This method can only be called by the owner of the contract. + // After the tokens are transferred, a DepositEvent is fired to notify any listeners about the deposit. + public override Empty Deposit(Int64Value input) + { + AssertIsOwner(); + + // Transfer the token from the sender to the contract + State.TokenContract.TransferFrom.Send(new TransferFromInput + { + From = Context.Sender, + To = Context.Self, + Symbol = TokenSymbol, + Amount = input.Value + }); + + // Emit an event to notify listeners about the deposit + Context.Fire(new DepositEvent + { + Amount = input.Value, + From = Context.Sender, + To = Context.Self + }); + + return new Empty(); + } + + // Transfers the ownership of the contract to a new owner. + // This method can only be called by the current owner of the contract. + public override Empty TransferOwnership(Address input) + { + AssertIsOwner(); + + // Set the new owner address + State.Owner.Value = input; + + return new Empty(); + } + + // A method that read the contract's play amount limit + public override PlayAmountLimitMessage GetPlayAmountLimit(Empty input) + { + // Wrap the value in the return type + return new PlayAmountLimitMessage + { + MinimumAmount = MinimumPlayAmount, + MaximumAmount = MaximumPlayAmount + }; + } + + // A method that read the contract's current balance + public override Int64Value GetContractBalance(Empty input) + { + // Get the balance of the contract + var balance = State.TokenContract.GetBalance.Call(new GetBalanceInput + { + Owner = Context.Self, + Symbol = TokenSymbol + }).Balance; + + // Wrap the value in the return type + return new Int64Value + { + Value = balance + }; + } + + // A method that read the contract's owner + public override StringValue GetOwner(Empty input) + { + return State.Owner.Value == null ? new StringValue() : new StringValue {Value = State.Owner.Value.ToBase58()}; + } + + // Determines if the player is a winner. + // This method generates a random number based on the current block height and checks if it's equal to 0. + // If the random number is 0, the player is considered a winner. + private bool IsWinner() + { + var randomNumber = Context.CurrentHeight % 2; + return randomNumber == 0; + } + + // This method is used to ensure that only the owner of the contract can perform certain actions. + // If the context sender is not the owner, an exception is thrown with the message "Unauthorized to perform the action." + private void AssertIsOwner() + { + Assert(Context.Sender == State.Owner.Value, "Unauthorized to perform the action."); + } + } + +} +``` + +#### Building Smart Contract + +Build the new code with the following commands inside `src/` folder: + +```bash +dotnet build +``` + +You should see **LotteryGame.dll.patched** in the directory `lottery_game/src/bin/Debug/net.6.0` + +## 3. Deploying Smart Contract + +### Preparing for deployment + +#### Create A Wallet + +To send transactions on the aelf blockchain, you must have a wallet. + +Run this command to create aelf wallet. + +```bash title="Terminal" +aelf-command create +``` + +![result](/img/create_wallet_output.png) + +#### Acquire Testnet Tokens(Faucet) for Development + +To deploy smart contracts or execute on-chain transactions on aelf, you'll require testnet ELF tokens. + +**Get ELF Tokens** + +import Tabs from '@theme/Tabs'; +import TabItem from '@theme/TabItem'; + + + + +Run the following command to get testnet ELF tokens from faucet. Remember to either export your wallet address and wallet password or replace $WALLET_ADDRESS and $WALLET_ADDRESS with your wallet address and wallet password respectively. + +```bash title="Terminal" +export WALLET_ADDRESS="YOUR_WALLET_ADDRESS" +curl -X POST "https://faucet.aelf.dev/api/claim?walletAddress=$WALLET_ADDRESS" -H "accept: application/json" -d "" +``` + +To check your wallet's current ELF balance: + +```bash title="Terminal" +export WALLET_PASSWORD="YOUR_WALLET_PASSWORD" +aelf-command call ASh2Wt7nSEmYqnGxPPzp4pnVDU4uhj1XW9Se5VeZcX2UDdyjx -a $WALLET_ADDRESS -p $WALLET_PASSWORD -e https://tdvw-test-node.aelf.io GetBalance +``` + +You will be prompted for the following: + +```sh +Enter the required param : ELF +Enter the required param : $WALLET_ADDRESS +``` + +You should see the Result displaying your wallet's ELF balance. + + + + +Go to this url [https://faucet-ui.aelf.dev](https://faucet-ui.aelf.dev). Enter your address and click `Get Tokens`. + +![result](/img/get-token-ui.png) + + + + +### Deploy Your Smart Contract + +The smart contract needs to be deployed on the chain before users can interact with it. + +Run the following command to deploy a contract. Remember to export the path of LotteryGame.dll.patched to CONTRACT_PATH. Remember to export CONTRACT_FILE equals to LotteryGame. + +```bash +export CONTRACT_PATH="SRC_DIRECTORY_PATH" + /bin/Debug/net6.0 +export CONTRACT_FILE=LotteryGame +aelf-deploy -a $WALLET_ADDRESS -p $WALLET_PASSWORD -c $CONTRACT_PATH/$CONTRACT_FILE.dll.patched -e https://tdvw-test-node.aelf.io/ +``` + +Please wait for approximately 1 to 2 minutes. If the deployment is successful, it will provide you with the contract address. + +![result](/img/deploy-result.png) + +## 4. Interact with Your Deployed Smart Contract + +### 4.1 Approving Smart Contract Spending + +```bash +aelf-command send ASh2Wt7nSEmYqnGxPPzp4pnVDU4uhj1XW9Se5VeZcX2UDdyjx -a $WALLET_ADDRESS -p $WALLET_PASSWORD -e https://tdvw-test-node.aelf.io Approve +``` + +`ASh2Wt7nSEmYqnGxPPzp4pnVDU4uhj1XW9Se5VeZcX2UDdyjx` is the contract address of **Multitoken Contract** on aelf Testnet Sidechain (tDVW). +When prompted, enter the following parameters to approve the spending of 90 ELF tokens: + +```terminal +Enter the params one by one, type `Enter` to skip optional param: +? Enter the required param : $CONTRACT_ADDRESS +? Enter the required param : ELF +? Enter the required param : 9000000000 +``` + +### 4.2 Initializing Lottery Game Contract + +```bash +aelf-command send $CONTRACT_ADDRESS -a $WALLET_ADDRESS -p $WALLET_PASSWORD -e https://tdvw-test-node.aelf.io Initialize +``` + +### 4.3 Depositing funds into the Lottery Game Contract + +```bash +aelf-command send $CONTRACT_ADDRESS -a $WALLET_ADDRESS -p $WALLET_PASSWORD -e https://tdvw-test-node.aelf.io Deposit +``` + +### 4.4 Playing the Lottery Game + +```bash +aelf-command send $CONTRACT_ADDRESS -a $WALLET_ADDRESS -p $WALLET_PASSWORD -e https://tdvw-test-node.aelf.io Play +``` + +Let's check the **balance** + +```bash +aelf-command call ASh2Wt7nSEmYqnGxPPzp4pnVDU4uhj1XW9Se5VeZcX2UDdyjx -a $WALLET_ADDRESS -p $WALLET_PASSWORD -e https://tdvw-test-node.aelf.io GetBalance +``` + +You will be prompted for the following: + +```terminal +Enter the required param : ELF +Enter the required param : $WALLET_ADDRESS +``` diff --git a/sidebars.ts b/sidebars.ts index fff69aa..904ae3a 100644 --- a/sidebars.ts +++ b/sidebars.ts @@ -1,4 +1,4 @@ -import type {SidebarsConfig} from '@docusaurus/plugin-content-docs'; +import type { SidebarsConfig } from "@docusaurus/plugin-content-docs"; /** * Creating a sidebar enables you to: @@ -24,6 +24,7 @@ const sidebars: SidebarsConfig = { items: [ { type: "doc", id: "quick-start/intro-to-aelf-development/index" }, { type: "doc", id: "quick-start/hello-world/index" }, + { type: "doc", id: "quick-start/lottery-game/index" }, { type: "category", label: "Become a Node Operator",