智能合约

现实生活中的合约,是由多方商讨制定,制定完成后,由专门的执行角色执行的约定。智能合约,顾名思义,是可以抛却专门的执行角色,一种可以自动化执行的规则。只要满足智能合约中制定的条件,合约就会自动被执行。

OpWerY.png

智能合约的结构主要分为两大部分: 合约变量 与 合约函数

从0到1 上的介绍

OpWrQv.png

区块链

区块链,就是一个又一个区块组成的链条。每一个区块中保存了一定的信息,它们按照各自产生的时间顺序连接成链条。这个链条被保存在所有的服务器中,只要整个系统中有一台服务器可以工作,整条区块链就是安全的。这些服务器在区块链系统中被称为节点,它们为整个区块链系统提供存储空间和算力支持。如果要修改区块链中的信息,必须征得半数以上节点的同意并修改所有节点中的信息(伪造区块链需要拥有超过51%的全网算力。),而这些节点通常掌握在不同的主体手中,因此篡改区块链中的信息是一件极其困难的事。相比于传统的网络,区块链具有两大核心特点:数据难以篡改和去中心化。基于这两个特点,区块链所记录的信息更加真实可靠,可以帮助解决人们互不信任的问题。

交易中的信息

在进行交易时,会产生与交易相关的信息

下面以 请求测试币 的一次交易为例子

image-20240411144059660

transaction hash 交易哈希:是在当前区块链上这笔交易的唯一ID

staus 交易状态 : 这里显示成功

Block : 区块高度

Timestamp 时间戳

from :发送交易的用户

to : 接受交易的用户

value :交易的资产价值是 0.05 以太币

transaction fee 交易手续费 :是付给矿工处理这笔交易的费用

gas :是交易中每个执行单位的花费 (一般以 ether 和 gwei 做单位)

解释transaction fee 和 gas

每当你创建一个交易时,会有一个节点(矿工)来运行区块链,他会被得到该区块链的原生代币(transaction fee),这份收入实际是由gas的使用量来决定的

gas ,是一种计量单位,想要使用更多的计算资源 ,就需要支付更多的gas

点开上面说的交易的细节

image-20240411150831651

gas的上限值(gas limit )为 60000 ,而这笔交易实际的使用量是 21000

用使用量 21000 去乘 gas费用(gas price)0**.**000000001003875679 ETH

正好是 transaction fee 的值

区块 -》 区块链 -》分布式区块链

区块将 产生哈希的原值 分为 block(区块高度)、nonce(要计算的答案)、data(区块中记录的数据)、

矿工计算nounce的值 来使得 整个区块计算得到的hash值符合某个限制条件

区块链 则加入 prev(上一个区块计算得到的hash值),使得整个区块计算得到的hash值符合某个限制条件 ps:block为1的区块的prev值由 0 填充

分布式区块链 则 将多条区块链 同时记录 同一系列交易 ,若某一区块链中携带的值被篡改,也能以 少数服从多数 的原则 发现 (理论上篡改者不可能同时胜过网络中至少一半的计算能力)

solidity开发基础

第一个智能合约

SPDX-License-Identifier 用来指定 License

pragma solidity 用来指定版本

部署一个合约实际上是发送一个交易

数据类型

最基础的数据类型 :boolean、uint、int、address、btyes

权限

public、private、external、internal、

(public 变量相当于给 变量加了一个 getter 函数)

基础函数

函数使用 fuction 关键字

像这里创建一个 changemynumber 函数

在合约部署之后,可以通过remix上面直接改

部署合约时 会产生 交易费用(transaction fee),如果合约的功能越复杂(操作越多),部署时产生的费用会越多

view 关键字

1
2
3
function retrieve() public view returns(uint256){
return aNumber;
}

如果是 view 函数,意味着只会读取该合约的状态,而不能修改;

调用此类函数时不会产生任何费用,除非在消耗gas的函数中调用它

pure 关键字

1
2
3
function retrieve() public pure returns(uint256){
return (1+2+3);
}

如果是 pure 函数,不能读取该合约的状态,也不能修改

调用此类函数时不会产生任何费用

calldata, memory,storage 关键字

calldata 用来修饰 不可以被修改的 临时变量

memory 用来修饰 可以被修改的 临时变量

storage 用来修饰 可以被修改的 永久变量

引入其他合约

OpBztG.png

这里在ContractFactory.sol文件中 使用 import 引入其他合约

这里创建一个合约的方式和创建一个对象类似

继承与重写

OpBNo6.png

使用 is 关键字来指定继承关系

当子合约要重写父合约的某个函数时,需要将子合约中的该函数加上 override 关键字;

父合约中的该函数加上 virtual 关键字。

FundMe合约

每个我们发送的合约都包含以下字段

OpTHZF.png

nonce:该账户的交易序号

。。。。(前面已经讲过)

v,r,s:交易签名时的技术

payable关键字

使函数进行ETH或者其他货币相关的操作

在函数中用全局关键字 msg.value ,来知道某人转账的金额

使用 require 来保证每次转账的金额的限制

revert

当require 里面的条件没有被满足时,会执行 回滚操作(revert),合约状态会回到执行所有操作之前,并且过程中消耗的gas会被返还

设置最小金额的usd

为了找到 usd 与ETH 之间的关系,我们需要利用 Chainlink(预言机)

Chainlink(预言机)

预言机是一种与世界交互,给智能合约提供外部数据或计算的工具

Chainlink 是一个 去中心化 的预言机网络

使用Chainlink的 喂价(datafeed)功能

这里使用docs.chain.link里面给的喂价合约 Using Data Feeds on EVM Chains | Chainlink Documentation

可以获得实时比例

OpTQ7b.md.png

要与项目之外的合约交互,我们需要

1.ABI(ABI实际上是一个合约中不同功能和交互的列表,接口可以被编译成ABI)

2.合约地址

引用接口

1.直接从docs.chain.link里面的gitbub仓库里面复制源代码到自己的.sol文件中

2.使用import

编写出的FundMe合约

OpT8Li.png

简单来说就是利用chainlink的喂价功能,算出最新ETH和usd的比例,利用这个比例写一个getConservetionRate函数进行转换,从而进行比较

库(library)

这里将 FundMe 合约中的函数 提取到一个新的库里面,并将该库依附到uint256的对象上

OpUQsU.png

OpU0aY.png

库中的函数必须使用 internal 关键字

库中函数被调用时,使用函数的对象会作为第一个传参,括号里面的参数从第二个传参开始

unchecked 与 safemath

在solidity 0.8 以前的版本中,不会进行基础性检查,如uint8 para =255时,对para加一会导致其重置为0,是unchecked

到了0.8版本以后,增加了safemath库,会进行基础性检查,当对 para加一 时会报错

然而,你可以在0.8以后的版本中使用 unchecked 关键字,就像这样:

OpUcev.png

在花括号中的内容将不会收到基础性检查,这样做的好处是可以节省 gas

transfer,send,call

这是三种从合约中提取货币的方式

transfer

transfer的gas限制为2300,超过或调用失败会抛出报错

1
payable(msg.sender).transfer(address(this.balance));

send

send 的gas限制也为2300,超过或调用失败会返回false的布尔值

1
2
bool sendSuccess = payable(msg.sender).send(address(this.balance));
require(sendSuccess,"send failed")

call

call的gas没有上限,调用失败会返回false的布尔值

1
2
(bool sendSuccess,) = payable(msg.sender).call{value:address(this).balance}("");
require(sendSuccess,"send failed")

限制withdraw函数的权限

这里使用 构造器constructor 和 修饰器modifier 来限制withdraw函数只能被部署合约的人调用

OpUdGq.png

在调用 withdraw函数之前,会先执行onlyowner里面的

1
require (msg.sender == owner, "you are not the owner!");

Gas优化

1.使用constant设置常量

2.使用 immutable 设置不可变

constant 的常量将会把赋值给它的表达式复制到所有访问它的位置,然后再进行求值的运算。 immutable 的不变量则是将表达式的值复制到访问它的位置,而且值占用固定的32个字节

3.使用 自定义报错(例如将require改成自定义报错)

OpUYhr.png

receive函数 和 fallback 函数

receive 函数

receive函数不需要使用 function 关键字,且没有参数、没有返回值

当合约接收到以太币但没有与之匹配的函数时,Solidity会尝试调用receive函数。receive函数只能有一个,并且必须是外部可见的(public或external)

使用格式

1
receive() external payable{....}

一个合约中最多只有一个receive函数.

fallback函数

fallback函数是一个没有函数名的函数

在合约接收到以太币时调用

1
fallback() external payable {... }

如果合约没有定义receive函数,但有定义fallback函数,那么在接收到以太币时会调用fallback函数。如果合约同时定义了receive函数和fallback函数,那么当合约接收到以太币时只会调用receive函数,而fallback函数只会处理未知函数调用。