Solidity快速梳理基础要点

128 阅读10分钟

前言

本文高效梳理Solidity编程语言基础知识点

类型

1. 值类型

  • bool(布尔)例子: bool public _bool
  • int or uint(整型)例子:int public _int or unt public _uint
  • address(地址)例子:address public _address
  • Bytes(定长字节数组)例子:bytes32 public _byte32
  • constant or immutable(常量)
# constant
string public constant _string = "Hello World";

# immutable
 uint public immutable _string1;
 //需要在构造函数中初始化;
  • string (字符串)string public _string
  • enum (枚举)
 # 例子:
 enum ActionWeek {
        Monday,
        Tuesday,
        Wednesday,
        Thursday,
        Friday,
        Saturday,
        Sunday,
 }
 ActionWeek action = ActionWeek.Monday
 function enumToUint() external view returns(uint){
    return uint(action);
    }//返回0

2. 引用类型

  • Structs(结构体)
// 结构体 
struct Student{ 
    uint256 id; 
    string name; 
    unit256 age;
}
Student student;
//赋值
//方法1
student(1,"Boykayuri",20)
//方法2
student({id:1,name:"Boykayuri",age:20})
//方法3
Student storage _student = student; 
            _student.id = 11;
            _student.name = "tom";
            _student.age= 20;
//方法4
student.id=10;
student.name = "tom";
student.age= 20;
  • Array(数组)
# 固定长
uint[5] public  arr1;//类型 长度
# 不定长
uint[] public  arr1;//类型 长度
# 方法、属性
lenght//长度
push//添加
pop//删除

3. 映射类型

  • mapping(映射)
# 通过权限设置的例子
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.13;
import "hardhat/console.sol";
contract mappingType {
    // 声明一个 mapping,键是 address,值是 bool
    mapping(address => bool) public hasPermission;

    constructor() {
        // 初始化时可以设置一些值
        hasPermission[msg.sender] = true;
    }

    // 设置权限
    function setPermission(address _address, bool _hasPermission) public {
        hasPermission[_address] = _hasPermission;
    }

    // 检查权限
    function checkPermission(address _address) public view returns (bool) {
        return hasPermission[_address];
    }

    // 删除权限
    function deletePermission(address _address) public {
        delete hasPermission[_address];
    }
}

循环和分支

  • for(循环)
for (unit i i<10;i++){
console.log(i)
}
  • if else(分支)or 三目运算
# 分支
if(){}else{}
# 或者
条件? 条件为真的表达式:条件为假的表达式
x>=y?x:y

函数和事件

函数

函数形式function <function name>([parameter types[, ...]]) {internal|external|public|private} [pure|view|payable] [virtual|override] [<modifiers>] [returns (<return types>)]{ <function body> }

函数说明:
  • function name:函数名
  • [parameter types[, ...]]:函数的参数,类型 参数
  • {internal|external|public|private}:可见性说明符
  • [pure|view|payable]:权限/功能的关键字
  • [virtual|override]:是否可以被重写,或者是否是重写方法
  • modifiers: 自定义的修饰器
  • [returns ()]:函数返回的变量类型和名称

可见性修饰符

  • public:内部和外部均可见。
  • private:只能从本合约内部访问,继承的合约也不能使用。
  • external:只能从合约外部访问(但内部可以通过 this.f() 来调用,f是函数名)。
  • internal: 只能从合约内部访问,继承的合约可以用。

权限/功能的关键字

  • pure:既不能读取也不能改写状态变量
  • view:可以读取状态变量,但不能改写
  • payable:可支付的

是否是重写方法

  • virtual:用在父合约上,标识的方法可以被子合约重写
  • override:用在自合约上,表名方法重写了父合约的方法
完整代码实例
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

contract Base {
    function foo() public virtual returns (string memory) {
        return "Base foo";
    }

    function bar() public virtual returns (string memory) {
        return "Base bar";
    }
}

contract Derived is Base {
    function foo() public override returns (string memory) {
        return "Derived foo";
    }

    function bar() public override returns (string memory) {
        return "Derived bar";
    }
}

修饰器

# 定义modifier
 modifier onlyOwner { 
 require(msg.sender == owner); // 检查调用者是否为owner地址
 _; // 如果是的话,继续运行函数主体;否则报错并revert交易 
 }
 function changeOwner(address _newOwner) external onlyOwner{ 
 owner = _newOwner; // 只有owner地址运行这个函数,并改变owner 
 }
# 特性
* 可见性修饰符:publicinternal、external 和 private
* 函数修饰符:pure、view、payable 和 virtual
*函数重写和重载:override、overload 

Event事件

# 定义事件
event EventName(address indexed _addressParam, uint _uintParam, string _stringParam);
说明:indexed 关键字用于指定该参数为索引参数,允许外部应用程序通过该参数进行快速检索
# 触发事件 传递对应的参数
emit EventName(0x4***,10,"str") 

继承、抽象合约与接口

继承

  • 基本的合约继承
contract Grandfather { 
    event Log(string msg); // 定义3个function: hip(), pop(), yeye(),Log值为Yeye。 
    function hip() public virtual{ emit Log("grandfather"); } 
    function pop() public virtual{ emit Log("grandfather"); } 
    function grandfather() public virtual { emit Log("grandfather"); }
}
contract Father is Grandfather{
// 继承两个function: hip()和pop(),输出改为father。
function hip() public virtual override{ emit Log("father"); } 
function pop() public virtual override{ emit Log("father"); }
function father() public virtual{ emit Log("father"); } }
  • 多重继承
contract Boykayuri is Grandfather, Father{ 
// 继承两个function: hip()和pop(),输出值为boykayuri。 
function hip() public virtual override(Grandfather, Father){ emit Log("boykayuri"); }
function pop() public virtual override(Grandfather, Father) { emit Log("boykayuri"); }
}
  • 构造函数的继承
# 使用`super`关键字
contract Base {
    address public owner;

    constructor() {
        owner = msg.sender;
    }
}

contract Derived is Base {
    constructor() {
        super.constructor();
        // 其他初始化代码
    }
}
# 直接调用父合约的构造函数
contract Derived is Base {
    constructor() {
        Base.constructor();
        // 其他初始化代码
    }
}
# 传递参数给父合约的构造函数

contract Derived is Base {
    constructor(address _owner) Base(_owner) {
        // 其他初始化代码
    }
}

抽象合约

  • 定义:抽象合约是一种不能被直接实例化的合约,它通常包含一些未实现的方法(抽象方法)。这些方法必须在继承抽象合约的具体合约中被实现。

  • 特点

    • 不能直接实例化:抽象合约不能直接创建实例,只能通过继承它的具体合约来实例化。
    • 包含抽象方法:抽象合约可以包含一些没有具体实现的方法,这些方法称为抽象方法。抽象方法必须在继承抽象合约的具体合约中被实现。
    • 提供接口规范:抽象合约定义了一组方法的接口和规范,但不提供具体实现。这使得抽象合约可以作为其他合约的模板或框架。
  • abstract:帮助开发者定义接口规范、提供通用功能以及确保合约实现特定接口

  • 语法

# 使用
abstract contract Verifiable {
    function verify(address user) external virtual returns (bool);
}
contract UserContract is Verifiable {
    function verify(address user) external override returns (bool) {
        // 实现用户验证逻辑
        return true;
    }
}

接口

  • 定义:接口是一种特殊的合约,它只能声明方法的签名,但不能实现这些方法。接口中的所有方法都是抽象的,且不能包含任何状态变量。
  • 特点
    • 定义接口规范:接口主要用于定义一组方法的签名,确保不同的合约实现这些方法,从而保证接口的一致性。
    • 轻量级:接口不包含任何实现,因此比抽象合约更轻量级,适合用于定义简单的接口规范。
    • 互操作性:接口可以用于不同合约之间的互操作,使得合约之间可以调用彼此的方法,而不需要知道具体的实现细节。
  • interface:适用于定义轻量级的接口规范,确保不同合约之间的一致性,不能包含实现、状态变量、构造函数和事件
  • 语法
# 使用
interface MyInterface {
    function myFunction() external;
}
contract MyContract is MyInterface {
    function myFunction() external {
        // 实现具体的功能
    }
}

变量作用域

  • 全局变量:

    定义:全局变量是Solidity提供的内置变量,它们可以在任何合约中直接使用,无需声明。全局变量通常用于访问区块链的某些基本信息,如区块信息、交易信息等。

    常见的全局变量

    block

    • block.number:当前区块的编号。
    • block.timestamp:当前区块的时间戳。
    • block.difficulty:当前区块的难度。
    • block.gaslimit:当前区块的气体限制。

    tx

    • tx.gasprice:交易的气体价格。
    • tx.origin:交易的发起者地址。

    msg

    • msg.sender:当前消息的发送者地址。
    • msg.value:发送给合约的以太币数量(单位为Wei)。
    • msg.data:发送给合约的数据。
    • msg.sig:调用函数的签名。

    abi

    • abi.encode:将参数编码为字节数组。
    • abi.encodePacked:将参数编码为紧凑的字节数组。
    • abi.encodeWithSelector:将参数编码为字节数组,并包含函数选择器。
    • abi.decode:将字节数组解码为指定类型的参数。
  • 局部变量:

    定义:局部变量是在函数内部声明的变量,它们的作用范围仅限于声明它们的函数。局部变量在函数调用时被创建,在函数返回时被销毁。

    使用案例

    pragma solidity ^0.8.0;
    
     contract MyContract {
         function myFunction() public pure {
             uint256 localVariable = 42; // 局部变量
             // 可以在函数内部使用 localVariable
         }
     }
    
  • 状态变量:

    定义:状态变量是存储在合约中的变量,它们的作用范围是整个合约。状态变量的值被永久存储在区块链上,因此它们在合约的整个生命周期中都存在

    使用案例

    pragma solidity ^0.8.0;
    
    contract MyContract {
        uint256 public stateVariable = 42; // 状态变量
    
        function myFunction() public {
            stateVariable = 100; // 修改状态变量
        }
    }
    

作用域总结

  • 全局变量:由Solidity提供,用于访问区块链的基本信息,无需声明。
  • 局部变量:在函数内部声明,仅在函数执行期间存在,存储在栈中。
  • 状态变量:在合约中声明,存储在区块链上,永久存在,可以通过合约的外部调用来访问和修改。

数据存储

Storage(存储)

特点:
  • 持久性:存储在storage中的数据是永久的,存储在区块链上。
  • 成本:访问和修改storage中的数据成本较高,因为每次读写操作都需要消耗Gas。
  • 可变性:存储在storage中的数据可以被修改。
用法
pragma solidity ^0.8.0;

contract MyContract {
    uint256 public stateVariable = 42; // 存储在 storage 中

    function updateStateVariable(uint256 newValue) public {
        stateVariable = newValue; // 修改 storage 中的数据
    }
}

Memory(内存)

特点:
  • 临时性:存储在memory中的数据是临时的,仅在函数调用期间存在。
  • 成本:访问和修改memory中的数据成本较低,因为这些操作不会写入区块链。
  • 不可变性:存储在memory中的数据在函数调用结束后被销毁。
用法
pragma solidity ^0.8.0;

contract MyContract {
    function myFunction(uint256 a, uint256 b) public pure returns (uint256) {
        uint256 localVariable = a + b; // 存储在 memory 中
        return localVariable;
    }
}

Calldata(调用数据)

特点:
  • 只读性:存储在calldata中的数据是只读的,不能被修改。
  • 临时性:存储在calldata中的数据是临时的,仅在函数调用期间存在。
  • 成本:访问calldata中的数据成本较低,因为这些数据不会写入区块链。
用法
pragma solidity ^0.8.0;

contract MyContract {
    function myFunction(uint256[] calldata values) public pure returns (uint256) {
        uint256 sum = 0;
        for (uint256 i = 0; i < values.length; i++) {
            sum += values[i];
        }
        return sum;
    }
}

Stack(栈)

特点:
  • 临时性:存储在stack中的数据是临时的,仅在当前操作的上下文中存在。
  • 成本:访问和修改stack中的数据成本极低,因为这些操作在内存中完成。
  • 不可变性:存储在stack中的数据在当前操作完成后被销毁。
用法
pragma solidity ^0.8.0;

contract MyContract {
    function add(uint256 a, uint256 b) public pure returns (uint256) {
        uint256 result = a + b; // ab 存储在 stack 中
        return result;
    }
}

数据存储总结

  • Storage:永久存储在区块链上,成本高,可变。
  • Memory:临时存储在合约执行期间的内存中,成本低,不可变。
  • Calldata:存储在调用交易数据中的只读数据,成本低,只读。
  • Stack:存储在执行栈中的临时数据,成本极低,不可变。

异常处理

  • require:用于输入验证和状态检查,失败时返回剩余 gas 并恢复状态。
  • assert:用于检测不变量和内部错误,失败时消耗所有剩余 gas 并恢复状态。
  • revert:用于显示抛出异常,支持携带错误信息,失败时返回剩余 gas 并恢复状态。
  • 自定义错误:从 Solidity 0.8.4 开始引入,使用 error 关键字定义,通过 revert 抛出,节省 gas 并提高代码可读性。
  • 事件日志:用于记录错误信息,便于后续查询和调试。

使用方法说明:

  • require
# 例子:
require(false,"错误信息描述");//判断条件为false
  • assert
# 例子:
assert(true)//判断条件为true
  • 自定义异常
# 例子
# 定义事件
error TransferNotOwner(address sender);
if(true){
 revert TransferNotOwner(msg.sender);
}

总结

以上内容对Solidity的基础要点进行了梳理。如需深入了解,可查阅Solidity官方文档。