跳到主要内容

常规基础

solidity基础

单词

contract 合约 constructor 构造函数 constant 常量

头部

// SPDX-License-Identifier: MIT
pragma solidity 0.8.24; // 固定版本
pragma solidity ^0.8.24; // 最低版本
pragma solidity >=0.8.19 < 0.9;

数据类型

基本变量类型(值类型)

uint  // 0
int // 0
strig // ''
bool // false
address // 0x0000...0000000000 40个0
bytes32 // 0x0000...00000000 64个0

可以查看int的最大最小值

    int public b = type(int).min;
int public c = type(int).max;

简写

int 默认为 int256
uint 默认为 uint256

引用类型

int[]
mapping(int->address)

bytes类型

https://chatgpt.com/s/t_68be42d2fed0819184d8b148f950e371 https://chatgpt.com/s/t_68be4467d6c48191864536636d0f6dbe

bytes1,2,3,4,,,32 这种加数字的是表示长度固定的字节数组,最大只到bytes32,没有bytes64,这受限于evm bytes 表示长度不固定,可以很长,理论上可以2的256次方-1的大小。 常见类型: bytes4: 函数选择器的类型 bytes20: address类型 bytes32: keccak256 的结果,存储槽大小

pragma solidity ^0.8.24;

contract Example {
bytes4 public sel;
bytes32 public hash;
bytes public data;

constructor() {
// 计算函数选择器
sel = bytes4(keccak256("transfer(address,uint256)"));
// 计算哈希
hash = keccak256(abi.encodePacked("hello"));
// 动态字节数组
data = abi.encodePacked(uint256(123), address(this));
}
}

unchecked

0.8的版本加入了自动检查。 用unchecked包裹的语句,当数据有溢出的时候,不会报错,溢出后从0开始。 正常语句是会报错的。但是unchecked相当于要就解释器,不去检查溢出问题,所以更节省一些gas

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

contract Math{
uint8 public num;

constructor(uint8 init){
num = init;
}

function inc() external returns(uint8){
unchecked{
return num += 1;
}
}
function edd() external returns(uint8){
return num -=1;
}
function getMax() external pure returns(uint8){
return type(uint8).max;
}
}

bytes 和 string 的成员方法

https://docs.soliditylang.org/zh-cn/v0.8.24/types.html#bytes-concat-string-concat

bytes.concat(...) returns (bytes memory): 将可变数量的参数连接成一个字节数组。
string.concat(...) returns (string memory): 将可变数量的参数连接成一个字符串数组。

变量

状态变量

如果把contract比作类,状态变量也就是类变量。是一种定义函数外,但是在合约内部的变量。状态变量会被记录在链上。也可以是理解成合约的全局变量

本地变量

定义在合约中的函数内,生命周期随着一次运行从创建到销毁,不会永久存在链上

全局变量 https://docs.soliditylang.org/zh-cn/v0.8.24/units-and-global-variables.html#index-3

solidity的全局变量,其实是一些内部变量,比如msg.sender,block.xxxx等

常量

使用constant关键字定义的状态变量称为常量,用大写字母,注意常量是状态变量,也就是定义在函数外面,合约内部的变量 因为常量是固定不变的。如果一个方法只返回了一个常量,那么这个方法的修饰符可以是 pure,而不是view

// 其实通过pure和view也可以看出,如果返回的是常量,就相当于内部操作了,需要用pure修饰
contract constantVar{
uint public constant A=122;
function getVar() external pure returns (uint){
uint b = 3;
return b+A;
}
}

流程控制语句

if...else

contract processNumber{
function ifElse(uint x) external pure returns(uint){
if (x<10){ // 判断条件加括号
return 1;
}else if (x < 20){ // else if 不是简写的
return 2;
}else{
return 3;
}
}
function tenary(uint x) external pure returns(uint){
return x<10 ? 1 : x<20 ? 2 :3; // 三元表达式嵌套
} // 可以用另一个语句替代else部分,就构成了三元表达式嵌套
}

for while

如果编译的时候报出类似的错误提示,表示可能列表索引超出范围了

output 0x4e487b710000000000000000000000000000000000000000000000000000000000000032
contract Summation{
function sum(uint x) external pure returns(uint){
uint res = 0;
for (uint i=0; i< x; i++){ // 标准的c语言的写法
res += i;
}
return res;
}

function sumWhile(uint x) external pure returns(uint){
uint total = 0;
uint i = 0;
while(i < x){
total += i;
i +=1;
}
return total;
}
}

错误

https://docs.soliditylang.org/zh-cn/v0.8.24/cheatsheet.html#index-6

assert(bool condition): 如果条件为 false,则中止执行并恢复状态变化(用于内部错误)。

require(bool condition): 如果条件为 false,则中止执行并恢复状态变化(用于错误的输入或外部组件的错误)。

require(bool condition, string memory message): 如果条件为 false,则中止执行并恢复状态变化(用于错误的输入或外部组件的错误)。同时提供错误信息。

revert(): 中止执行并恢复状态变化

revert(string memory message): 中止执行并恢复状态变化,提供一个解释性的字符串

// 用自定义的错误,比使用require+字符串表述更省gas
error NotOwner(string message);

modifier onlyOwner(){
if (msg.sender != i_Owner){
revert NotOwner("only owner can de this");
}
// require(msg.sender == i_Owner, "only owner can de this");
_;
}

自定义错误 error关键字

error MyError(address sender, uint value);

抛出错误

revert MyError(msg.sender, x);

判断错误

        require(x > 10, "x <= 10");
assert(x >10);

引用类型

Array

创建

定长数组

uint256[3] public names = [1,2,3];
uint256[3] public names; // 没有设置初始值,全都是零

动态数组: 数组长度是可变的,可以被push。

uint256[] public names;  // 初始长度为0,names[0],names[1]会报错,现在names只是支持长度增加,还没有被增加
uint256[] public names = [1,2,3]; // 将1,2,3,填充到最前头


names = new uint256[](3); // 这种new方法,自能创建动态数组。3表示是的长度设为3,这三个长度的元素初始值都是0.等到push的时候,索引会是从4开始

names = new uint256[3](3); // 不存在这种写法。new关键字的时候[]不写东西。需要想着这种写法是创建固定数组。new只能创建动态数组

属性

获取长度,注意这里是属性,不是方法

uint[] a;
a.length

int[] a;   // 不指定具体长度,动态数组
int[3] b = [1,2,3] // 长度固定
a.push(2) // 固定长度的不支持push,因为长度已经固定了,push会增加长度

a.pop() 弹出最后一个  拿不到值

取到最后一个值,并且删除应该这样写

uint val = a[a.length-1]
a.pop()
delete a[1]; // 这会使a[1]变为该类型的默认值

两种删除方法 一,不打乱排序,gas高

function orderDelEle(uint index) external {
require(index < arr.length, "out of index");
for(uint i = index; i< arr.length-1; i++){
arr[i] = arr[i+1];
}
arr.pop();
}

二, 直接交换目标索引与最后一个元素

    function delEle(uint index) external {
require(index < arr.length, "out of index");
arr[index] = arr[arr.length -1];
arr.pop();
}

delete a[1];  //可使该索引变为默认值
a[2] = 3; // 一般重新赋值的方法
address[] public funders; // 声明一个不定长空数组
// 这里用new 相当于把address[] 当成了一个对象或者叫类型。
funders = new address[](funders.length); // 直接给原有的数组,赋值个新的一样长度的0地址数组

uint val = a[index]  // 通过索引查

函数返回数组类型

contract Sale{
uint256[] public vestingPortionsUnlockTime; // 代币分发时间
uint256[] public vestingPercentPerPortion; // 代币解锁分发比例
function getVestingInfo() external view returns (uint256[] memory, uint256[] memory){ // 获取关于分配的所有信息,也就是分批解锁的时间,份额信息
return (vestingPortionsUnlockTime, vestingPercentPerPortion);
}
}

映射 (字典)

mapping(address => uint) public balance; // 
mapping(address => mapping(address => bool)) public isFriend; // 嵌套

增删改查,就是很常规用[]对应取值或赋值

删除:

balances[msg.sender] = amount;
delete balance[msg.sender]; // 然后再次获取balance[2] 得到默认值
balances[msg.sender]

示例

要实现可迭代的mapping,需要再配合一个mapping类型和array类型一块

contract SimpleBank{
mapping(address => uint) public balances; // 存放余额
mapping(address => bool) public isCustomer; // 便捷判断
address[] public customers; //相当于存放所有的key,通过遍历这个array就可以遍历balance所有的值
address public owner;

constructor(){
owner = msg.sender;
}

modifier onlyOwner(){
require(owner == msg.sender, "only owner can do this");
_;
}

function clearCustomer() external onlyOwner {
customers = new address[](0);
}

function deposit(uint amount) external returns(uint){
if (isCustomer[msg.sender] == false){
balances[msg.sender] = amount;
isCustomer[msg.sender] = true;
customers.push(msg.sender);
}else{
balances[msg.sender] += amount;
}
return balances[msg.sender];
}

function withdrawn(uint amount) external returns(uint){
require(balances[msg.sender] >= amount, "Insufficient Balance");
balances[msg.sender] -= amount;
return balances[msg.sender];
}

function checkBalance() external view returns(uint){
return balances[msg.sender];
}

function getSize() public view returns(uint){
return customers.length;
}

function getAllCustomers() external view returns(address[] memory){
uint size = getSize();
address[] memory _customers = new address[](size);
for (uint i = 0; i< size; i++){
_customers[i] = customers[i];
}
return _customers;
}
function delSomeone(address addr) external onlyOwner{
delete isCustomer[addr];
delete balances[addr];
}

function firstBalance() external view returns(uint, uint){
return (balances[customers[0]], balances[customers[customers.length-1]]);
}
}

结构体 struct

官方解释为:一组变量,表示将数据分组。应该是不能包含方法,只是包含基本类型和引用mapping 可以理解成一个自定义类型,类似于uint int等

定义

    struct Vehicle{
string make;
string model;
uint year;
address owner;
}

有了Vehicle结构体,可以定义Vehicle类型的变量,Vehicle类型的array,Vehicle类型的mapping

Vehicle public car; // 定义一辆车
Vehicle[] public cars; // 定义一个array,用来存放Vehicle类型的数据
mappint(address => Vehicle[]); // 定义一个映射,一个地址有一个Vehicle类型的array


function buy(string memory make, string memory model, uint year) external{
Vehicle memory car = Vehicle(make, model, year, msg.sender); // 第一种 直接用参数创建一个实例
Vehicle memory car2 = Vehicle({make:make, model:model, year:year, owner:msg.sender}); // 第二种 把参数放入一个类似python字典的结构中,创建实例
Vehicle memory car3; // 第三种 先创建实例,然后通过点的方式赋值
car3.make = make;
car3.model = model;
car3.year = year;
car3.owner = msg.sender;
carpark.push(car);
carpark.push(car2);
}

完整案例

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

contract Structs{

struct Vehicle{
string make;
string model;
uint year;
address owner;
}

// Vehicle public car;
// Vehicle public car2;
Vehicle[] public carpark;

function buy(string memory make, string memory model, uint year) external{
Vehicle memory car = Vehicle(make, model, year, msg.sender);
Vehicle memory car2 = Vehicle({make:make, model:model, year:year, owner:msg.sender});
Vehicle memory car3;
car3.make = make;
car3.model = model;
car3.year = year;
car3.owner = msg.sender;
carpark.push(car);
carpark.push(car2);
}

function carInfo(uint index) external view returns(string memory, string memory, uint){
Vehicle memory _car = carpark[index];
return(_car.make, _car.model, _car.year);
}

function delCar(uint index) external{
carpark[index] = carpark[carpark.length-1];
carpark.pop();
}

function changeInfo(uint index, string memory make, string memory model, uint year) external{
carpark[index].make = make;
carpark[index].model = model;
carpark[index].year = year;
}

}

枚举

通过关键词enum定义

    enum OrderStatus{
None, // 对应 0
Pending, // 1
Shipped, // 2
Completed,
Rejected,
Cancelled
}

枚举叫做枚举类型,定义好了枚举类型,是可以创建实例的

    struct Order{
address buyer;
OrderStatus orderStatus;
}

完整案例

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

contract OrderSystem{

enum OrderStatus{
None,
Pending,
Shipped,
Completed,
Rejected,
Cancelled
}

struct Order{
address buyer;
OrderStatus orderStatus;
}
Order[] private orders;

function newOrder(address buyer, OrderStatus orderStatus) public{
Order memory order = Order(buyer, orderStatus);
orders.push(order);
}

function changeStatus(uint index, OrderStatus orderStatus) public{
Order memory order = orders[index];
order.orderStatus = orderStatus;
orders[index] = order;
}

// function getStatus(uint index) public view returns(address, OrderStatus){
// Order memory order = orders[index];
// return (order.buyer, order.orderStatus);
// }

function getStatus(uint index) public view returns(Order memory){
Order memory order = orders[index];
return order;
}
}

存储位置

https://docs.soliditylang.org/zh-cn/v0.8.24/types.html#data-location-assignment storage, memory, calldata

对于更新数组里面结构体元素的值,

// 如果只改一两个值,这样方便,也节省gas
tasks[index].completed = true;


Task memory _task = tasks[index];
_task.completed = true; // 如果有多个值要改,就先把元素从列表中取出来,再改,更节省gas
_task.completed = true;
_task.completed = true;
_task.completed = true;
_task.completed = true;

tasks[index] = _task;

事件event

index其实是有4个,但是默认被占用了一个,所以给用户可用的只有三个了 index1是 The keccak256 hash of the event signature.

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

contract MessageSys{

// 定义事件,并指定索引参数
event LogMessage(address indexed sender, address indexed reveive, string indexed message);
// 不指定索引,结果是除了index0外在topics中,其他数据在data中
event noindex(address sender, address reveive, string message);
function sendMessage(address to, string calldata message) external {
emit LogMessage(msg.sender, to, message);
emit noindex(msg.sender, to, message);
}
}

继承

要想函数可以被override,父合约中需要把方法改为virtual

继承+构造函数

contract ContractC is ContractB("QFM"), ContractA{  // 可以在创建合约的时候,给父合约传值
string public text_C;
constructor(string memory _text, string memory cc) ContractA(_text) { // 在子合约的构造函数被调用的时候,声明一下那些是给父合约的构造函数的
text_C = cc;
}
}

应该这种更常用,在子合约的创建的时候一并传入

contract ContractC is ContractB, ContractA{
string public text_C;
constructor(string memory _text, string memory cc, string memory bb) ContractA(_text) ContractB(bb) {
text_C = cc;
}
}

完整示例

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

contract ContractA{
string public name;
constructor(string memory _name){
name = _name;
}
}

contract ContractB{
string public text;
constructor(string memory _text){
text = _text;
}
}

// contract ContractC is ContractB("QFM"), ContractA{
// string public text_C;
// constructor(string memory _text, string memory cc) ContractA(_text) {
// text_C = cc;
// }
// }

contract ContractC is ContractB, ContractA{
string public text_C;
constructor(string memory _text, string memory cc, string memory bb) ContractA(_text) ContractB(bb) {
text_C = cc;
}
}

调用父类的方法

string memory tmp_b = super.too(); //用super调用,最后一个继承的父类中,同名的方法,最后一个就是在合约声明继承的时候,最后一个父合约
or
string memory tmp_b = ContractB.too(); // 指定调用具体父类的方法

完整示例

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

contract ContractA{

uint public age = 20;
function foo() public pure returns(string memory){
return "foo A";
}
function too() public pure virtual returns(string memory){
return "too A";
}
}

contract ContractB is ContractA{
function too() public pure override virtual returns(string memory){
return "too B";
}
}

contract ContractC is ContractA{
function too() public pure override virtual returns(string memory){
return "too C";
}
}

contract ContractD is ContractB, ContractC{
function too() public pure override(ContractB, ContractC) returns(string memory){
return "too D";
}
function call() external pure returns(string memory, string memory) {
string memory tmp_b = ContractB.too();
string memory tmp_c = ContractC.too();
return (tmp_b, tmp_c);
}
function call2() external pure {
super.too();
}
}

可见性

private:仅在定义它们的合约内访问。
internal:在定义它们的合约及其⼦合约中访问。
public:在合约内部和外部都可以访问。
external:只能从其他合约或外部账⼾调⽤。 不能修饰状态变量

private 和 virtual不可同时使用

    function fu1() private virtual pure returns(uint){
return 2;
}

immutable

定义和特性 ◦ 只能在合约部署时初始化 ◦ 初始化后不能改变 相当于丰富了constant 常量,使其一个变量,可以在初始化时被赋值一次。然后就充当了常量的作用了

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.19;
contract A{
// 2492 gas
address public immutable i_owner; // 要加个i_前缀
// 358 gas
uint public constant MINIMUM_NUM = 100; // 要大写
constructor(){
i_owner = msg.sender;
}
function get() external view returns(address){
return i_owner;
}
}

函数相关

函数可见性

https://docs.soliditylang.org/zh-cn/v0.8.24/cheatsheet.html#index-10
public: 内部、外部都可见

private: 仅在当前合约内可见
internal: 同项目可见,仅在内部可见(也就是在当前 Solidity 源代码文件内均可见,不仅限于当前合约内,译者注)
external: 部署完后的调用,仅在外部可见(仅可修饰函数)——就是说,仅可用于消息调用(即使在合约内调用,也只能通过 this.func 的方式)

构造函数

写在合约内部的特色方法,一个合约只能有一个,一般作用为给合约设定初始的状态 constructor 没啥可说的,类似于python的init,其他语言中,当创建一个对象的时候,会有一个与该对象同名的函数,在对象初始化的时候被调用一次,这就是构造函数

contract SimpleStorage{
uint public number;
constructor(uint x){
number = x;
}
}

修饰器

https://docs.soliditylang.org/zh-cn/v0.8.24/cheatsheet.html#index-11
pure 修饰函数时:不允许修改或访问状态变量。

view 修饰函数时:不允许修改状态变量。

payable 修饰函数时:允许从调用中接收以太币。

constant 修饰状态变量时:不允许赋值(除初始化以外),不会占据存储插槽(storage slot)。

immutable 修饰状态变量时:允许在构造时分配并在部署时保持不变。存储在代码中。

anonymous 修饰事件时:不把事件签名作为 topic 存储。

indexed 修饰事件参数时:将参数作为 topic 存储。

virtual 修饰函数和修改时:允许在派生合约中改变函数或修改器的行为。

override 表示该函数、修改器或公共状态变量改变了基类合约中的函数或修改器的行为

函数修饰器

modifier 类似于python里面的装饰器的作用,就是给方法添加一段代码功能

modifier whenNotPaused(){
assert(!paused);
_;
}

函数返回值

返回多个变量,用(,,)括号逗号隔开,类似于python中的元祖 一般写法,显式返回,写出return语句 隐式返回,在定义返回类型的时候写好返回的变量的变量名

contract MultipleOutput{
address public a;
uint public b;
string public c;
address public d;
uint public e;
string public f;

function MultipleOut() private view returns(address, uint, string memory){
return(msg.sender, 234, "hello world");
}

// 隐式返回:将需要返回的变量名写到方法定义的时候。
// 注意配合修饰器,view/pure等
// view/pure 等叫做Modifiers modifier 关键字 称为function modifier
function display() private view returns(address addr, uint num, string memory name){
// addr = address(0);
// addr = msg.sender;
addr = a;
num = 234;
name = "hello world";
}

function CapOut() external {
(a,b,c) = MultipleOut(); // 注意,多返回值需要用()括号
// 如果只需要其中一个值,可以用逗号隔开
// (a,,) = MultipleOut();
(d,e,f) = display();
}
}

new的使用

用来创建合约,和内存定长数组

合约

ContractName newInstance = new ContractName{value: <可选Wei>, gas: <可选Gas>}(构造函数参数...);

完整示例

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

contract Account{
address public owner;
constructor(address _owner) payable{ // 加上payable可以接受eth
owner = _owner;
}
}

contract AccountFactory{
Account[] public accounts;

function createAccount(address owner, uint num) external payable { // 创建Account的时候需要传eth所以这里也是payable
Account acc = new Account{value: num}(owner); // {}是传给evm的参数,表示携带num wei的eth
accounts.push(acc);
}
}

数组

只能在内存中,并且长度固定

Type[] memory arr = new Type[](长度);

示例

function getArray(uint len) public pure returns (uint[] memory) {
uint[] memory arr = new uint[](len); // 长度为 len 的数组
for (uint i = 0; i < len; i++) {
arr[i] = i + 1;
}
return arr;
}

library 库相关

除了正常的作为工具库方法,使代码可以复用以外,还可以增强数据类型,使其包含library中定义的方法 注意library中方法的可见性,只有是internal类型的,才能用using xxx for xxx 的方式来加强一个类型。public,external都得单独部署这个libray

用法

Math.min   Math.max 等直接 库.方法

using ArrayLib for uint[] 增强型用法

实际案例

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

library MathLib{
function min(uint x, uint y) external pure returns(uint){
return x < y ? x:y;
}
}

library ArrayUtils{
function sum(uint[] memory array) external pure returns(uint){
uint tmp;
for (uint i =0; i< array.length; i++){
tmp += array[i];
}
return tmp;
}
}

contract TestLibrarys{
// using MathLib for uint;
using ArrayUtils for uint[];
uint[] public arr = [1,2,3,4,5,6,7,8,9];
function getMin(uint x, uint y) public pure returns(uint){
return MathLib.min(x, y);
}
function getSum() public view returns(uint){
return arr.sum();
}
}
📢 Share this article