Solidity学习笔记1


Solidity笔记

原项目参考:https://github.com/AmazingAng/WTF-Solidity


1、开发工具:Remix

https://remix.ethereum.org


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

  • 包含pureview关键字的函数不改写链上状态,调用不消耗gas。
  • pureview函数调用pureview函数需要消耗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),由于这类变量比较复杂,占用存储空间大,我们在使用时必须要声明数据存储的位置。

  • 数据位置storagememorycalldata,不同存储位置的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) 当前区块的gaslimit
    • block.number: (uint) 当前区块的number
    • block.timestamp: (uint) 当前区块的时间戳,为unix纪元以来的秒
    • gasleft(): (uint256) 剩余 gas
    • msg.data: (bytes calldata) 完整call data
    • msg.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: 1
    • gwei: 1e9 = 1000000000
    • ether: 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: 1
    • minutes: 60 seconds = 60
    • hours: 60 minutes = 3600
    • days: 24hours = 86400
    • weeks: 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});
}

文章作者: ye
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 ye !
  目录