How to write a smart contract for WebAssembly on an Ontology network? Part 2: C ++

image



In this article, we will look at two examples of how to write a smart contract in C ++ using WASM based on the Ontology blockchain network. Today, after several months of stable operation in test mode, Ontology launched WASM on the main network, which allows painlessly and with lower costs transfer dApp contracts with complex business logic to the blockchain, thereby significantly enriching the dApp ecosystem.



Ontology Wasm also supports the creation of smart contracts in the Rust language, you can read about it here .



Below we consider two examples of a smart contract: first we will write β€œHello world!” And then we will create a virtual money envelope that can be sent to a friend as a gift.



Development of a WASM contract using C ++





Example 1. Hello World



Let's start with Hello World:



#include<ontiolib/ontio.hpp> #include<stdio.h> using namespace ontio; class hello:public contract { public: using contract::contract: void sayHello(){ printf("hello world!"); } }; ONTIO_DISPATCH(hello, (sayHello));
      
      





Contract creation



The Ontology Wasm CDT compiler contains an entry point and parsing parameters, so developers do not need to redefine input methods. Further, to write the logic of the service, you can call the API methods of the smart contract.



 ONTIO_DISPATCH(hello, (sayHello));
      
      





In the above example, we only support sayHello so far:



 printf("hello world!");
      
      





β€œHello World!” Will be displayed in the debug node log. When writing a smart contract directly, printf can only be used for debugging, as the smart contract itself contains more functional commands.



Smart contract API



Ontology Wasm provides the following APIs for interacting with the server blockchain:



image



Example 2: Money Envelope



Now let's look at a more complex example using the Wasm smart contract API.



In this example, we will write a virtual money envelope, an analog of the red envelope (hongbao) is a popular feature of the Chinese messenger Wechat, which allows you to send money to friends in a chat. The user receives a message in the form of a red envelope, opens it and the money is automatically credited to the account balance.



As part of a smart contract, users can use this contract to send ONT, ONG, or OEP-4 tokens using virtual money envelopes to their friends, who can then transfer tokens to their blockchain wallets.



Preparing to create a contract



First, create the source contract file and name it redEnvelope.cpp. Next, we will need three APIs for this contract:





 #include<ontiolib/ontio.hpp> using namespace ontio; class redEnvlope: public contract{ } ONTIO_DISPATCH(redEnvlope, (createRedEnvlope)(queryEnvlope)(claimEnvelope));
      
      





Now we need to save the key-value. In a smart contract, the data is stored in the context of the contract as key-value, and we need to add a prefix to the KEY data for a subsequent request.



Below we define three prefixes that we will use:



 std::string rePrefix = "RE_PREFIX_"; std::string sentPrefix = "SENT_COUNT_"; std::string claimPrefix = "CLAIM_PREFIX_";
      
      





Since the contract supports both Ontology tokens - ONT and ONG, we can determine their contract address in advance. Unlike a standard smart contract, the address of Ontology's own contract is fixed and is not derived from the hash of the contract code.



 address ONTAddress = {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1}; address ONGAddress = {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2};
      
      





Next, you need to save information about the used token in the contract: the address of the contract token, the total amount of the envelope and the number of envelopes.



 struct receiveRecord{ address account; //User address asset amount; //Received amount ONTLIB_SERIALIZE(receiveRecord,(account)(amount)) }; struct envlopeStruct{ address tokenAddress; //Token asset address asset totalAmount; //Total amount asset totalPackageCount; //Total number of red envelope asset remainAmount; //Remaining amount asset remainPackageCount; //Remaining number of red envelope std::vector<struct receiveRecord> records; //Received records ONTLIB_SERIALIZE( envlopeStruct, (tokenAddress)(totalAmount)(totalPackageCount)(remainAmount)(remainPackageCount)(records) ) };
      
      





The following is the macro operation defined by the Ontology Wasm CDT, which is used for serialization before data structuring.



 ONTLIB_SERIALIZE(receiveRecord,(account)(amount))
      
      





Creating an Envelope



Now that we have completed the necessary preparations, we will begin the development of the API logic.



1. When creating a money envelope, it is necessary to indicate the address of the owner, the number and amount of envelopes, as well as the address of the token:



 bool createRedEnvlope(address owner,asset packcount, asset amount,address tokenAddr ){ return true; }
      
      





2. Check the owner’s signature, otherwise we will rollback (rollback the transaction) and exit:



 ontio_assert(check_witness(owner),"checkwitness failed");
      
      





Note : ontio_assert (expr, errormsg): false expr returns an error and exit the contract.



3. If an ONT token is used in an envelope, it is important to remember that, ONT is not split (at least 1 ONT). Then the total amount of the money envelope must be greater than or equal to the number of tokens in order to ensure that there is at least 1 ONT in each envelope:



 if (isONTToken(tokenAddr)){ ontio_assert(amount >= packcount,"ont amount should greater than packcount"); }
      
      





4. Next, we determine for the envelope holder the total number of money envelopes that he sends:



 key sentkey = make_key(sentPrefix,owner.tohexstring()); asset sentcount = 0; storage_get(sentkey,sentcount); sentcount += 1; storage_put(sentkey,sentcount);
      
      





5. Generate the hash of the envelope - the identifier that marks this envelope:



 H256 hash ; hash256(make_key(owner,sentcount),hash) ; key rekey = make_key(rePrefix,hash256ToHexstring(hash));
      
      





6. We will translate the tokens into the contract. We find out the address of the contract that is currently being executed using the self_address () command, then we will transfer the assigned amount of tokens to the contract based on the type of tokens:



 address selfaddr = self_address(); if (isONTToken(tokenAddr)){ bool result = ont::transfer(owner,selfaddr ,amount); ontio_assert(result,"transfer native token failed!"); }else if (isONGToken(tokenAddr)){ bool result = ong::transfer(owner,selfaddr ,amount); ontio_assert(result,"transfer native token failed!"); }else{ std::vector<char> params = pack(std::string("transfer"),owner,selfaddr,amount); bool res; call_contract(tokenAddr,params, res ); ontio_assert(res,"transfer oep4 token failed!"); }
      
      





Note 1: for ONT and ONG, Ontology Wasm CDT provides the ont :: transfer API for token transfer; OEP-4 tokens must be sent using the conventional cross-contract call method.



Note 2: like a regular wallet address, the contract address can accept any type of token. However, the address of the contract is generated by a compiled binary hash and thus does not have a corresponding private key and cannot use contract tokens. If you have not set up a private key, then you will not be able to manage these tokens.



7. Save the information about the contract in the data warehouse:



 struct envlopeStruct es ; es.tokenAddress = tokenAddr; es.totalAmount = amount; es.totalPackageCount = packcount; es.remainAmount = amount; es.remainPackageCount = packcount; es.records = {}; storage_put(rekey, es);
      
      





8. Send a notification about the creation of the envelope. This is an asynchronous process for invoking a smart contract, the contract will also send a notification of the result of the execution. The execution format can be determined by the author of the contract.



 char buffer [100]; sprintf(buffer, "{\"states\":[\"%s\", \"%s\", \"%s\"]}","createEnvlope",owner.tohexstring().c_str(),hash256ToHexstring(hash).c_str()); notify(buffer); return true;
      
      





Hooray, the money envelope is almost ready. Now let's see how to request envelope information.



Query Envelope (Query



The logic of the request is quite simple, you only need to get the information and format from the data store, and then output:



 std::string queryEnvlope(std::string hash){ key rekey = make_key(rePrefix,hash); struct envlopeStruct es; storage_get(rekey,es); return formatEnvlope(es); }
      
      





Note: for read-only smart contract operations (e.g. query), you can check the result through pre-exec. Unlike a regular contract call, pre-exec does not require a wallet signature and therefore does not require commission in ONG. Having done this, other users can now pretend to be an envelope if they have an envelope hash (envelope ID).



Receiving Envelope



At this stage, we have already successfully transferred tokens to a smart contract, now, so that your friends can successfully claim a share in the envelope, you need to send them the envelope identifier (hash).



1. To receive an envelope, you must enter the address of your account and the hash of the envelope:



 bool claimEnvlope(address account, std::string hash){ return true; }
      
      





2. Next, the contract will verify the signature of your account to make sure that you own it. Each account can apply for an envelope only once:



 ontio_assert(check_witness(account),"checkwitness failed"); key claimkey = make_key(claimPrefix,hash,account); asset claimed = 0 ; storage_get(claimkey,claimed); ontio_assert(claimed == 0,"you have claimed this envlope!");
      
      





3. Check whether the envelope is received in accordance with the hash information received from the store:



 key rekey = make_key(rePrefix,hash); struct envlopeStruct es; storage_get(rekey,es); ontio_assert(es.remainAmount > 0, "the envlope has been claimed over!"); ontio_assert(es.remainPackageCount > 0, "the envlope has been claimed over!");
      
      





4. Creating a claim record:



 struct receiveRecord record ; record.account = account; asset claimAmount = 0;
      
      





5. Calculation of the amount for each envelope applicant.

For the last participant, the amount of the balance is determined, for any other, the declared amount is determined by a random number calculated by the hash of the current block and current information about the envelope:



 if (es.remainPackageCount == 1){ claimAmount = es.remainAmount; record.amount = claimAmount; }else{ H256 random = current_blockhash() ; char part[8]; memcpy(part,&random,8); uint64_t random_num = *(uint64_t*)part; uint32_t percent = random_num % 100 + 1; claimAmount = es.remainAmount * percent / 100; //ont case if (claimAmount == 0){ claimAmount = 1; }else if(isONTToken(es.tokenAddress)){ if ( (es.remainAmount - claimAmount) < (es.remainPackageCount - 1)){ claimAmount = es.remainAmount - es.remainPackageCount + 1; } } record.amount = claimAmount; } es.remainAmount -= claimAmount; es.remainPackageCount -= 1; es.records.push_back(record);
      
      





6. Crediting funds



The corresponding amount of tokens is transferred to the account of the envelope applicants in accordance with the calculation result:



 address selfaddr = self_address(); if (isONTToken(es.tokenAddress)){ bool result = ont::transfer(selfaddr,account ,claimAmount); ontio_assert(result,"transfer ont token failed!"); } else if (isONGToken(es.tokenAddress)){ bool result = ong::transfer(selfaddr,account ,claimAmount); ontio_assert(result,"transfer ong token failed!"); } else{ std::vector<char> params = pack(std::string("transfer"),selfaddr,account,claimAmount); bool res = false; call_contract(es.tokenAddress,params, res ); ontio_assert(res,"transfer oep4 token failed!"); }
      
      





7. We will write down information about the receipt of funds and updated information about the envelope in the vault and send a notification on the fulfillment of the contract:



 storage_put(claimkey,claimAmount); storage_put(rekey,es); char buffer [100]; std::sprintf(buffer, "{\"states\":[\"%s\",\"%s\",\"%s\",\"%lld\"]}","claimEnvlope",hash.c_str(),account.tohexstring().c_str(),claimAmount); notify(buffer); return true;
      
      





As mentioned above, this contract can send tokens from the contract through the claimEnvelope API. This ensures the security of tokens while they are in the envelope, since no one can withdraw assets without fulfilling the necessary requirements.



Done! You wrote your first smart contract. The full contract code can be found on GitHub here .



Contract testing



There are two ways to verify a contract:



  1. Use CLI
  2. Use Golang SDK


Conclusion



In this article, we talked about how to write a smart contract for Ontolgy Wasm using the blockchain API. It remains to solve the privacy problem so that the smart contract turns into a full-fledged product. At this stage of the code, anyone can get a hash of the red envelope by tracking the records of the contract, which means that anyone can claim a share in the envelope. This problem can be solved simply - we define a list of accounts that can apply for an envelope when it is created. If desired, this function can also be tested.






Get an Ontology grant for dApp development from $ 20,000



Apply for Ontology Student Talent Program






Are you a developer? Join our tech community on Discord . In addition, take a look at the Developer Center on our website, where you can find developer tools, documentation and much more.



Ontology






All Articles