# Solidity8_perfect
**Repository Path**: jacky2code/solidity8_perfect
## Basic Information
- **Project Name**: Solidity8_perfect
- **Description**: Solidity 8.0 精通教程
- **Primary Language**: Unknown
- **License**: GPL-2.0
- **Default Branch**: master
- **Homepage**: None
- **GVP Project**: No
## Statistics
- **Stars**: 3
- **Forks**: 4
- **Created**: 2022-04-21
- **Last Updated**: 2022-11-18
## Categories & Tags
**Categories**: Uncategorized
**Tags**: None
## README
# Solidity 8.0 perfect
## 1. Environment 环境部署
部署开发环境,及本地的发布测试环境
### 1.1 Develop 开发环境
本教程使用 vscode ,在插件市场中下载 Ethereum Remix 插件,自动安装所需插件。编译版本选择 0.8.7及以上
### 1.2 Testnet 发布测试环境
下载安装 Ganache 选择 quickstart 部署本地发布测试环境
## 2. HelloWorld
- 代码部分
创建文件夹 solidity8_perfect,在根目录创建 HelloWorld.sol 文件
``` solidity
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.7;
contract HelloWorld {
string public myString = "Hello world!";
}
```
- 编译 (compile)
- 点击 Run & Deploy 先连接本地测试环境,获取 Ganache 中RPC Server 地址,默认 http://127.0.0.1:7545
- compile
输出显示成功
``` bash
[4:51:24 PM]: Loading remote version v0.8.7+commit.e28d00a7... please wait
[4:51:35 PM]: Remote version v0.8.7+commit.e28d00a7 loaded.
[4:51:36 PM]: Compilation finished for token/solidity/solidity8_perfect/HelloWorld.sol with solidity version 0.8.7+commit.e28d00a7.Emscripten.clang.
```
- 部署 (deploy)
- ABI 其中生成合约的 ABI,可复制用于其他 Dapp 项目
- 合约地址:Deployed Contranct Address
- CALL 输出内容:
``` bash
[5:00:11 PM]: Calling method 'myString' with [] from 0xa1A59E6D9B69D50aaF7ead540f5bE888B6ee47b3 at contract address 0x64342db66D48fd4e7923828608aa3ee77BFc76e9
[5:00:11 PM]: "hello world!"
```
## 3. Types 变量类型
bool, uint, int, address, bytes32
- bool
```solidity
bool public b = true;
```
- uint
- uint 是 uint256 默认缩写,为了好的代码习惯,最好写成uint256,以辨认范围
- 无符号整数,正整数,没有负数
- 范围 0 to 2 ** 256 - 1
- 其他范围
- uint8: 范围 0 to 2 ** 8 - 1
- uint16: 范围 0 to 2 ** 16 - 1
- int
- int 是 int256 的缩写
- 范围 -2 ** 255 to 2 ** 255 - 1
- 其他范围
- int128:-2 ** 127 to 2 ** 127 - 1
- address
address 是一个16进制数字,通过私钥公钥计算出来
``` solidity
address public addr = 0x08655Ac0d18E0a77C04cdec8bd53A38a925d27f6;
```
- bytes32
``` solidity
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.7;
// Data types -- value and references
contract Types {
// bool
bool public b = true;
// uint = uint256 范围 0 to 2**256 -1
// uint8 范围 0 to 2**8 - 1
// uint16 范围 0 to 2**16 - 1
uint256 public u256 = 2 ** 256 - 1;
uint8 public u8 = 2 ** 8 - 1;
uint16 public u16 = 2 ** 16 -1;
// int = int256 范围 -2**255 to 2**255 -1
int256 public i256 = -123;
// int 最小值
int public minInt = type(int).min;
// int 最大值
int public maxInt = type(int).max;
// address 是一个16进制数字,通过私钥公钥计算出来
address public addr = 0x08655Ac0d18E0a77C04cdec8bd53A38a925d27f6;
bytes32 public b32 = 0x657468657265756d000000000000000000000000000000000000000000000000;
}
```
## 4. Functions 函数
函数的类型有两类,默认的是内部函数,因此不需要声明 internal 关键字
- 内部函数类型(internal)
内部函数只能在当前合约内被调用(在当前代码块内,包括内部库函数和继承的函数中),因为它们不能在当前合约上下文的外部被执行。
- 外部函数类型(external)
外部函数由一个地址和一个函数签名组成,可以通过外部函数调用传递或者返回。
关键字
- pure 纯函数,不能够读写状态变量,只能拥有局部变量
- view 能读写状态变量、读取全局变量
``` solidity
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.7;
// Functions
// 函数类型有两类:
// 函数类型默认是内部函数,因此不需要声明 internal 关键字
// 内部函数类型(internal)
// 内部函数只能在当前合约内被调用(在当前代码块内,包括内部库函数和继承的函数中),因为它们不能在当前合约上下文的外部被执行。
// 外部函数类型(external)
// 外部函数由一个地址和一个函数签名组成,可以通过外部函数调用传递或者返回。
contract Functions {
// external : 外部函数,只能在外部读取的函数
// pure : 纯函数,不能够读写状态变量,只能拥有局部变量
function add(uint256 x, uint256 y) external pure returns (uint256) {
return x + y;
}
function sub(uint256 x, uint256 y) external pure returns (uint256) {
if(x 10
require(i <= 10, "i > 10");
// more code...
}
// 在控制代码中直接抛出错误
function testRevert(uint i) public pure {
if(i > 10) {
// 直接抛出错误信息
revert("i > 10");
}
}
uint public num = 123;
// 断言,成功则执行后面代码,否则不执行(不包含报错信息),一般用智能合约写测试
function testAssert() public view {
assert(num == 123);
// more code...
}
// custom error
error MyError(address caller, uint i);
function testCustomError(uint _i) public view {
if(_i > 10)
{
revert MyError(msg.sender, _i);
}
}
}
```
## 13.Function modifier 函数修改器
在调用 modifier 的函数中(代码前或者代码后)复用报错代码
- basic:基本用法
``` solidity
// ------ 原始函数 ------
function inc() external {
require(!paused, "paused");
count += 1;
}
function dec() external {
require(!paused, "paused");
count -= 1;
}
// ------ end -------
// ------ 使用函数修改器 ------
// 把报错提取出来作为一个复用的函数修改器
modifier whenNotPaused() {
require(!paused, "paused");
// 下划线位置表示,调用函数的其他代码要在修改器的什么位置运行
_;
}
function incNew() external whenNotPaused {
count += 1;
}
function decNew() external whenNotPaused {
count -= 1;
}
// ------ end -------
```
- inputs:带参数的用法
``` solidity
// ----- inputs:带参数的函数修改器-原始函数 ------
function incBy(uint _x) external whenNotPaused {
require(_x < 100, "x >= 100");
count += _x;
}
// ------ end -------
// ----- inputs:带参数的函数修改器 ------
modifier cap(uint _x) {
// 在修改器中检查
require(_x < 100, "x >= 100");
_;
}
function incByNew(uint _x) external whenNotPaused cap(_x) {
count += _x;
}
// ------ end -------
```
- sandwich:三明治用法
代码执行顺序
- 先运行sandwich修改器中 _ 以上的代码(count += 10;)
- 运行foo函数中的代码 (count += 1;)
- 再运行sandwich修改器中 _ 以下的代码 (count *= 2;)
``` solidity
// ------ sandwich ------
modifier sandwich() {
// code here
count += 10;
_;
// more code here
count *= 2;
}
// 代码运行顺序
// 0: 先运行sandwich修改器中 _ 以上的代码
// 1: 运行foo函数中的代码
// 2: 运行sandwich修改器中 _ 以下的代码
function foo() external sandwich {
count += 1;
}
// ------ end -------
```
完整代码,注意代码执行顺序。
``` solidity
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.7;
/**
* Function modifier 函数修改器
* - reuse code before and / or after function
* Basic:基本用法
* inputs:带参数的用法
* sandwich:三明治用法
*/
contract FunctionModifier {
bool public paused;
uint public count;
function setPause(bool _paused) external {
paused = _paused;
}
// ------ 原始函数 ------
function inc() external {
require(!paused, "paused");
count += 1;
}
function dec() external {
require(!paused, "paused");
count -= 1;
}
// ------ end -------
// ------ 使用函数修改器 ------
// 把报错提取出来作为一个复用的函数修改器
modifier whenNotPaused() {
require(!paused, "paused");
// 下划线位置表示,调用函数的其他代码要在修改器的什么位置运行
_;
}
function incNew() external whenNotPaused {
count += 1;
}
function decNew() external whenNotPaused {
count -= 1;
}
// ------ end -------
// ----- inputs:带参数的函数修改器-原始函数 ------
function incBy(uint _x) external whenNotPaused {
require(_x < 100, "x >= 100");
count += _x;
}
// ------ end -------
// ----- inputs:带参数的函数修改器 ------
modifier cap(uint _x) {
// 在修改器中检查
require(_x < 100, "x >= 100");
_;
}
function incByNew(uint _x) external whenNotPaused cap(_x) {
count += _x;
}
// ------ end -------
// ------ sandwich ------
modifier sandwich() {
// code here
count += 10;
_;
// more code here
count *= 2;
}
// 代码运行顺序
// 0: 先运行sandwich修改器中 _ 以上的代码
// 1: 运行foo函数中的代码
// 2: 运行sandwich修改器中 _ 以下的代码
function foo() external sandwich {
count += 1;
}
// ------ end -------
}
```
## 14. Constructor 构造函数
构造函数:只在合约部署的时候调用一次,之后不再调用
``` solidity
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.7;
/**
* Constructor - 构造函数
* 只在合约部署的时候调用一次,之后不再调用
*/
contract Constructor {
address public owner;
uint public x;
constructor(uint _x) {
// 让 owner 是合约的部署者
owner = msg.sender;
// 用户输入 x 值
x = _x;
}
}
```
## 15. Ownable 权限管理合约
- 通过构造函数设置合约管理员
- 添加函数修改器,设置必要函数必须通过管理员调用
- 设置新的管理员方法,必须是管理员调用,并且地址不能是0地址,不然会被锁死
``` solidity
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.7;
/**
* Ownable 权限管理合约
* 重新设置合约拥有者
* 只有合约拥有者能调用的函数
* 其他人可以调用的函数
*/
contract Ownable {
address public owner;
// 构造函数
constructor() {
owner = msg.sender;
}
// 函数修改器
modifier onlyOwner() {
// 调用者必须是合约拥有者
require(msg.sender == owner, "not owner");
_;
}
// 设置新的所有者
function setNewOwner(address _newOwner) external onlyOwner {
// 不可以是 0 地址
require(_newOwner != address(0), "invalid address");
owner = _newOwner;
}
// 只有管理员可以调用
function onlyOwnerCanCallThisFunc() external onlyOwner {
// more code
}
// 任何人可以调用
function anyOneCanCallThisFunc() external {
// more code
}
}
```
## 16. Function outputs 函数返回值
``` solidity
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.7;
/**
* 函数返回值
*/
contract FunctionOutputs {
// 普通返回值函数
function returnManay() public pure returns (uint, bool) {
return (1, true);
}
// 返回值命名
function named() public pure returns (uint x, bool b) {
return (1, true);
}
// 隐式返回,直接通过参数名赋值
function namedAssigned() public pure returns (uint x, bool b) {
x = 1;
b = false;
}
// 获取返回值
function destructingAssigments() public pure {
(uint x, bool b) = returnManay();
// 只取一个返回值
(, bool c) = returnManay();
}
}
```
## 17. Array 数组
数组分类
- Dynamic Array 动态数组
- Fixed size Array 定长数组
数组操作
* Initialization 初始化
* push 插入
* get 获取
* update 更新
* delete 删除
* pop 弹出
* length 长度
* Creating array in memory 内存中创建数组
* Returning array from function 函数返回数组
``` solidity
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.7;
/**
* 数组
* 数组分类
* - Dynamic Array 动态数组
* - Fixed size Array 定长数组
* 数组操作
* Initialization 初始化
* push 插入
* get 获取
* update 更新
* delete 删除
* pop 弹出
* length 长度
* Creating array in memory 内存中创建数组
* Returning array from function 函数返回数组
*/
contract Array {
// 动态数组
uint[] public nums = [1, 2, 3];
// 定长数组
uint[5] public numsFixed = [4, 5, 6, 7, 8];
function examples() external {
// 向数组尾部推入数据
nums.push(4); // [1, 2, 3, 4]
uint x = nums[0]; // x = 1
nums[2] = 777; // [1, 2, 777, 4]
// 删除数组的值,但不会更改长度,赋 0
delete nums[1]; // [1, 0, 777, 4]
// 弹出数组最后一位数据
nums.pop(); // [1, 2, 777]
uint len = nums.length;
// 内存中创建数组
uint[] memory a = new uint[](5);
// 内存中只能定义定长数组,根据索引赋值,不能pop或push
a[1] = 123;
}
// 返回数组所有内容,内存类型
function returnArray() external view returns (uint[] memory) {
return nums;
}
}
```
## 18. * Array Shift 删除数组元素通过移位/替换
- 通过移位删除数组元素
- 实现思路:先把 index 后的值前移 1 位,然后再 pop 掉最后一位元素
- 示例:[1, 2, 3] --> remove(1) --> [1, 3, 3] --> [1, 3]
- 优点:数组顺序没有变化
- 缺点:消耗大量gas
- 通过替换删除数组元素
- 实现思路:把要删除的元素数据用最后一个元素数据替换,最后再pop掉最后一个数据
- 示例:[1, 2, 3, 4] --> removeNew(1) --> [1, 4, 3, 4] --> [1, 4, 3]
- 优点:消耗少量gas
- 缺点:数组顺序发生变化
``` solidity
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.7;
contract ArrayShift {
uint[] public arr;
function example() public {
arr = [1, 2, 3];
delete arr[1]; // [1, 0, 3]
}
// 实现 remove 数组中的某个值。(数组的顺序不变,但是比较消耗gas)
// 例如
// [1, 2, 3] --> remove(1) --> [1, 3, 3] --> [1, 3]
// 实现思路 先把 index 后的值前移 1 位,然后再 pop 最后一位
function remove(uint _index) public {
require(_index < arr.length, "index out of array range!");
for (uint i = _index; i < arr.length; i++) {
// 从需要替换的位置开始,后一个值赋值给前一个值
arr[i] = arr[i + 1];
}
// 弹出最后一个值,实现数组顺序不变
arr.pop();
}
// 实现方法二:比较少消耗gas,但是顺序打乱了
// 实现思路:把要删除的数据用最后一个数据替换,最后再pop掉最后一个数据
// [1, 2, 3, 4] --> removeNew(1) --> [1, 4, 3, 4] --> [1, 4, 3]
function removeNew(uint _index) public {
require(_index < arr.length, "index out of array range!");
// 数组最后一个值,赋值给要删除的值
arr[_index] = arr[arr.length - 1];
arr.pop();
}
// 测试
function testNew() external {
arr = [1, 2, 3, 4];
// remove(1);
removeNew(1);
assert(arr.length ==3);
assert(arr[0] == 1);
assert(arr[1] == 4);
assert(arr[2] == 3);
}
}
```
## 19. Mapping 映射
如何声明映射
- 声明简单映射
``` solidity
// 地址-余额 映射
mapping(address => uint) public balances;
```
- 声明嵌套映射
```solidity
// 多重映射
mapping(address => mapping(address => bool)) public isFirend;
```
操作映射
- set
```solidity
balances[msg.sender] = 1234; // 赋值
```
- get
```solidity
uint bal = balances[msg.sender]; // 获取值
```
- delete
```solidity
delete balances[msg.sender]; // 删除后,变成默认值 0
```
从查找是否有 "tom" 这个值
- 第一种
- 示例:["jacky1", "jacky2", "jacky3", "jacky4"]
- 思路:挨个循环查找,消耗大量gas;
- 第二种
- 示例:["jacky1":true, "jacky2":true, "jacky3":true, "jacky4":true]
- 思路:键值映射 true,只要判断arr["tom"]是否true就可以。
``` solidity
// SPDX-License-Identifier: GPL-3.0
pragma solidity >=0.8.7 <0.9.0;
// Mapping
// How to declare mapping (simple and nested)
// Set, get, delete
contract Mapping {
// 地址,余额映射
mapping(address => uint) public balances;
// 多重映射
mapping(address => mapping(address => bool)) public isFirend;
function examples() external {
// 赋值
balances[msg.sender] = 1234;
// 获取值
uint bal = balances[msg.sender];
uint bal2 = balances[address(1)]; // 不存在应设置,默认返回 uint 默认值0
balances[msg.sender] += 456; // 123 + 456
delete balances[msg.sender]; // 删除后,变成默认值 0
isFirend[msg.sender][address(this)] = true;
}
}
```
## 20. IterableMapping 可迭代映射
``` solidity
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.7;
/**
* IterableMapping 可迭代映射
*/
contract IterableMapping {
// 地址/余额
mapping(address => uint) public balances;
// 地址/是否存在
mapping(address => bool) public isInserted;
// 所有存在地址
address[] public keys;
/** 添加地址 */
function set(address _key, uint _val) external {
// 给地址赋值余额
balances[_key] = _val;
// 判断地址是否在映射中
if (!isInserted[_key]) {
// 添加映射
isInserted[_key] = true;
// 添加存在地址
keys.push(_key);
}
}
/** 获取地址数组长度 */
function getSize() external view returns (uint) {
return keys.length;
}
/** 获取第一个地址余额 */
function getFirstAddressBal() external view returns (uint) {
return balances[keys[0]];
}
/** 获取最后一个地址的余额 */
function getLastAddressBal() external view returns (uint) {
return balances[keys[keys.length - 1]];
}
/** 获取任意位置地址的余额 */
function getAddressBalAtIndex(uint _index) external view returns (uint)
{
return balances[keys[_index]];
}
/** 遍历所有地址余额,getSize + getAddressBalAtIndex 结合使用 */
}
```
## 21. Structs 结构体
``` solidity
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.7;
// Structs 结构体
contract Structs {
struct Car {
string model;
uint year;
address owner;
}
// 单辆车
Car public car;
// 多辆车
Car[] public cars;
// 某人有多辆车
mapping(address => Car[]) public carsByOwner;
function example() external {
Car memory ec6 = Car("NIO EC6", 2021, msg.sender);
Car memory et7 = Car({year: 2022, model: "NIO ET7", owner: msg.sender});
Car memory es8;
es8.model = "NIO ES8";
es8.year = 2019;
es8.owner = msg.sender;
cars.push(ec6);
cars.push(et7);
cars.push(es8);
// 取出到内存中
Car memory _car = cars[1];
_car.year;
// 取出到存储中
Car storage _carModify = cars[0];
_carModify.year = 2020;
delete _carModify.owner;
delete cars[1];
}
}
```
## 22. Enum 枚举
``` solidity
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.7;
// Enum 枚举
contract Enum {
enum Status {
None, // 第一个值是默认值
Pending, // 处理中
Shipped, // 装载
Completed, // 已完成
Rejected, // 拒绝
Canceled // 取消
}
Status public status;
struct Order {
address buyer;
Status status;
}
Order[] public orders;
function get() external view returns (Status) {
return status;
}
function set(Status _status) external {
status = _status;
}
function ship() external {
status = Status.Shipped;
}
function reset() external {
delete status; // 恢复默认值 None
}
}
```
## 23. 部署 Constract
## 24. Data Location 数据存储位置
- storage,用于状态变量
- memory,用于局部变量
- calldata,只用于输入参数,可以节约gas
``` solidity
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.7;
/**
* 数据存储位置
* - storage,用于状态变量
* - memory,用于局部变量
* - calldata,只用于输入参数,可以节约gas
*/
contract DataLocations {
struct MyStruct {
uint foo;
string text;
}
mapping(address => MyStruct) public myStructs;
function examples(uint[] memory y, string memory s) external returns (uint[] memory) {
myStructs[msg.sender] = MyStruct(123, "bar");
MyStruct storage myStruct = myStructs[msg.sender];
// 修改后,状态变量的值随之改变
myStruct.text = "newBar";
MyStruct memory myStructMem = myStructs[msg.sender];
// 修改后,状态变量不改变,只不过是局部变量myStructMem改变,并随着函数执行完而从内存中消失
myStructMem.foo = 235;
uint[] memory memArr = new uint[](3);
memArr[0] = 345;
return memArr;
}
function examples2(uint[] calldata y, string calldata s) external returns (uint[] memory) {
myStructs[msg.sender] = MyStruct(123, "bar");
MyStruct storage myStruct = myStructs[msg.sender];
// 修改后,状态变量的值随之改变
myStruct.text = "newBar";
MyStruct memory myStructMem = myStructs[msg.sender];
// 修改后,状态变量不改变,只不过是局部变量myStructMem改变,并随着函数执行完而从内存中消失
myStructMem.foo = 235;
_internal(y);
uint[] memory memArr = new uint[](3);
memArr[0] = 345;
return memArr;
}
function _internal(uint[] calldata y) private pure {
uint x = y[0];
x++;
}
}
```
## 25. Simple storage 简单存储
``` solidity
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.7;
// 简单存储
contract SimpleStorage {
string public text;
// 存储字符串:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
// 使用 calldata 花费 89626 gas
// 使用 memory 花费 90114 gas
function set(string calldata _text) external {
text = _text;
}
// 智能合约将状态变量拷贝到内存中再返回
function get() external view returns (string memory) {
return text;
}
}
```
## 26. Todo list 待办事项列表
``` solidity
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.7;
/**
* 待办事项列表
* - Insert, update, read from array of structs
*/
contract TodoList {
// 事项结构体
struct Todo {
string text;
// 待办状态
bool completed;
}
// 待办数组
Todo[] public todos;
/** 创建待办事项 */
function create(string calldata _text) external {
todos.push(Todo({
text: _text,
completed: false
}));
}
/**
* 更新待办事项
* _index: 待办事项索引
* _text: 更新内容
*/
function updateText(uint _index, string calldata _text) external {
// 35138 gas 如果只更新一个属性,则节省gas,更新多个多次装入内存反而消耗更多gas
todos[_index].text = _text;
// todos[_index].text = _text;
// todos[_index].text = _text;
// todos[_index].text = _text;
// 34578 gas 更新多个属性则节省gas
// Todo storage todo = todos[_index];
// todo.text = _text;
// todo.text = _text;
// todo.text = _text;
// todo.text = _text;
}
/**
* 获取待办事项
* params
* _index: 事项索引
*/
function get(uint _index) external view returns (string memory, bool) {
// storage - 29397 直接从状态变量中拷贝过来
Todo storage todo = todos[_index];
// memory - 29480 从状态变量中拷贝到内存
// Todo memory todo = todos[_index];
return (todo.text, todo.completed);
}
/**
* 更新是否完成
* _index: 事项索引
*/
function toggleCompleted(uint _index) external {
todos[_index].completed = !todos[_index].completed;
}
}
```
## 27. Event 事件
``` solidity
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.7;
/**
* 事件:记录当前智能合约运行状态
* 事件存储更节约gas
*/
contract Event {
// 声明事件
event Log(string message, uint val);
// 带有索引的事件,最多不超过3个索引
event IndexedLog(address indexed sender, uint val);
function example() external {
// 触发事件
emit Log("foo", 123);
emit IndexedLog(msg.sender, 345);
}
event Message(address _from, address _to, string message);
function sendMsg(address _to, string calldata message) external {
emit Message(msg.sender, _to, message);
}
}
```
## 28. is 继承
B 合约继承 A 合约,A 合约中的逻辑,复用到B合约中,节省代码量
关键字:
- 父合约关键字:virtual
- 子合约关键字:override
``` solidity
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.7;
contract A {
function foo() public pure virtual returns (string memory) {
return "A";
}
function bar() public pure virtual returns (string memory) {
return "A";
}
function baz() public pure returns (string memory) {
return "A";
}
}
// B 继承 A
// A 合约中的逻辑,复用到B合约中,节省代码量
// 父合约关键字 virtual,子合约关键字 override
contract B is A {
function foo() public pure override returns (string memory) {
return "B";
}
function bar() public pure override returns (string memory) {
return "B";
}
// B合约因为继承A合约仍然包含 baz() 函数
}
```
## 29. 多线继承,向父级函数的构造函数传参
- 多线继承,要把继承少的基础合约写在更前面,例如:
X、Y、Z 三个合约中,Y 继承 X,Z 继承X和Y,这Z合约要写成 contract Z is X,Y {}
- 继承后向父级函数的构造函数传参
- 方法一:直接在函数定义继承是传递参数
```solidity
contract U is S("s"), T("t") {}
```
- 方法二:在继承函数的构造方法中定义参数
``` sol
contract V is S, T {
constructor(string memory _name, string memory _text) S(_name) T(_text) {
}
}
```
- 调用父级合约的函数
- 方法一:直接使用父级合约的名称调用
```solidity
S.foo();
```
- 方法二:使用super关键字调用,但是执行所有父级合约中包含foo的函数
```solidity
super.foo();
```
全部代码
``` solidity
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.7;
// 多线继承,要把继承少的基础合约写在更前面
// X、Y、Z 三个合约中,Y 继承 X,Z 继承X和Y,这Z合约要写成 contract Z is X,Y {}
// 继承后向父级函数的构造函数传参
contract S {
string public name;
event Log(string message);
constructor(string memory _name) {
name = _name;
}
function foo() public virtual {
emit Log("s.foo");
}
}
contract T {
string public text;
constructor(string memory _text) {
text = _text;
}
}
// 继承后向构造函数传参
// 方法一
contract U is S("s"), T("t") {
}
// 多线继承,要把继承少的写在更前面
// 方法二
contract V is S, T {
constructor(string memory _name, string memory _text) S(_name) T(_text) {
}
function foo() public override {
emit Log("V.foo");
// 调用父级合约的函数
// 方法一
S.foo();
// 方法二,执行所有父级合约中包含foo的函数
super.foo();
}
}
contract VV is S("s"), T {
constructor(string memory _text) T(_text) {
}
}
```
## 30. Visibility 可视范围
- private 私有的 - 合约内部访问
- internal 内部的 - 合约内部和子合约可以访问
- public 公开的 - 内部和外部都可以访问
- external 外部的 - 外部合约可以访问
``` solidity
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.7;
// Visibility 可视范围
// private 私有的- only inside contract
// internal 内部的 - only inside contract and child contracts
// public 公开的 - inside and outside contract
// external 外部的 - only from outside contract
contract VisibilityBase {
uint private x = 0;
uint internal y = 1;
uint public z = 2;
function privateFunc() private pure returns (uint) {
return 0;
}
function internalFunc() internal pure returns (uint) {
return 100;
}
function publicFunc() public pure returns (uint) {
return 200;
}
function externalFunc() external pure returns (uint) {
return 300;
}
function examples() external view {
x + y + z;
privateFunc();
internalFunc();
publicFunc();
// 外部函数可以使用this关键字访问,比较浪费gas,不建议使用
this.externalFunc();
}
}
// 外部合约继承于父合约
contract VisibilityChild is VisibilityBase {
function examples2() external view {
y + z;
// 可以调用内部方法
internalFunc();
// 可以调用公开方法
publicFunc();
}
}
```
## 31. Immutable 赋值常量关键字
```solidity
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.7;
// 必须部署的时候赋值
contract Immutable {
// 方法一,不适用immutable关键字 消耗 45718 gass
// address public owner = msg.sender;
// 方法二 使用 immutable 关键字消耗 43585 gas,更节省gas
// address public immutable owner = msg.sender;
address public immutable owner;
// 方法二-2,也可以构造函数中赋值
constructor() {
owner = msg.sender;
}
uint public x;
function foo() external {
require(msg.sender == owner);
x += 1;
}
}
```
## 32. payable 接收以太坊主币关键字
``` solidity
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.7;
// 向合约发送主币
contract Payable {
// 地址可以发送ETH主币,使用关键字
address payable public owner;
constructor() {
owner = payable(msg.sender);
}
// 向合约发送主币
function deposit() external payable {
}
// 当前合约余额
function getBalance() external view returns(uint) {
return address(this).balance;
}
}
```
## 33. fallback 执行回退
```solidity
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.7;
/**
Fallback executed when
- function does't exist
- directly send ETH
fallback() or receive()?
eth is send to contract
|
is msg.data empty?
/ \
yes no
/ \
receive() exist? fallback()
/ \
yes no
/ \
receive() fallback()
*/
contract Fallback {
event Log(string func, address sender, uint value, bytes data);
// 调用不存在函数和直接发送以太币都可以用
fallback() external payable {
// 参数:调用的方法,调用地址,发送数量,调用数据
emit Log("fallback", msg.sender, msg.value, msg.data);
}
// 单独发送以太币使用
receive() external payable {
emit Log("receive", msg.sender, msg.value, "");
}
}
```
## 34. Send ETH 发送ETH
三种方法发送 ETH 主币
- transfer,消耗 2300 gas,如果失败 revert
- send,消耗 2300 gas,返回 boll 值表示是否成功
- call,消耗所有gas,返回boll值和数据
```solidity
// SPDX-License-Identifier: GPL-3.0
pragma solidity ^0.8.7;
// 3 ways to send ETH
// transfer - 2300 gas, reverts
// send - 2300 gas, returns bool
// call - all gas, returns bool and data
/**
三种方法发送 ETH 主币
1. transfer,消耗 2300 gas,如果失败 revert
2. send,消耗 2300 gas,返回 boll 值表示是否成功
3. call,消耗剩余所有gas,返回boll值和数据
*/
contract SendETH {
// 接收主币方法1
constructor() payable {}
// 接收主币方法2
receive() external payable {}
function sendViaTransfer(address payable _to) external payable {
_to.transfer(9);
}
function sendViaSend(address payable _to) external payable {
bool sent = _to.send(8);
require(sent, "send failed");
}
function sendViaCall(address payable _to) external payable {
(bool success, ) = _to.call{value: 7}("");
require(success, "call failed");
}
}
contract EthReceiver {
event Log(uint amount, uint gas);
receive() external payable {
emit Log(msg.value, gasleft());
}
}
```
## 35. EtherWalllet
```solidity
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.7;
contract EtherWallet {
// 管理员
address payable public owner;
constructor() {
owner = payable(msg.sender);
}
// 只发送主币,不带任何参数,用receive
receive() external payable {
}
// 提取主币
function withdraw(uint _amount) external {
require(msg.sender == owner, "caller is not owner");
payable(msg.sender).transfer(_amount);
// 方法二
(bool sent,) = msg.sender.call{value: _amount}("");
require(sent, "Failed to send Ether");
}
function getBalance() external view returns (uint) {
return address(this).balance;
}
}
```
## 36. Call Contract 调用其他合约
```solidity
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.7;
// 调用其他合约
contract CallTestContract {
// set 方法1
function setX1(address _test, uint _x) external {
TestContract(_test).setX(_x);
}
// set 方法2
function setX2(TestContract _test, uint _x) external {
_test.setX(_x);
}
function getX (address _test) external view returns (uint x){
x = TestContract(_test).getX();
}
function setXandReceiveEther(address _test, uint _x) external payable {
TestContract(_test).setXandReceiveEther{ value: msg.value }(_x);
}
function getXandValue(address _test) external view returns (uint x, uint value) {
(x, value)= TestContract(_test).getXandValue();
}
}
contract TestContract {
uint public x;
uint public value = 123;
function setX(uint _x) external {
x = _x;
}
function getX() external view returns (uint) {
return x;
}
function setXandReceiveEther(uint _x) external payable {
x = _x;
value = msg.value;
}
function getXandValue() external view returns (uint, uint) {
return (x, value);
}
}
```
## 37. Interface 接口合约
```solidity
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.7;
contract Counter {
uint public count;
function inc() external {
count += 1;
}
function dec() external {
count -= 1;
}
}
```
```solidity
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.7;
// 不知道合约代码或者合约代码太庞大,写接口进行调用
interface ICounter {
function count() external view returns (uint);
function inc() external;
}
contract CallInterface {
uint public count;
function examples(address _counter) external {
ICounter(_counter).inc();
count = ICounter(_counter).count();
}
}
```
## 38. 低级call
```solidity
// SPDX-License-Identifier: GPL-3.0
pragma solidity ^0.8.7;
contract TestCall {
string public message;
uint public x;
event Log(string message);
fallback() external payable {
emit Log("fallback was called");
}
function foo(string memory _message, uint _x) external payable returns (bool, uint){
message = _message;
x = _x;
return (true, 999);
}
receive() external payable{}
}
contract Call {
bytes public data;
function callFoo(address _test) external payable {
(bool success, bytes memory _data) = _test.call{value: 111, gas: 5000}(
abi.encodeWithSignature("foo(string, uint256)",
"call foo",
123));
require(success, "called failed");
data = _data;
}
// 调用合约不存在的函数
function callDoesNotExitFunc(address _test) external {
(bool success, ) = _test.call(abi.encodeWithSignature("DoesNotExit()"));
require(success, "call failed");
}
}
```
## 39. 委托调用
```solidity
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.7;
/**
A calls B, sends 100 wei
B calls C, sends 50 wei
A --> B --> C
msg.sender = B
msg.value = 50
execute code on C's state variables 改变C合约的状态变量
use ETH in C
A calls B, sends 100 wei
B delegatecall C
A --> B --> C
msg.sender = A
msg.value = 100
execute code on B's state variables 改变B合约的状态变量
use ETH in B
*/
contract TestDelegateCall {
uint public num;
address public sender;
uint public value;
address public owner;
function setVars(uint _num) external payable {
num = 2*_num;
sender = msg.sender;
value = msg.value;
}
}
// 用于升级合约,代理合约状态变量要和被调用合约状态变量顺序要一致。
contract DelegateCall {
uint public num;
address public sender;
uint public value;
function setVars(address _test, uint _num) external payable{
// _test.delegatecall(abi.encodeWithSignature("setVars(uint256)", _num));
(bool success, bytes memory data) = _test.delegatecall(
abi.encodeWithSelector(TestDelegateCall.setVars.selector, _num)
);
require(success, "delegatecall failed");
}
}
```
## 40. 工厂合约
```solidity
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.7;
contract Account {
address public bank;
address public owner;
constructor(address _owner) payable {
bank = msg.sender;
owner = _owner;
}
}
// 在工厂合约内创建账户合约
contract AccountFactory {
Account[] public accounts;
function createAccount(address _owner) external payable {
// 创建账户合约并返回地址, value 传入主币
Account account = new Account{value: 123}(_owner);
accounts.push(account);
}
}
```
## 41. Library 库合约
```solidity
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.7;
// 返回最大值
library Math {
function max(uint x, uint y) internal pure returns (uint) {
return x >= y ? x : y;
}
}
contract Test {
function testMax(uint _x, uint _y) external pure returns (uint) {
return Math.max(_x, _y);
}
}
// 查找对应索引
library ArrayLib {
function findIndex(uint[] storage arr, uint x) internal view returns (uint) {
for (uint i = 0; i< arr.length; i++) {
if (arr[i] == x) {
return i;
}
}
revert("not found");
}
}
contract TestArray {
// 应用库函数
using ArrayLib for uint[];
uint[] public arr = [3, 2, 1];
// 查找数字在数组中的索引
function testFind() external view returns (uint i) {
// return ArrayLib.find(arr, 2);
return arr.findIndex(2);
}
}
```
## 42. Hash 哈希函数
哈希算法特性
- 1.输入值相同,输出值一定相同
- 2.不管输入值有多大,输出值是定长的,不可逆
通常用在签名的运算或获取一个特定的ID
abi.encode() 和 abi.encodePacked() 区别
- 后者不同的输入值可能造成相同的输出值,如('AAAA','BBB')和('AAA','ABBB')输出值相同,
因为后者在运算结果时,没有给字符间隔补0。解决办法是给中间添加数字
```solidity
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.7;
/**
哈希算法特性
1.输入值相同,输出值一定相同
2.不管输入值有多大,输出值是定长的,不可逆
通常用在签名的运算或获取一个特定的ID
abi.encode() 和 abi.encodePacked() 区别
后者不同的输入值可能造成相同的输出值,如('AAAA','BBB')和('AAA','ABBB')输出值相同,
因为后者在运算结果时,没有给字符间隔补0。
解决办法是给中间添加数字
*/
contract HashFunc {
function hash(string memory text, uint num, address addr) external pure returns (bytes32) {
return keccak256(abi.encode(text, num, addr));
}
function encode(string memory text1, string memory text2) external pure returns (bytes memory) {
return abi.encode(text1, text2);
}
function encodePacked(string memory text1, string memory text2) external pure returns (bytes memory) {
return abi.encodePacked(text1, text2);
}
// 解决函数
function collision(string memory text1, uint x, string memory text2) external pure returns (byte32) {
return keccak256(abi.encodePacked(arg);)
}
}
```
## 43. Verify Sign 验证签名
步骤
- message to sign 消息签名
- hash(message) 将消息进行哈希值
- sign(hash(message), private key) | offchain 消息和私钥进行签名,线下完成
- ecrecover(hash(message), signature) == signer 恢复签名,如果签名地址等于想要的地址则正确
```solidity
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.7;
/**
Verify Sign 验证签名
步骤
1.message to sign 消息签名
2.hash(message) 将消息进行哈希值
3.sign(hash(message), private key) | offchain 消息和私钥进行签名,线下完成
4.ecrecover(hash(message), signature) == signer 恢复签名,如果签名地址等于想要的地址则正确
*/
contract VerifySign {
function verify(address _singer, string memory _message, bytes memory _sign) external pure returns (bool) {
bytes32 messageHash = getMessageHash(_message);
bytes32 ethSignedMsgHash = getEthSignedMessageHash(messageHash);
return recover(ethSignedMsgHash, _sign) == _singer;
}
// 将消息进行哈希运算
function getMessageHash(string memory _message) public pure returns (bytes32) {
return keccak256(abi.encodePacked(_message));
}
function getEthSignedMessageHash(bytes32 _msgHas) public pure returns (bytes32) {
return keccak256(abi.encodePacked("\x19Ethereum Signed Message:\n32", _msgHas));
}
function recover(bytes32 _ethSignedMsgHash, bytes memory _sign) public pure returns (address) {
// 非对称加密
(bytes32 r, bytes32 s, uint8 v) = _split(_sign);
return ecrecover(_ethSignedMsgHash, v, r, s);
}
function _split(bytes memory _sign) internal pure returns (bytes32 r, bytes32 s, uint8 v) {
require(_sign.length == 65, "error sign length!");
assembly {
r := mload(add(_sign, 32))
s := mload(add(_sign, 64))
v := byte(0, mload(add(_sign, 96)))
}
return (r, s, v);
}
}
```
## 44. Access Control 权限控制
```solidity
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.7;
/**
权限控制合约
*/
contract AccessControl {
event GrantRole(bytes32 indexed role, address indexed addr);
event RevokeRole(bytes32 indexed role, address indexed addr);
// role nick name => address => bool 角色映射
mapping(bytes32 => mapping(address => bool)) public roles;
// role name hash
bytes32 private constant ADMIN = keccak256(abi.encodePacked("ADMIN"));
bytes32 private constant USER = keccak256(abi.encodePacked("USER"));
// 管理员权限
modifier onlyRole(bytes32 _role) {
require(roles[_role][msg.sender], "not authorized");
_;
}
// 通过构造函数给部署合约者授予管理员权限
constructor() {
_grantRole(ADMIN, msg.sender);
}
/**
* grant role
*/
function _grantRole(bytes32 _role, address _addr) internal {
roles[_role][_addr] = true;
// 向外部抛出执行状态事件
emit GrantRole(_role, _addr);
}
function grantRole(bytes32 _role, address _addr) external onlyRole(ADMIN){
roles[_role][_addr] = true;
// 向外部抛出执行状态事件
emit GrantRole(_role, _addr);
}
/**
* revoke role
*/
function revokeRole(bytes32 _role, address _addr) external onlyRole(ADMIN) {
roles[_role][_addr] = false;
// 向外部抛出执行状态事件
emit RevokeRole(_role, _addr);
}
}
```
## 45. selfdestruct 自毁合约
通过调用自毁合约方法,强制发送主币至调用合约
```solidity
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.7;
/**
* selfdestruct 自毁合约
* - delete contract
* - force send Ether to any address
*/
contract Kill {
constructor() payable{}
function kill() external {
selfdestruct(payable(msg.sender));
}
function testCall() external pure returns (uint) {
return 123;
}
}
contract Helper {
function getBalance() external view returns (uint) {
return address(this).balance;
}
// 调用合约的自毁方法,将被调用合约的主币发送至此合约
function kill(Kill _kill) external {
_kill.kill();
}
}
```
## 46. Piggy Bank 小猪存钱罐
其他人可以发送主币,拥有者可以取出主币同时毁掉合约
```solidity
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.7;
/**
* PiggyBank 小猪存钱罐
* 其他人可以发送主币,拥有者可以取出主币同时毁掉合约
*/
contract PiggyBank {
// 收款数量事件
event Deposit(uint amount);
// 取款数量事件
event Withdraw(uint amount);
address public owner = msg.sender;
// 接收主币
receive() external payable {
emit Deposit(msg.value);
}
// 提取主币
function withdraw() external {
require(msg.sender == owner, "not owner");
emit Withdraw(address(this).balance);
// 自毁将主币发送给拥有者
selfdestruct(payable(msg.sender));
}
}
```
## 47. ERC20
```solidity
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.7;
/**
* 合约包含IERC20接口,代表满足标准
*/
interface IERC20 {
/**
* 当前合约的 token 总量
*/
function totalSupply() external view returns (uint);
/**
* 某一个账户的当前余额
*/
function balanceOf(address account) external view returns (uint);
/**
* 把账户中的余额,由调用者发送到另一个账户中,并向链外汇报事件
*/
function transfer(address recipient, uint amount) external returns (bool);
/**
* 查询某账户向另一账户批准额度有多少
*/
function allowance(address owner, address spender) external view returns (uint);
/**
* 把账户中的数量批准给另一个账户
*/
function approve(address spender, uint amount) external returns (bool);
/**
* 向另一个合约存款的时候,另一个合约需要调用 transferFrom 把token拿到它的合约中
*/
function transferFrom(address sender, address recipient, uint amount) external returns (bool);
event Transfer(address indexed from, address indexed to, uint amount);
event Approval(address indexed owner, address indexed spender, uint amount);
}
contract ERC20 is IERC20 {
// 当前合约的token总量
uint public totalSupply;
// 账本映射
mapping(address => uint) public balanceOf;
// 批准映射
mapping(address => mapping(address => uint)) public allowance;
// token name
string public name = "Test";
string public symbol = "TEST";
// token 精度
uint8 public decimals = 18;
/**
* 把账户中的余额,由调用者发送到另一个账户中,并向链外汇报事件
*/
function transfer(address recipient, uint amount) external returns (bool) {
// 发送者减掉数量
balanceOf[msg.sender] -= amount;
// 接受者增加数量
balanceOf[recipient] -= amount;
emit Transfer(msg.sender, recipient, amount);
return true;
}
/**
* 把账户中的数量批准给另一个账户
*/
function approve(address spender, uint amount) external returns (bool) {
allowance[msg.sender][spender] = amount;
emit Approval(msg.sender, spender, amount);
return true;
}
/**
* 向另一个合约存款的时候,另一个合约需要调用 transferFrom 把token拿到它的合约中
*/
function transferFrom(address sender, address recipient, uint amount) external returns (bool) {
allowance[sender][msg.sender] -= amount;
balanceOf[sender] -= amount;
balanceOf[recipient] += amount;
emit Transfer(sender, recipient, amount);
return true;
}
/**
* 筑币方法
*/
function mint(uint amount) external {
balanceOf[msg.sender] += amount;
totalSupply += amount;
emit Transfer(address(0), msg.sender, amount);
}
/**
* 销毁方法
*/
function burn(uint amount) external {
balanceOf[msg.sender] -= amount;
totalSupply -= amount;
emit Transfer(msg.sender, address(0), amount);
}
}
```
## 48. MultiSignWallet 多签钱包
MultiSignWallet 多签钱包
多个人同意的情况下才能转出主币
```solidity
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.7;
/**
* MultiSignWallet 多签钱包
* 多个人同意的情况下才能转出主币
*/
contract MultiSignWallet {
// 收款事件
event Deposit(address indexed sender, uint amount);
// 提交交易申请
event Submit(uint indexed txId);
// 签名人批准
event Approve(address indexed owner, uint indexed txId);
// 撤销
event Revoke(address indexed owner, uint indexed txId);
// 执行发送主币
event Execute(uint indexed txId);
// 交易结构体
struct Transaction {
address to;
uint value;
bytes data;
bool executed;
}
// 保存所有的合约拥有者
address[] public owners;
// 判断是否签名人
mapping (address=>bool) public isOwner;
// 最小确认数
uint public required;
// 交易列表
Transaction[] public transactions;
// 某个交易下,签名人是否同意
mapping (uint=>mapping(address=>bool)) public approved;
// 验证拥有者
modifier onlyOwner() {
require(isOwner[msg.sender], "not owner");
_;
}
/**
* 验证交易id是否存在
* _txId: 交易id
*/
modifier txExists(uint _txId) {
require(_txId < transactions.length, "tx does not exist!");
_;
}
/**
* 验证交易是否被该合约调用者同意
* _txId: 交易id
*/
modifier notApproved(uint _txId) {
require(!approved[_txId][msg.sender], "tx was approved!");
_;
}
/**
* 验证交易是否被执行过
* _txId: 交易id
*/
modifier notExecuted(uint _txId) {
require(!transactions[_txId].executed, "tx was executed");
_;
}
/**
* 构造方法
* _owners: 添加多签地址
* _required: 最小同意数
*/
constructor(address[] memory _owners, uint _required) {
require(_owners.length > 0, "owners required");
require(_required > 0 && _required <= _owners.length, "invalid required number of owners");
// 判断地址的有效性
for (uint index = 0; index < _owners.length; index++) {
address owner = _owners[index];
// 非 0 地址
require(owner != address(0), "invalid owner");
// 不能是已存在地址
require(!isOwner[owner], "owner is not unique");
// 更新所有者映射
isOwner[owner] = true;
// 加入所有者中
owners.push(owner);
}
required = _required;
}
/**
* 收款方法
*/
receive() external payable {
emit Deposit(msg.sender, msg.value);
}
/**
* 提交交易
* _to : 目标地址
* _value : 交易数量
* _data : 如果目标地址是合约可以在此调用合约函数 (低级calldata)
*/
function submit(address _to, uint _value, bytes calldata _data) external onlyOwner{
transactions.push(Transaction({
to: _to,
value: _value,
data: _data,
executed: false
}));
emit Submit(transactions.length - 1);
}
/**
* 批准
* 条件 conditions
* 1.管理员
* 2.交易id存在
* 3.交易没有被批准
* 4.交易没有被执行
*/
function approve(uint _txId) external onlyOwner txExists(_txId) notApproved(_txId) notExecuted(_txId) {
approved[_txId][msg.sender] = true;
emit Approve(msg.sender, _txId);
}
/**
* 获取交易管理员同意数
* _txId: 交易id
*/
function _getApprovalCount(uint _txId) private view returns (uint count) {
for (uint i = 0; i < owners.length; i++) {
if (approved[_txId][owners[i]]) {
count += 1;
}
}
}
/**
* 执行
* _txId: 交易id
*/
function execute(uint _txId) external txExists(_txId) notExecuted(_txId) {
require(_getApprovalCount(_txId) >= required, "approvals < required!");
Transaction storage transaction = transactions[_txId];
(bool success, ) = transaction.to.call{value:transaction.value}(
transaction.data
);
require(success, "tx failure!");
transaction.executed = true;
emit Execute(_txId);
}
/**
* 撤销
* _txId: 交易id
*/
function revoke(uint _txId) external onlyOwner txExists(_txId) notExecuted(_txId) {
require(approved[_txId][msg.sender], "tx was approved");
approved[_txId][msg.sender] = false;
emit Revoke(msg.sender, _txId);
}
}
```
## 49. FunctionSign 函数签名
```solidity
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.7;
/**
* @name: Receiver
* @desc: 函数签名,代表虚拟机如何找到合约中的函数
*/
contract Receiver {
event Log(bytes data);
/**
* @name: transfer
* @desc: 发送主币
* @param {address} _to
* @param {uint} _amount
* @return {*}
*/
function transfer(address _to, uint _amount) external {
emit Log(msg.data);
// 取出data前面数值:0xa9059cbb,这是虚拟机对函数名和参数("transfer(address,uint256)")进行的哈希加密得到的bytes4返回值
}
}
/**
* @name: FunctionSelector
* @desc: 验证上面的函数签名,通过验证"transfer(address,uint256)",得到0xa9059cbb
*/
contract FunctionSelector {
/**
* @name: getSelector
* @desc: 验证函数签名
* @param {string calldata} _funcStr,example: "transfer(address,uint256)"
* @return {*} result: 0xa9059cbb
*/
function getSelector(string calldata _funcStr) external pure returns (bytes4) {
return bytes4(keccak256(bytes(_funcStr)));
}
}
```
## 50. DutchAuction 荷兰拍卖
```solidity
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.7;
interface IERC721 {
function transferFrom(address _from, address _to, uint _nftId) external;
}
/**
* @name: DutchAuction
* @desc: 荷兰拍卖
*/
contract DutchAuction {
// 拍卖持续时间
uint private constant DURATION = 7 days;
// 拍卖 nft 合约
IERC721 public immutable nft;
// 拍卖 nft id
uint public immutable nftId;
// 拍卖人
address payable public immutable seller;
// 起始价格
uint public immutable startPrice;
// 开始时间
uint public immutable startAt;
// 到期时间
uint public immutable expiresAt;
// 每秒折扣率
uint public immutable discountRate;
constructor(
uint _startPrice,
uint _discountRate,
address _nft,
uint _nftId
) {
// 拍卖完后要把主币发送给seller
seller = payable(msg.sender);
startPrice = _startPrice;
discountRate = _discountRate;
startAt = block.timestamp;
expiresAt = startAt + DURATION;
// 随着每秒折扣,价格不能大于起拍价
require(_startPrice >= _discountRate * DURATION, "starting price < discount");
// 将address转换成IERC721合约
nft = IERC721(_nft);
nftId = _nftId;
}
/**
* @name: getPrice
* @desc: 随着时间的流逝,获取当前价格
* @param {public view} returns
* @return {*}
*/
function getPrice() public view returns (uint) {
// 流逝时间
uint timeElapsed = block.timestamp - startAt;
// 需要折扣的价格
uint discount = discountRate * timeElapsed;
// 当前价
return startPrice - discount
}
/**
* @name: buy
* @desc: 购买nft
* @return {*}
*/
function buy() external payable {
// 拍卖没有到期
require(block.timestamp < expiresAt, "auction expired");
// 获取价格
uint price = getPrice();
// 购买的ETH必须大于价格
require(msg.value >= price, "ETH < price");
// 发送nft
nft.transferFrom(seller, msg.sender, nftId);
// 当ETH>价格,要把多余eth退款给用户
uint refund = msg.value - price;
if (refund > 0) {
payable(msg.sender).transfer(refund);
}
// 自毁并发送主币给seller
selfdestruct(seller);
}
}
```
## 51. EnglishAuction 英式拍卖
```solidity
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.7;
interface IERC721 {
function transferFrom(
address _from,
address _to,
uint _nftId
) external;
}
contract EnglishAuction {
event Start();
// 增加索引,提供链外查询
event Bid(address indexed sender, uint amount);
event Withdraw(address indexed bidder, uint amount);
event End(address highestBidder, uint amount);)
// 竞拍的NFT
IERC721 public immutable nft;
// 竞拍的NFTId
uint public immutable nftId;
// 销售者地址
address payable public immutable seller;
// 结束时间
uint32 public entAt;
// 是否开始
bool public started;
// 是否结束
bool public ended;
// 最高出价者
address public highestBidder;
// 最高出价
uint public highestBid;
// 最高价除外的所有其他出价
mapping (address=>uint) public bids;
constructor(
address _nft,
uint _nftId,
uint _startingBid
) {
nft = IERC721(_nft);
nftId = _nftId;
seller = payable(msg.sender);
highestBid = _startingBid;
}
/**
* @name: start
* @desc: 开始
* @return {*}
*/
function start() external {
require(msg.sender == seller, "not seller");
require(!started, "started");
started = true;
endAt = uint32(block.timestamp + 7 days);
// 把NFT发送到竞拍合约中
nft.transferFrom(seller, address(this), nftId);
emit Start();
}
/**
* @name: bid
* @desc: 竞拍
* @return {*}
*/
function bid() external payable {
require(started, "not started!");
require(block.timestamp < endAt, "ended");
require(msg.value > highestBid, "value < highest bid");
if (highestBidder != address(0)) {
// 某账户竞拍累加记录,通过累加记录退还竞价的代币
bids[highestBidder] += highestBid;
}
highestBid = msg.value;
highestBidder = msg.sender;
emit Bid(msg.sender, msg.value);
}
/**
* @name: 取出
* @desc: 取出自己的出价
* @return {*}
*/
function withdraw() external {
// 数组中存储除了最高出价外的每个人的出价
uint bal = bids[msg.sender];
bids[msg.sender] = 0;
// 发送
payable(msg.sender).transfer(bal);
emit Withdraw(msg.sender, bal);
}
/**
* @name: end
* @desc: 结束
* @return {*}
*/
function end() external {
require(started, "not started");
require(!ended, "ended");
require(block.timestamp >= endAt, "not ended");
ended = true;
// 如果有人竞拍
if (highestBidder != address(0)) {
// 发送nft
nft.transferFrom(address(this), highestBidder, nifId);
// 把合约中的主币发送给销售者
seller.transfer(highestBid);
} else { // 无人竞拍,nft发送回seller
nft.transferFrom(address(this), seller, nftId);
}
emit End(highestBidder, highestBid);
}
}
```