Lottery Game Contract
Description: This contract is moderately complex. It demonstrates the use of state variables, user interactions, and random number generation to create a basic lottery game.
Purpose: To introduce you to more advanced concepts such as state management, event handling, and randomization in smart contracts.
Difficulty Level: Moderate
Step 1 - Setting up your development environment#
Install Required Packages
1dotnet new --install AElf.ContractTemplates
AELF.ContractTemplates contains various predefined templates for the ease of developing smart contracts on the aelf blockchain.
1dotnet 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.
Note: If you have installed aelf.deploy and your terminal says that there is no such command available, please uninstall and install aelf.deploy.
Install Node.js and Yarn
1sudo 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.
Step 2 - Develop Smart Contract#
Start Your Smart Contract Project#
Open your Terminal.
Enter the following command to generate a new project:
1mkdir lottery-game2cd lottery-game3dotnet new aelf -n LotteryGame
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.
1cd src
Defining Methods and Messages#
Firstly, rename Protobuf/contract/hello_world_contract.proto to lottery_game_contract.proto:
1mv Protobuf/contract/hello_world_contract.proto Protobuf/contract/lottery_game_contract.proto
Next, open the project with your IDE.
The implementation of file src/Protobuf/contract/lottery_game_contract.proto is as follows:
1syntax = "proto3";23import "aelf/core.proto";4import "aelf/options.proto";5import "google/protobuf/empty.proto";6import "google/protobuf/wrappers.proto";7import "Protobuf/reference/acs12.proto";8// The namespace of this class9option csharp_namespace = "AElf.Contracts.LotteryGame";1011service LotteryGame {12// The name of the state class the smart contract is going to use to access blockchain state13option (aelf.csharp_state) = "AElf.Contracts.LotteryGame.LotteryGameState";14option (aelf.base) = "Protobuf/reference/acs12.proto";1516rpc Initialize (google.protobuf.Empty) returns (google.protobuf.Empty) {17}1819rpc Play (google.protobuf.Int64Value) returns (google.protobuf.Empty) {20}2122rpc Withdraw (google.protobuf.Int64Value) returns (google.protobuf.Empty) {23}2425rpc Deposit (google.protobuf.Int64Value) returns (google.protobuf.Empty) {26}2728rpc TransferOwnership (aelf.Address) returns (google.protobuf.Empty) {29}3031rpc GetPlayAmountLimit (google.protobuf.Empty) returns (PlayAmountLimitMessage) {32option (aelf.is_view) = true;33}3435rpc GetContractBalance (google.protobuf.Empty) returns (google.protobuf.Int64Value) {36option (aelf.is_view) = true;37}3839rpc GetOwner (google.protobuf.Empty) returns (google.protobuf.StringValue) {40option (aelf.is_view) = true;41}42}4344// An event that will be emitted from contract method call when Play is called.45message PlayOutcomeEvent {46option (aelf.is_event) = true;47int64 amount = 1;48int64 won = 2;49}5051// An event that will be emitted from contract method call when Withdraw is called.52message WithdrawEvent {53option (aelf.is_event) = true;54int64 amount = 1;55aelf.Address from = 2;56aelf.Address to = 3;57}5859// An event that will be emitted from contract method call when Deposit is called.60message DepositEvent {61option (aelf.is_event) = true;62int64 amount = 1;63aelf.Address from = 2;64aelf.Address to = 3;65}6667// The message containing the play amount limits68message PlayAmountLimitMessage {69int64 minimumAmount = 1;70int64 maximumAmount = 2;71}
Define Contract States#
The implementation of file src/LotteryGameState.cs is as follows:
1using AElf.Sdk.CSharp.State;2using AElf.Types;34namespace AElf.Contracts.LotteryGame5{6// The state class is access the blockchain state7public partial class LotteryGameState : ContractState8{9// A state to check if contract is initialized10public BoolState Initialized { get; set; }11// A state to store the owner address12public SingletonState<Address> Owner { get; set; }13}14}
Contract Reference State#
Create a new file token_contract.proto under src/Protobuf/reference/ The implementation of file token_contract.proto:
1/**2* MultiToken contract.3*/4syntax = "proto3";56package token;78import "aelf/core.proto";9import "aelf/options.proto";10import "google/protobuf/empty.proto";11import "google/protobuf/wrappers.proto";1213option csharp_namespace = "AElf.Contracts.MultiToken";1415service TokenContract {16// Create a new token.17rpc Create (CreateInput) returns (google.protobuf.Empty) {18}1920// Issuing some amount of tokens to an address is the action of increasing that addresses balance21// for the given token. The total amount of issued tokens must not exceed the total supply of the token22// and only the issuer (creator) of the token can issue tokens.23// Issuing tokens effectively increases the circulating supply.24rpc Issue (IssueInput) returns (google.protobuf.Empty) {25}2627// Transferring tokens simply is the action of transferring a given amount of tokens from one address to another.28// The origin or source address is the signer of the transaction.29// The balance of the sender must be higher than the amount that is transferred.30rpc Transfer (TransferInput) returns (google.protobuf.Empty) {31}3233// The TransferFrom action will transfer a specified amount of tokens from one address to another.34// For this operation to succeed the from address needs to have approved (see allowances) enough tokens35// to Sender of this transaction. If successful the amount will be removed from the allowance.36rpc TransferFrom (TransferFromInput) returns (google.protobuf.Empty) {37}3839// The approve action increases the allowance from the Sender to the Spender address,40// enabling the Spender to call TransferFrom.41rpc Approve (ApproveInput) returns (google.protobuf.Empty) {42}4344rpc BatchApprove (BatchApproveInput) returns (google.protobuf.Empty) {45}4647// This is the reverse operation for Approve, it will decrease the allowance.48rpc UnApprove (UnApproveInput) returns (google.protobuf.Empty) {49}5051// This method can be used to lock tokens.52rpc Lock (LockInput) returns (google.protobuf.Empty) {53}5455// This is the reverse operation of locking, it un-locks some previously locked tokens.56rpc Unlock (UnlockInput) returns (google.protobuf.Empty) {57}5859// This action will burn the specified amount of tokens, removing them from the tokenβs Supply.60rpc Burn (BurnInput) returns (google.protobuf.Empty) {61}6263// Set the primary token of side chain.64rpc SetPrimaryTokenSymbol (SetPrimaryTokenSymbolInput) returns (google.protobuf.Empty) {65}6667// This interface is used for cross-chain transfer.68rpc CrossChainTransfer (CrossChainTransferInput) returns (google.protobuf.Empty) {69}7071// This method is used to receive cross-chain transfers.72rpc CrossChainReceiveToken (CrossChainReceiveTokenInput) returns (google.protobuf.Empty) {73}7475// The side chain creates tokens.76rpc CrossChainCreateToken(CrossChainCreateTokenInput) returns (google.protobuf.Empty) {77}7879// When the side chain is started, the side chain is initialized with the parent chain information.80rpc InitializeFromParentChain (InitializeFromParentChainInput) returns (google.protobuf.Empty) {81}8283// Handle the transaction fees charged by ChargeTransactionFees.84rpc ClaimTransactionFees (TotalTransactionFeesMap) returns (google.protobuf.Empty) {85}8687// Used to collect transaction fees.88rpc ChargeTransactionFees (ChargeTransactionFeesInput) returns (ChargeTransactionFeesOutput) {89}9091rpc ChargeUserContractTransactionFees(ChargeTransactionFeesInput) returns(ChargeTransactionFeesOutput){9293}9495// Check the token threshold.96rpc CheckThreshold (CheckThresholdInput) returns (google.protobuf.Empty) {97}9899// Initialize coefficients of every type of tokens supporting charging fee.100rpc InitialCoefficients (google.protobuf.Empty) returns (google.protobuf.Empty){101}102103// Processing resource token received.104rpc DonateResourceToken (TotalResourceTokensMaps) returns (google.protobuf.Empty) {105}106107// A transaction resource fee is charged to implement the ACS8 standards.108rpc ChargeResourceToken (ChargeResourceTokenInput) returns (google.protobuf.Empty) {109}110111// Verify that the resource token are sufficient.112rpc CheckResourceToken (google.protobuf.Empty) returns (google.protobuf.Empty) {113}114115// Set the list of tokens to pay transaction fees.116rpc SetSymbolsToPayTxSizeFee (SymbolListToPayTxSizeFee) returns (google.protobuf.Empty){117}118119// Update the coefficient of the transaction fee calculation formula.120rpc UpdateCoefficientsForSender (UpdateCoefficientsInput) returns (google.protobuf.Empty) {121}122123// Update the coefficient of the transaction fee calculation formula.124rpc UpdateCoefficientsForContract (UpdateCoefficientsInput) returns (google.protobuf.Empty) {125}126127// This method is used to initialize the governance organization for some functions,128// including: the coefficient of the user transaction fee calculation formula,129// the coefficient of the contract developer resource fee calculation formula, and the side chain rental fee.130rpc InitializeAuthorizedController (google.protobuf.Empty) returns (google.protobuf.Empty){131}132133rpc AddAddressToCreateTokenWhiteList (aelf.Address) returns (google.protobuf.Empty) {134}135rpc RemoveAddressFromCreateTokenWhiteList (aelf.Address) returns (google.protobuf.Empty) {136}137138rpc SetTransactionFeeDelegations (SetTransactionFeeDelegationsInput) returns (SetTransactionFeeDelegationsOutput){139}140141rpc RemoveTransactionFeeDelegator (RemoveTransactionFeeDelegatorInput) returns (google.protobuf.Empty){142}143144rpc RemoveTransactionFeeDelegatee (RemoveTransactionFeeDelegateeInput) returns (google.protobuf.Empty){145}146147rpc SetSymbolAlias (SetSymbolAliasInput) returns (google.protobuf.Empty){148}149150// Get all delegatees' address of delegator from input151rpc GetTransactionFeeDelegatees (GetTransactionFeeDelegateesInput) returns (GetTransactionFeeDelegateesOutput) {152option (aelf.is_view) = true;153}154155// Query token information.156rpc GetTokenInfo (GetTokenInfoInput) returns (TokenInfo) {157option (aelf.is_view) = true;158}159160// Query native token information.161rpc GetNativeTokenInfo (google.protobuf.Empty) returns (TokenInfo) {162option (aelf.is_view) = true;163}164165// Query resource token information.166rpc GetResourceTokenInfo (google.protobuf.Empty) returns (TokenInfoList) {167option (aelf.is_view) = true;168}169170// Query the balance at the specified address.171rpc GetBalance (GetBalanceInput) returns (GetBalanceOutput) {172option (aelf.is_view) = true;173}174175// Query the account's allowance for other addresses176rpc GetAllowance (GetAllowanceInput) returns (GetAllowanceOutput) {177option (aelf.is_view) = true;178}179180// Query the account's available allowance for other addresses181rpc GetAvailableAllowance (GetAllowanceInput) returns (GetAllowanceOutput) {182option (aelf.is_view) = true;183}184185// Check whether the token is in the whitelist of an address,186// which can be called TransferFrom to transfer the token under the condition of not being credited.187rpc IsInWhiteList (IsInWhiteListInput) returns (google.protobuf.BoolValue) {188option (aelf.is_view) = true;189}190191// Query the information for a lock.192rpc GetLockedAmount (GetLockedAmountInput) returns (GetLockedAmountOutput) {193option (aelf.is_view) = true;194}195196// Query the address of receiving token in cross-chain transfer.197rpc GetCrossChainTransferTokenContractAddress (GetCrossChainTransferTokenContractAddressInput) returns (aelf.Address) {198option (aelf.is_view) = true;199}200201// Query the name of the primary Token.202rpc GetPrimaryTokenSymbol (google.protobuf.Empty) returns (google.protobuf.StringValue) {203option (aelf.is_view) = true;204}205206// Query the coefficient of the transaction fee calculation formula.207rpc GetCalculateFeeCoefficientsForContract (google.protobuf.Int32Value) returns (CalculateFeeCoefficients) {208option (aelf.is_view) = true;209}210211// Query the coefficient of the transaction fee calculation formula.212rpc GetCalculateFeeCoefficientsForSender (google.protobuf.Empty) returns (CalculateFeeCoefficients) {213option (aelf.is_view) = true;214}215216// Query tokens that can pay transaction fees.217rpc GetSymbolsToPayTxSizeFee (google.protobuf.Empty) returns (SymbolListToPayTxSizeFee){218option (aelf.is_view) = true;219}220221// Query the hash of the last input of ClaimTransactionFees.222rpc GetLatestTotalTransactionFeesMapHash (google.protobuf.Empty) returns (aelf.Hash){223option (aelf.is_view) = true;224}225226// Query the hash of the last input of DonateResourceToken.227rpc GetLatestTotalResourceTokensMapsHash (google.protobuf.Empty) returns (aelf.Hash){228option (aelf.is_view) = true;229}230rpc IsTokenAvailableForMethodFee (google.protobuf.StringValue) returns (google.protobuf.BoolValue) {231option (aelf.is_view) = true;232}233rpc GetReservedExternalInfoKeyList (google.protobuf.Empty) returns (StringList) {234option (aelf.is_view) = true;235}236237rpc GetTransactionFeeDelegationsOfADelegatee(GetTransactionFeeDelegationsOfADelegateeInput) returns(TransactionFeeDelegations){238option (aelf.is_view) = true;239}240241rpc GetTokenAlias (google.protobuf.StringValue) returns (google.protobuf.StringValue) {242option (aelf.is_view) = true;243}244245rpc GetSymbolByAlias (google.protobuf.StringValue) returns (google.protobuf.StringValue) {246option (aelf.is_view) = true;247}248}249250message TokenInfo {251// The symbol of the token.f252string symbol = 1;253// The full name of the token.254string token_name = 2;255// The current supply of the token.256int64 supply = 3;257// The total supply of the token.258int64 total_supply = 4;259// The precision of the token.260int32 decimals = 5;261// The address that has permission to issue the token.262aelf.Address issuer = 6;263// A flag indicating if this token is burnable.264bool is_burnable = 7;265// The chain id of the token.266int32 issue_chain_id = 8;267// The amount of issued tokens.268int64 issued = 9;269// The external information of the token.270ExternalInfo external_info = 10;271// The address that owns the token.272aelf.Address owner = 11;273}274275message ExternalInfo {276map<string, string> value = 1;277}278279message CreateInput {280// The symbol of the token.281string symbol = 1;282// The full name of the token.283string token_name = 2;284// The total supply of the token.285int64 total_supply = 3;286// The precision of the token287int32 decimals = 4;288// The address that has permission to issue the token.289aelf.Address issuer = 5;290// A flag indicating if this token is burnable.291bool is_burnable = 6;292// A whitelist address list used to lock tokens.293repeated aelf.Address lock_white_list = 7;294// The chain id of the token.295int32 issue_chain_id = 8;296// The external information of the token.297ExternalInfo external_info = 9;298// The address that owns the token.299aelf.Address owner = 10;300}301302message SetPrimaryTokenSymbolInput {303// The symbol of the token.304string symbol = 1;305}306307message IssueInput {308// The token symbol to issue.309string symbol = 1;310// The token amount to issue.311int64 amount = 2;312// The memo.313string memo = 3;314// The target address to issue.315aelf.Address to = 4;316}317318message TransferInput {319// The receiver of the token.320aelf.Address to = 1;321// The token symbol to transfer.322string symbol = 2;323// The amount to to transfer.324int64 amount = 3;325// The memo.326string memo = 4;327}328329message LockInput {330// The one want to lock his token.331aelf.Address address = 1;332// Id of the lock.333aelf.Hash lock_id = 2;334// The symbol of the token to lock.335string symbol = 3;336// a memo.337string usage = 4;338// The amount of tokens to lock.339int64 amount = 5;340}341342message UnlockInput {343// The one want to un-lock his token.344aelf.Address address = 1;345// Id of the lock.346aelf.Hash lock_id = 2;347// The symbol of the token to un-lock.348string symbol = 3;349// a memo.350string usage = 4;351// The amount of tokens to un-lock.352int64 amount = 5;353}354355message TransferFromInput {356// The source address of the token.357aelf.Address from = 1;358// The destination address of the token.359aelf.Address to = 2;360// The symbol of the token to transfer.361string symbol = 3;362// The amount to transfer.363int64 amount = 4;364// The memo.365string memo = 5;366}367368message ApproveInput {369// The address that allowance will be increased.370aelf.Address spender = 1;371// The symbol of token to approve.372string symbol = 2;373// The amount of token to approve.374int64 amount = 3;375}376message BatchApproveInput {377repeated ApproveInput value = 1;378}379380message UnApproveInput {381// The address that allowance will be decreased.382aelf.Address spender = 1;383// The symbol of token to un-approve.384string symbol = 2;385// The amount of token to un-approve.386int64 amount = 3;387}388389message BurnInput {390// The symbol of token to burn.391string symbol = 1;392// The amount of token to burn.393int64 amount = 2;394}395396message ChargeResourceTokenInput {397// Collection of charge resource token, Symbol->Amount.398map<string, int64> cost_dic = 1;399// The sender of the transaction.400aelf.Address caller = 2;401}402403message TransactionFeeBill {404// The transaction fee dictionary, Symbol->fee.405map<string, int64> fees_map = 1;406}407408message TransactionFreeFeeAllowanceBill {409// The transaction free fee allowance dictionary, Symbol->fee.410map<string, int64> free_fee_allowances_map = 1;411}412413message CheckThresholdInput {414// The sender of the transaction.415aelf.Address sender = 1;416// The threshold to set, Symbol->Threshold.417map<string, int64> symbol_to_threshold = 2;418// Whether to check the allowance.419bool is_check_allowance = 3;420}421422message GetTokenInfoInput {423// The symbol of token.424string symbol = 1;425}426427message GetBalanceInput {428// The symbol of token.429string symbol = 1;430// The target address of the query.431aelf.Address owner = 2;432}433434message GetBalanceOutput {435// The symbol of token.436string symbol = 1;437// The target address of the query.438aelf.Address owner = 2;439// The balance of the owner.440int64 balance = 3;441}442443message GetAllowanceInput {444// The symbol of token.445string symbol = 1;446// The address of the token owner.447aelf.Address owner = 2;448// The address of the spender.449aelf.Address spender = 3;450}451452message GetAllowanceOutput {453// The symbol of token.454string symbol = 1;455// The address of the token owner.456aelf.Address owner = 2;457// The address of the spender.458aelf.Address spender = 3;459// The amount of allowance.460int64 allowance = 4;461}462463message CrossChainTransferInput {464// The receiver of transfer.465aelf.Address to = 1;466// The symbol of token.467string symbol = 2;468// The amount of token to transfer.469int64 amount = 3;470// The memo.471string memo = 4;472// The destination chain id.473int32 to_chain_id = 5;474// The chain id of the token.475int32 issue_chain_id = 6;476}477478message CrossChainReceiveTokenInput {479// The source chain id.480int32 from_chain_id = 1;481// The height of the transfer transaction.482int64 parent_chain_height = 2;483// The raw bytes of the transfer transaction.484bytes transfer_transaction_bytes = 3;485// The merkle path created from the transfer transaction.486aelf.MerklePath merkle_path = 4;487}488489message IsInWhiteListInput {490// The symbol of token.491string symbol = 1;492// The address to check.493aelf.Address address = 2;494}495496message SymbolToPayTxSizeFee{497// The symbol of token.498string token_symbol = 1;499// The charge weight of primary token.500int32 base_token_weight = 2;501// The new added token charge weight. For example, the charge weight of primary Token is set to 1.502// The newly added token charge weight is set to 10. If the transaction requires 1 unit of primary token,503// the user can also pay for 10 newly added tokens.504int32 added_token_weight = 3;505}506507message SymbolListToPayTxSizeFee{508// Transaction fee token information.509repeated SymbolToPayTxSizeFee symbols_to_pay_tx_size_fee = 1;510}511512message ChargeTransactionFeesInput {513// The method name of transaction.514string method_name = 1;515// The contract address of transaction.516aelf.Address contract_address = 2;517// The amount of transaction size fee.518int64 transaction_size_fee = 3;519// Transaction fee token information.520repeated SymbolToPayTxSizeFee symbols_to_pay_tx_size_fee = 4;521}522523message ChargeTransactionFeesOutput {524// Whether the charge was successful.525bool success = 1;526// The charging information.527string charging_information = 2;528}529530message CallbackInfo {531aelf.Address contract_address = 1;532string method_name = 2;533}534535message ExtraTokenListModified {536option (aelf.is_event) = true;537// Transaction fee token information.538SymbolListToPayTxSizeFee symbol_list_to_pay_tx_size_fee = 1;539}540541message GetLockedAmountInput {542// The address of the lock.543aelf.Address address = 1;544// The token symbol.545string symbol = 2;546// The id of the lock.547aelf.Hash lock_id = 3;548}549550message GetLockedAmountOutput {551// The address of the lock.552aelf.Address address = 1;553// The token symbol.554string symbol = 2;555// The id of the lock.556aelf.Hash lock_id = 3;557// The locked amount.558int64 amount = 4;559}560561message TokenInfoList {562// List of token information.563repeated TokenInfo value = 1;564}565566message GetCrossChainTransferTokenContractAddressInput {567// The chain id.568int32 chainId = 1;569}570571message CrossChainCreateTokenInput {572// The chain id of the chain on which the token was created.573int32 from_chain_id = 1;574// The height of the transaction that created the token.575int64 parent_chain_height = 2;576// The transaction that created the token.577bytes transaction_bytes = 3;578// The merkle path created from the transaction that created the transaction.579aelf.MerklePath merkle_path = 4;580}581582message InitializeFromParentChainInput {583// The amount of resource.584map<string, int32> resource_amount = 1;585// The token contract addresses.586map<int32, aelf.Address> registered_other_token_contract_addresses = 2;587// The creator the side chain.588aelf.Address creator = 3;589}590591message UpdateCoefficientsInput {592// The specify pieces gonna update.593repeated int32 piece_numbers = 1;594// Coefficients of one single type.595CalculateFeeCoefficients coefficients = 2;596}597598enum FeeTypeEnum {599READ = 0;600STORAGE = 1;601WRITE = 2;602TRAFFIC = 3;603TX = 4;604}605606message CalculateFeePieceCoefficients {607// Coefficients of one single piece.608// The first char is its type: liner / power.609// The second char is its piece upper bound.610repeated int32 value = 1;611}612613message CalculateFeeCoefficients {614// The resource fee type, like READ, WRITE, etc.615int32 fee_token_type = 1;616// Coefficients of one single piece.617repeated CalculateFeePieceCoefficients piece_coefficients_list = 2;618}619620message AllCalculateFeeCoefficients {621// The coefficients of fee Calculation.622repeated CalculateFeeCoefficients value = 1;623}624625message TotalTransactionFeesMap626{627// Token dictionary that charge transaction fee, Symbol->Amount.628map<string, int64> value = 1;629// The hash of the block processing the transaction.630aelf.Hash block_hash = 2;631// The height of the block processing the transaction.632int64 block_height = 3;633}634635message TotalResourceTokensMaps {636// Resource tokens to charge.637repeated ContractTotalResourceTokens value = 1;638// The hash of the block processing the transaction.639aelf.Hash block_hash = 2;640// The height of the block processing the transaction.641int64 block_height = 3;642}643644message ContractTotalResourceTokens {645// The contract address.646aelf.Address contract_address = 1;647// Resource tokens to charge.648TotalResourceTokensMap tokens_map = 2;649}650651message TotalResourceTokensMap652{653// Resource token dictionary, Symbol->Amount.654map<string, int64> value = 1;655}656657message StringList {658repeated string value = 1;659}660661message TransactionFeeDelegations{662// delegation, symbols and its' amount663map<string, int64> delegations = 1;664// height when added665int64 block_height = 2;666//Whether to pay transaction fee continuously667bool isUnlimitedDelegate = 3;668}669670message TransactionFeeDelegatees{671map<string,TransactionFeeDelegations> delegatees = 1;672}673674message SetTransactionFeeDelegationsInput {675// the delegator address676aelf.Address delegator_address = 1;677// delegation, symbols and its' amount678map<string, int64> delegations = 2;679}680681message SetTransactionFeeDelegationsOutput {682bool success = 1;683}684685message RemoveTransactionFeeDelegatorInput{686// the delegator address687aelf.Address delegator_address = 1;688}689690message RemoveTransactionFeeDelegateeInput {691// the delegatee address692aelf.Address delegatee_address = 1;693}694695message GetTransactionFeeDelegationsOfADelegateeInput {696aelf.Address delegatee_address = 1;697aelf.Address delegator_address = 2;698}699700message GetTransactionFeeDelegateesInput {701aelf.Address delegator_address = 1;702}703704message GetTransactionFeeDelegateesOutput {705repeated aelf.Address delegatee_addresses = 1;706}707708message SetSymbolAliasInput {709string symbol = 1;710string alias = 2;711}712713// Events714715message Transferred {716option (aelf.is_event) = true;717// The source address of the transferred token.718aelf.Address from = 1 [(aelf.is_indexed) = true];719// The destination address of the transferred token.720aelf.Address to = 2 [(aelf.is_indexed) = true];721// The symbol of the transferred token.722string symbol = 3 [(aelf.is_indexed) = true];723// The amount of the transferred token.724int64 amount = 4;725// The memo.726string memo = 5;727}728729message Approved {730option (aelf.is_event) = true;731// The address of the token owner.732aelf.Address owner = 1 [(aelf.is_indexed) = true];733// The address that allowance be increased.734aelf.Address spender = 2 [(aelf.is_indexed) = true];735// The symbol of approved token.736string symbol = 3 [(aelf.is_indexed) = true];737// The amount of approved token.738int64 amount = 4;739}740741message UnApproved {742option (aelf.is_event) = true;743// The address of the token owner.744aelf.Address owner = 1 [(aelf.is_indexed) = true];745// The address that allowance be decreased.746aelf.Address spender = 2 [(aelf.is_indexed) = true];747// The symbol of un-approved token.748string symbol = 3 [(aelf.is_indexed) = true];749// The amount of un-approved token.750int64 amount = 4;751}752753message Burned754{755option (aelf.is_event) = true;756// The address who wants to burn token.757aelf.Address burner = 1 [(aelf.is_indexed) = true];758// The symbol of burned token.759string symbol = 2 [(aelf.is_indexed) = true];760// The amount of burned token.761int64 amount = 3;762}763764message ChainPrimaryTokenSymbolSet {765option (aelf.is_event) = true;766// The symbol of token.767string token_symbol = 1;768}769770message CalculateFeeAlgorithmUpdated {771option (aelf.is_event) = true;772// All calculate fee coefficients after modification.773AllCalculateFeeCoefficients all_type_fee_coefficients = 1;774}775776message RentalCharged {777option (aelf.is_event) = true;778// The symbol of rental fee charged.779string symbol = 1;780// The amount of rental fee charged.781int64 amount = 2;782// The payer of rental fee.783aelf.Address payer = 3;784// The receiver of rental fee.785aelf.Address receiver = 4;786}787788message RentalAccountBalanceInsufficient {789option (aelf.is_event) = true;790// The symbol of insufficient rental account balance.791string symbol = 1;792// The balance of the account.793int64 amount = 2;794}795796message TokenCreated {797option (aelf.is_event) = true;798// The symbol of the token.799string symbol = 1;800// The full name of the token.801string token_name = 2;802// The total supply of the token.803int64 total_supply = 3;804// The precision of the token.805int32 decimals = 4;806// The address that has permission to issue the token.807aelf.Address issuer = 5;808// A flag indicating if this token is burnable.809bool is_burnable = 6;810// The chain id of the token.811int32 issue_chain_id = 7;812// The external information of the token.813ExternalInfo external_info = 8;814// The address that owns the token.815aelf.Address owner = 9;816}817818message Issued {819option (aelf.is_event) = true;820// The symbol of issued token.821string symbol = 1;822// The amount of issued token.823int64 amount = 2;824// The memo.825string memo = 3;826// The issued target address.827aelf.Address to = 4;828}829830message CrossChainTransferred {831option (aelf.is_event) = true;832// The source address of the transferred token.833aelf.Address from = 1;834// The destination address of the transferred token.835aelf.Address to = 2;836// The symbol of the transferred token.837string symbol = 3;838// The amount of the transferred token.839int64 amount = 4;840// The memo.841string memo = 5;842// The destination chain id.843int32 to_chain_id = 6;844// The chain id of the token.845int32 issue_chain_id = 7;846}847848message CrossChainReceived {849option (aelf.is_event) = true;850// The source address of the transferred token.851aelf.Address from = 1;852// The destination address of the transferred token.853aelf.Address to = 2;854// The symbol of the received token.855string symbol = 3;856// The amount of the received token.857int64 amount = 4;858// The memo.859string memo = 5;860// The destination chain id.861int32 from_chain_id = 6;862// The chain id of the token.863int32 issue_chain_id = 7;864// The parent chain height of the transfer transaction.865int64 parent_chain_height = 8;866// The id of transfer transaction.867aelf.Hash transfer_transaction_id =9;868}869870message TransactionFeeDelegationAdded {871option (aelf.is_event) = true;872aelf.Address delegator = 1 [(aelf.is_indexed) = true];873aelf.Address delegatee = 2 [(aelf.is_indexed) = true];874aelf.Address caller = 3 [(aelf.is_indexed) = true];875}876877message TransactionFeeDelegationCancelled {878option (aelf.is_event) = true;879aelf.Address delegator = 1 [(aelf.is_indexed) = true];880aelf.Address delegatee = 2 [(aelf.is_indexed) = true];881aelf.Address caller = 3 [(aelf.is_indexed) = true];882}883884message SymbolAliasAdded {885option (aelf.is_event) = true;886string symbol = 1 [(aelf.is_indexed) = true];887string alias = 2 [(aelf.is_indexed) = true];888}889890message SymbolAliasDeleted {891option (aelf.is_event) = true;892string symbol = 1 [(aelf.is_indexed) = true];893string alias = 2 [(aelf.is_indexed) = true];894}
Contract Reference State#
Navigate to src and create a new file ContractReferences.cs The implementation of file src/ContractRefefrence.cs is as follows:
1using AElf.Contracts.MultiToken;23namespace AElf.Contracts.LotteryGame4{5public partial class LotteryGameState6{7internal TokenContractContainer.TokenContractReferenceState TokenContract { get; set; }8}9}
Implement Lottery Game Smart Contract#
Navigate to src/LotteryGame.cs
1using AElf.Contracts.MultiToken;2using AElf.Sdk.CSharp;3using AElf.Types;4using Google.Protobuf.WellKnownTypes;56namespace AElf.Contracts.LotteryGame7{8// Contract class must inherit the base class generated from the proto file9public class LotteryGame : LotteryGameContainer.LotteryGameBase10{11private const string TokenContractAddress = "ASh2Wt7nSEmYqnGxPPzp4pnVDU4uhj1XW9Se5VeZcX2UDdyjx"; // tDVW token contract address12private const string TokenSymbol = "ELF";13private const long MinimumPlayAmount = 1_000_000; // 0.01 ELF14private const long MaximumPlayAmount = 1_000_000_000; // 10 ELF1516// Initializes the contract17public override Empty Initialize(Empty input)18{19// Check if the contract is already initialized20Assert(State.Initialized.Value == false, "Already initialized.");21// Set the contract state22State.Initialized.Value = true;23// Set the owner address24State.Owner.Value = Context.Sender;2526// Initialize the token contract27State.TokenContract.Value = Address.FromBase58(TokenContractAddress);2829return new Empty();30}3132// Plays the lottery game with a specified amount of tokens.33// The method checks if the play amount is within the limit.34// If the player wins, tokens are transferred from the contract to the sender and a PlayOutcomeEvent is fired with the won amount.35// If the player loses, tokens are transferred from the sender to the contract and a PlayOutcomeEvent is fired with the lost amount.36public override Empty Play(Int64Value input)37{38var playAmount = input.Value;3940// Check if input amount is within the limit41Assert(playAmount is >= MinimumPlayAmount and <= MaximumPlayAmount, "Invalid play amount.");4243// Check if the sender has enough tokens44var balance = State.TokenContract.GetBalance.Call(new GetBalanceInput45{46Owner = Context.Sender,47Symbol = TokenSymbol48}).Balance;49Assert(balance >= playAmount, "Insufficient balance.");5051// Check if the contract has enough tokens52var contractBalance = State.TokenContract.GetBalance.Call(new GetBalanceInput53{54Owner = Context.Self,55Symbol = TokenSymbol56}).Balance;57Assert(contractBalance >= playAmount, "Insufficient contract balance.");5859if(IsWinner())60{61// Transfer the token from the contract to the sender62State.TokenContract.Transfer.Send(new TransferInput63{64To = Context.Sender,65Symbol = TokenSymbol,66Amount = playAmount67});6869// Emit an event to notify listeners about the outcome70Context.Fire(new PlayOutcomeEvent71{72Amount = input.Value,73Won = playAmount74});75}76else77{78// Transfer the token from the sender to the contract79State.TokenContract.TransferFrom.Send(new TransferFromInput80{81From = Context.Sender,82To = Context.Self,83Symbol = TokenSymbol,84Amount = playAmount85});8687// Emit an event to notify listeners about the outcome88Context.Fire(new PlayOutcomeEvent89{90Amount = input.Value,91Won = -playAmount92});93}9495return new Empty();96}9798// Withdraws a specified amount of tokens from the contract.99// This method can only be called by the owner of the contract.100// After the tokens are transferred, a WithdrawEvent is fired to notify any listeners about the withdrawal.101public override Empty Withdraw(Int64Value input)102{103AssertIsOwner();104105// Transfer the token from the contract to the sender106State.TokenContract.Transfer.Send(new TransferInput107{108To = Context.Sender,109Symbol = TokenSymbol,110Amount = input.Value111});112113// Emit an event to notify listeners about the withdrawal114Context.Fire(new WithdrawEvent115{116Amount = input.Value,117From = Context.Self,118To = State.Owner.Value119});120121return new Empty();122}123124// Deposits a specified amount of tokens into the contract.125// This method can only be called by the owner of the contract.126// After the tokens are transferred, a DepositEvent is fired to notify any listeners about the deposit.127public override Empty Deposit(Int64Value input)128{129AssertIsOwner();130131// Transfer the token from the sender to the contract132State.TokenContract.TransferFrom.Send(new TransferFromInput133{134From = Context.Sender,135To = Context.Self,136Symbol = TokenSymbol,137Amount = input.Value138});139140// Emit an event to notify listeners about the deposit141Context.Fire(new DepositEvent142{143Amount = input.Value,144From = Context.Sender,145To = Context.Self146});147148return new Empty();149}150151// Transfers the ownership of the contract to a new owner.152// This method can only be called by the current owner of the contract.153public override Empty TransferOwnership(Address input)154{155AssertIsOwner();156157// Set the new owner address158State.Owner.Value = input;159160return new Empty();161}162163// A method that read the contract's play amount limit164public override PlayAmountLimitMessage GetPlayAmountLimit(Empty input)165{166// Wrap the value in the return type167return new PlayAmountLimitMessage168{169MinimumAmount = MinimumPlayAmount,170MaximumAmount = MaximumPlayAmount171};172}173174// A method that read the contract's current balance175public override Int64Value GetContractBalance(Empty input)176{177// Get the balance of the contract178var balance = State.TokenContract.GetBalance.Call(new GetBalanceInput179{180Owner = Context.Self,181Symbol = TokenSymbol182}).Balance;183184// Wrap the value in the return type185return new Int64Value186{187Value = balance188};189}190191// A method that read the contract's owner192public override StringValue GetOwner(Empty input)193{194return State.Owner.Value == null ? new StringValue() : new StringValue {Value = State.Owner.Value.ToBase58()};195}196197// Determines if the player is a winner.198// This method generates a random number based on the current block height and checks if it's equal to 0.199// If the random number is 0, the player is considered a winner.200private bool IsWinner()201{202var randomNumber = Context.CurrentHeight % 2;203return randomNumber == 0;204}205206// This method is used to ensure that only the owner of the contract can perform certain actions.207// If the context sender is not the owner, an exception is thrown with the message "Unauthorized to perform the action."208private void AssertIsOwner()209{210Assert(Context.Sender == State.Owner.Value, "Unauthorized to perform the action.");211}212}213214}
Building Smart Contract#
Build the new code with the following commands inside src folder:
1dotnet build
You should see LotteryGame.dll.patched in the directory lottery_game/src/bin/Debug/net.6.0
Step 3 - Deploy Smart Contract#
Create A Wallet#
To send transactions on the aelf blockchain, you must have a wallet.
Run this command to create aelf wallet.
1aelf-command create
You will be prompted to save your account, please do save your account as shown below:
1? Save account info into a file? (Y/n) Y
Next, enter and confirm your password. Then export your wallet password as shown below:
1export WALLET_PASSWORD="YOUR_WALLET_PASSWORD"
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
Run the following command to get testnet ELF tokens from faucet. Remember to either export your wallet address and wallet password or replace ππ΄πΏπΏπΈππ΄π·π·π πΈπππππWALLETADDRESSandWALLET_ADDRESS with your wallet address and wallet password respectively.
1export WALLET_ADDRESS="YOUR_WALLET_ADDRESS"2curl -X POST "https://faucet.aelf.dev/api/claim?walletAddress=$WALLET_ADDRESS" -H "accept: application/json" -d ""
To check your wallet's current ELF balance:
1aelf-command call ASh2Wt7nSEmYqnGxPPzp4pnVDU4uhj1XW9Se5VeZcX2UDdyjx -a $WALLET_ADDRESS -p $WALLET_PASSWORD -e https://tdvw-test-node.aelf.io GetBalance
You will be prompted for the following:
1Enter the required param <symbol>: ELF2Enter the required param <owner>: $WALLET_ADDRESS
You should see the result displaying your wallet's ELF balance.
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.
1export CONTRACT_PATH=$(find ~+ . -path "*patched*" | head -n 1)
1aelf-deploy -a $WALLET_ADDRESS -p $WALLET_PASSWORD -c $CONTRACT_PATH -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.
Export your smart contract address:
1export CONTRACT_ADDRESS="YOUR_SMART_CONTRACT_ADDRESS e.g. 2LUmicHyH4RXrMjG4beDwuDsiWJESyLkgkwPdGTR8kahRzq5XS"
Step 4 - Interact with Your Deployed Smart Contract#
Approving Smart Contract Spending#
1aelf-command send ASh2Wt7nSEmYqnGxPPzp4pnVDU4uhj1XW9Se5VeZcX2UDdyjx -a $WALLET_ADDRESS -p $WALLET_PASSWORD -e https://tdvw-test-node.aelf.io Approve
Note: 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:
1Enter the params one by one, type `Enter` to skip optional param:2? Enter the required param <spender>: "INSERT_YOUR_CONTRACT_ADDRESS_HERE"3? Enter the required param <symbol>: ELF4? Enter the required param <amount>: 9000000000
Initializing Lottery Game Contract#
1aelf-command send $CONTRACT_ADDRESS -a $WALLET_ADDRESS -p $WALLET_PASSWORD -e https://tdvw-test-node.aelf.io Initialize
Depositing funds into the Lottery Game Contract#
1aelf-command send $CONTRACT_ADDRESS -a $WALLET_ADDRESS -p $WALLET_PASSWORD -e https://tdvw-test-node.aelf.io Deposit
Playing the Lottery Game#
1aelf-command send $CONTRACT_ADDRESS -a $WALLET_ADDRESS -p $WALLET_PASSWORD -e https://tdvw-test-node.aelf.io Play
Let's check the balance
1aelf-command call ASh2Wt7nSEmYqnGxPPzp4pnVDU4uhj1XW9Se5VeZcX2UDdyjx -a $WALLET_ADDRESS -p $WALLET_PASSWORD -e https://tdvw-test-node.aelf.io GetBalance
You will be prompted for the following:
1Enter the required param <symbol>: ELF2Enter the required param <owner>: $WALLET_ADDRESS
Edited on: 12 July 2024 06:02:04 GMT+0