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
Install aelf-command Linux and macOs
1sudo npm i -g aelf-command
Window
1npm 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#
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#
1mv Protobuf/contract/hello_world_contract.proto Protobuf/contract/lottery_game_contract.proto
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#
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#
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#
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.
1aelf-command create
1? Save account info into a file? (Y/n) Y
Make sure to choose Y to save your account information.
ℹ️ Note: If you do not save your account information (by selecting n or N), do not export the wallet password. Only proceed to the next step if you have saved your account information.
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
1. Get Testnet ELF Tokens:
To receive testnet ELF tokens, run this command after replacing $WALLET_ADDRESS and $WALLET_PASSWORD with your wallet details:
1export WALLET_ADDRESS="YOUR_WALLET_ADDRESS"2curl -X POST "https://faucet.aelf.dev/api/claim?walletAddress=$WALLET_ADDRESS" -H "accept: application/json" -d ""
2. Check ELF Balance:
To check your ELF balance, use:
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.
Deploy 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.
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: 18 July 2024 03:45:20 GMT+0