1. 程式人生 > >The Hitchhiker’s Guide to Waves Smart Contracts. Part 2

The Hitchhiker’s Guide to Waves Smart Contracts. Part 2

The Hitchhiker’s Guide to Waves Smart Contracts. Part 2

On September 10 Waves Platform released a new version of node, which has the support of Smart Contracts in their first implementation. In the first part we focused on the idea of Waves Smart Accounts and what makes it different than other existing solutions. In this part we will focus more on smart contracts language and tools for developers. This article is technical, so if you feel problems understanding, please make sure, that you read

Part 1. Special thanks to Waves Research Engineer Nazim Faour for helping with these articles.

The most important part of previous article is:

Basically, the smart account is an account with attached transactions checking script. In other words, a script which is attached to an account so the account can validate every transaction before confirming it.

The Waves contract for Smart Account should always return True or False as follow.

About GAS and fees

Before you go to more technical things. let’s talk about gas and fees. One of the main tasks that we set ourselves is to get rid of gas for simple operations. It does not mean that there are no fees, because miners should also be interested in running contracts. We solved the problem of gas from a practical point of view. We made performance tests and calculate the execution speed of various operations. Benchmarks was build using JMH and results are available

here. As a result of our approach there is no gas for non-Turing Complete Smart Accounts, only fixed fee. Besides, benchmarks led to some constraints:

  1. Script size can’t be more than 8 kB and should be faster than 20 operations of signature verification. The second rule means that verification for the Smart Account will be no more than 20 times slower than for the regular account.
  2. Smart Accounts pay additional fee 0.004 WAVES for each transaction. The minimum fee per transaction in the Waves network is 0.001 WAVES for regular accounts and 0.005 for smart accounts.

RIDE language

A script (contract) should be written using our RIDE language. RIDE is a non-Turing Complete lazy, strong typed, statically typed expression-based language. These features makes it simple, expressive and bug-free.

Scala (Waves node is written in it) along with F# influenced RIDE. In the documentation you can find full description of standard library (built-in functions) and data types.

One of the most interesting features is pattern matching, which makes it very convenient to describe different conditions for different types of transactions:

Allow only Transfer and MassTransfer transactions

If you’re interested in how RIDE works, you can find more details in the white paper. There are descriptions of all stages: parsing, compilation, deserialization, cost computation, evaluation. Now it is good to understand, that first two stages are off-chain, deserialization, cost computation and evaluation are on-chain.

Getting Started with Waves Smart Accounts

Let’s start the most interesting and exciting part — let’s write our first contract for smart account. We will implement very simple example with 2-of-3 multisignature account. Multisignature is the requirement that transactions have two or more signatures before they can be executed.

If you start writing a waves smart contract, you will need to go to our IDE. Click New Empty Contract to create new file.

As first step, let’s define public keys of Alice, Bob and Cooper. They control the account and only two of them can send a transaction.

In the documentation we can find sigVerify function, which allows to check a signature:

sigVerify accepts transaction body, signature and public key as arguments. There is an object tx, which stores transaction data. There is field tx.bodyBytes with bytes of checking transaction. There is also field tx.proofs, which is an array with singatures (up to 8).

As second step, let’s check signatures in right order:

And the last step — check that we have more than 2 signatures:

That’s it! Our 2-of-3 multisig contract on RIDE is ready. You can get compiled version from the IDE or deploy it right in the console. The last version of console commands are in documentation, here is an example of usage:

Waves IDE console usage example

Let’s wrap up with our multisignature example. Whole contract listing:

let alicePubKey  = base58'B1Yz7fH1bJ2gVDjyJnuyKNTdMFARkKEpV'let bobPubKey    = base58'7hghYeWtiekfebgAcuCg9ai2NXbRreNzc'let cooperPubKey = base58'BVqYXrapgJP9atQccdBPAgJPwHDKkh6A8'
let aliceSigned  = if(sigVerify(tx.bodyBytes, tx.proofs[0], alicePubKey  )) then 1 else 0
let bobSigned    = if(sigVerify(tx.bodyBytes, tx.proofs[1], bobPubKey    )) then 1 else 0
let cooperSigned = if(sigVerify(tx.bodyBytes, tx.proofs[2], cooperPubKey )) then 1 else 0
aliceSigned + bobSigned + cooperSigned >= 2

Note: there is no return statement. Last line of contract is a result.

For comparison, one of common multisignatures on Ethereum looks like below:

pragma solidity ^0.4.22;
contract SimpleMultiSig {
uint public nonce;                 // (only) mutable stateuint public threshold;             // immutable statemapping (address => bool) isOwner; // immutable stateaddress[] public ownersArr;        // immutable state
// Note that owners_ must be strictly increasing, in order to prevent duplicates
constructor(uint threshold_, address[] owners_) public {
  require(owners_.length <= 10 && threshold_ <= owners_.length && threshold_ >= 0);
  address lastAdd = address(0);
  for (uint i = 0; i < owners_.length; i++) {    require(owners_[i] > lastAdd);    isOwner[owners_[i]] = true;    lastAdd = owners_[i];  }
  ownersArr = owners_;  threshold = threshold_;}
function execute(uint8[] sigV, bytes32[] sigR, bytes32[] sigS, address destination, uint value, bytes data) public {  require(sigR.length == threshold);  require(sigR.length == sigS.length && sigR.length == sigV.length);  bytes32 txHash = keccak256(byte(0x19), byte(0), this, destination, value, data, nonce);
  address lastAdd = address(0); // cannot have address(0) as an owner
  for (uint i = 0; i < threshold; i++) {    address recovered = ecrecover(txHash, sigV[i], sigR[i], sigS[i]);    require(recovered > lastAdd && isOwner[recovered]);    lastAdd = recovered;}
// If we make it here all signatures are accounted for.// The address.call() syntax is no longer recommended, see:// https://github.com/ethereum/solidity/issues/2884
  nonce = nonce + 1;  bool success = false;  assembly { success := call(gas, destination, value, add(data, 0x20), mload(data), 0, 0) }
  require(success);
}
function () payable public {}
}

The Importance of Data Transactions in Smart Accounts

Data Transactions are designed to store arbitrary data as key-value pairs. They’re also related to an account (address). Each value has a data type associated with it. Four data types are supported: boolean, integer, string, and byte array. It is possible to get data from Data Transactions in your contract.

This small example shows how to read data from Data Transactions and validate external transactions depending on the value. It is very useful for oracles and real-world data applications. There is a huge potential of the feature, but it is a topic for another article.

Further learning

Documentation

  1. The full details about Waves smart accounts can be found here.
  2. RIDE language supports different data types and all of them are described here.
  3. There are many built-in functions such as addressFromPublicKey and sigVerify, you can find the full list of RIDE built-in functions here.

Tutorials

Where to go if you need our help?

Waves community is always ready to help you and answer all your questions as soon as possible, so please do not hesitate to write to us here:

What’s next?

Further development of Waves Smart Contracts is the Turing-complete language (or system), which will allow to solve all types of tasks. Another interesting idea related to smart contracts in the ecosystem of Waves are Smart Assets. They work in a similar way — non-Turing complete contracts that can be attached to the token. For example, with Smart Assets it will be possible to hold tokens up to a certain blockchain height or to prohibit P2P trades. More details about smart assets were in this article.

In the last release, smart assets were not included, but they are already implemented in the Waves code base. If you have ideas or use-cases, write them in the comments, we will be happy to discuss.