Blockchain Learning (Solidity) [Day07-10 | 5.22-5.25]

Using solidity in Vs Code

 Install plugin

Install solidity plugin

writing contract

When writing solidity contracts, add the MIT license on the first line and choose the appropriate SPDX license identifier.

// SPDX-License-Identifier: MIT
pragma solidity 0.8.19;

// Contract code goes here...

contract Hello{
    function hello() public pure returns(string memory){
        return "hello world";
    }
}

Compile code

F5 compiles the current contract, Cmd+F5 compiles all contracts


solidity learning

Getting Started

pragma

        Specify the version of solidity, which only takes effect on your own source files.

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.18;

contract Hello {
    // 在solidity 中中文字符无法被识别,要使用Unicode编码进行转译
    string public message = unicode"你好";

    function helloWorld() view public returns (string memory) {
        return message; // 返回 message 变量的值
    }
}

Introducing SPDX license

        Solidity ^0.6.8 and above require the introduction of SPDX license, otherwise a warning will appear

        For a detailed analysis of SPDX, you can jump to view: What is Solidity SPDX - Programming Treasure House (codebaoku.com)

一般是在 .sol 文件第一句加上:
// SPDX-License-Identifier: MIT 

或者
// SPDX-License-Identifier: SimPL-3.0

contract/smart contract

        A smart contract is a collection of code and data located at a specific address on the Ethereum blockchain.

        The contract keyword represents a smart contract.

Importing files

//要从当前目录导入文件 x,请使用 import "./x"。如果不指定当前路径,可能会在全局 “include” 目录中引用另一个文件。

// 从 data01.sol 中导入所有全局符号
import "./data01.sol";


// 创建一个新的全局符号 symbolName,它的成员都是来自 data01.sol 的全局符号。
import * as storedData from "./data01.sol";

reserved keywords

        Reserved keywords in Solidity are shown at:

        Solidity basic syntax - Programming treasure house (codebaoku.com)

Code comments

function getResult() public view returns(uint){
   // 这是一行注释,类似于c++中的注释

   /*
    * 这是多行注释
    * 类似于c语言中的注释
    */
   uint a = 1;
   uint b = 2;
   uint result = a + b;
   return result;
}

type of data

  • value type
    • Boolean         bool
    • well-formed        int/uint int8 to int256 uint8 to unit256
    • Fixed-length floating point type        fixed/unfixed fixedMxN ufixedMxN
uint x = 100;
int x = -200;
byte32 b = 0x01020304
// 测量类型的最大值和最小值:

// SPDX-License-Identifier: MIT 
pragma solidity ^0.8.0; 

contract Test { 
    uint public a = type(uint).min;
    uint public b = type(uint).max;
}
  • Address type
    • The address type represents the Ethereum address and is 20 bytes long.
    • Addresses can obtain their balance using the .balance property
    • The balance can also be transferred to another address using the .transfer() method.
address x = 0x212;
address myAddress = this;

if (x.balance < 10 && myAddress.balance >= 10) 
    x.transfer(10);
  • reference type
    • Arrays (strings and bytes are special arrays, so they are also reference types)
    • struct _
    • map _

variable

Variable basic content

  • State variables
    • Variable values ​​are permanently stored in the smart contract storage space.
    • The definition form is similar to the member variables in a class.
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

contract SolidityTest {
   uint storedData;      // 状态变量
   constructor() {
      storedData = 10;   // 使用状态变量
   }
}
  • local variables
    • The variable value is only valid during the execution of the function. After the function exits, the variable is invalid.
    • Function parameters are also local variables.
    • Local variables will not be chained and only exist during the life cycle of the function in which they are located.
pragma solidity ^0.8.0;

contract SolidityTest {
   function sum() public pure returns(uint){
      uint a = 1; // 局部变量
      uint b = 2;
      uint result = a + b;
      return result; // 访问局部变量
   }
}

// 运行上述程序,输出:
0: uint256: 3
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

contract SolidityTest {
   // 获取当前区块号、时间戳、调用者地址
   function getGlobalVars() public view returns(uint,uint,address){
     return (block.number,block.timestamp,msg.sender);
   }
}
  • Variable naming rules
    • Solidity reserved keywords should not be used as variable names. For example: break or boolean variable name is invalid.
    • Should not start with a number (0-9), must start with a letter or underscore.
    • Variable names are case-sensitive. For example: Name and name are two different variables.

Special variables/global variables

name return
blockhash(uint blockNumber) returns (bytes32) The hash value of a given block – only applies to the 256 most recent blocks, not including the current block.
block.coinbase (address payable) The address of the current block miner
block.difficulty (uint) The difficulty of the current block
block.gaslimit (uint) The gaslimit of the current block
block.number (uint) The number of the current block
block.timestamp (uint) The timestamp of the current block, in seconds since the unix epoch
gasleft() returns (uint256) remaining gas
msg.data (bytes calldata) complete calldata
msg.sender (address payable) Message sender (current caller)
msg.sig (bytes4) The first four bytes of calldata (function identifier)
msg.value (uint) The wei value of the current message
now (uint) The timestamp of the current block
tx.gasprice (uint) Transaction gas price
tx.origin (address payable)

Transaction sender

// 示例展示如何使用特殊变量msg,该变量在Solidity中用于获取发送者地址。

pragma solidity ^0.5.0;

contract LedgerBalance {
   mapping(address => uint) public balances;

   function updateBalance(uint newBalance) public {
      balances[msg.sender] = newBalance;
   }
}
contract Updater {
   function updateBalance() public returns (uint) {
      LedgerBalance ledgerBalance = new LedgerBalance();
      ledgerBalance.updateBalance(10);
      return ledgerBalance.balances(address(this));
   }
}

这段代码定义了两个 Solidity 合约:LedgerBalance 和 Updater。

LedgerBalance 合约定义了一个名为 balances 的公共状态变量,它是一个映射,将每个地址映射到一个无符号整数。该合约还定义了一个名为 updateBalance 的公共函数,它接受一个参数 newBalance,将调用者的地址映射到 newBalance。

Updater 合约定义了一个名为 updateBalance 的公共函数,该函数创建新的 LedgerBalance 实例,并调用 updateBalance 函数,将调用者地址映射到值 10。最后,该函数返回 address(this) 在 balances 映射中对应的值,因此相当于查询当前合约在 LedgerBalance 合约中的余额。

需要注意的是,由于 Solidity 适用于以太坊虚拟机(EVM),因此这些合约的执行和状态更改都发生在以太坊区块链上。例如,在 updateBalance 函数中, balances[msg.sender] = newBalance; 将会修改以太坊区块链上的状态,而不仅仅是本地内存变量。

variable default value

  • The default value of bool type variable is false

  • The default value of int type variable is 0

  • The default value of uint type variable is 0

  • The default value of address type variable is: 0x000...., 40 in total 0

  • The default value of bytes32 type variable is: 0x000000000000000000000...., a total of 64 0

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

contract SolidityTest {
   bool public a;    // false
   int public b;     // 0
   uint public c;     // 0
   address public d;     // 0x0000000000000000000000000000000000000000
   bytes32 public e;     // 0x0000000000000000000000000000000000000000000000000000000000000000
}

variable scope

  • public
    • Public state variables can be accessed internally or externally
    • For public state variables, a getter function is automatically generated.
  • private
    • Private state variables can only be accessed from within the current contract and cannot be accessed from derived contracts.
  • internal
    • Internal state variables can only be accessed from within the current contract or its derived contracts.
  • external
    • External state variables can only be called outside the contract and cannot be called by other functions within the contract.
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
contract C{
    uint public data = 30;
    uint internal iData = 20;
    uint private pData = 10;

    function x() public returns (uint){
        data = 3;   // 内部访问
        iData = 2;  // 内部访问
        pData = 1;  // 内部访问
        return data;
    }
}

// 调用外部合约
contract Caller{
    C c = new C();
    function f() public view returns(uint){
    //  return c.data();    // 外部访问
    //  return c.iData();   // error 不允许外部访问
    //  return c.pData();   // error 不允许外部访问
    }
}

// 派生合约
contract D is C{
    uint storedData;    // 状态变量
    function y() public returns (uint){
        data = 3;   // 派生合约内部访问
        iData = 2;  // 派生合约内部访问
    //  pData = 1;  //  error 不允许派生合约内部访问

        return iData;
    }

    function getResult() public view returns(uint){
        return storedData;   // 访问状态变量
    }
    
}


这段 Solidity 代码定义了三个合约 C、Caller 和 D。

C 合约定义了三个状态变量:data(公共)、iData(内部)和 pData(私有)。它还定义了名为 x() 的公共函数,该函数可以从内部访问所有三个状态变量并将 data 的值返回。

Caller 合约是一个外部合约,它创建了一个新的 C 实例,并定义了名为 f() 的公共函数。然而,在该函数中,尝试调用 c.data()、c.iData() 和 c.pData() 会导致编译错误,因为 iData 和 pData 是不能被外部访问的。

D 合约是一个派生合约,它继承了 C 合约所有的状态变量和函数。它定义了两个新的函数:y() 和 getResult()。函数 y() 可以从派生合约内部访问 data 和 iData 状态变量,但不能访问 pData,因为它是私有的。函数 getResult() 是一个查看函数,它可以返回 storedData 状态变量的值,该状态变量在函数中被声明,但没有被使用。

总体来说,此代码演示了 Solidity 中的状态变量的不同可见性级别。同时也展示了如何在内部、外部和派生合约中访问这些状态变量。
  • public 与 private

    • Variables and functions modified by public can be called and accessed by any user or contract.

    • Privately modified variables and functions can only be called and accessed in the contract in which they are located, and even their sub-contracts do not have permission to access them.

  • external and internal

    • In addition to the public and private properties, Solidity uses two other modifiers to describe function visibility: internal and external.

    • internal is similar to private, except that if a contract inherits from its parent contract, the contract can access "internal" functions defined in the parent contract.

    • external is similar to public, but these functions can only be called outside the contract and cannot be called by other functions within the contract.

constant

  • If the value of a state variable is constant, it can be  constant modified and defined as a constant .

    • Constants are often named with capital letters, and words are connected with underscores "_".

    • Not all types support constants, currently only 值类型and 字符串.

    • constant常量Must be assigned via an expression during compilation

    • The compiler does not reserve space constant常量forstorage

  • Constant features

    • Constants, as opposed to variables, need to be hard-coded in the contract and cannot be changed after the contract is deployed.

    • Constants save gas more, and are generally represented in uppercase letters.

operator

arithmetic Compare logic Bit Assignment condition
+(addition)
summation
Example: A + B = 30
== (equal to) && (Logical AND)
The condition is true if both operands are non-zero.
Example: (A && B) is true
& (Bitwise AND)
performs a bitwise AND operation on each bit of its integer argument.
Example: (A & B) is 2.
= (simple assignment)
assigns the value of the right operand to the left operand.
Example: C = A + B means A + B is assigned to C
? : (Conditional operator)
If the condition is true? then value X : otherwise value Y
- (Subtract)
Subtraction
Example: A - B = -10
!= (not equal to) || (Logical OR)
The condition is true if either of the two operands is non-zero.
Example: (A || B) is true
| (bit-OR)
performs a bit-OR operation on each bit of its integer argument.
Example: (A | B) is 3.
+= (additive assignment)
adds the right operand to the left operand and assigns the result to the left operand.
Example: C += A is equivalent to C = C + A
* (Multiplication)
Example
: A * B = 200
> (greater than) ! (logical NOT)
inverts the logical state of the operand. If the condition is true, the logical NOT operation will make it false.
Example: ! (A && B) is false
^ (Bitwise XOR)
performs a bitwise XOR operation on each bit of its integer argument.
Example: (A ^ B) is 1.
−= (subtraction assignment)
subtracts the right operand from the left operand and assigns the result to the left operand.
Example: C -= A is equivalent to C = C – A
/ (division)
Example
: B / A = 2
< (less than) ~ (bit not)
unary operator, inverts all bits in the operand.
Example: (~B) is -4.
*= (multiply assignment)
multiplies the right operand by the left operand and assigns the result to the left operand.
Example: C *= A is equivalent to C = C * A
% (modulo)
modulo operation
Example: B % A = 0
>= (greater than or equal to) << (left shift))
shifts all bits in the first operand to the left by the number of positions specified by the second operand, filling the new bits with zeros. Moving a value one position to the left is equivalent to multiplying by 2, moving it two positions is equivalent to multiplying by 4, and so on.
Example: (A << 1) is 4.
/= (division assignment)
separates the left operand from the right operand and assigns the result to the left operand.
Example: C /= A is equivalent to C = C / A
++ (increment)
increment
Example: A++ = 11
<= (less than or equal to) >> (right shift)
The value of the left operand is moved to the right, and the number of shifting positions is specified by the right operand.
Example: (A >> 1) is 1.
%= (modulo assignment)
takes modulo the two operands and assigns the result to the left operand.
Example: C %= A is equivalent to C = C % A
-- (Decreasing)
Decreasing
Example: A--= 9'

arithmetic operators

+        -        *        /        %        ++        --

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

contract SolidityTest{
    uint storedData;
    constructor(){
        storedData = 10;
    }
    function getResult() public pure returns(string memory){
        uint a = 1;     // 局部变量
        uint b = 2;
        uint result = a + b;
        return integerToString(result);
    }
    function integerToString(uint _i) internal pure returns(string memory _uintAsString){
        if(_i == 0){
            return "0";
        }
        uint j = _i;
        uint len;
        while(j!=0){
            len++;
            j/=10;
        }
        bytes memory bstr = new bytes(len);
        uint k = len - 1;
        while(_i != 0){
            bstr[k--] = bytes1(uint8(48 + _i % 10 ));
            _i /= 10;
        }
        return string(bstr);    // 访问局部变量
    }
}


这段 Solidity 代码定义了一个名为 SolidityTest 的合约。它有一个状态变量 storedData 和一个构造函数,在构造函数中将 storedData 的初始值设置为 10。

合约还定义了两个函数:getResult() 和 integerToString()。getResult() 函数是一个查看函数,它执行两个无符号整数相加并返回结果的字符串表示形式。为了实现这一点,它声明了三个局部变量 a、b 和 result,并使用 integerToString() 函数将结果从无符号整数转换为字符串。需要注意的是,getResult() 函数被声明为 pure,因为它不会修改任何状态变量。

integerToString() 函数接受一个无符号整数 _i,并返回其字符串表示形式。在该函数中声明了两个局部变量 j 和 len,分别用于存储 _i 的副本和 _i 的位数。然后,在 while 循环中,使用整数除法操作和递减运算符来计算 _i 的长度。接下来,该函数创建了一个长度为 len 的 bytes 数组,并从右往左填充该数组的每个位置,以便生成 _i 的字符串表示形式。最后,integerToString() 函数将 bytes 数组转换为字符串并将其返回。

值得注意的是,该函数中的所有变量都是局部变量,因此它不会修改任何状态变量。

comparison operator

==        !=        >        <        >=        <=

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
contract SolidityTest{
    uint storedData;
    constructor(){
        storedData  = 10;
    }

    function getResult() public pure returns(string memory){
        uint a = 1; // 局部变量 
        uint b = 2;
        uint result = a + b;
        return integerToString(result);
    }
    function integerToString(uint _i) internal pure returns(string memory _uintAsString){
        if(_i == 0){
            // 比较运算符
            return "0";
        }
        uint j = _i;
        uint len;
        
        while (j!=0){
            // 比较运算符
            len++;
            j /= 10;
        }
        bytes memory bstr = new bytes(len);
        uint k = len - 1;

        while(_i != 0){
            bstr[k--] = bytes1(uint8(48 + _i % 10));
            _i /= 10;
        }
        return string(bstr);    // 访问局部变量
    }
}

Logical Operators

&&        ||        !

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
contract SolidityTest{
    uint storedData;
    constructor(){
        storedData = 10;
    }
    function getResult() public pure returns(string memory){
        uint a = 2; // 局部变量
        uint b = 2;
        uint result = a & b;
        return integerToString(result);
    }
    function integerToString(uint _i)internal pure returns(string memory){
        if(_i == 0){
            return "0";
        }
        uint j = _i;
        uint len = 0;
        while(j!=0){
            len++;
            j/=10;
        }
        bytes memory bstr = new bytes(len);
        uint k = len - 1;

        while(_i != 0){
            bstr[k--] = bytes1(uint8(48 + _i % 10));
            _i/=10;
        }
        return string(bstr);    // 访问局部变量

    }
}

Bit operators

&        |        ^        ~        <<        >>

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
contract SolidityTest{
    uint storedData;
    constructor(){
        storedData = 10;
    }
    function getResult() public pure returns(string memory){
        uint a = 2;     // 局部变量 
        uint b = 2;
        uint result = a & b;    // 位与
        return integerToString(result);
    }
    function integerToString(uint _i)internal pure returns (string memory){
        if(_i == 0){
            return "0";
        }
        uint j = _i;
        uint len;
        while ( j!=0){
            len++;
            j/=10;
        }
        bytes memory bstr = new bytes(len);
        uint k = len - 1;
        while(_i != 0){
            bstr[k--] = bytes1(uint8(48+_i%10));
            _i /= 10;
        }
        return string(bstr);    // 访问局部变量
    }
}

storedData 是一个无符号整数类型的状态变量,用于存储数据。默认初始值为 10。
constructor 是合约的构造函数,用于在创建合约时初始化 storedData 值为 10。
getResult() 函数是一个公共函数,其返回值类型为 string memory。它定义了两个无符号整数类型的局部变量 a 和 b,并使用位运算符 & 来计算它们的与结果。然后,它将此结果转换为字符串并返回。
integerToString() 函数是一个内部函数,其输入参数 _i 是一个无符号整数类型。它将整数 _i 转换为字符串类型并返回。此函数首先检查传入的整数是否为 0。如果是,则直接返回字符串“0”。否则,它使用循环来计算此整数的位数,并创建一个长度为 len 的字节数组来存储相应的字符。最后它通过将字节数组转换为字符串来返回结果。

assignment operator

=        +=        -=        *=        /=        %=

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
contract SolidityTest {
   uint storedData; 
   constructor() {
      storedData = 10;   
   }
   function getResult() public pure returns(string memory){
      uint a = 1; 
      uint b = 2;
      uint result = a + b;
      return integerToString(result); 
   }
   function integerToString(uint _i) internal pure 
      returns (string memory) {
      if (_i == 0) {
         return "0";
      }
      uint j = _i;
      uint len;
      while (j != 0) {
         len++;
         j /= 10; // 赋值运算
      }
      bytes memory bstr = new bytes(len);
      uint k = len - 1;
      while (_i != 0) {
         bstr[k--] = bytes1(uint8(48 + _i % 10));
         _i /= 10;// 赋值运算
      }
      return string(bstr);  // 访问局部变量
   }
}

storedData 是一个无符号整数类型的状态变量,用于存储数据。默认初始值为 10。
constructor 是合约的构造函数,用于在创建合约时初始化 storedData 值为 10。
getResult() 函数是一个公共函数,其返回值类型为 string memory。它定义了两个无符号整数类型的局部变量 a 和 b,并使用加法运算符 + 来计算它们的和。然后,它将此结果转换为字符串并返回。
integerToString() 函数是一个内部函数,其输入参数 _i 是一个无符号整数类型。它将整数 _i 转换为字符串类型并返回。此函数首先检查传入的整数是否为 0。如果是,则直接返回字符串“0”。否则,它使用循环来计算此整数的位数,并创建一个长度为 len 的字节数组来存储相应的字符。最后它通过将字节数组转换为字符串来返回结果。

conditional operator

?

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
contract SolidityTest{
    uint storedData;
    constructor(){
        storedData =  10;
    }
    function getResult() public pure returns(string memory){
        uint a = 1;
        uint b = 2;
        uint result = (a>b?a:b);
        return integerToString(result); 
    }
    function integerToString(uint _i)internal pure returns(string memory){
        if(_i == 0){
            return "0";
        }
        uint j = _i;
        uint len;
        while(j!=0){
            len++;
            j/=10;
        }
        bytes memory bstr = new bytes(len);
        uint k = len - 1;
        while(_i != 0){
            bstr[k--] = byte(uint8(48 + _i % 10));
            _i /= 10;
        }
        return string(bstr);
    }
}

storedData 是一个无符号整数类型的状态变量,用于存储数据。默认初始值为 10。
constructor 是合约的构造函数,用于在创建合约时初始化 storedData 值为 10。
getResult() 函数是一个公共函数,其返回值类型为 string memory。它定义了两个无符号整数类型的局部变量 a 和 b,并使用三元运算符 ? : 来计算它们的最大值。然后,它将此结果转换为字符串并返回。
integerToString() 函数是一个内部函数,其输入参数 _i 是一个无符号整数类型。它将整数 _i 转换为字符串类型并返回。此函数首先检查传入的整数是否为 0。如果是,则直接返回字符串“0”。否则,它使用循环来计算此整数的位数,并创建一个长度为 len 的字节数组来存储相应的字符。最后它通过将字节数组转换为字符串来返回结果。

Conditional statements

  • if 
if (条件表达式) {
   被执行语句(如果条件为真)
}
  • if...else
if (条件表达式) {
   被执行语句(如果条件为真)
} else {
   被执行语句(如果条件为假)
}
  • if...else if
if (条件表达式 1) {
   被执行语句(如果条件 1 为真)
} else if (条件表达式 2) {
   被执行语句(如果条件 2 为真)
} else if (条件表达式 3) {
   被执行语句(如果条件 3 为真)
} else {
   被执行语句(如果所有条件为假)
}

cycle

  • while
while (表达式) {
   // 如果表达式的结果为真,就循环执行以下语句
   ......
}
  • do ... while
do {
   // 如果表达式的结果为真,就循环执行以下语句
   ......
} while (表达式);
  • for
for (初始化; 测试条件; 迭代语句) {
   // 如果表达式的结果为真,就循环执行以下语句
   ......
}
  • Loop control statements: break, continue
    • continue – jump out of this loop and continue executing the next loop
    • break – break out of the loop and no longer execute the current loop

string

  • 字符串值使用双引号(")和单引号(')包括,字符串类型用 string 表示。
  • 字符串是特殊的数组,是引用类型。
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

contract SolidityTest{
    string public data1 = "test1";
    bytes public data2 = "test2";
}


/*在上面的例子中,"test" 是一个字符串,data 是一个字符串变量。

Solidity 提供字节与字符串之间的内置转换,可以将字符串赋给 bytes 类型变量。*/

转义字符

序号 转义字符 序号 转义字符
1 \n
开始新的一行
7 \r
回车
2 \\
反斜杠
8 \t
制表符
3 \'
单引号
9 \v
垂直制表符
4 \"
双引号
10 \xNN
表示十六进制值并插入适当的字节
5 \b
退格
11 \uNNNN
表示Unicode值并插入UTF-8序列
6 \f
换页

bytes 到字符串的转换

  • 可以使用 string() 构造函数将 bytes 转换为字符串
bytes memory bstr = new bytes(10);
string message = string(bstr);   
  • bytes 到字符串的转换
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

contract SolidityTest {
   string public data;
   bytes  bstr = new bytes(2);
   function trans() external{
      bstr[0] = 'a';
      bstr[1] = 'b';
      data = string(bstr);
   }
}

字符串到 bytes 的转换

  • 可以使用bytes()构造函数将字符串转换为bytes
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

contract SolidityTest {   
   constructor(){       
   }
   
   function getResult(string memory s) public pure returns(bytes memory){
      return bytes(s); // 字符串转换到bytes
   }
}

数组

声明数组

  • 固定长度数组
    • 声明一个固定长度的数组,需要指定元素类型和数量
    • arraySize 必须是一个大于零的整数数字,type 可以是任何数据类型
type arrayName [arraySize];

// 声明一个 uint 类型,长度为10的数组:balance,如下所示:
uint balance[10];
  • 动态数组
    • 声明一个动态数组,只需要指定元素类型,无需指定数量
    • arraySize 必须是一个大于零的整数数字,type 可以是任何数据类型
type arrayName [];

// 声明一个 uint 类型的动态数组:balance,如下所示:
uint balance[];

初始化数组

uint balance[3] = [1,2,3];    // 初始化固定长度数组
uint balance[] = [1,2,3];     // 初始化动态数组
balance[2] = 5;               // 设置第3个元素的值为5

访问数组元素

// 可以通过索引访问数组元素
uint salary = balance[2];

动态数组和固定长度数据的区别

  • 固定长度的数组在运行期间,是无法改变长度的。而动态数组可以改变
  • 动态数组使用 push 方法,在末尾追加一个元素
  • 使用 pop 方法,截掉末尾元素
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
contract SolidityTest{
    // 动态数组
    uint[] public arr = [1,2,3,4];
    // 固定长度数组
    uint[4] public arrFixed=[1,2,3,4];

    // 获得数组全部元素
    function getArr() external view returns(uint[] memory){
        return arr;
    }
    
    // 固定长度数组操作
    function operatFixedArr() external{
        // 获得数组长度
        uint len1 = arrFixed.length;    // len=4
        // 获得第二个元素
        uint x = arrFixed[1];   // x=2
        // 删除第二个元素,delete只将元素设置为初值,并不改变数组长度
        delete arrFixed[1];     // [1,0,3,4]
    }

    // 动态数组操作
    function operateDynamicArr() external{
        // 获得数组长度
        uint len2 = arr.length; // len=4
        // 追加一个元素
        arr.push(5);    // [1,2,3,4,5]
        // 弹出一个元素
        arr.pop();  // [1,2,3,4]
        // 获得第二个元素
        uint x = arr[1];    // x=2
        // 删除第二个元素,delete 只将元素设置为初值,并不改变数组长度
        delete arr[1];      // [1,0,3,4]
        // 删除第二个元素,并且长度减1
        removeAt(1);        // [1,3,4]
    }   

    // 删除数组元素,并且长度减1
    function removeAt(uint i) public {
        require(i>=0&&i<arr.length);
        for(uint k=i;k<arr.length-1;k++){
            arr[k] = arr[k+1];
        }
        arr.pop();
    }
}

/*getArr:返回动态数组 arr 中的所有元素。
operatFixedArr:演示如何使用固定长度数组 arrFixed,获取数组长度、访问特定元素以及删除特定元素。
operateDynamicArr:演示如何使用动态数组 arr,获取数组长度、追加/弹出元素、访问特定元素以及删除特定元素。
removeAt:删除指定索引位置的元素,并将数组长度减1,其中使用了 for 循环和 pop() 函数。*/

创建内存数组

使用 new 关键字在内存中创建动态数组。内存动态数组的长度一旦确定,不能改变。

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
contract SolidityTest{
    function getMemoryArr() external pure returns(uint[] memory){
        uint[] memory arr = new uint[](3);
        arr[0] = 1;
        return arr;
    }
}

结构体

定义结构体

struct struct_name{
    type1 type_name_1;
    type2 type_name_2;
    type3 type_name_3;
}

// 示例
struct Book{
    string title;
    string author;
    uint book_id;
}

访问结构体成员

// 要访问结构体的任何成员,要使用成员访问操作符(.)

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

contract SolidityTest{
struct Book{
    string title;
    string author;
    uint id;
}
Book book;

function setBook() public {
    book = Book('learn','codebaoku.com',1);
}
function getBookAuthor() public view returns(string memory){
    return book.author;
    }
}

结构体操作方法

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

contract SolidityTest{
    struct Book{
        string title;
        string author;
        uint id;
        address owner;
    }
    Book public book;
    Book[] public books;
    mapping(address=>Book[])public booksByOwner;

    function operations() external{
        // 结构体直接按照字段顺序,进行初始化
        Book memory book1 = Book('learn Java','codebaoku.com',1,msg.sender);

        // 结构体按照字段名,进行初始化
        Book memory book2 = Book({title:'learn',author:'codebaoku.com',id:2,owner:msg.sender});

        // 结构体按照默认值,进行初始化
        Book memory book3;
        book3.id = 3;
        book3.title = 'learn';
        book3.author = '11';
        book3.owner = msg.sender;

        // 结构体数组操作
        books.push(book1);
        books.push(book2);
        books.push(book3);

        // 结构体状态变量操作
        Book storage _book = books[0];
        delete _book.id;
        delete books[0];
        _book.id = 100;
    }
}

映射

  • mapping 用于以键值对的形式存储数据,等同于其它编程语言的哈希表或字典
    • _KeyType:可以是任何内置类型,或者types和字符串,不允许使用引用类型或复杂对象
    • _ValueType:可以是任何类型
mapping(_KeyType => _ValueType)
  • 注意
    • 映射的数据位置(data location)只能是 storage,通常用于状态变量
    • 映射可以标记为 public,Solidity 自动为它创建 getter
    • 映射可以视为哈希表,在实际的初始化过程中创建每个可能的 key,并将其映射到字节形式全是零的值:一个类型的 默认值
    • 映射和哈希表不同:在映射中,实际并不存储 key,而是存储它的 keccak256哈希值,从而便于查询实际的值
    • 正因为如此,映射是没有长度的,也没有 Key 的集合或 value 的集合的概念。映射只能是存储的数据位置,因此只存在作为状态变量或作为函数内的存储引用 或 作为库函数的参数。它们不能用于合约共有函数的参数或返回值。
    • 可以将映射声明为 public,然后来让 Solidity 创建一个 getter 函数。_KeyType 将成为 getter 的必须参数,并且 getter 会返回 _ValueType
  • 状态变量示例
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

contract LederBalance{
    mapping(address => uint)public balances;
    function updateBalance(uint newBalance) public {
        // 设置 mapping 的 key 和 value
        balances[msg.sender] = newBalance;
    }

    function get() public view returns(uint){
        // 通过 key 获取 mapping 的 value
        return balances[msg.sender];
    }
}

contract Updater{
    function updateBalance() public returns(uint){
        LederBalance ledgerBalance = new LederBalance();
        ledgerBalance.updateBalance(10);
        return ledgerBalance.get();
    }
}
  • 局部变量示例
    • mapping 类型可以用做局部变量,但只能引用状态变量,而且存储位置为 storeage
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

contract SolidityTest{
    struct Book{
        string title;
        string author;
        uint id;
        address owner;
    }
    Book public book;
    Book[] public books;
    mapping(address=>Book[])public booksByOwner;

    function operations() external{
        // 结构体直接按照字段顺序,进行初始化
        Book memory book1 = Book('learn Java','codebaoku.com',1,msg.sender);

        // 结构体按照字段名,进行初始化
        Book memory book2 = Book({title:'learn',author:'codebaoku.com',id:2,owner:msg.sender});

        // 结构体按照默认值,进行初始化
        Book memory book3;
        book3.id = 3;
        book3.title = 'learn';
        book3.author = '11';
        book3.owner = msg.sender;

        // 结构体数组操作
        books.push(book1);
        books.push(book2);
        books.push(book3);

        // 结构体状态变量操作
        Book storage _book = books[0];
        delete _book.id;
        delete books[0];
        _book.id = 100;
    }
}

枚举

  • enum 是一种用户自定义类型,用于表示多种状态
  • enum 内部是一个自定义的整型,默认的类型为 uint8,当枚举数足够多时,自动变成 uint16
  • enum至少应该有一名成员
  • enum 可以与整数进行显式转换,但不能进行隐式转换。
    • 显示转换会在运行时检查数值范围,如果不匹配,将会引起异常
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

contract SolidityTest{
    enum Status{
        None,
        Pending,
        Completed,
        Canceled
    }


Status public status;

// 获取状态
function getStatus() external view returns(Status){
    return status;
}

// 设置状态
function setStatus(Status _status) external{
    status = _status;
}

//设置完成状态
function setCompleted() external{
    status = Status.Completed;
    }
}

类型转换

隐式转换

  • 隐式转换时必须符合一定条件,不能导致信息丢失。
    • uint8 可以转换为 uint16
    • 但是 int8 不可以转换为 uint256,因为 int8 可以包含 uint256中不允许的负值

显式转换

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

contract SolidityTest{
    function test1() public pure returns(int){
        int8 y = -3;
    //  uint x = uint(y);   // int8不可以转换为uint256

        // 转换成更小的类型,会丢失高位
        uint32 a = 0x12345678;
        uint16 b = uint16(a);       // b = 0x5678
       
        // 转换为更大的类型,将会向左侧添加填充位
        uint16 a1 = 0x1234;
        uint32 b1 = uint32(a1);    // b1 = 0x00001234

        // 转换到更小的字节类型,会丢失后面数据
        bytes2 a2 = 0x1234;
        bytes1 b2 = bytes1(a2);     // b2 = 0x12

        // 转换为更大的字节类型时,向右添加填充位
        bytes2 a3 = 0x1234;
        bytes4 b3 = bytes4(a3);     // b3 = 0x12340000

        // 只有当字节类型和 int 类型大小相同时,才可与进行类型转换
        bytes2 a4 = 0x1234;
        uint32 b4 = uint16(a4);         // b4 = 0x00001234
        uint32 c = uint32(bytes4(a4));  // c = 0x12340000
        uint8 d = uint8(uint16(a4));    // d = 0x34
        uint8 e = uint8(bytes1(a4));     // e = 0x12

        // 把整数赋值给整型时,不能超出范围而发生截断,否则会报错
        uint8 a5 = 12;          // no error
        uint32 b5 = 1234;       // no error
    //  uint16 c5 = 0x123456;   // error,有截断,变成0x3456
       
       
        return 0;

    }
}

数据位置

引用类型 / 复合数据类型

  • 有一些数据类型由简单数据类型组合而成,通过名称引用,这些类型通常被称为引用类型
    • 数组(字符串与 bytes 是特殊的数组,也是引用类型)
    • struct (结构体)
    • map (映射)
  • 这些类型涉及到的数据量较大,复制它们可能要消耗大量Gas,非常昂贵,所以使用它们时,必须考虑存储位置。例如,是保存在内存中,还是在 EVM 存储区中。

数据位置(data location)

  • 在合约中声明和使用的变量都有一个数据位置,指明变量值应该存储在哪里
    • 合约变量的数据位置将会影响 Gas 消耗量
    • Solidity 提供 3 种类型的数据位置
      • storage
      • memory
      • calldata
  1. storage
    1. 存储位置 storage 用来存储永久数据,可以被合约中的所有函数访问
    2. storage 变量,通常用于存储智能合约的状态变量,它在函数调用之间保持持久性
    3. storage 与其他数据位置相比,成本较高
    4. 可以理解为计算机的硬盘数据,所有数据都永久存储
      // SPDX-License-Identifier: MIT
      pragma solidity ^0.8.0;
      contract SolidityTest{
          struct MyData{
              uint id;
              string value;
          }
          MyData[] public myData;
          constructor(){
              myData.push(MyData(1,"value1"));
              myData.push(MyData(2,"value2"));
          }
          
          function operations() external{
              // storage 存储位置
              MyData storage d = myData[0];
      
              // 修改状态变量 myData 的内容
              d.value = "new value";
          }
      }
      
      /*合约 SoldityTest 部署后,调用 operations 方法,状态变量 myData 的数据被修改,其中 myData[0].value 变为 "new value"。*/
  2. memory
    1. 存储位置 memory 用来存储临时数据,比 storage 便宜
    2. memory 变量,通常用于保存临时变量,以便在函数执行期间进行计算
    3. 一旦函数执行完毕,它的内容就会被丢弃,它只能在所在的函数中访问
    4. 可以理解为每个单独函数的内存 RAM 
      // SPDX-License-Identifier: MIT
      pragma solidity ^0.8.0;
      
      contract SolidityTest{
          struct MyData{
              uint id;
              string value;
          }
      
          MyData[] public myData;
      
          constructor(){
              myData.push(MyData(1,"value1"));
              myData.push(MyData(2,"value2"));
          }
      
          function operations() external{
              // memory 存储位置
              MyData memory d = myData[0];
      
              // 修改状态变量 myData 的内容
              d.value = "new value";
          }
      }
      
      /*合约 SoldityTest 部署后,调用 operations 方法,状态变量 myData 的数据未被修改,其中 myData[0].value 仍然为 "value1"*/
  3. calldata
    1. 是不可修改的非持久性数据位置,类似memory,但只能出现在函数的输入参数位置
    2. 外部函数 external function 的传入参数强制为 calldata 类型
    3. 使用 calldata 的好处是在内部函数中作为输入参数传递时,省掉了数据拷贝的成本
    4. 节省了 gas 费用,memory 作为参数时,需要进行数据拷贝
      // SPDX-License-Identifier: MIT
      pragma solidity ^0.8.0;
      
      contract SolidityTest{
          function operations(string calldata val)pure external{
              // 使用 calldata 传递参数,省去复制成本
              internalFunc1(val);
      
              // 使用 memory 传递参数,需要进行复制
              internalFunc2(val);
          }
          
          function internalFunc1(string calldata val) internal pure{
              //...
          }
      
          function internalFunc2(string memory val) internal pure{
              //...
          }
      }

变量数据位置

  1. 规则1 - 状态变量
    1. 状态变量总是存储在存储区中
    2. 此外,不能显式地标记状态变量的位置
      // SPDX-License-Identifier: MIT
      pragma solidity ^0.8.0;
      
      contract DataLocation{
          // 状态变量总是存储在存储区中。
          // storage
          uint stateVariable;
          uint[] stateArray;
      
          // 此外,不能显式地标记状态变量的位置
          uint storage stateVariable;     // 错误
          uint[] memory stateArray;       // 错误
      }
  2. 规则2 - 函数参数与返回值
    1. 函数参数包括返回参数都存储在内存中
      // SPDX-License-Identifier: MIT
      pragma solidity ^0.8.0;
      
      contract DataLocation{
          // storage
          uint stateVariable;
          uint[] stateArray;
      
          // 函数参数 uint num1 与 uint num2,返回值 uint result 都存储在内存中。
          function calculate(uint num1,uint num2) public pure returns(uint result){
              return num1 + num2;
          }
      }
  3. 规则3 - 局部变量
    1. 值类型的局部变量存储在内存中。
    2. 对于引用类型的局部变量,需要显式地指定数据位置
      // SPDX-License-Identifier: MIT
      pragma solidity ^0.8.0;
      contract Locations{
          /*  此处都是状态变量 */
          //  存储在storage(存储区)中 
          bool flag;
          uint number;
          address account;
      
          function doSomething() public pure{
              /*  此处都是局部变量 */
              //  值类型
              //  所以它们被存储在memory(内存)中
              bool flag2;
              uint number2;
              address account2;
      
              // 引用类型,需要显示指定数据位置,此处指定为内存
              uint[] memory localArray;
          }
      }
    3. 不能显式覆盖具有值类型的局部变量

      // SPDX-License-Identifier: MIT
      pragma solidity ^0.8.0;
      
      function doSomething() public {
          /*  此处都是局部变量 */
          //  值类型
          bool memory flag2;      // 错误 
          uint storage number2;   // 错误
          address account2;
      }
  4. 规则4 - 外部函数的参数

    1. The parameters of the external function (excluding return parameters) are stored in calldata

Assign data location

  • solidity data can be copied from one variable to another in two ways.

    • One way is to copy the entire data (copy by value)

    • Another method is to copy by reference.

  1. Rule 1 - Assign state variables to state variables

    1. Assigning a state variable to another state variable creates a new copy
      // SPDX-License-Identifier: MIT
      pragma solidity ^0.8.0;
      
      contract Locations{
          uint public stateVar1 = 10;
          uint public stateVar2 = 20;
      
          function doo() public returns(uint){
              stateVar1 = stateVar2;
              stateVar2 = 30;
      
              return stateVar1;   // returns 20
      
          /*  本例中,stateVar1 和 stateVar2 是状态变量。
              在 do 函数中,我们将 stateVar2 的值赋值给 stateVar1。
              stateVar1 的值是 20,但是为了确定它创建了一个新的副本,我们改变了stateVar2 的值。
              因此,如果它不创建副本,那么 stateVar1 的值应该是30,创建副本则是20。
              这同样适用于引用类型的状态变量。 */
          }
      }
  2. Rule 2 - Memory local variables are copied to state variables

    1. Copying from a memory local variable to a state variable always creates a new copy

      // SPDX-License-Identifier: MIT
      pragma solidity ^0.8.0;
      
      contract Locations{
          uint stateVar = 10;     // storage
      
          function doone() public returns(uint){
              uint localVar = 20; // memory
              stateVar = localVar;
              localVar = 40;
      
              return stateVar;    // returns 20 
      
          /*    在上面的例子中,我们有一个状态变量和一个局部内存变量。
                在函数中,我们把局部变量的值赋给状态变量。
                为了检查赋值后的行为,我们改变了局部变量的值,并返回状态变量的值。
                这里可以看到,它会返回20,说明从内存局部变量复制到状态变量,会创建一个新的副本。*/
          }
      }
  3. Rule 3 - State variables are copied to memory local variables

    1. Copying from a state variable to a memory local variable creates a copy.

      // SPDX-License-Identifier: MIT
      pragma solidity ^0.8.0;
      
      contract Locations{
          uint stateVar = 10;     // storage
      
          function dothree() public returns(uint){
              uint localVar = 20; // memory
              localVar = stateVar;
              stateVar = 40;
              return localVar;     // returns 10
      
          /*  在上面的例子中,我们有一个状态变量和一个局部内存变量。在函数中,我们把局部变量的值赋给状态变量。
              为了检查赋值后的行为,我们改变了局部变量的值,并返回状态变量的值。
              这里可以看到,它会返回20,说明从内存局部变量复制到状态变量,会创建一个新的副本。  */
          }
      }
  4. Rule 4 - Copy memory variables to memory variables

    1. For local variables of reference type, copying from one memory variable to another does not create a copy

      // SPDX-License-Identifier: MIT
      pragma solidity ^0.8.0;
      
      contract Locations{
          function doSomething() public view returns(uint[] memory,uint[] memory){
              uint[] memory localMemoryArray1 = new  uint[](3);
              localMemoryArray1[0] = 4;
              localMemoryArray1[1] = 5;
              localMemoryArray1[2] = 6;
      
              uint[] memory localMemoryArray2 = localMemoryArray1;
              localMemoryArray1[0] = 10;
      
              return(localMemoryArray1,localMemoryArray2);
              // returns 10,4,6 | 10,4,6
          
          /*  在上面的示例中,我们在内存中初始化了一个名为localMemoryArray1的数组变量,并赋值为4、5和6。
              然后,我们将该变量复制到名为localMemoryArray2的新内存变量中。
              然后,我们修改了localMemoryArray1中第一个索引的值,并返回了两个数组。
              这将得到相同的结果,因为它们都指向相同的位置。 */
          } 
      }
    2. For local variables of value type, copying from one memory variable to another still creates a new copy

      // SPDX-License-Identifier: MIT
      pragma solidity ^0.8.0;
      
      contract Locations{
          function dofour() public pure returns(uint){
              uint localVar1 = 10;    // memory
              uint localVar2 = 20;    // memory
      
              localVar1 = localVar2;
              localVar2 = 40;
      
              return localVar1;       // returns 20
          }
      }

Guess you like

Origin blog.csdn.net/BingjieDreams/article/details/130812149