Advanced features of solidity in smart contract writing

Time:2021-2-12

Advanced features of solidity in smart contract writing

preface

FISCO bcos uses solidity language to develop smart contract. Solid is a Turing complete programming language for blockchain platform design. It supports function call, modifier, overload, event, inheritance, library and other high-level languages.

In the first two articles of this series, we introduced the concept of smart contract and the basic features of solidness. This article will introduce some advanced features of solid to help readers get started quickly and write high-quality and reusable solid code.

Types of reasonable control functions and variables

Based on the classical object-oriented programming principle of least knowledge principle, an object should keep a minimum understanding of other objects. Good solid programming practice should also conform to this principle: each contract clearly and reasonably defines the visibility of functions, exposes the least information to the outside, and manages the visibility of internal functions.

At the same time, correctly modifying the types of functions and variables can provide different levels of protection to the internal data of the contract, so as to prevent the unexpected operation in the program from causing data errors; it can also improve the readability and quality of the code, reduce misunderstanding and bugs; it is more conducive to optimize the cost of contract execution, and improve the efficiency of the use of resources on the chain.

Keep the large value of function operationDoors: function visibility

There are two ways to call solid functions:

  • Internal call: also known as “message call”. There are common contract internal function, parent contract function and library function call. (for example, if f function exists in a contract, other functions in a contract call F function in F ().)
  • External call: also known as “EVM call”. Generally, it is a cross contract function call. In the same contract, external calls can also be generated. (for example, if f function exists in contract a, it can be called by using a.f() in contract B. Inside the a contract, you can use this. F() to call.).

Functions can be specified as external, public, internal, or private identifiers.
Advanced features of solidity in smart contract writing
Based on the above table, we can get the visibility of functions: public > external > internal > private.

In addition, if the function does not use the above type identifier, the function type is public by default.

To sum up, we can summarize the different use scenarios of the above identifiers

  • Public, public function, system default. Usually used to modifyA function that can be exposed to the outside world and may be called internally at the same time.
  • External, external function, recommendationExternal exposure onlyUse the function of. When a parameter of a function is very large, if the function is explicitly marked as external, the storage location of the function can be forced to be calldata, which will save the storage or computing resources required for function execution.
  • Internal, internal function, recommended for all contractsNo exposure outside the contractBy using the function of, you can avoid the risk of being attacked due to permission exposure.
  • Private, private function, in a few strictly protected contract functionsIt is not open to the outside of the contract and cannot be inheritedIt can be used in different scenarios.

However, it should be noted that no matter what identifier is used, even private, the whole process and data of function execution are visible to all nodes, and other nodes can verify and replay any historical function. In fact, all data of the whole smart contract are transparent to the participating nodes of the blockchain.

Users who have just come into contact with the blockchain often misunderstand that the privacy of data on the blockchain can be controlled and protected through permission control operation.

This is a wrong view. In fact, without special encryption of blockchain business data, all data in the same account book of the blockchain are sent to all nodes after consensus. The data on the chain is globally open and the same, and the smart contract can only control and protect the execution authority of the contract data.

How to choose function modifiers correctly is a “required course” in contract programming practice. Only by mastering the essence of this section can we freely control the access rights of contract functions and improve the security of contracts.

The least exposed necessary information: the visibility of variables

As with functions, you need to pay attention to visibility modifiers for state variables. The modifier of the state variable is internal by default and cannot be set to external. In addition, when a state variable is modified as public, the compiler generates a function with the same name as the state variable.

For details, please refer to the following examples:
Advanced features of solidity in smart contract writing
This mechanism is a bit like the @ getter annotation provided by Lombok Library in Java language. By default, it generates get functions for a POJO class variable, which greatly simplifies the writing of some contract codes.

Similarly, the visibility of variables also needs to be modified reasonably. Variables that should not be disclosed should be modified with private decisively to make the contract code more in line with the “least known” design principle.

Categorizing functions precisely: types of functions

Functions can be declared as pure and view. The functions of the two are shown in the figure below.
Advanced features of solidity in smart contract writing
So what is read or modify state? In short, the two states are reading or modifying the data related to the ledger.

In FISCO bcos, the read status may be:

  1. Read state variables.

  2. Access any member of block, TX, MSG (except msg.sig And msg.data In addition).

  3. Call any function that is not marked pure.

  4. Use inline assembly that contains some opcodes.

The modification status may be:

  1. Modify the state variable.

  2. An event occurred.

  3. Create other contracts.

  4. Use self destruct.

  5. Call any function that is not marked view or pure.

  6. Use the underlying call.

  7. Use inline assembly with a specific opcode.

It should be noted that in some versions of the compiler, there is no mandatory syntax check for these two keywords.

It is recommended to use pure and view to declare functions as much as possible, for example, to declare library functions that have not read or modified any state as pure, which not only improves the readability of the code, but also makes it more enjoyable. Why not?

Value determined at compile time: state constant

A state constant is a state variable declared as a constant.

Once a state variable is declared as constant, its value can only be determined at compile time and cannot be modified. Generally, the compiler will calculate the actual value of the variable in the compiled state, and will not reserve storage space for the variable. Therefore, constant only supports modifying value types and strings.

State constants are generally used to define business constant values with clear meaning.

Slice oriented programming: function modifier

Solid provides a powerful syntax for changing the behavior of functions: function modifiers. Once a decorator is added to a function, the code defined in the decorator can be executed as decoration of the function, similar to the concept of decorator in other high-level languages.

This is very abstract. Let’s take a concrete example

pragma solidity ^0.4.11;

contract owned {
    function owned() public { owner = msg.sender; }
    address owner;
    
    //Functions modified by modifiers are inserted into special symbols_ The location of the.
    modifier onlyOwner {
        require(msg.sender == owner);
        _;
    }
    
    //Using the onlyowner modifier, you need to execute the "onlyowner" before executing the changeowner function_ The statement before; ".
    function changeOwner(address _owner) public onlyOwner {
        owner = _owner;
    }
}

As shown above, after defining the onlyowner modifier, in the modifier, the require statement requires msg.sender Must be equal to owner. In the back of it is_ ; “represents the code in the decorated function.

Therefore, the actual execution order of the code becomes:

  1. Execute the statement of the onlyowner modifier, and execute the require statement first. (execute line 9)

  2. The statement that executes the changeowner function. (execute line 15)

Since the changeowner function is modified with onlyowner, only if msg.sender Only the owner can successfully call this function, otherwise an error will be reported and the rollback will occur.

At the same time, the modifier can also pass in parameters. For example, the above modifier can also be written as:
Advanced features of solidity in smart contract writing
The same function can have more than one decorator, separated by spaces, and the decorator checks and executes in turn. In addition, modifiers can be inherited and overridden.

Because of its powerful functions, modifiers are often used to control permissions, check input, and log.

For example, we can define a modifier that tracks the execution of a function
Advanced features of solidity in smart contract writing
In this way, any function modified by logmethod modifier can record the log before and after the function is executed, so as to realize the log wrapping effect. If you are used to using AOP of spring framework, you can also try to use modifier to implement a simple AOP function.

The most common way to open a modifier is by providing a function’s validator. In practice, some check statements of contract code are often abstracted and defined as a modifier. For example, onlyowner in the above example is the most classic permission checker. In this way, even the checking logic can be reused quickly, and users no longer have to worry about parameter checking or other checking codes everywhere in the smart contract.

Log that can be debugged: events in contracts

After introducing functions and variables, let’s talk about one of the more unique advanced features of solidness — event mechanism.

Events allow us to easily use EVM’s logging infrastructure, while solidness’s events have the following functions:

  1. The parameters defined by events are recorded and stored in the transaction log of blockchain to provide cheap storage.

  2. A callback mechanism is provided. After the event is successfully executed, the node sends a callback notification to the registered listening SDK to trigger the callback function to be executed.

  3. Provide a filter to support parameter retrieval and filtering.

The use of events is very simple. You can play in two steps.

  • First, use the keyword “event” to define an event. It is suggested that the naming of events should start with a specific prefix or end with a specific suffix, so that it is easier to distinguish them from functions. In this paper, we will use the “log” prefix to name events. Next, we use “event” to define an event tracked by a function call
event LogCallTrace(address indexed from, address indexed to, bool result);

Events can be inherited in the contract. When they are called, the parameters are stored in the transaction log. These logs are stored in the blockchain and associated with the address. In the above example, the parameters are searched with indexed tag. Otherwise, these parameters are stored in the log data and cannot be searched.

  • The second step is to trigger the definition event in the corresponding function. When calling an event, add the keyword “emit” before the event name

Advanced features of solidity in smart contract writing

In this way, when the function body is executed, logcalltrace will be triggered.

Finally, in the Java SDK of FISCO bcos, the contract event push function provides an asynchronous push mechanism for contract events. The client sends a registration request to the node, carries the contract event parameters concerned by the client in the request, and the node filters the event log within the request block according to the request parameters, and pushes the results to the client in batches. For more details, please refer to the contract event push function document. In the SDK, you can search by specific value according to the indexed attribute of the event.

Contract event push function document:

https://fisco-bcos-documentat…_CN/latest/docs/sdk/java_sdk.html#id14

However, logs and events cannot be accessed directly, even in contracts created.

But the good news is that the definition and declaration of logs are very conducive to tracing and exporting after the event.

For example, we can define and embed enough events in the writing of contracts. Through the data export subsystem of webase, we can export all logs to MySQL and other databases. This is especially suitable for generating reconciliation files, generating reports, complex business OLTP queries and other scenarios. In addition, webase provides a special code generation subsystem to help analyze specific business contracts and generate corresponding codes automatically.

The data export subsystem of webase is as follows

https://webasedoc.readthedocs…_CN/latest/docs/WeBASE-Collect-Bee/index.html

Code generation subsystem:

https://webasedoc.readthedocs…_CN/latest/docs/WeBASE-Codegen-Monkey/index.html

In solidness, event is a very useful mechanism. If the biggest difficulty of smart contract development is debugging, making good use of event mechanism can let you quickly subdue solidness development.

Object oriented overloading

Overload is a function with the same name that has many different parameters. For callers, the same function name can be used to call multiple functions with the same function but different parameters. In some scenarios, this operation can make the code clearer and easier to understand. I believe readers who have some programming experience will have a deep understanding of this.

Here’s a typical overloading syntax:

pragma solidity ^0.4.25;

contract Test {
    function f(uint _in) public pure returns (uint out) {
        out = 1;
        
    }
    
    function f(uint _in, bytes32 _key) public pure returns (uint out) {
        out = 2;
    }
}

It should be noted that each contract has only one constructor, which means that the constructor of the contract does not support overloading.

We can imagine a world without overloading. Programmers must rack their brains and try to name functions. We may lose a few more hairs.

Object oriented inheritance

Solid uses’ is’ as the inheritance keyword. Therefore, the following code indicates that contract B inherits contract a:
Advanced features of solidity in smart contract writing
The inherited contract B can access all the non private functions and state variables of the inherited contract a.

In solidness, the underlying implementation principle of inheritance is: when a contract inherits from multiple contracts, only one contract is created on the blockchain, and the code of all base class contracts is copied into the created contract.

Compared with the inheritance mechanism of C + + or Java, the inheritance mechanism of solid is a bit similar to python, which supports multiple inheritance mechanism. Therefore, one contract can be used in solidity to inherit multiple contracts.

In some high-level languages, such as Java, for the sake of security and reliability, only single inheritance is supported, and multiple inheritance is realized by using interface mechanism. For most scenarios, the single inheritance mechanism can meet the requirements.

Multiple inheritance will bring many complex technical problems, such as the so-called “Diamond inheritance”. It is suggested that we should avoid complex multiple inheritance as much as possible in practice.

Inheritance simplifies people’s understanding and description of abstract contract model, clearly reflects the hierarchical relationship between related contracts, and provides software reuse function. In this way, code and data redundancy can be avoided and program reusability can be increased.

Object oriented abstract classes and interfaces

According to the principle of dependency inversion, the smart contract should program to the interface as much as possible, without depending on the specific implementation details.

Solid supports the mechanism of abstract contract and interface.

If a contract has an unimplemented method, it is an abstract contract. For example:
Advanced features of solidity in smart contract writing
Abstract contracts cannot be compiled successfully, but they can be inherited.

Interface uses the keyword interface, and the above abstraction can also be defined as an interface.
Advanced features of solidity in smart contract writing
Interface is similar to abstract contract, but it can’t implement any function. At the same time, there are further restrictions

  1. Cannot inherit other contracts or interfaces.
  2. Cannot define constructor.
  3. Cannot define variable.
  4. Structure cannot be defined
  5. Enumeration cannot be defined.

Appropriate use of interface or abstract contract can enhance the scalability of contract design. However, due to the limitation of computing and storage resources on the block chain EVM, we should avoid over design, which is also a pitfall for the old drivers who switch from high-level language technology stack to solid development.

Avoid duplication: Library

In software development, many classic principles can improve the quality of software, among which the most classic is to reuse high-quality code that has been tested, polished and strictly tested as much as possible. In addition, reusing mature library code can also improve the readability, maintainability and even extensibility of the code.

Like all mainstream languages, solidity also provides a library mechanism. The library of solidity has the following basic characteristics:

  1. Users can use the keyword library to create contracts just like contracts.

  2. Libraries cannot be inherited or inherited.

  3. The internal functions of the library are visible to the caller.

  4. The library is stateless and cannot define state variables, but it can access and modify the state variables explicitly provided by the call contract.

Next, let’s look at a simple example. The following is a libsafemath code base in the FISCO bcos community. We simplify it and only keep the function of addition
Advanced features of solidity in smart contract writing
We just need to import the library file in the contract, and then use L.F () to call the function (for example LibSafeMath.add (a,b))。

Next, we write a test contract to call this library. The contract content is as follows:

Advanced features of solidity in smart contract writing

In the FISCO bcos console, we can test the results of the contract (for the introduction of the console, see the detailed explanation of the FISCO bcos console and the general blockchain experience). The running results are as follows:

Advanced features of solidity in smart contract writing

Through the above examples, we can clearly understand how to use the library in solidity.

Similar to python, in some scenarios, the instruction “using a for B;” can be used to attach library functions (from library a) to any type (b). These functions will receive the object calling them as the first parameter (like Python’s self variable). This function makes the use of the library more simple and intuitive.

For example, we make the following simple changes to the code:

Advanced features of solidity in smart contract writing

Verify that the result is still correct.

Advanced features of solidity in smart contract writing

Better use of solid library helps developers reuse code better. In addition to a large number of open source and high-quality code bases provided by the solidness community, FISCO bcos community also plans to launch a new solidness code base, which is open to community users. Please look forward to it.

Of course, you can also do it yourself, write reusable code base components and share them with the community.

summary

This paper introduces some high-level syntax features of solid contract, aiming to help readers quickly immerse themselves in the world of solid programming.

The key to writing high-quality and reusable solid code is to see more excellent code from the community, practice more coding, summarize more and evolve more. We look forward to more friends sharing solid’s valuable experience and wonderful stories in the community

Next issue notice

Advanced features of solidity in smart contract writing

The code of FISCO bcos is completely open source and free of charge

Download address↓↓↓

https://github.com/FISCO-BCOS…
Advanced features of solidity in smart contract writing