aelf 백서

bitpro가 원문에서 글자만 모은 내용이에요. 표·그림·수식은 빠지거나 깨질 수 있어요. 전체 41쪽 중 32쪽

elf . Hash request_id = 1 ; aelf . Address transmitter = 2 ; } message RequestTypeIndexSet { option ( aelf . is_event ) = true ; int32 request_type_index = 1 ; } message CommitmentDeleted { option ( aelf . is_event ) = true ; aelf . Hash request_id = 1 ; } Oracle Common Message Contract Reference State ​ Create a new file oracle_common_message.proto under src/Protobuf/reference/ . Replace this code of implementation file of oracle_common_message.proto : oracle_common_message.proto // the version of the language, use proto3 for contracts syntax = "proto3" ; // some core imports for AElf chain types import "aelf/core.proto" ; import "aelf/options.proto" ; import "Protobuf/reference/acs12.proto" ; package oracle ; // import for using the google.protobuf.* type. import "google/protobuf/empty.proto" ; import "google/protobuf/wrappers.proto" ; import "google/protobuf/timestamp.proto" ; // the name of the C# namespace in which the contract code will be, message Commitment { aelf . Hash request_id = 1 ; aelf . Address coordinator = 2 ; // coordinator contract address aelf . Address client = 3 ; // consumer contract address int64 subscription_id = 4 ; google . protobuf . Timestamp timeout_timestamp = 5 ; bytes specific_data = 6 ; int32 request_type_index = 7 ; aelf . Hash trace_id = 8 ; } message Report { bytes result = 1 ; bytes error = 2 ; bytes on_chain_metadata = 3 ; // serialized Commitment bytes off_chain_metadata = 4 ; // TODO use in getDonFee() for dynamic billing } VRF Coordinator Contract Reference State ​ Create a new file vrf_coordinator_contract.proto under src/Protobuf/reference/ . Replace this code of implementation file of vrf_coordinator_contract.proto : vrf_coordinator_contract.proto // the version of the language, use proto3 for contracts syntax = "proto3" ; package vrf ; // some core imports for AElf chain types import "aelf/core.proto" ; import "aelf/options.proto" ; import "Protobuf/reference/acs12.proto" ; import "Protobuf/reference/oracle_common_message.proto" ; import "Protobuf/reference/coordinator_contract.proto" ; // import for using the google.protobuf.* type. import "google/protobuf/empty.proto" ; import "google/protobuf/wrappers.proto" ; import "google/protobuf/timestamp.proto" ; // the name of the C# namespace in which the contract code will be, // generated code will also be in this namespace. option csharp_namespace = "AetherLink.Contracts.VRF.Coordinator" ; // the contract definition: a gRPC service definition. service VrfCoordinatorContract { // the full name of the C# class that will contain the state (here <namespace>.<state-class-name> format). option ( aelf . base ) = "Protobuf/reference/acs12.proto" ; option ( aelf . base ) = "Protobuf/reference/coordinator_contract.proto" ; option ( aelf . csharp_state ) = "AetherLink.Contracts.VRF.Coordinator.VrfCoordinatorContractState" ; rpc SetConfig ( Config ) returns ( google . protobuf . Empty ) { } rpc GetConfig ( google . protobuf . Empty ) returns ( Config ) { option ( aelf . is_view ) = true ; } } message Config { int64 request_timeout_seconds = 1 ; int64 minimum_request_confirmations = 2 ; int64 max_request_confirmations = 3 ; int64 max_num_words = 4 ; } message SpecificData { int64 block_number = 1 ; int64 num_words = 2 ; // amount of random values aelf . Hash key_hash = 3 ; // hash of the public key int64 request_confirmations = 4 ; // amount of blocks to wait aelf . Hash pre_seed = 5 ; } // log event message ConfigSet { option ( aelf . is_event ) = true ; Config config = 1 ; } Message Request Interface Contract State ​ Create a new file request_interface.proto under src/Protobuf/message/ . Replace this code of implementation file of request_interface.proto : request_interface.proto // the version of the language, use proto3 for contracts syntax = "proto3" ; // some core imports for AElf chain types import "aelf/core.proto" ; import "aelf/options.proto" ; import "Protobuf/reference/acs12.proto" ; package oracle ; // import for using the google.protobuf.* type. import "google/protobuf/empty.proto" ; import "google/protobuf/wrappers.proto" ; import "google/protobuf/timestamp.proto" ; // the name of the C# namespace in which the contract code will be, // generated code will also be in this namespace. option csharp_namespace = "AetherLink.Contracts.Consumer" ; // the contract definition: a gRPC service definition. service RequestInterface { // the full name of the C# class that will contain the state (here <namespace>.<state-class-name> format). rpc StartOracleRequest ( StartOracleRequestInput ) returns ( google . protobuf . Empty ) ; rpc HandleOracleFulfillment ( HandleOracleFulfillmentInput ) returns ( google . protobuf . Empty ) ; } message StartOracleRequestInput { int64 subscription_id = 1 ; int32 request_type_index = 2 ; bytes specific_data = 3 ; aelf . Hash trace_id = 4 ; } message HandleOracleFulfillmentInput { aelf . Hash request_id = 1 ; bytes response = 2 ; bytes err = 3 ; int32 request_type_index = 4 ; aelf . Hash trace_id = 5 ; } message OracleResponse { bytes response = 1 ; bytes err = 2 ; } Contract Reference State ​ Navigate to src . create a new file ContractReferences.cs . The implementation of file src/ContractReferences.cs is as follows: ContractReferences.cs using AElf . Contracts . MultiToken ; using AetherLink . Contracts . Oracle ; namespace AElf . Contracts . DiceMaster { public partial class DiceMasterState { internal TokenContractContainer . TokenContractReferenceState TokenContract { get ; set ; } internal OracleContractContainer . OracleContractReferenceState OracleContract { get ; set ; } } } Implement dice Game Smart Contract ​ Navigate to src/DiceMaster.cs DiceMaster.cs using AElf . Contracts . MultiToken ; using AElf . Sdk . CSharp ; using AElf . Types ; using AetherLink . Contracts . Consumer ; using AetherLink . Contracts . Oracle ; using AetherLink . Contracts . VRF . Coordinator ; using Google . Protobuf ; using Google . Protobuf . WellKnownTypes ; namespace AElf . Contracts . DiceMaster { // Contract class must inherit the base class generated from the proto file public class DiceMaster : DiceMasterContainer . DiceMasterBase { private const string OracleContractAddress = "21Fh7yog1B741yioZhNAFbs3byJ97jvBmbGAPPZKZpHHog5aEg" ; // tDVW //private const string OracleContractAddress = "BGhrBNTPcLccaxPv6hHJrn4CHHzeMovTsrkhFse5o2nwfvQyG"; // tDVV 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 = Context . GetContractAddressByName ( SmartContractConstants . TokenContractSystemName ) ; State . OracleContract . Value = Address . FromBase58 ( OracleContractAddress ) ; return new Empty ( ) ; } public override Empty HandleOracleFulfillment ( HandleOracleFulfillmentInput input ) { var playedRecord = State . PlayedRecords [ input . TraceId ] ; if ( playedRecord == null || playedRecord . Address == null ) return new Empty ( ) ; var address = playedRecord . Address ; var blockNumber = playedRecord . BlockNumber ; if ( blockNumber != State . PlayerInfos [ address ] . BlockNumber ) { return new Empty ( ) ; } var randomHashList = HashList . Parser . ParseFrom ( input . Response ) ; var dice1 = randomHashList . Data [ 0 ] . ToInt64 ( ) % 6 ; var dice2 = randomHashList . Data [ 1 ] . ToInt64 ( ) % 6 ; dice1 = ( ( dice1 < 0 ) ? dice1 * - 1 : dice1 ) + 1 ; dice2 = ( ( dice2 < 0 ) ? dice2 * - 1 : dice2 ) + 1 ; State . PlayerInfos [ address ] . Dice1 = dice1 ; State . PlayerInfos [ address ] . Dice2 = dice2 ; State . PlayerInfos [ address ] . Pending = false ; var playAmount = State . PlayerInfos [ address ] . Amount ; if ( IsWinner ( dice1 , dice2 ) ) { // Transfer the token from the contract to the sender State . TokenContract . Transfer . Send ( new TransferInput { To = address , Symbol = TokenSymbol , Amount = playAmount * 2 } ) ; State . PlayerInfos [ address ] . Win = true ; // Emit an event to notify listeners about the outcome Context . Fire ( new PlayOutcomeEvent { Amount = playAmount , Won = playAmount , From = address } ) ; } else { State . PlayerInfos [ address ] . Win = false ; // Emit an event to notify listeners about the outcome Context . Fire ( new PlayOutcomeEvent { Amount = playAmount , Won = - playAmount , From = address } ) ; } 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 ( State . PlayerInfos [ Context . Sender ] == null ) { State . PlayerInfos [ Context . Sender ] = new PlayerInfo { Pending = false , Win = false , Dice1 = 1 , Dice2 = 1 , Amount = playAmount , Address = Context . Sender , BlockNumber = Context . CurrentHeight } ; } Assert ( State . PlayerInfos [ Context . Sender ] . Pending == false , "Pending result. Please wait for the result." ) ; // use VRF to get random number var keyHashs = State . OracleContract . GetProvingKeyHashes . Call ( new Empty ( ) ) ; var keyHash = keyHashs . Data [ State . OracleNodeId . Value ] ; var specificData = new SpecificData { KeyHash = keyHash , NumWords = 2 , RequestConfirmations = 1 } . ToByteString ( ) ; var request = new SendRequestInput { SubscriptionId = State . SubscriptionId . Value , RequestTypeIndex = 2 , SpecificData = specificData , } ; var traceId = HashHelper . ConcatAndCompute ( HashHelper . ConcatAndCompute ( HashHelper . ComputeFrom ( Context . CurrentBlockTime ) , HashHelper . ComputeFrom ( Context . Origin ) ) , HashHelper . ComputeFrom ( request ) ) ; request . TraceId = traceId ; State . OracleContract . SendRequest . Send ( request ) ; var blockNumber = Context . CurrentHeight ; State . PlayedRecords [ traceId ] = new PlayedRecord { Address = Context . Sender , BlockNumber = blockNumber , } ; State . PlayerInfos [ Context . Sender ] . Pending = true ; State . PlayerInfos [ Context . Sender ] . Win = false ; State . PlayerInfos [ Context . Sender ] . Amount = playAmount ; State . PlayerInfos [ Context . Sender ] . BlockNumber = blockNumber ; // 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 } ) ; 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 ( ) ; } public override Empty SetSubscriptionId ( Int64Value input ) { AssertIsOwner ( ) ; State . SubscriptionId . Value = input . Value ; return new Empty ( ) ; } public override Empty SetOracleNodeId ( Int32Value input ) { AssertIsOwner ( ) ; State . OracleNodeId . Value = input . 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 ( ) } ; } public override Int64Value GetSubscriptionId ( Empty input ) { return new Int64Value { Value = State . SubscriptionId . Value } ; } public override Int32Value GetOracleNodeId ( Empty input ) { return new Int32Value { Value = State . OracleNodeId . Value } ; } public override PlayerInfo GetPlayerInfo ( Address address ) { Assert ( State . PlayerInfos [ address ] != null , "No player info found." ) ; return State . PlayerInfos [ address ] ; } // Determines if the player is a winner. // The player is considered a winner if he has an odd number. private bool IsWinner ( long dice1 , long dice2 ) { var result = ( dice1 + dice2 ) % 2 ; return result == 1 ; } // 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: Terminal dotnet build You should see DiceMaster.dll.patched in the directory dice-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. Terminal aelf-command create You will be prompted to save your account, please do save your account as shown below: Terminal ? Save account info into a file? ( Y/n ) Y Make sure to choose Y to save your account information. tip ℹ️ 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. Next, enter and confirm your password. Then export your wallet password as shown below: Linux and macOs Windows Terminal export WALLET_PASSWORD = "YOUR_WALLET_PASSWORD" Command Prompt $env :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 Go to https://faucet-ui.aelf.dev Enter your address and click Get Tokens . 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 DiceMaster.dll.patched to CONTRACT_PATH. Linux and macOs Windows Terminal export CONTRACT_PATH = $( find ~+ . -path "*patched*" | head -n 1 ) Terminal aelf-deploy -a $WALLET_ADDRESS -p $WALLET_PASSWORD -c $CONTRACT_PATH -e https://tdvw-test-node.aelf.io/ Command Prompt $CONTRACT_PATH = Get-ChildItem -Recurse -Filter "*patched*" | Select-Object -First 1 -ExpandProperty FullName $env :CONTRACT_PATH = $CONTRACT_PATH Command Prompt aelf-deploy -a $env :WALLET_ADDRESS -p $env :WALLET_PASSWORD -c $env :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. Copy the smart contract address from the address field Export your smart contract address: Linux and macOs Windows Terminal export CONTRACT_ADDRESS = "YOUR_SMART_CONTRACT_ADDRESS e.g. 2LUmicHyH4RXrMjG4beDwuDsiWJESyLkgkwPdGTR8kahRzq5XS" Command Prompt $env :CONTRACT_ADDRESS = "YOUR_SMART_CONTRACT_ADDRESS e.g. 2LUmicHyH4RXrMjG4beDwuDsiWJESyLkgkwPdGTR8kahRzq5XS" tip ℹ️ Note: You are to copy the smart contract address as we will be referencing it in the next steps! info 🎉 You have successfully deployed your dApp smart contract on the aelf testnet! In the next steps, we will be building the frontend components that allow us to interact with our deployed smart contract! Step 4 - Interact with Your Deployed Smart Contract ​ Approving Smart Contract Spending ​ Terminal aelf-command send ASh2Wt7nSEmYqnGxPPzp4pnVDU4uhj1XW9Se5VeZcX2UDdyjx -a $WALLET_ADDRESS -p $WALLET_PASSWORD -e https://tdvw-test-node.aelf.io Approve tip ℹ️ Note: ASh2Wt7nSEmYqnGxPPzp4pnVDU4uhj1XW9Se5VeZcX2UDdyjx is the contract address of Multitoken Contract on aelf Testnet dAppChain (tDVW). When prompted, enter the following parameters to approve the spending of 90 ELF tokens:
프로젝트로 돌아가기