Table of contents
· Declare the compiler version
Solidity is a programming language for writing smart contracts. Smart contracts are automated contracts executed on the blockchain platform, which define the rules and conditions between participating parties and ensure that these rules are executed in the contract.
Solidity was originally designed for the Ethereum blockchain platform, but has also been widely adopted by other Ethereum Virtual Machine (EVM) compatible blockchain platforms. It provides a rich set of libraries and functions that enable developers to implement complex logic and business processes in smart contracts.
Smart contracts written in Solidity can implement a variety of functions, including creating and managing digital assets (such as tokens), implementing multi-party signatures, performing voting and elections, creating decentralized applications (DApps), and more.
1.first contract
A contract usually consists of state variables (contract data) and contract functions . We use the simplest Counter counter as the entry contract
· Declare the compiler version
The first thing to do when writing a contract is to declare the compiler version and tell the compiler what version of the compiler to use to compile
pragma solidity >=0.8.0; //Compile the Counter contract with a version greater than or equal to 0.8.0
· Define the contract
Solidity uses contract
to define a contract, which is very similar to classes ( ) in other languages class
. The contract itself is also a data type, called the contract type. In addition, the contract can also define events, custom types, etc.
contract Counter { //Defines a contract named Counter }
·Contract constructor
A constructor is a special function executed when creating a contract, used to initialize the contract, constructor
a constructor declared by the keyword.
If there is no initialization code compiler will add a default constructor constructor() public {}
.
The initialization of the state variable can also be specified at the time of declaration. When not specified, it defaults to 0.
contract Base { uint x; address owner; constructor(uint _x) public { x = _x; owner = msg.sender; } }
· Define variables
Solidity is a statically typed language. When defining each variable, you need to declare the type of the variable. Define variables in the format: 变量类型
变量可见性
变量名
. Variable visibility is optional, and default values will be used when declaration visibility is not shown internal
.
uint public counter; //declare a variable named counter, the type is uint (256-bit unsigned integer)
A variable in a contract is assigned a storage unit on the blockchain. In Ethereum, all variables constitute the state of the entire blockchain network, so variables in contracts are usually called state variables .
But there are two special "variables", they do not allocate storage on the chain:
1) constant
constant
It is used to declare a constant, which does not occupy the storage space of the contract. The corresponding expression value is used to replace the constant name at compile time, that is, the constant
modified state variable can only be assigned to the variable with an expression that has a definite value at compile time.
contract C { uint constant x = 32**22 + 8; string constant text = "abc"; }
2) immutable
Immutable variables are assigned in the constructor, and the constructor is executed at deployment time, so this is a runtime assignment.
It is used in Solidity immutable
to define an immutable variable, which does not occupy the state variable storage space. When deployed, the value of the variable will be appended to the runtime bytecode, so it is much cheaper than using state variables, and it also brings More security (to ensure that this value can no longer be modified), so immutable variables are very useful in many cases, such as saving the creator address, associated contract address, etc.
contract Example { uint immutable decimals; uint immutable maxBalance; constructor(uint _decimals, address _reference) public { decimals = _decimals; maxBalance = _reference.balance; } }
· Define function
The function keyword is used to define a function
function count() public { //The function named count() adds 1 to the counter state variable counter = counter + 1; }
Due to the change of state variables, calling this function will modify the state of the blockchain. At this time, we need to call this function through a transaction . The caller provides Gas for the transaction, and the verifier (miner) collects Gas to package the transaction. After the chain consensus, counter
the variable is really counted as adding 1;
We can also define the parameters and return values of the function as needed and specify whether the function should modify the state. A function definition form can be expressed as follows:
function function name(<parameter type> <parameter name>) <visibility> <state mutability> [returns(<return type>)]{ }
1) Function return value
In Solidity, the return value is treated in the same way as the parameter. The return value in the code result
is also called the output parameter. We can assign it directly in the function body, or directly return
provide the return in the statement. The return value can only specify other parameters. Type, name omitted:
function addAB(uint a, uint b) public returns (uint) { .... return counter + a + b; }
At the same time, Solidity supports functions with multiple return values:
function f() public pure returns (uint, bool, uint) { return (7, true, 2); } function g() public { *// get return value* (uint x, bool b, uint y) = f (); } }
2) View function
A function decorated with view is called a view function. It can only read the state, but cannot modify the state. When calling the view function, only the node currently linked needs to be executed, and the result can be returned
function cal(uint a, uint b) public view returns (uint) { return a * (b + 42) + now; }//cal() function does not modify the state, it does not need to submit transactions, and does not need to spend transaction fees
If the view function is called in a function that modifies the state, the view function will consume Gas, such as:
function set(uint a, uint b) public returns (uint) { return cal(a, b); }
Because the gas price is 0 when the view function is called externally, and in the function of modifying the state, the gas price is set with the transaction
3) Pure functions
A function decorated with pure is called a pure function, it can neither read nor modify the state, it is only used for calculation
function f(uint a, uint b) public pure returns (uint) { return a * (b + 42); }
2.data type
· Value Types
Value type variables represent data that can be represented by 32 bytes, including the following types, which are always copied when assigning or passing parameters.
1) Integers
When a value is to be represented, it is usually expressed with an integer
int/unit
int/uint represent signed and unsigned integers of different digits. The keyword uint8
is supported uint256
, uint
and int
the default is uint256
and int256
.
The number at the end of the keyword is in steps of 8, indicating the size of the space occupied by the variable. The range of integer values is related to the space. For example, the value range uint32
of the type is 0 to 2^32-1
(2 to the 32nd power minus 1), when there is no integer variable When assigning a value, the default value 0 will be used.
The operators supported by integer types include the following:
-
Comparison operators:
<=
(less than or equal to), < (less than),==
(equal to), != (not equal to),>=
(greater than or equal to), > (greater than) -
Bitwise operators:
&
(and), | (or),^
(exclusive or),~
(bit inversion) -
Arithmetic operators:
+
(plus sign),-
(subtraction), - (minus sign),*
,/
, % (remainder),**
(power) -
Shift:
<<
(shift left),>>
(shift right)
2 ) Address type address
In the Solidity contract program, the address type is used to represent our account. In addition, the contract and ordinary addresses can be address
represented by the type. There are two types of addresses:
-
address
: holds a 20-byte value (the size of an Ethereum address). -
address payable
: Indicates the payable address. In terms of the address format, it is actuallyaddress
exactly the same as , which is also 20 bytes. It has two member functionstransfer
andsend
can transfer money to this address. When you need to transfer money to an address, you can use the following code toaddress
convert toaddress payable
:address payable ap = payable(addr);
[Note]: Make this distinction, the displayed expression, an address can accept ETH, which means that it has the logic to process ETH (EOA account itself can transfer ETH); if no distinction is made, when we transfer ETH to an address, It happens that if the latter is a contract address and there is no logic to deal with ETH, then ETH will be locked on the contract address forever, and no one can withdraw and use it.
Some member functions of the address type :
<address>.balance
: Return the balance of the address, the balance is in wei (uint256).
<address payable>.transfer(uint256 amount)
: It is used to send the value of amount
ether (wei) to the address. The transfer function only uses a fixed gas of 2300, and an exception is thrown when the transmission fails.
<address payable>.send(uint256 amount) returns (bool)
: send
Same transfer
as the function, it also uses a fixed gas of 2300, but returns when the sending fails false
without throwing an exception.
eg:
contract testAddr { // If the balance of the contract is greater than or equal to 10, and x is less than 10, transfer 10 wei to x function testTrasfer(address payable x) public { address myAddress = address(this);//convert the contract to an address type if (x.balance < 10 && myAddress.balance >= 10) { //.balance gets balance x.transfer(10); //transfer } } }
3) Contract type
A contract is a type through which we can create a contract (that is, deploy a contract), and then interact with the functions in the contract. For example, calling a function of a contract can create another contract:
pragma solidity ^0.8.0; contract Hello { function sayHi() public view returns (uint) { return 10; } } contract HelloCreator { uint public x; Hello public h; • function createHello() public returns (address) { • h = new Hello(); • return address(h); } }
Contract type data members:
For some contract c there is
(1) type(C).name
: Get the name of the contract.
(2) type(C).creationCode
: Obtain the bytecode for creating the contract.
(3) type(C).runtimeCode
: Obtain the bytecode when the contract is running.
[Question] : How to distinguish contract and external address
A: To distinguish whether an address is a contract address or an external account address, the key is to see if there is any code associated with the address. EVM provides an opcode EXTCODESIZE
to obtain the code size (length) associated with the address. If it is an external account address, no code is returned. Therefore, we can use the following methods to determine the contract address and external account address.
function isContract(address addr) internal view returns (bool) { uint256 size; assembly { size := extcodesize(addr) } return size > 0; }
If it is judged outside the contract, you can use web3.eth.getCode()
(a Web3 API), or the corresponding JSON-RPC method - eth_getcode. getCode() is used to obtain the code of the contract corresponding to the parameter address. If the parameter is an external account address, it will return "0x"; if the parameter is a contract, it will return the corresponding bytecode. The following two lines of code correspond to no code and There is output of the code.
>web3.eth.getCode(“0xa5Acc472597C1e1651270da9081Cc5a0b38258E3”) “0x” >web3.eth.getCode(“0xd5677cf67b5aa051bb40496e68ad359eb97cfbf8”) “0x600160008035811a818181146012578301005b601b6001356025565b8060005260206000f25b600060078202905091905056”
By comparing getCode()
the output content, you can determine which address it is.
· Reference Types
The reference type is used to represent a complex type, which occupies more than 32 bytes. When declaring a variable of the reference type, the location of the variable needs to be specified, and the cost of copying is very high. Therefore, the method of reference can be used to pass multiple different names. Variables point to a value, including arrays and structures .
When defining a reference type, there is an additional property to identify where the data is stored:
memory (memory): The variable exists at runtime, and its life cycle only exists during the function call, and the gas overhead is small.
storage (storage): save state variables, as long as the contract exists, it will always be saved in the blockchain, and the gas cost is the largest.
calldata (call data): a special data location for storing function parameters, which is used to receive external data. It is an unmodifiable, non-persistent function parameter storage area with minimal gas overhead.
[Note]: When assigning values of different reference types, a copy will be made only when assigning values in different data locations, and a reference is usually added in the same data location .
1) array
def:
Arrays, like most languages, add a type after a type []
to indicate that a set of values of that type can be stored. There are two types of arrays: fixed length and dynamic length
// The default location of state variables is storage uint [10] tens; // Fixed-length array uint [] numbers; // Dynamic-length array address [10] admins; // admins has up to 10 addresses // as parameters , use calldata function copy(uint[] calldata arrs) public { numbers = arrs; // When assigning values, variables in different data locations will be copied. } // As parameter, use memory function handle(uint[] memory arrs) internal { } }
The initialization of the array can be carried out at the time of declaration, and can also be declared with the new keyword to create a memory array based on the runtime length. When using new to create a memory array, the corresponding space will be allocated in the memory according to the length; if the variable is stored In , it means to allocate a starting space, which can be expanded in the later running process
Array members:
-
length
: Indicates the length of the current array (read-only). -
push()
: Used to add a new zero-initialized element to the end of the array, and return a reference to the element in order to modify the content of the element, such as:x.push().t = 2
orx.push() = b
, only valid for dynamic arrays in storage. -
push(x): Add the given element to the end of the array. No return value, only valid for dynamic arrays in storage
-
pop()
: Delete elements from the end of the array, the length of the array will be reduced by 1, and delete will be implicitly called on the removed elements to release unused space in time and save gas. pop() has no return value and is only valid for dynamic arrays in storage.
Special array types:
string: A string can also be a character array, but the push&pop method of the array is not supported
bytes: An array of dynamically allocated bytes, similar to byte[], but the gas cost of bytes is lower. bytes can also be used to express strings, but is usually used for raw byte data; supports push&pop
//声明 bytes bs; bytes bs0 = "12abcd"; string str1 = "TinyXiong"; string name;
[Note]: The string s is converted into bytes(s)
a byte, and the subscript access bytes(s)[i]
is not the corresponding character, but the corresponding UTF-8 encoding; string
the function provided by the Solidity language itself is relatively weak, and does not provide some practical functions
Array gas consumption :
function sum() public { uint len = numbers.length; for (uint i = 0; i < len; i++) { total += numbers[i]; }
Analysis of the above sum() function shows that the gas consumption numbers
increases linearly with the elements. If numbers
there are too many elements, sum()
the gas consumption will exceed the block gas limit and cannot be executed. Common solutions:
-
Move non-essential computations off-chain.
-
Find a way to control the length of the array.
-
Find a way to calculate in sections, so that the calculation workload of each section can be controlled.
2) Structure
Solidity uses struct
the keyword to define a custom composite type
At the same time, you need to define its type for each member. In addition to using basic types as members, you can also use arrays , structures, and maps as members:
struct Student { string name; mapping(string=>uint) score; int age; }
Struct declaration assignment
// Declare variables without initializing Person person; // Can only be used as a state variable, assigning Person in the order of members (the order in which the structure is declared) person = Person(address(0x0), false, 18) ; // Declare Person memory in the function person = Person(address(0x0), false, 18) ; // Use named variable initialization, you can not assign Person in the order of member definition person = Person({account: address(0x0), gender: false, age: 18}) ; // Declare Person memory inside the function person = Person({account: address(0x0), gender: false, age: 18}) ;
· Mapping Types
A mapping relationship storage structure of key-value pairs, defined as mapping, similar in function to Java's Map and Python's Dict.