timezone |
---|
Asia/Shanghai |
请在上边的 timezone 添加你的当地时区,这会有助于你的打卡状态的自动化更新,如果没有添加,默认为北京时间 UTC+8 时区 时区请参考以下列表,请移除 # 以后的内容
timezone: Pacific/Honolulu # 夏威夷-阿留申标准时间 (UTC-10)
timezone: America/Anchorage # 阿拉斯加夏令时间 (UTC-8)
timezone: America/Los_Angeles # 太平洋夏令时间 (UTC-7)
timezone: America/Denver # 山地夏令时间 (UTC-6)
timezone: America/Chicago # 中部夏令时间 (UTC-5)
timezone: America/New_York # 东部夏令时间 (UTC-4)
timezone: America/Halifax # 大西洋夏令时间 (UTC-3)
timezone: America/St_Johns # 纽芬兰夏令时间 (UTC-2:30)
timezone: Asia/Dubai # 海湾标准时间 (UTC+4)
timezone: Asia/Kolkata # 印度标准时间 (UTC+5:30)
timezone: Asia/Dhaka # 孟加拉国标准时间 (UTC+6)
timezone: Asia/Bangkok # 中南半岛时间 (UTC+7)
timezone: Asia/Shanghai # 中国标准时间 (UTC+8)
timezone: Asia/Tokyo # 日本标准时间 (UTC+9)
timezone: Australia/Sydney # 澳大利亚东部标准时间 (UTC+10)
-
自我介绍 【关于我】lucky-ti【坐标】上海【关于⼯作】 运营【本期目标】学习Web3,寻找新机会
-
你认为你会完成本次残酷学习吗?Y
章节01
笔记:
// 许可证申明,不写会报警告
// SPDX-License-Identifier: MIT
// 版本申明,想运行当前版本要>0.8.18且小于0.9。当前remix版本最高0.8.27,默认的是0.8.18,需要注意。
pragma solidity ^0.8.18;
contract HelloWeb3 {
string public _string = "Hello Web3!";
}
答案:
PART 01
- Solidity是什么?编写智能合约的语言
- Remix是什么?智能合约开发IDE
- 什么是IDE?集成开发环境(Integrated Development Environment)
- 带有 pragma solidity ^0.8.4; 的智能合约能否被 solidity 0.8版本编译?不可以
- Remix没有以下哪个面板?版本
- Remix的本地测试账户中有多少个ETH?100
- Solidity中每行代码需要以什么符号结尾?分号;
- String是什么类型的变量?字符串
章节02——08
笔记:
solidity中变量类型:
值类型(Value Type):布尔型bool(默认值false,逻辑运算遵循短路规则),整型(uint等效uint256 0——2^256,int等效int256 -2^255——2^255,默认值0),address(20位字节,默认值0地址),定长字节数组(如bytes32,默认空字符串),枚举值enum(和数组一样,返回时返回uint,从0开始,默认第一个元素)。
引用类型(Reference Type):数组(array),结构体(struct),映射(mapping)(mapping(_KeyType => _ValueType)) 映射的_KeyType只能选择Solidity内置的值类型,_ValueType可以使用自定义的类型。映射的存储位置必须是storage,不能用于public函数的参数或返回结果中。给映射新增的键值对的语法为:_Var[_Key] = _Value。
函数类型(Function Type):function () {internal|external|public|private} [pure|view|payable] [returns ()] public全可访问,external合约外访问(内部可用this.函数名()访问,但是这样gas消耗会很高,一般不推荐),internal和private都是内部访问,但internal可用于继承函数。 pure和view的区别:view比pure能多访问状态变量,消耗gas更多,需要修改状态变量可不添加这俩。 用于转账时必须包含payable。注意函数名后用returns,内部主体返回时用return public|private|internal 可用于修饰状态变量,未标明可见范围的,默认internal。
数据位置:gas消耗:storage > memory(一般返回类型为变长数组用memory,因为calldata在函数返回结束后无法访问) > calldata(calldata变量不能修改(immutable)) 同位置变量赋值会创建引用,修改新变量会修改原变量。(如合约状态变量赋值给函数内状态变量)不同位置的会创建副本。 1 ether = 1e9 gwei = 1e18 wei bytes比较特殊,是数组,但是不用加[]。声明单字节数组,可以使用bytes或bytes1[]。bytes 比 bytes1[] 省gas。 对于memory修饰的动态数组,可以用new操作符来创建,但是必须声明长度。如:uint[] memory _array = new uint; 如果一个值没有指定type的话,会根据上下文推断出元素的类型,默认就是最小单位的type。 push():数组最后添加一个元素,pop():数组最后添加一个元素。 delete a会让变量a的值变为初始值。
// 结构体
struct Student{
uint256 id;
uint256 score;
}
Student student; // 初始一个student结构体
答案:
PART 02
- 以下属于solidity变量类型的是?数值类型(Value Type)、引用类型(Reference Type)、映射类型(Mapping Type)、函数类型(Function Type)
- solidity中数值类型(Value Type)不包括:float
- 请解释下面这段代码的意思:合约向addr转账1wei
- bytes4类型具有几个16进制位?8
- 以下运算能使a返回true的是?bool a=1-1==0&&1%2==1
PART 03
- 下面哪一项不是 Solidity 中的函数可见性说明符?protected
- 下面关于view关键字的描述,哪一项是准确的?包含view关键字的函数,可以读取但不能写入存储在链上的状态变量
- 下面关于pure和view两个关键字的描述,哪一项是错误的?每个函数在声明时必须包含这两个关键字中的一个
- 以下代码截取自 SafeMath Library,其定义了一个函数以替代“加法”,如果加法的结果溢出则会返回异常:pure
- 以下代码截取自 USDT 的 Token 合约,其声明了一个函数,任何用户都可以调用这个函数以查询某个地址的 USDT 余额:public
PART 04
- returns关键字的作用:加在函数名后面,用于声明返回的变量类型及变量名
- return关键字的作用:用于函数主体中,返回指定的变量
- 采用命名式返回的函数主体中能否使用return?能
- 解构式赋值能否只读取函数部分返回值?能
- 假设存在如下函数:return (64,true,"abcd",[1,2,3])
- 下列属于命名式返回的是:function returnNamed() public pure returns(uint256 _number,bool _bool,uint256[3] memory _array)
- 假设存在如下函数:0:uint256: _number 2
- 假设存在如下函数:bool _bool; (, _bool, ) = returnMultiple()
PART 05
- 引用类型(Reference Type)包含以下:数组(array)、结构体(struct)、映射(mapping)
- Solidity数据存储位置的类型不包含以下:stack
- 合约中状态变量默认的存储位置类型为以下的:storage
- 不同类型的引用变量相互赋值时,修改其中一个的值,不会导致另一个的值随之改变的是以下哪种情况:合约中的storage赋值给本地的memory
- Solidity中变量按作用域划分,可分为:状态变量(state variable)、局部变量(local variable)、全局变量(global variable)
- 消耗gas最多的变量类型为:状态变量
- 下列表示“请求发起地址”的为:msg.sender
- 下列表示“当前区块的矿工地址”的为:block.coinbase
PART 06
- 以下选项中不属于固定长度数组的是:address[] array1;
- 以下选项中不属于可变长度数组的是:address[6] array2;
- 以下关于数组的说法中,正确的是:内存数组的长度在创建后是固定的
- 数组和结构体分别属于什么类型:引用类型和引用类型
- 以下关于结构体的说法中,错误的是:结构体内可以包含其本身
- 有如下一段合约代码,执行initStudent方法后,student.id和student.score的值分别为:300 400
PART 07
- 如果我们要声明一个mapping变量,记录不同地址的持仓数量,应该怎么做?mapping(address => uint) public balanceof
- 不可以作为mapping中键(key)的变量类型的是:struct
- 可以作为mapping中值(value)的变量类型是:struct、string、address
- Mapping的存储位置可以是:storage
- 给映射变量map新增键值对的方法:map[_Key] = _Value;
- mapping变量是否存长度信息?否
PART 08
- address类型的初始值是:0x0000000000000000000000000000000000000000
- bool类型的初始值是:false
- bytes1的初始值是:0x00
- 调用函数d,将返回:""
- 下面是ERC20合约中的一行代码,其中未记录的用户的_balances值是:0
章节09——12
笔记:
只有数值变量可以声明constant和immutable;string和bytes可以声明为constant,但不能为immutable。 constant变量必须在声明的时候初始化,之后再也不能改变。尝试改变的话,编译不通过(TypeError: Cannot assign to a constant variable)。 immutable变量可以在声明时或构造函数中初始化,在Solidity v8.0.21以后,immutable变量不需要显式初始化。如果既在声明时初始化,又在constructor中初始化,会返回constructor初始化的值。尝试修改初始化的值,会编译不通过(TypeError: Immutable state variable already)。
// 插入排序 正确版
//这里和python代码的区别,是由于solidity最常用的变量类型是uint,无法取到-1。当一组循环中如果要变动第一位也就是a[0]时,可能会使变量取到-1值,solidity中需要避免这种情况。不然遇到这种情况会报错underflow。
function insertionSort(uint[] memory a) public pure returns(uint[] memory) {
// note that uint can not take negative value
for (uint i = 1;i < a.length;i++){
uint temp = a[i];
uint j=i;
while( (j >= 1) && (temp < a[j-1])){
a[j] = a[j-1];
j--;
}
a[j] = temp;
}
return(a);
}
// 构造函数代码示例:
address owner; // 定义owner变量
// 构造函数
constructor(address initialOwner) {
owner = initialOwner; // 在部署合约的时候,将owner设置为传入的initialOwner地址
}
// 修饰器代码示例:
// 定义modifier
modifier onlyOwner {
require(msg.sender == owner); // 检查调用者是否为owner地址
_; // 如果是的话,继续运行函数主体;否则报错并revert交易
}
// 使用修饰器,在函数位置之后加上修饰器名称。使用修饰器的目的,是为了减少代码重复,降低gas。一般多用于要多次使用的用户检测或者错误检测中。
function changeOwner(address _newOwner) external onlyOwner{
owner = _newOwner; // 只有owner地址运行这个函数,并改变owner
}
应用程序(ethers.js)可以通过RPC接口订阅和监听事件,并在前端做响应。使用事件比存储链上花费更少的gas。 事件的声明由event关键字开头,接着是事件名称,括号里面写好事件需要记录的变量类型和变量名。通过emit来释放事件。 以太坊虚拟机(EVM)用日志Log来存储Solidity事件,每条日志记录都包含主题topics(长度不超过4,0标记事件hash,后续标记分别记录index参数标记的返回,至多3个;每个index参数固定256比特,当超过长度时,也会计算成hash存储起来,确保长度大小)和数据data(不含index的部分,这部分消耗的gas比index部分少)两部分。 indexed关键字不能修饰string类型,因为string类型的不能呗索引。
答案:
PART 09
- 下面定义变量的语句中,会报错的一项是:uint256 public constant x1;
- 下面定义变量的语句中,会报错的一项是:string immutable x7 = "hello world";
- 下面哪一项不符合对 constant 和 immutable 的描述?constant和immutable的使用并不会节省gas
- 在如下的合约中,我们定义了四个 immutable 的变量 y1, y2, y3, y4。其中,确实有必要在构造函数 constructor 中才赋值的一项是:y4
- 下列哪一个变量不适合用 constant 或 immutable 来修饰?合约中的ETH数量
PART 10
- 下面哪个选项是if-else语句?if(a > b){return(a);}else{return(b);}
- 下面哪个选项是for循环语句?for(uint i = 5;i > 0;i--){x=2**i;}
- 下面哪个选项是while循环语句?uint256 i = 5; while(i >= 0){x=2**i;i--;}
- 下面哪个选项是do-while循环语句?uint256 i = 5; do{x = 2**1;i--;}while(i >= 0);
- 下面哪个选项出现了 三元运算符 ?return x > y ? (x > Z ? x : z):(y > z ? y : z);
- 使用哪个关键字可以直接跳到下一个循环?continue
- 使用哪个关键字可以跳出当前循环?break
- 下面四张图中哪个是正确的插入排序代码?(图在下方)
function insertionSort(uint[] memory a) public pure returns(uint[] memory) {
// note that uint can not take negative value
for (uint i = 1;i < a.length;i++){
uint temp = a[i];
uint j=i;
while( (j >= 1) && (temp < a[j-1])){
a[j] = a[j-1];
j--;
}
a[j] = temp;
}
return(a);
}
PART 11
- 一个合约可以定义多个构造函数(constructor):错误
- 构造函数的运行,只能通过手动调用:错误
- 构造函数是否可以用来初始化合约参数?是
- 关于solidity中的修饰器(modifier),以下描述中正确的个数为:4
- 阅读图中代码,非owner地址是否可以调用函数F?否
PART 12
- 下列关于事件的说法中,错误的是:链上存储数据比存储事件的成本低。
- 可以使用emit关键字来释放一个事件:正确
- 每个事件可以有无限个带indexed的变量:错误
- indexed关键字可以修饰任意类型的变量:错误
- 下列可以查询事件的是:Etherscan
章节13——15
笔记:
继承(inheritance),包括简单继承,多重继承,以及修饰器(Modifier)和构造函数(Constructor)的继承。继承用is来表示,如:contract B is A。 virtual: 父合约中的函数,如果希望子合约重写,需要加上virtual关键字。 override:子合约重写了父合约中的函数,需要加上override关键字。 多重继承中,is后接的顺序按最原始父辈的开始扩展,如:contract erzi is yeye,baba。如果某一个函数在多个继承的合约里都存在,在子合约里必须重写,否则会报错。重写在多个父合约中都重名的函数时,override关键字后面要加上所有父合约名字,但不强制要求顺序。
// 多重继承中,is后接的顺序按最原始父辈的开始扩展
contract Erzi is Yeye, Baba{
// 继承两个function: hip()和pop(),输出值为Erzi。
// 重写了在Yeye,Baba的两个函数,override后标注出来,但不做顺序要求
function hip() public virtual override(Yeye, Baba){
emit Log("Erzi");
}
function pop() public virtual override(Yeye, Baba) {
emit Log("Erzi");
}
}
// 修饰器的继承
contract Base1 {
modifier exactDividedBy2And3(uint _a) virtual {
require(_a % 2 == 0 && _a % 3 == 0);
_;
}
}
contract Identifier is Base1 {
//计算一个数分别被2除和被3除的值,但是传入的参数必须是2和3的倍数
function getExactDividedBy2And3(uint _dividend) public exactDividedBy2And3(_dividend) pure returns(uint, uint) {
return getExactDividedBy2And3WithoutModifier(_dividend);
}
//计算一个数分别被2除和被3除的值
function getExactDividedBy2And3WithoutModifier(uint _dividend) public pure returns(uint, uint){
uint div2 = _dividend / 2;
uint div3 = _dividend / 3;
return (div2, div3);
}
}
// 构造函数的继承
abstract contract A {
uint public a;
constructor(uint _a) {
a = _a;
}
}
// 在继承时声明父构造函数的参数
contract B is A(1)
// 在子合约的构造函数中声明构造函数的参数
contract C is A {
constructor(uint _c) A(_c * _c) {}
}
子合约调用父合约函数:直接调用和利用super关键字调用(super只会调用最近的父辈合约,在钻石继承(也就是菱形结构的继承中,super会调用继承链条上的每一个合约的相关函数,并且按照最近的父辈到最原始的父辈的顺序))。
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.13;
/* 继承树:
God
/ \
Adam Eve
\ /
people
*/
contract God {
event Log(string message);
function foo() public virtual {
emit Log("God.foo called");
}
function bar() public virtual {
emit Log("God.bar called");
}
}
contract Adam is God {
function foo() public virtual override {
emit Log("Adam.foo called");
super.foo();
}
function bar() public virtual override {
emit Log("Adam.bar called");
super.bar();
}
}
contract Eve is God {
function foo() public virtual override {
emit Log("Eve.foo called");
super.foo();
}
function bar() public virtual override {
emit Log("Eve.bar called");
super.bar();
}
}
contract people is Adam, Eve {
function foo() public override(Adam, Eve) {
super.foo();
}
function bar() public override(Adam, Eve) {
super.bar();
}
}
如果一个智能合约里至少有一个未实现的函数,即某个函数缺少主体{}中的内容,则必须将该合约标为abstract,不然编译会报错;另外,未实现的函数需要加virtual,以便子合约重写。
接口类似于抽象合约,但它不实现任何功能。接口的规则:
1.不能包含状态变量 2.不能包含构造函数 3.不能继承除接口外的其他合约 4.所有函数都必须是external且不能有函数体 5.继承接口的非抽象合约必须实现接口定义的所有功能
接口提供了两个重要的信息:合约里每个函数的bytes4选择器,以及函数签名函数名(每个参数类型);接口id。 接口与合约ABI(Application Binary Interface)等价,可以相互转换。
error是solidity 0.8.4版本新加的内容,方便且高效(省gas)地向用户解释操作失败的原因,同时还可以在抛出异常的同时携带参数,且可以在constract之外定义。在执行当中,error必须搭配revert(回退)命令使用。 require命令是solidity 0.8版本之前抛出异常的常用方法,gas随着描述异常的字符串长度增加,比error命令要高。使用方法:require(检查条件,"异常的描述")。 assert使用方法:assert(检查条件)。
gas消耗;require > error(带参数)> assert > error(不带参数)。
error TransferNotOwner(address sender); // 自定义的带参数的error
function transferOwner1(uint256 tokenId, address newOwner) public {
if(_owners[tokenId] != msg.sender){
revert TransferNotOwner();
// revert TransferNotOwner(msg.sender);
}
_owners[tokenId] = newOwner;
}
答案:
PART 13
- 对virtual关键字描述正确的是:父合约中的函数,如果希望子合约重写,需要加上该关键字。
- 对override关键字描述正确的是:子合约中重写了父合约中的函数,需要加上该关键字。
- 下面哪个选项表示A合约继承了B合约:contract A is B
- function a() public override{} 意思是:函数a0重写了父合约中的同名函数
- 如果合约B继承了合约A,合约C要继承A和B,要怎么写?contract C is A,B
- 合约B继承了合约A,下面选项中,正确调用父合约构造函数的是:constructor(uint _num) A (_num){}
- 合约B继承了合约A,两个合约都有pop()函数,下面选项中,正确调用父合约函数的是:都正确
PART 14
- 已知foo是一个未实现的函数,那么下面代码中书写正确的是? abstract contract A{function foo(uint a)internal pure virtual returns(uint);}
- 被标记为abstract的合约能否被部署?不能
- 下列关于接口的规则中,错误的是:继承接口的合约可以不实现接口定义的全部功能
- 下列关于接口的描述,错误的是:如果已知一个合约实现了ERC20接口,那么还需要知道它具体代码实现,才可以与之交互
- 已经知Azuki的合约地址为0xED5AF388653567Af2F388E6224dC7C4b3241C544,那么利用该地址创建接口合约变量的语句是: IERC721 Azuki = IERC721(0xED5AF388653567Af2F388E6224dC7C4b3241C544)
- 已知Azuki合约中存在ownerOf(uint256 tokenId)函数可用来查询某一NFT的拥有者,现在vitalik想利用上文中创建的接口合约变量调用这一函数,并写出了如下代码:return Azuki.ownerof(id);
- 已知Azuki合约中存在approve(address to, uint256 tokenId)函数可以让NFT的拥有者将自己某一NFT的许可权授予另一地址,且该函数没有返回值,现在某个Azuki拥有者想利用上文中创建的接口合约变量调用这一函数 ,那么他写出的代码可能是? function approveAzuki(address to,uint256 id) external{Azuki.approve(to,id);}
PART 15
- Solidity抛出异常的方法有:error、require、assert
- 判断:对于error, 在合约执行过程中,可以搭配revert,也可以单独使用。错误
- 判断:error可以带有参数。正确
- require的使用方法为:require(检查条件,"异常的描述")判断:require消耗gas的数量会随着检查条件的增多以及"异常的描述"变长而增加。正确
- 判断:assert和require一样,可以解释抛出异常的原因。错误
- error,require和assert三种方法的gas消耗,从大到小依次为: require > assert > error
- 在有以下定义的前提下,以下实现中能够正确抛出异常的写法为:require方法
章节16——18
笔记:
Solidity中允许函数进行重载(overloading),即名字相同但输入参数类型不同的函数可以同时存在,他们被视为不同的函数。注意,Solidity不允许修饰器(modifier)重载。
function f(uint8 _in) public pure returns (uint8 out) {
out = _in;
}
function f(uint256 _in) public pure returns (uint256 out) {
out = _in;
}
// 调用f(50),因为50既可以被转换为uint8,也可以被转换为uint256,因此会报错。
库合约和普通合约主要有以下几点不同:
1.不能存在状态变量 2.不能够继承或被继承 3.不能接收以太币 4.不可以被销毁
库合约可见性被设置为public或者external,在调用函数时会触发一次delegatecall。设置为internal,则不会引起。
使用库合约的方法:
1.利用using for指令:指令using A for B;可用于附加库合约(从库 A)到任何类型(B)。添加完指令后,库A中的函数会自动添加为B类型变量的成员,可以直接调用。注意:在调用的时候,这个变量会被当作第一个参数传递给函数:
// 利用using for指令
using Strings for uint256;
function getString1(uint256 _number) public pure returns(string memory){
// 库合约中的函数会自动添加为uint256型变量的成员
return _number.toHexString();
}
2.通过库合约名称调用函数:
// 直接通过库合约名调用
function getString2(uint256 _number) public pure returns(string memory){
return Strings.toHexString(_number);
}
常用的有:
1.Strings:将uint256转换为String 2.Address:判断某个地址是否为合约地址 3.Create2:更安全的使用Create2 EVM opcode 4.Arrays:跟数组相关的库合约
import用法:
// 通过源文件相对位置导入
import './Yeye.sol';
// 通过网址引用
import 'https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/utils/Address.sol';
// 通过npm的目录导入
import '@openzeppelin/contracts/access/Ownable.sol';
// 通过指定全局符号导入合约特定的全局符号
import {Yeye} from './Yeye.sol';
引用(import)在代码中的位置为:在声明版本号之后,在其余代码之前。
答案:
PART 16
- 什么是函数重载(Overloading)? 名字相同但输入参数类型不同的函数可以同时存在,他们被视为不同的函数。
- Solidity中是否允许修饰器(modifier)重载?不允许
- 下面两个函数的函数选择器是否相同?不相同
- 使用上一题代码,如果我们调用 saySomething("WTF"),返回值为:”WTF”
- 下面两个函数的函数选择器是否相同?不相同
PART 17
- 以下,通过库合约名称调用toHexString(),返回值return出正确的写法为: Strings.toHexString(_number);
- 关于库函数的描述,下列描述错误的是:库函数一般需要你自己实现
- 库合约和普通合约的区别,下列描述错误的是:库合约可以被继承
- 以下属于常用库函数的有:String、Address、Create2、Arrays
- String库合约是将uint256类型转换为相应的string类型的代码库,toHexString()为其中将uint256转换为16进制,再转换为string。 _number.toHexString();
PART 18
- Solidity中import的作用是:导入其他合约中的全局符号
- import导入文件的来源可以是:源文件网址、npm的目录、本地文件
- 以下import写法错误的是:import from "./Yeye.sol";
- import导入文件中的全局符号可以单独指定其中的:合约、函数、结构体类型
- 被导入文件中的全局符号想要被其他合约单独导入,应该怎么编写?与合约并列在文件结构中
章节19——20
笔记:
receive()函数是在合约收到ETH转账时被调用的函数。一个合约最多有一个receive()函数,声明方式与一般函数不一样,不需要function关键字:receive() external payable { ... }。receive()函数不能有任何的参数,不能返回任何值,必须包含external和payable。 当合约接收ETH的时候,receive()会被触发。receive()最好不要执行太多的逻辑因为如果别人用send和transfer方法发送ETH的话,gas会限制在2300,receive()太复杂可能会触发Out of Gas报错;如果用call就可以自定义gas执行更复杂的逻辑。 警惕恶意合约,会在receive() 函数(老版本0.6.x的话,就是 fallback() 函数)嵌入恶意消耗gas的内容或者使得执行故意失败的代码。
// 定义事件
event Received(address Sender, uint Value);
// 接收ETH时释放Received事件
receive() external payable {
emit Received(msg.sender, msg.value);
}
fallback()函数会在调用合约不存在的函数时被触发。可用于接收ETH,也可以用于代理合约proxy contract。fallback()声明时不需要function关键字,必须由external修饰,一般也会用payable修饰,用于接收ETH:fallback() external payable { ... }。
receive和fallback的区别:
触发fallback() 还是 receive()?
接收ETH
|
msg.data是空?
/ \
是 否
/ \
receive()存在? fallback()
/ \
是 否
/ \
receive() fallback()
// 合约接收ETH时,msg.data为空且存在receive()时,会触发receive()
transfer(接收方地址.transfer(发送ETH数额))、send(接收方地址.send(发送ETH数额))、call(接收方地址.call{value: 发送ETH数额}(""))都可用于发送ETH,三者的区别:
1.transfer、send的gas限制都在2300,足够用于转账,但对方合约的fallback()或receive()函数不能实现太复杂的逻辑。区别在于,交易失败transfer会自动revert,send不会(send只会返回成功与否的bool值,需要额外代码处理一下)。 2.call()没有gas限制,可以支持对方合约fallback()或receive()函数实现复杂逻辑,不会revert,call()的返回值是(bool, bytes),其中bool代表着转账成功或失败,需要额外代码处理一下。
error SendFailed(); // 用send发送ETH失败error
// send()发送ETH
function sendETH(address payable _to, uint256 amount) external payable{
// 处理下send的返回值,如果失败,revert交易并发送error
bool success = _to.send(amount);
if(!success){
revert SendFailed();
}
}
error CallFailed(); // 用call发送ETH失败error
// call()发送ETH
function callETH(address payable _to, uint256 amount) external payable{
// 处理下call的返回值,如果失败,revert交易并发送error
(bool success,) = _to.call{value: amount}("");
if(!success){
revert CallFailed();
}
}
答案:
PART 19
- 下面哪个选项语法是正确的?receive() external payable {}
- fallback(or receive)函数能否在合约内部调用?不能
- vitalik想部署一个能接收ETH和msg.data的合约,那么他部署的合约中: 必须含有fallback函数
- 假设存在如下合约,现在vitalik向该合约发起一笔低级交互,value=100Wei,msg.data=0xaa,那么会发生下面选项中的哪种情况? error:Fallback function is not defined,value和msg.data均发送失败
- 假设存在如下合约,现在vitalik想调用该合约中不存在的函数,他在calldata中输入函数选择器,并将value设置为1ETH,那么会发生下面选项中的哪种情况? error:'Fallback'function is not defined,value和msg.data均发送失败
PART 20
- 下面三种发送ETH的方法中,哪一种没有gas限制?call
- 下面三种发送ETH的方法中,哪一种发送失败会自动revert交易?transfer
- transfer的gas限制为?2300
- vitalik写了一个合约,并且该合约在被部署时可以转ETH进去,那么该合约的构造函数可能为?constructor() payable {}
- vitalik写了一个用send()发送ETH的函数: bool success =_to.send(amount); if(!success) { revert SendFailed(); }
- vitalik又写了一个用call()发送ETH的函数: (bool success,) =_to.call {value:amount} (""); if(!success) {revert CallFailed();}
- 假设存在如下两个合约(sendETH和ReceiveETH),两个合约目前ETH余额皆为0,现在vitalik想通过SendETH合约的callETH函数往ReceiveETH合约转入1ETH,他将交易的value设置为2ETH,同时交易成功执行,那么此时sendETH合约和ReceiveETH的ETH余额分别为?1ETH;1ETH
章节21-22
笔记:
contract CallContract{
// 合约名(地址).函数名()的方式调用其他合约函数
function callSetX(address _Address, uint256 x) external{
OtherContract(_Address).setX(x);
}
// 相当于参数已经提前指定合约地址引用,直接调用
function callGetX(OtherContract _Address) external view returns(uint x){
x = _Address.getX();
}
// 将指定好的赋值给一个变量,通过变量来调用具体函数
function callGetX2(address _Address) external view returns(uint x){
OtherContract oc = OtherContract(_Address);
x = oc.getX();
}
// 这边对比第一条callSetX实际多了个转账
function setXTransferETH(address otherContract, uint256 x) payable external{
OtherContract(otherContract).setX{value: msg.value}(x);
}
}
在外面完成,笔记后续补充 章节23-28
完成章节29-32
完成章节32-35