Solidity笔记
原项目参考:https://github.com/AmazingAng/WTF-Solidity
1、开发工具:Remix
2、数据类型
- **值类型(Value Type)**:包括布尔型,整数型等等,这类变量赋值时候直接传递数值。
- **引用类型(Reference Type)**:包括数组和结构体,这类变量占空间大,赋值时候直接传递地址(类似指针)。
- 映射类型(Mapping Type): Solidity中存储键值对的数据结构,可以理解为哈希表。
// 布尔
boool public _boll = true;
// 整型
int public _int = -1;
uint public _uint = 1; // 正整数
uint256 public _number = 20220330; // 256位正整数
// 地址
address public _address = 0x7A58c0Be72BE218B41C608b7Fe7C5bB630736C71;
address payable public _address1 = payable(_address); // payable address,可以转账、查余额
// 地址类型成员
uint256 public balance = _address1.balance;
// 定长字节数组
bytes32 public _byte32 = "MiniSolidity";
bytes1 public _byte = _byte32[0];
// 枚举
enum ActionSet { Buy, Hold, Sell }
ActionSet action = ActionSet.Buy;
// enum可以和uint显式的转换
function enumToUint() external view returns(uint) {
return uint(action);
}
3、函数
function <function name>(<parameter types>) {internal|external|public|private} [pure|view|payable] [returns (<return types>)]
public
:内部和外部均可见。private
:只能从本合约内部访问,继承的合约也不能使用。external
:只能从合约外部访问(但内部可以通过this.f()
来调用,f
是函数名)。internal
: 只能从合约内部访问,继承的合约可以用。
3.1、Pure和View
- 包含
pure
和view
关键字的函数不改写链上状态,调用不消耗gas。 - 非
pure
和view
函数调用pure
和view
函数需要消耗gas。
pragma solidity ^0.8.4;
contract FunctionTypes {
uint256 public number = 5;
}
// 定义一个add
function add() external {
number = number + 1;
}
// Pure:不能读写
// 如果add被标记为pure,如function add() pure external,就会报错
// 使用pure标记,可以给函数传递一个参数,然后返回,这样就不会报错
function addPure(uint256 _number) external pure returns(uint256 new_number) {
new_number = _number + 1;
}
// View:能读不能写
// 用一个新变量返回
function addView() external view reuturns(uint256 new_number) {
new_number = number +1;
}
3.2、payable
- 能给合约支付eth的函数。
// internal:内部函数
function minus() internal {
number = number - 1;
}
// external:合约外部访问
function minusCall() external {
minus(); // 再调用内部
}
function minusPayable() external payable returns(uint256 balance) {
minus();
balance = address(this).balance;
}
4、返回值
return
:跟在函数名后面,用于声明返回的变量类型及变量名。returns
:用于函数主体中,返回指定的变量。
function returnMultiple() public pure returns(uint256, bool, uint256[3] memory) { // memory:存放内存中,运行完销毁
return(1, true, [uint256(1), 2, 5]);
}
// 事实上,returns中标明返回变量的名称,Solidity会自动初始化,无需使用return
function returnNamed() public pure returns(uint256 _number, bool _bool, uint256[3] memory _array) {
_number = 2;
_boll = false;
_array = [uint256(3), 2, 1];
}
// 解构式赋值(这个可以类比python)
uint256 _number;
bool _bool;
uint256[3] memory _array;
(_number, _bool, _array) = returnNamed();
(, _bools2, ) = returnNamed();
5、变量数据存储和作用域storage/memory/calldata
**引用类型(Reference Type)**:包括数组(
array
)和结构体(struct
),由于这类变量比较复杂,占用存储空间大,我们在使用时必须要声明数据存储的位置。数据位置:
storage
,memory
和calldata
,不同存储位置的gas不同。storage
:存储在链上(类似硬盘),消耗的gas多,合约里的状态变量默认都是storage。memory
:存储在内存,不上链,函数里的参数和临时变量一般用memory
。calldata
:和memory
类似,存储在内存,不上链,与memory
的区别是calldata
不能修改(immutable
),一般用于函数参数。
function fCalldata(uint[] calldata _x) public pure returns(uint[] calldata) {
// _x[0] = 0; // 报错
return(_x);
}
5.1、数据位置和赋值规则
- 在不同存储类型相互赋值时候,有时会产生独立的副本(修改新变量不会影响原变量),有时会产生引用(修改新变量会影响原变量)。
- 赋值本质上是创建引用指向本体,因此修改本体或者是引用,变化可以被同步。
storage
(合约的状态变量)赋值给本地storage
(函数里的)时候,会创建引用,改变新变量会影响原变量。memory
赋值给memory
,会创建引用,改变新变量影响原变量。- 其他情况下,赋值创建的是本体的副本,修改不会同步。
uint[] x = [1, 2, 3];
function fStorage() public {
// 声明一个storage的变量xStorage,指向x,修改xStorage
uint[] storage xStorage = x;
xStorage[0] = 100;
}
5.2、变量的作用域
状态变量(state variable)
:数据存储在链上,所有合约内函数均可访问,gas消耗高。
// 在合约内,函数外声明
contract Variables {
uint public x = 1;
uint public y;
string public z;
}
// 函数体内改变值
function foo() external {
x = 5;
y = 2;
z = "0xAA";
}
局部变量(local variable)
:仅在函数执行过程中有效的变量,函数退出后,变量失效,数据存储在内存,不上链,gas低。
function bar() external pure returns(uint) {
uint xx = 1;
uint yy = 3;
uint zz = xx + yy;
return(zz);
}
全局变量(global variable)
:都是Solidity
预留的关键字,可以在这里查询。下面是一些常用的全局变量:blockhash(uint blockNumber)
: (bytes32
)给定区块的哈希值 – 只适用于256最近区块, 不包含当前区块。block.coinbase
: (address payable
) 当前区块矿工的地址block.gaslimit
: (uint
) 当前区块的gaslimitblock.number
: (uint
) 当前区块的numberblock.timestamp
: (uint
) 当前区块的时间戳,为unix纪元以来的秒gasleft()
: (uint256
) 剩余 gasmsg.data
: (bytes calldata
) 完整call datamsg.sender
: (address payable
) 消息发送者 (当前 caller)msg.sig
: (bytes4
) calldata的前四个字节 (function identifier)msg.value
: (uint
) 当前交易发送的wei
值
function global() external view returns(address, uint, bytes memory) {
address sender = msg.sender;
uint blockNum = block.number;
bytes memory data = msg.data;
return(sender, blockNum, data);
}
以太单位
:Solidity不存在小数点,以0代替为小数点确保交易精度并防止精度损失。wei
: 1gwei
: 1e9 = 1000000000ether
: 1e18 = 1000000000000000000
function weiUnit() external pure returns(uint) {
assert(1 wei == 1e0);
assert(1 wei == 1);
return 1 wei;
}
function gweiUnit() external pure returns(uint) {
assert(1 gwei == 1e9);
assert(1 gwei == 1000000000);
return 1 gwei;
}
function etherUnit() external pure returns(uint) {
assert(1 ether == 1e18);
assert(1 ether == 1000000000000000000);
return 1 ether;
}
时间单位
:可以在合约中规定一个操作的执行时间或时间范围等,有助于提高合约的可读性和可维护性。seconds
: 1minutes
: 60 seconds = 60hours
: 60 minutes = 3600days
: 24hours = 86400weeks
: 7 days = 604800
function secondsUnit() external pure returns(uint) {
assert(1 seconds == 1);
return 1 seconds;
}
function minutesUnit() external pure returns(uint) {
assert(1 minutes == 60);
assert(1 minutes == 60 seconds);
return 1 minutes;
}
function hoursUnit() external pure returns(uint) {
assert(1 hours == 3600);
assert(1 hours == 60 minutes);
return 1 hours;
}
function daysUnit() external pure returns(uint) {
assert(1 days == 86400);
assert(1 days == 24 hours);
return 1 days;
}
function weeksUnit() external pure returns(uint) {
assert(1 weeks == 604800);
assert(1 weeks == 7 days);
return 1 weeks;
}
6、引用类型
6.1、数组array
// 固定长度数组:声明时指定长度 T[k]
uint[8] array1;
bytes1[5] array2;
address[100] array3;
// 可变长度(动态)数组:声明时不指定数组的长度 T[]
uint[] array4;
bytes1[] array5;
address[] array6;
bytes array7; // bytes比较特殊,本身就是数组
// 对于用memory修饰的动态数组,可以用new来创建,但须声明长度,且长度不可改变
// 动态数组需要一个个赋值
uint[] memory array8 = new uint[](5);
bytes memory array9 = new bytes(9);
- 数组字面常数(Array Literals)是写作表达式形式的数组,用方括号包着来初始化array的一种方式,并且里面每一个元素的type是以第一个元素为准的,例如
[1,2,3]
里面所有的元素都是uint8
类型,因为在solidity中如果一个值没有指定type的话,默认就是最小单位的该type,这里uint
的默认最小单位类型就是uint8
。而[uint(1),2,3]
里面的元素都是uint
类型,因为第一个元素指定了是uint
类型了,我们都以第一个元素为准。
// 这段代码对传入g()的数组进行uint转换,会报错
contract C {
function f() public pure {
g([uint(1), 2, 3]);
}
function g(uint[3] memory _data) public pure {
// ...
}
}
- 数组成员
length
:数组有一个包含元素数量的length
成员,memory
数组的长度在创建后是固定的。push()
:动态数组
拥有push()
成员,可以在数组最后添加一个0
元素,并返回该元素的引用。push(x)
:动态数组
拥有push(x)
成员,可以在数组最后添加一个x
元素。pop()
:动态数组
拥有pop()
成员,可以移除数组最后一个元素。
6.2、结构体struct
Solidity
支持通过构造结构体的形式定义新的类型。结构体中的元素可以是原始类型,也可以是引用类型;结构体可以作为数组或映射的元素。
struct Student {
uint256 id;
uint256 score;
}
Student student; // 初始化
// 给结构体赋值
// 方法1:在函数中创建一个storage的struct的引用
function initStudent1() external {
Student storage _student = student;
_student.id = 11;
_student.score = 100;
}
// 方法2:直接引用状态变量的struct
function initStudent2() external {
student.id = 12;
student.score = 95;
}
// 方法3:构造函数式
function initStudent3() external {
student = Student(13, 88);
}
// 方法4:key-value
function initStudent4() external {
student = Student({id: 14, score: 80});
}