跳转至

引言

官方文档

Solidity 是一种面向对象的高级语言,用于实现智能合约。

智能合约是管理以太坊状态下账户行为的程序

开发环境

本地开发可使用:VSCode + Solidity 插件 + Brownie 或 Harhat 等框架

在线开发可以使用以太坊官方推荐的 Remix IDE,集成了 Solidity 编译器和运行时环境

Docs: https://remix-ide.readthedocs.io/en/latest/index.html

蓝色按钮表示只读方法,不需要支付 Gas,可变性为 view 或 pure 的函数,或者状态变量
橙色按钮表示写入方法,需要支付 Gas
红色按钮表示可以向合约支付,可变性为 payable 的函数

规范

// SPDX-License-Identifier: MIT
pragma solidity >=0.8.4 <0.9.0;
/**
 * 上面第一行代码:需要注释智能合约所用的许可证,如果不写编译时会警告
 *     遵循 SPDX(Software Package Data Exchange) 国际标准规范的许可证
 *     如果没想好或者没有则可以写成:UNLICENSED
 * 上面第二行代码:Pragmas 是编译器关于如何处理源代码的常用指令,比如用来声明编译器版本
 *     可简写为:^0.8.4,^表示向上兼容
 */

// 语句以分号结尾
contract Coin {
    // 声明一个address类型的状态变量
    address public minter;  // 关键字public使变量可以被其他合约访问
    mapping (address => uint) public balances;

    // 凡是引起状态改变的操作,都应该记一个事件,类似日志,命名通常首字母大写
    event Sent(address from, address to, uint amount);

    // 构造函数代码只有在合约创建时运行
    constructor() {
        minter = msg.sender;
    }

    // 向一个地址发送一定数量的新创建的代币
    // 但只能由合约创建者调用
    function mint(address receiver, uint amount) public {
        require(msg.sender == minter);
        balances[receiver] += amount;
    }

    // 错误类型变量允许您提供关于操作失败原因的信息,它们会返回给函数的调用者
    error InsufficientBalance(uint requested, uint available);

    // 从任何调用者那里发送一定数量的代币到一个地址
    function send(address receiver, uint amount) public {
        if (amount > balances[msg.sender])
            revert InsufficientBalance({
                requested: amount,
                available: balances[msg.sender]
            });

        balances[msg.sender] -= amount;
        balances[receiver] += amount;
        emit Sent(msg.sender, receiver, amount);
    }
}

数据存储位置

每个账户都有一个持久的键值存储,将 256 位的字映射到 256 位的字,称为存储

主要针对于这几种类型:数组(包含 bytes 和 string)、结构体、映射

  • Storage 类似计算机硬盘,永久存储在区块链上,消耗 Gas 最多,合约里的状态变量默认都存在这里
  • Memory 类似计算机 RAM 内存,用于存储函数调用过程中的临时变量
  • Calldata 和 Memory 类似,但是只读,用于存储函数调用的输入参数,Gas 更少
  • Stack 主要用于存储小型数据,如 uint256 或地址等,空间有限
  • Code 保存并执行智能合约代码,只读,无法修改
  • Logs 记录事件

函数参数只能用 Memory 或 Calldata

常量和不变量

在被合约调用时所消耗的 Gas 要比变量少,可以用来优化 Gas Fee

只有数值型可以声明为不变量,string 和 bytes 只可以声明为常量

  • constant 常量,必须在声明的时候初始化
  • immutable 不变量,可以在声明时或构造函数中初始化
// 长量一般大写
uint256 public constant MY_NUMBER = 777;
address public constant MY_ADDRESS = 0xabc123...;

// 不变量可以写成:i_xxx
address public immutable i_owner;

变量

  • 局部变量(local variable)

在函数内声明,仅在函数执行过程中有效,存储在虚拟机内存里,即 Memory,不上链

  • 状态变量(state variable)

在合约内、函数外声明,状态变量数据默认存储在链上,即 Storage,所有合约内函数都可以访问

  • 全局变量(global variable)

预留关键字,无需声明,可直接使用

block.number: (uint)  // 当前区块高度
block.timestamp: (uint)  // 当前区块的时间戳
block.coinbase: (address payable)  // 当前区块矿工的地址
block.gaslimit: (uint)  // 当前区块的gaslimit

msg.sender: (address payable)  // 调用者地址 caller
msg.sender.balance  // 进行交易的用户的余额,如果msg.sender是另一个合约,那么它是另一个合约的余额
msg.sig: (bytes4)  // calldata的前四个字节 (function identifier)
msg.value: (uint)  // 当前交易发送的wei值
msg.data: (bytes calldata)  // 完整call data

变量默认初始值

声明但没赋值的变量都有它的默认初始值

delete x 方法可以让变量恢复为初始值

bool public _bool; // false
string public _string; // ""
uint public _uint; // 0
int public _int; // 0
address public _address; // 20Bytes,十六进制40个0,0x0000000000000000000000000000000000000000
bytes32 public b32;  // 32Bytes,十六进制64个0,0x000...000

enum ActionSet { Buy, Hold, Sell}
ActionSet public _enum; // 第一个元素 0

function fi() internal{} // internal空白方程
function fe() external{} // external空白方程

// Reference Types
uint[8] public _staticArray; // 所有成员设为其默认值的静态数组[0,0,0,0,0,0,0,0]
uint[] public _dynamicArray; // `[]`
mapping(uint => address) public _mapping; // 所有元素都为其默认值的mapping

// 所有成员设为其默认值的结构体 0, 0
struct Student{
    uint256 id;
    uint256 score; 
}
Student public student;