W2-1 Solidity基本类型
W2-1 Solidity基本类型
Solidity 概览图
基本类型、数组、结构体、映射
合约的组成
pragma solidity ^0.8.0; // 1.编译器版本声明
contract Counter {// 2.定义合约
uint public counter;// 3.状态变量
constructor() {
counter = 0;}
function count() public {// 4.合约函数
counter = counter + 1;
}
}
Solidity语言
- 静态类型、编译型、高级语言
- 针对EVM专门设计
- 受C++、JavaScript等语言影响
- 如:变量声明、for循环、重载函数等概念来自于C++
- 函数关键字、导入语法来自于JavaScript
- ⽂档
- 中⽂:https://learnblockchain.cn/docs/solidity/
- 英⽂:https://docs.soliditylang.org/
数据类型
值类型
- 布尔
- 整型
- int/uint,uint8 … unit256
- 在使⽤整型时,要特别注意整型的⼤⼩及所能容纳的最⼤值和最⼩值, 如uint8的最⼤值为0xf(255),最⼩值是0 从 solidity 0.6.0 版本开始可以通过 Type(T).min 和 Type(T).max 获得整型的最⼩值与最⼤值。
- 定长浮点型
- 定长字节数组
- 枚举
- 函数类型
- 地址类型
- Solidity使用地址类型来表示一个账号,地址类型有两种形式
- address: 一个20字节的值
- address payable: 表示可支付地址,与address相同也是20字节,不过它有成员函数transfer和send
- 成员函数
<address>.balance(uint256)
: 返回地址的余额<address payable>.transfer(uint256 amount)
: 向地址发送以太币,失败时抛出异常 (gas:2300)<address payable>.send(uint256 amount) returns (bool)
: 向地址发送以太币,失败时返回false
- 3个底层成员函数,通常用于合约交互,直接控制编码的方式调用合约函数
- call
- 切换上下⽂,附加gas {gas: } , 附加value {value: }
addr.call{value: 1ether}("")
功能等价transfer(1 ether)
,但是没有 gas 限制- 失败不是发⽣异常,⼀定要检查返回值
- delegatecall
- 保持上下⽂
- 不⽀持附加value {value: }
- staticcall
- call
- Solidity使用地址类型来表示一个账号,地址类型有两种形式
- 十六进制常量
- 有理数和整型常量
- 字符串常量
- 地址常量
引用类型
值类型赋值时总是完整拷⻉。⽽复杂类型占⽤的空间较⼤(>32个字节),拷⻉开销很⼤,这时就可以使⽤引⽤的⽅式,即通过多个不同名称的变量指向⼀个值。
引⽤类型都有⼀个额外属性来标识数据的存储位置:
- memory(内存): ⽣命周期只存在于函数调⽤期间
- storage(存储): 状态变量保存的位置,gas开销最⼤
- calldata(调⽤数据): ⽤于函数参数不可变存储区域
结构体
使⽤Struct 声明⼀个结构体,定义⼀个新类型。
contract testStruct {
struct Funder {
address addr;
uint amount;}
mapping(uint => Funder) funders;
function contribute(uint id) public payable {
funders[id] = Funder({addr : msg.sender, amount : msg.value});
funders[id] = Funder(msg.sender, msg.value);
}
function getFund(uint id) public view returns (address, uint) {
return (funders[id].addr, funders[id].amount);
}
}
数组
- T[k] : 元素类型为T,固定⻓度为k的数组
- T[] : 元素类型为T,⻓度动态调整
- 数组通过下标进⾏访问,序号是从0开始
- bytes、string 是⼀种特殊的数组
- bytes是动态分配⼤⼩字节的数组,类似于byte[],但是bytes的gas费⽤更低,bytes和string 都可以⽤来表达字符串,对任意⻓度的原始字节数据使⽤bytes,对任意⻓度字符串 (UTF-8)数据使⽤string。
- 成员
- Lengh属性:表示当前数组的⻓度
- push():添加新的零初始化元素到数组末尾,返回引⽤
- push(x): 数组末尾添加⼀个给定的元素
- pop(): 从数组末尾删除元素
注意数组不能过大,导致整个区块的gas都够执行成功
映射类型
- 声明形式:
mapping(KeyType => ValueType)
- 例如:
mapping(address => uint) public balances;
- 例如:
- 使⽤⽅式类似数组,通过 key 访问,例如: balances[userAddr];
- 映射没有⻓度、没有 key 的集合或 value 的集合的概念
- 只能作为状态变量
- 如果访问⼀个不存在的键,返回的是默认值。
pragma solidity >=0.8.0;
contract MappingExample {
mapping(address => uint) public balances;
function update(uint newBalance) public {
balances[msg.sender] = newBalance;
}
}
合约类型
- 每个合约都是⼀个类型,可声明⼀个合约类型。
- 如:Hello c; 则可以使⽤c.sayHi() 调⽤函数
- 合约可以显式转换为address类型,从⽽可以使⽤地址类型的成员函数
使用工厂模式进行部署可以大大节省Gas费用
call跟delegatecall的区别
全局变量及函数
- block.number ( uint ): 当前区块号
- block.timestamp ( uint): ⾃ unix epoch 起始当前区块以秒计的时间戳
- msg.sender ( address ): 消息发送者(当前调⽤)
- msg.value ( uint ): 随消息发送的 wei 的数量
- tx.origin (address payable): 交易发起者(完全的调⽤链)
API介绍
合约函数、函数修改器、函数修饰符,及各类特殊函数
合约
使⽤contract关键字来声明⼀个合约,⼀个合约通常由状态变量、函数、函数修改器以及事件组成。
pragma solidity >=0.8.0;
contract C {
function f(uint a) private pure returns (uint b) {return a + 1;}
function setData(uint a) internal {data = a;}
uint public data;
}
创建合约的⼏个⽅法
- 外部部署(Remix/Hardhat/Truffle)Web.js
- 合约使⽤New
- 最⼩代理合约(克隆):
- https://eips.ethereum.org/EIPS/eip-1167
- https://github.com/optionality/clone-factory
- Create2
C c = new C{salt: _salt}();
合约地址的确认
- 根据创建者(sender)的地址以及创建者发送过的交易数量(nonce)来计算确定
- keccak256(rlp.encode([normalize_address(sender), nonce]))[12:]
- Create2
- keccak256(0xff ++ senderAddress ++ salt ++ keccak256(init_code))[12:]
可见性
使⽤public 、private、external、internal
可⻅性关键字来控制变量和函数是否可以被外部使⽤
- public 内/外
- private 内部
- external 外部访问
- internal 内部及继承
合约函数
- 构造函数(constructor): 初始化逻辑
- 视图函数(view)、纯函数:(pure) 不修改状态,不⽀付⼿续费
- 但同时也要满足区块gas的限制
- getter 函数: 所有 public 状态变量创建 getter 函数
- receive 函数: 接收以太币时回调。
- fallback 函数: 没有匹配函数标识符时, fallback 会被调⽤。
- 函数修改器(modifier): 可⽤来改变⼀个函数的⾏为,如检查输⼊条件、控制访问、重⼊控制
pragma solidity ^0.8.0;
contract testFunc {
address public owner;
uint private deposited;
constructor() {
owner = msg.sender;
}
modifier onlyOwner() {
require(msg.sender == owner, "Not owner");
_;
// 函数修改器修饰函数时,函数体被插⼊到 “_;”
}
fallback() external payable {
deposited += msg.value;}
function getDeposited() public view returns (uint) {
return deposited;}
function withdraw() public onlyOwner {
payable(owner).transfer(deposited);
}
}
重入问题
调用外部函数时,要时刻注意重入问题
sequenceDiagram
User ->>+ A: 1.Func1
A ->>+ B: 2.Func2
B ->>- A: 3.Func1
# ( A ->> A: Re-Entrance)
Note over A,B: Re-Entrance
A ->>- User:
错误处理、合约继承、接口、库及OpenZeppelin合约库
错误处理
- 在程序发⽣错误时的处理⽅式:EVM通过回退状态来处理错误的,以便保证状态修改的事务性
- assert()和require()⽤来进⾏条件检查,并在条件不满⾜时抛出异常
- revert():终⽌运⾏并撤销状态更改
- try/catch: 捕获合约中外部调⽤的异常
继承
- 和⼤多数⾼级语⾔⼀样,Solidity 也⽀持继承
- 使⽤关键字 is
- 继承时,链上实际只有⼀个合约被创建,基类合约的代码会被编译进派⽣合约。
- 派⽣合约可以访问基类合约内的所有⾮私有(private)成员,因此内部(internal)函数和状态变量在派⽣合约⾥是可以直接使⽤的
接口
- 函数的抽象,作为⼀个类型声明,⼴泛⽤于合约之间的调⽤
- 不可以有实现的函数
- 不能继承⾃其他接⼝
- 没有构造⽅法
- 没有状态变量
pragma solidity ^0.8.0;
contract Counter {
uint public count;
function increment() external {
count += 1;
}
}
interface ICounter {
function count() external view returns (uint);
function increment() external;}
contract MyContract {
function incrementCounter(address _counter) external {
ICounter(_counter).increment();
}
function getCount(address _counter) external view returns (uint) {
return ICounter(_counter).count();
}
}
库
- 与合约类似(⼀个特殊合约),是函数的封装,⽤于代码复⽤。
- 如果库函数都是 internal 的,库代码会嵌⼊到合约。
- 如果库函数有external或 public ,库需要单独部署,并在部署合约时进⾏链接,使⽤委托调⽤
- 没有状态变量
- 不能给库发送 Ether
- 给类型扩展功能:Using lib for type; 如: using SafeMath for uint;
pragma solidity ^0.8.0;
library SafeMath {
function add(uint x, uint y) internal pure returns (uint) {
uint z = x + y;
require(z >= x, "uint overflow");
return z;
}
}
contract TestLib {
using SafeMath for uint;
function testAdd(uint x, uint y) public pure returns (uint) {
return x.add(y);
}
}
OpenZeppelin
- 善于复⽤库,不仅提⾼效率,还可以提⾼安全性
- OpenZeppelin 功能丰富、经过反复验证的库函数集合
理解ABI
ABI:Application Binary Interface 应⽤程序⼆进制接⼝
- ABI 接⼝描述: 定义如何与合约交互
- ABI 编码
- 函数选择器:对函数签名计算Keccak-256哈希,取前 4 个字节
bytes4(keccak256("count()")) = 0x06661abd
- 参数编码
- 函数选择器:对函数签名计算Keccak-256哈希,取前 4 个字节
工具
推荐学习资料
- https://ethernaut.openzeppelin.com/
- https://cryptozombies.io/en/course/
- https://solidity-by-example.org/
- https://learnblockchain.cn/column/1
This post is licensed under CC BY 4.0 by the author.