// SPDX-License-Identifier: MIT pragma solidity ^0.8.0; contract GlobalContract { function globalVars() external view returns(address,uint,uint){ address sender = msg.sender; uint timestamp = block.timestamp; uint blockNum = block.number; return(sender,timestamp,blockNum); } } contract ViewAndPure{ uint public num; function viewFunc() external view returns(uint){ return num; } function pureFunc() external pure returns(uint){ return 1; } function addToNum(uint x) external view returns(uint){ return num + x; } function add(uint x,uint y) external pure returns(uint){ return x + y; } } contract Counter { uint public count; function inc() external { count += 1; } function dec() external { count -= 1; } } // 默认值 contract DefaultValues { bool public b; // false uint public u; // 0 int public i; // 0 address public a; // 0x0000000000000000000000000000000000000000 bytes32 public b32; // 0x0000000000000000000000000000000000000000000000000000000000000000 } // 常量 contract Constants { // 400 gas address public constant A_ADDRESS = 0xd9145CCE52D386f254917e481eB44e9943F39138; // 307 gas uint public constant MY_UINT = 123; // 2511 gas address public B_ADDRESS = 0xd9145CCE52D386f254917e481eB44e9943F39138; } contract IfElse { function example(uint _x) external pure returns(uint){ if(_x < 10) { return 1; }else if(_x <20) { return 2; }else{ return 3; } } function ternary(uint _x) external pure returns(uint){ return _x < 10 ? 1 : 2; } } contract ForAndWhile { function loops() external pure { uint j = 0; while (j <10) { j++; } } function sum(uint _n) external pure returns(uint){ uint s; for(uint i = 1;i <= _n ;i++) { s += i; } return s; } } // require,revert,assert // require 首先检查 condition,如果条件为真则继续执行 contract Error { function testRequire(uint _i) public pure { require(_i <= 10 ,"1>10"); } function testRevert(uint _i) public pure { if(_i > 10){ revert("i > 10"); } } uint public num = 123; function testAssert() public view { assert(num == 123); } function foo(uint _i) public { num +=1; // 报错后,数据回滚,gas费退还 require(_i < 10); } error MyError(address caller,uint i); // 自定义报错,节省gas费 function testError(uint _i) public view { if (_i > 10){ revert MyError(msg.sender , _i); } } } // 函数修改器 contract FunctionModifer { bool public paused; uint public count; function setPause(bool _paused) external { paused = _paused; } // 定义函数修改器,使用modifer,在相同函数中抽取出来,节约代码量. _;让其他代码运行 modifier whenNotPaused() { require(!paused,"paused"); _; } // 函数执行先执行修改器 function inc() external whenNotPaused { count += 1; } function dec() external whenNotPaused { count -= 1; } // 定义带参数修改器 modifier cap(uint _x) { require(_x < 100,"x >= 100"); _; } function incBy(uint _x) external whenNotPaused cap(_x) { count += _x; } // 三明治修改器 modifier sendwich() { count += 10; _; count *= 2; } function foo() external sendwich { count += 1; } } contract Constructor { address public owner; uint public x; constructor(uint _x) { owner = msg.sender; x = _x; } } // 总结练习 管理员权限设置 contract Ownable { address public owner; constructor(){ owner = msg.sender; } modifier onlyOwner() { require(msg.sender == owner,"not owner"); _; } function setOwner(address _newOwner) external onlyOwner { require(_newOwner != address(0),"invalid address"); owner = _newOwner; } // 管理员权限方法 function onlyOwnerCanCall() external onlyOwner { // code } function anyOneCanCall() external { // code } } // 函数返回值 contract FunctionOutputs { function returnMany() public pure returns(uint,bool) { return (1,true); } function named() public pure returns(uint x,bool b){ return (1,true); } // 不加return同样返回 function assigned() public pure returns(uint x,bool b) { x = 1; b = true; } // 函数接收调用函数返回值写法 function receiveFunReturn() public pure { //(uint _x,bool _b) = returnMany(); // 不需要第一个返回值可以不接收,节省gas,逗号要留着 (, bool _b) = returnMany(); } } // 数组 contract Array { uint[] public nums = [1,2,3]; // 动态数组 uint[3] public numsFixed = [4,5,6]; // 定长数组 function examples() external { nums.push(4); // [1,2,3,4] uint x = nums[1]; // 访问数组元素,x = 2 nums[2] = 7; // 修改数组元素,[1,2,7,4] delete nums[1]; // 删除数组元素,[1,0,7,4] nums.pop(); // 弹出数组最后一个元素,数组长度减少,[1,0,7] uint len = nums.length; // 获取数组长度 // 在内存中创建数组,必须指定长度,在内存中不能创建动态数组,不能使用pop、push等方法,只能根据索引修改它的值 uint[] memory a = new uint[](5); a[1] = 123; } // 通过函数返回数组的全部内容 function retuanArray() external view returns(uint[] memory){ return nums; } } // 删除数组元素,数组长度减少 ,缺点:数组过长的话浪费gas contract ArrayShift { uint[] public arr; function example() public { arr = [1,2,3]; delete arr[1]; // [1,0,3] delete arr[1] 只会删除数组对应下标的值,数组长度不变 } // 改变数组长度删除 原理:[1,2,3] -- remove(1) --> [1,3,3] --> pop --> [1,3] function remove(uint _index) public { require(_index < arr.length,"index out of bound"); for (uint i=_index; iuint) public balnaces; // 嵌套映射 mapping(address => mapping(address => bool)) public isFriend; function examples() external { // 给映射设置数据 balnaces[msg.sender] = 123; // 获取映射数据 uint amount = balnaces[msg.sender]; // 获取没有在映射里定义过的值,返回0 uint a = balnaces[address(1)]; // 修改映射值,重新定义即可 balnaces[msg.sender] += 456; // 123+456=579 // 删除映射数据,并不是真的删除,只是恢复默认值,0 delete balnaces[msg.sender]; // 嵌套映射赋值 isFriend[msg.sender][address(this)] = true; } } // 映射 contract IterableMapping { // 地址是否有余额 mapping(address => uint) public balances; // 地址是否存在 mapping(address => bool) public inserted; // 地址数组 address[] public keys; // 数组与映射结合,既可以遍历又可以快速查找内容方案 function set(address _key,uint _val) external { balances[_key] = _val; // 判断映射是否在数组中 if (!inserted[_key]) { inserted[_key] = true; keys.push(_key); } } // 获取有多少持币地址 function getSize() external view returns(uint) { return keys.length; } // 返回数组第一个地址的余额,数组和映射结合到一起使用,去返回该值 function getFirstAmount() external view returns(uint) { return balances[keys[0]]; } // 返回数组最后一个地址的余额,数组和映射结合到一起使用,去返回该值 function getLastAmount() external view returns(uint) { return balances[keys[keys.length -1]]; } // 或者数组任意一个索引的余额 function getAmount(uint _i) external view returns(uint) { return balances[keys[_i]]; } } // 结构体 contract Structs { // 定义汽车结构体,结构体是将很多变量打包在一起的一种的数据格式 struct Car { string model; // 汽车型号 uint year; // 汽车年份 address owner; // 汽车拥有者 } // 声明状态变量,状态变量会记录在链上 Car public car; // 以汽车结构体为类型定义汽车变量 Car[] public cars; // 以汽车结构体为类型定义汽车数组变量 mapping(address => Car[]) public carsByOwner; // 以汽车结构体为类型定义映射,让地址映射到汽车结构体数组,代表一个人可能拥有多辆汽车。 function examples() external { // 在函数中声明局部变量,局部变量运行在内存中,方法调用完成之后,局部变量会消失 // 定义汽车局部变量方式1,标记位置为内存memory,也就是函数运行完之后就不存在了。必须按顺序填入值 Car memory byd = Car("Byd",2010,msg.sender); // 定义汽车局部变量方式2,用大括号方式定义名称,不需要考虑值顺序 Car memory bmw = Car({year: 1980,model:"BWM",owner: msg.sender}); // 定义汽车局部变量方式3,定义结构体变量,变量会有默认值,在默认值基础之上修改默认值 Car memory tesla; tesla.model = "Tesla"; tesla.year = 2010; tesla.owner = msg.sender; // 将汽车变量推入到数组 cars.push(byd); cars.push(bmw); cars.push(tesla); // 在推入数组的时候可以直接定义,推入到数组中之后,就会记录到状态变量中 cars.push(Car("Ferrari",2020,msg.sender)); // 获取结构体中的值,把变量装入到内存中,使用memory,内存中不能修改删除数据 Car memory _car = cars[0]; // 使用变量的值 _car.model; // 如果定义在存储中,就可以执行修改删除操作,修改了0下标结构体中年份的值 Car storage _car2 = cars[0]; _car2.year = 1999; // 删除变量值,该字段值恢复到默认值,删除了0下标结构体中地址的值 delete _car2.owner; // 还可以删除整个数组索引,这样数组中为1下标的三个值都恢复成默认值 delete cars[1]; } } // 枚举 可以定义多种状态 contract Enum { enum Status { None, // 无状态,默认值 Pending, // 处理中 Shipped, // 装载中 Completed, // 已完成的 Rejected, // 已拒绝的 Canceled // 已取消的 } // 枚举和结构体都是一种类型,用这种类型去定义变量 Status public status; // 可以把枚举写在结构体内部 struct Order { address buyer; Status status; } // 将枚举定义在结构体的数组中,嵌套使用 Order[] public orders; // 返回的枚举的索引值,设置为3,返回为3 function getStatus() external view returns (Status) { return status; } // 设置枚举值,以索引形式设置,例如传入3 function setStatus(Status _status) external { status = _status; } // 修改为指定枚举变量值 function ship() external { status = Status.Shipped; } // 让枚举变量恢复到默认值,枚举的默认值就是枚举的第一个字段 function reset() external { delete status; } } /***************************** 代理合约部署开始 **********************************/ // 用合约部署合约 contract TestContract1 { address public owner = msg.sender; function setOwner(address _owner) public { require(msg.sender == owner,"not owner"); owner = _owner; } } contract TestContract2 { address public owner = msg.sender; uint public value = msg.value; uint public x; uint public y; constructor(uint _x,uint _y) payable { x = _x; y = _y; } } // 代理合约,可以指定部署某个合约 contract Proxy { // 声明事件 event Deploy(address); // 把合约的机器码输入进来进行指定合约的部署 payable可以发送币,方法返回新部署的地址 function deploy(bytes memory _code) external payable returns (address addr){ // 不采用new合约的方式 // new TestContract1(); // 采用内联汇编方式 create方法再加三个参数 assembly { // create(v,p,n) // v = amount of ETH to send 代表主币发送数量 // p = pointer in memory to start of code 代表内存中机器码开始的位置 // n = size of code 代表机器码在内存中大小 // 参数1 通常msg.value表示,但是在内联汇编中采用callvalue(); 参数2 使用add()获取位置并跳过0x20位置,参数3 使用mload()获取大小。 隐式返回 addr := create(callvalue(), add(_code, 0x20), mload(_code)) } // 部署不一样成功,需再次确认,部署后的合约地址不能等于0地址,否则报出部署失败消息 require(addr != address(0) , "deploy failed"); // 触发事件,将部署成功向外报出来 emit Deploy(addr); } // 合约1设置管理员方法只能由合约部署者调用,怎么把管理员改为代理,使用工具合约中的新的机器码 function execute(address _target,bytes memory _data) external payable { (bool success,) = _target.call{value: msg.value}(_data); require(success, "failed"); } } // 助手函数 工具类合约,返回合约的机器码 contract Helper { // 返回test合约1机器码,无参构造 function getBytesCode1() external pure returns (bytes memory) { bytes memory bytecode = type(TestContract1).creationCode; return bytecode; } // 返回test合约2机器码,带参构造 构造参数就是连接在bytecode之后的一段十六进制数字 所以我们把输入参数_x,_y,通过打包的形式连接在test合约2 bytecode之后,形成新的bytecode function getBytesCode2(uint _x,uint _y) external pure returns (bytes memory) { bytes memory bytecode = type(TestContract2).creationCode; return abi.encodePacked(bytecode,abi.encode(_x,_y)); } // 调用设置管理员方法,返回新的机器码 function getCalldata(address _owner) external pure returns (bytes memory) { return abi.encodeWithSignature("setOwner(address)" , _owner); } }
部署流程:
1. 部署助手合约、代理合约
2. 通过助手合约获取Test1合约十六进制机器码
3. 将Test1合约十六进制机器码拷贝下来当做代理合约参数,生成合约地址,找到声明的Deploy事件,找到地址
4. 拷贝地址,找到Test1合约,使用At address 将Test1合约加载出来
5. 可以看到加载出来的Test1合约,不是部署出来的,点击获取管理员,这个时候管理员地址就是代理合约地址
6. 把代理合约地址设置成自己的地址,但是Test1 setOwner方法判断了修改人是不是管理员,用自己地址调用会报错,因为调用者并不是当前管理员,当前管理员是代理合约,必须通过代理合约把管理员换成自己的地址
7. 通过代理合约把管理员换成自己的地址,调用代理合约的execute方法,然后设置目标地址,就是测试1的合约地址,data就是调用setOwner加参数打包之后的十六进制编码,使用助手合约生成,在助手合约填入自己地址作为参数,调用getCalldata方法获取机器码,拷贝出来
8.调用代理合约execute方法,参数1为Test1的合约地址,参数2为上图地址
9. 调用成功过之后,重新查看Test1合约的管理员地址,已经改成了自己的地址
10.部署测试合约2,测试合约2有两个构造参数,并且构造函数带有payable,可以在部署的时候发送主币,使用助手合约部署测试2合约,生成测试2的机器码,拷贝出来
11. 将拷贝出来的机器码放在代理合约的部署方法里,部署生成合约2的地址,同时可以先填写一定数量的主币
12. 部署成之后找到事件,找到测试合约2的地址,拷贝出来
13.加载测试合约2的地址,找到测试合约2的代码,填写测试合约2地址,点击At Address,此时测试合约2就加载出来了
13.可以看到测试合约2的管理员地址就是代理地址,同时value就是发送的123个主币的数量,还有x,y的值和我们输入的是一样的
/***************************** 代理合约部署结束 **********************************/
/** 数据存储位置 在智能合约中数据存储在:storage、memory、calldata位置上 存储在storage上的是状态变量 存储在memory上的是局部变量 存储在calldata上的变量和memory有点类型,但是只能用在输入的参数中 **/ contract DataLocations { struct MyStruct { uint foo; string text; } mapping(address => MyStruct) public myStructs; // 如果参数是数组,必须定义memory或者calldata类型 ,字符串参数也要加memory,因为字符串类型的本质也是一个数组 // 返回值如果是结构体或者是字符串或者是数组,也要加上存储位置 function examples(uint[] memory y, string memory s) external returns (uint[] memory){ // 把结构体装在映射中 myStructs[msg.sender] = MyStruct({foo:123, text: "bar"}); // 从映射中读取结构体,使用了storage存储位置,代表把状态变量读取到了myStruct变量中,这时候就可以针对这个变量进行读写操作 MyStruct storage myStruct = myStructs[msg.sender]; // 把变量改变成一个新的值 下次读取合约的时候,MyStruct.text的值就是现在改的值 myStruct.text = "foo"; // 如果变量定义到内存位置,也是可以修改变量的值的,但是只在方法内有效,函数调用完成之后会消失,并不会记录在链上 MyStruct memory readOnly = myStructs[msg.sender]; readOnly.foo = 456; // 返回内存数组需要先定义一个数组,数组加上一个长度,因为内存中的数组必须是定长数组,不能是动态数组。局部变量必须是定长数组 uint[] memory memArr = new uint[](3); // 给数组赋值,必须带上索引,不能使用push memArr[0] = 123; return memArr; } // 使用calldata,可以在两个函数之间传递参数 function examples2(uint[] calldata y, string calldata s) external returns (uint[] memory){ _internal(y); } // 如果这里参数使用memory定义,智能合约会把上一个类型的calldata参数数组值重新赋值到memory类型中,要拷贝所有内容,这样做会浪费很多gas,如果使用calldata就可以把参数直接传递到下一个函数中 function _internal(uint[] calldata y) private { uint x = y[0]; } } // 简单存储 contract SimpleStorage { // 定义字符串,进行读写操作 string public text; // 定义函数给字符串赋值,采用下划线用于区分输入的变量和状态变量 设置external外部可见,合约内部其他函数不能调用该函数 // aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa // calldata 22993 gas 执行成本略低 // memory 23475 gas function setText(string memory _text) external { text = _text; } // 智能合约把状态变量拷贝到内存中,然后返回回去的 function getText() external view returns (string memory) { return text; } } // 通过智能合约实现待办事项列表 contract TodoList { // 声明待办列表的结构体 struct Todo { string text; // 待办事项文本 bool completed; // 待办事项是否完成 } // 用这个结构体创建一个数组,有很多待办事项 Todo[] public todos; // 创建待办事项,类型为calldata function create(string calldata _text) external { // 将参数插入到数组 todos.push(Todo({ text: _text, completed: false })); } // 更新待办事项,需要指定数组的索引 function updateText(uint _index, string calldata _text) external { // 更新方式1:直接更新 todos[_index].text = _text; // 更新方式2:将数组取值装入到storage存储中,然后再去更新 // 两者更新区别: 如果方式1的有4个数据要更新,就会把整个结构体装入到内存4次,gas消耗大,而方式2只需装载1次,节省gas。 // 如果方式1只有1个数据更新,不占用存储,gas消耗小于方式2,方式2适用于有很多数据要存储 // Todo storage todo = todos[_index]; // todo.text = _text; } // 获取待办,参数输入下标 function get(uint _index) external view returns (string memory,bool){ // 装到storage存储中消耗gas略少,因为存储中的todo是直接从状态变量中读取出来的,返回值的文本需要经过一次拷贝返回 // 如果装到memory内存中,是从todos状态变量拷贝到内存中,然后再返回的时候又经历一次拷贝,拷贝2次,所以memory有两次拷贝,而storage只有一次拷贝 Todo memory todo = todos[_index]; return (todo.text,todo.completed); } // 改变是否完成的状态,原来状态反转即可 function toggleCompleted(uint _index) external { todos[_index].completed = !todos[_index].completed; } } /** 事件 事件是一种记录当前智能合约运行状态的方法,但是并不记录在状态变量里,而是会体现在区块链浏览器上或者体现在交易记录中的logs里 通过事件可以查询一些改变过的状态 **/ contract Event { // 声明事件,以大写字母开头,参数为事件要报告的类型 event Log(string message, uint val); // 在类型之后规定一个索引,indexed标记过的变量就可以在链外进行搜索查询,在链外可以利用工具查询某地址报出来的所有事件 // 在事件中可以定义多个变量,但是indexed标记的变量不能超过3个,超过会报错 event IndexedLog(address indexed sender, uint val); // 事件会改变链上事件的状态,所以不能标记pure或view function example() external { // 触发事件 采用emit触发 调用该函数会触发该事件,该事件会记录在交易记录的logs里,然后也会体现在区块链浏览器上 emit Log("foo", 123); // 触发indexed标记事件,使用方法一样 emit IndexedLog(msg.sender, 456); } // 定义事件,事件的存储更节约gas event Message(address indexed _from, address indexed _to, string message); function sendMessage(address _to, string calldata message) external { // 触发事件 emit Message(msg.sender, _to, message); } } // 继承 contract A { // 关键词 virtual 表示可以被子合约重写 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"; } } // 继承A合约,可直接部署B合约 contract B is A { // 关键词 override 表示覆盖掉父合约,重写 function foo() public pure override returns (string memory) { return "B"; } // virtual 让子合约重写 function bar() public pure virtual override returns (string memory) { return "B"; } } // C继承B,B继承A,直接部署C contract C is B { function bar() public pure override returns (string memory) { return "C"; } } /*********************** 多线继承 ***************************/ // 多线继承,遵循基类到派生合约的顺序关系,就是Z同时继承了X、Y,同时,Y继承了X,Z合约中的X、Y到底哪个写在前面 X,Y,Z contract X { function foo() public pure virtual returns (string memory) { return "X"; } function bar() public pure virtual returns (string memory) { return "X"; } function x() public pure returns (string memory) { return "X"; } } contract Y is X { function foo() public pure virtual override returns (string memory) { return "Y"; } function bar() public pure virtual override returns (string memory) { return "Y"; } function y() public pure returns (string memory) { return "y"; } } // Z合约同时继承X、Y合约,要注意先后顺序,写错会报错。先写X,X是基础的。这样是正确的线性继承。 contract Z is X,Y { // 覆盖两个合约都有的函数要加括号,代表同时覆盖两个相同源函数,这里的顺序可以不按顺序,颠倒也没事 function foo() public pure override(X,Y) returns (string memory) { return "Z"; } function bar() public pure override(Y,X) returns (string memory) { return "Z"; } } /*********************** 运行父合约的构造函数 ***************************/ // 运行父合约的构造函数 contract S { string public name; constructor(string memory _name) { name = _name; } } contract T { string public text; constructor(string memory _text) { text = _text; } } // 两种输入父合约构造函数的方法: // 第一种已知参数,直接写在继承的括号里 contract U is S("s"), T("t") {} // 第二种,不知道参数,在部署的时候由部署者再输入 合约构造初始化顺序按照继承的顺序执行,S,T,V contract V is S, T { constructor(string memory _name,string memory _text) S(_name) T(_text) {} } // 也可以混合使用 contract W is S("s"),T { constructor(string memory _text) T(_text) {} } /*********************** 调用父合约的函数 ***************************/ // 调用父合约的函数 contract E { event Log(string message); function foo() public virtual { emit Log("E.foo"); } function bar() public virtual { emit Log("E.bar"); } } contract F is E { function foo() public virtual override { emit Log("F.foo"); // 再次调用父级合约的foo方法,有两种方法,1是直接父合约名字点 E.foo(); } function bar() public virtual override { emit Log("F.bar"); // 调用父级合约的第二种方法,使用super super.bar(); } } contract G is E { function foo() public virtual override { emit Log("G.foo"); // 再次调用父级合约的foo方法,有两种方法,1是直接父合约名字点 E.foo(); } function bar() public virtual override { emit Log("G.bar"); // 调用父级合约的第二种方法,使用super super.bar(); } } contract H is F,G { function foo() public virtual override(F,G) { // 只调用了F合约的foo方法,F合约继承了E合约,日志显示F、E F.foo(); } function bar() public virtual override(F,G) { // 调用所有父级合约的bar方法,日志显示G、F、E super.bar(); } } /* 可视范围 private 私有,只在合约内部可见 internal 内部,合约内部和被继承的可见 public 公开,外部可见 external 外部,外部可见,内部不可访问,只能外部其他合约调用 */ contract VisibilityBase { uint private x = 0; uint internal y = 1; uint public z = 2; function privateFun() private pure returns (uint) { return 0; } function internalFun() internal pure returns (uint) { return 100; } function publicFun() public pure returns (uint) { return 200; } function externalFun() external pure returns (uint) { return 300; } function examples() external view { x + y + z; privateFun(); internalFun(); publicFun(); // 内部不可用访问external修饰方法,该方法主要给外部提供。 // externalFun(); // 如果内部访问external修饰方法,加上this 这样是先从外部访问合约再访问方法,浪费gas 不建议使用这样的方式访问外部函数,想访问的话,定义成public就可以了 // this.externalFun(); } } contract VisibilityChild is VisibilityBase { function examples2() external view { // 子合约不可用访问父合约私有变量和方法 y + z; internalFun(); publicFun(); // 外部函数也不能访问,外部函数,只能由外部调用的时候访问,也不可用重写 // externalFun(); } } // 不可变量 常量的另一种表达方式 contract Immutable { // 使用immutable修饰常量既可以节约gas费,又可以在合约部署的时候定义常量值 // address public immutable owner = msg.sender; address public immutable owner; uint public x; // 使用immutable修饰也可以在构造中赋值 constructor(address _addr) { owner = _addr; } function foo() external { require(msg.sender == owner); x +=1; } } // 支付eth关键词payable contract Payable { // 地址使用payable标记,该地址可以接收主币 address payable public owner; // 使用构造,也用payable括起来 constructor() { owner = payable(msg.sender); } // 函数使用payable标记,该函数可以接受主币 function deposit() external payable { } // 获取当前合约余额函数,只读 function getBalance() external view returns (uint) { // 直接访问当前地址余额,当前地址使用address(this)就能代表 return address(this).balance; } // 提现,转移合约中的全部资金到owner地址 function withdraw() external { //payable(msg.sender).transfer(address(this).balance); owner.transfer(address(this).balance); } } /* 回退函数 回退函数在智能合约中有两个触发,当调用的函数在合约中不存在的时候,或者在合约中发送ETH主币的时候都会调用回退函数 回退函数对外不可见,外部使用calldata调用,点击transact。 如果合约中两个函数都存在,如果calldata有值,只调用fallback函数,如果calldata没值,则只调用receive函数 如果只有fallback函数,不管calldata是否有值,都会调用fallback函数 */ contract Fallback { event Log(string func, address sender, uint value, bytes data); // 回退函数写法1 可视范围为外部可视,不加payable时,当调用合约不存在函数时触发,加上payable时,当合约中发送主币时也能触发,两个都能触发了。 fallback() external payable { emit Log("fallback", msg.sender, msg.value, msg.data); } // 回退函数写法2 receive不接收数据 receive() external payable { emit Log("receive", msg.sender, msg.value, ""); } } /*************************** 合约之间发送主币与接收开始 ***************************************/ /* 发送eth主币,在智能合约中有三种方法发送主币: transfer 只会带有2300gas,如果失败会reverts send 只会带有2300gas,只会返回成功或失败 call 会发送所有剩余的gas,会返回是否成功的布尔值,还会返回一个data数据 */ contract SendEther { // 存储主币方法1,在构造中传入,使用payable修饰 constructor() payable {} // 存储主币方法2,使用回退函数fallback、receive接收主币,用payable修饰 // 只接收主币,不接收数据使用receive就可以了,用于接收transfer发送失败退回来的币 receive() external payable {} function sendViaTransfer(address payable _to) external payable { // 向_to地址发送123数量主币 固定携带gas2300 发送失败没有返回值,直接报错 _to.transfer(123); } function sendViaSend(address payable _to) external payable { // 会返回成功失败bool值,固定携带gas2300 bool send = _to.send(123); require(send, "send failed"); } function sendViaCall(address payable _to) external payable { // call发送语法:大括号加小括号,小括号不携带数据用空字符串。有两个返回值,返回值1是否成功,返回值2,如果是智能合约,就有可能返回一个data数据,可以先忽略 // (bool success,bytes memory data) = _to.call{value: 123}(""); (bool success,) = _to.call{value: 123}(""); require(success, "call failed"); } } // 定义接收者合约,上个合约发送主币,这个合约接收主币。 contract EthReceiver { event Log(uint amount, uint gas); receive() external payable { emit Log(msg.value, gasleft()); } } /*************************** 合约之间发送主币与接收结束 ***************************************/ /* 制作ETH钱包合约 通过这个钱包,可以向合约中存入一定数量的主币,并且可以随时从钱包中取出存入的主币 */ contract EtherWallet { // 定义管理员 address payable public owner; constructor() { owner = payable(msg.sender); } // 定义回退函数,用于接收主币。因为只需让合约接收主币,而并不需要让人调用不存在的函数。 点击calldate Transact 不加参数 receive() external payable {} // 提现到管理员账户 function withdraw(uint _amount) external { require(msg.sender == owner,"caller is not owner"); // 可以直接使用payable(msg.sender) 节省gas,因为owner是从状态变量中读取出来的,如果使用内存中变量就能节约gas // owner.transfer(_amount); payable(msg.sender).transfer(_amount); // 也可以使用call方法发送,使用call发送不需要加payable()属性 // (bool send,) = msg.sender.call{value: _amount}(""); // require(send,"Failed to call Ether"); } // 获取余额 function getBalance() external view returns (uint) { return address(this).balance; } } /************************ 合约调用其他合约开始 ***********************************/ // 合约调用其他合约 contract CallTestContract { // 调用Test合约setX函数 调用方法1,以另一个合约为类型,加括号,在括号里写入另一个合约地址。另一个合约也在当前文件中,知道合约类型 // function setX(addrss _test, uint _x) external { // TestContract(_test).setX(_x); // } // 调用Test合约setX函数 调用方法2,参数直接写另一个合约 function setX(TestContract _test, uint _x) external { _test.setX(_x); } // 调用Test合约getX函数 function getX(address _test) external view returns (uint x) { x = TestContract(_test).getX(); } // 调用Test合约setXandReceiveEther函数,同时发送主币 msg.value是主币数量,这里也要用payable,因为要把主币传递过去 function setXandReceiveEther(address _test, uint _x) external payable { // 向test传主币使用大括号,value值为传递的数量,msg.value表示全部传过去 TestContract(_test).setXandReceiveEther{value: msg.value}(_x); } // 调用Test合约getXandValue函数 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); } } /* 接口调用其他合约 如果不知道另一个合约源代码,或者另一个合约的源代码特别大,就可以通过接口方法来调用另一个合约 */ // 定义接口合约 interface ICounter { // 在接口中写一下要调用合约的函数名称 只读用view 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(); } } // 该合约写到其他文件,测试时不要写在一起,就是要调它 // contract Counter { // uint public count; // // function inc() external { // count += 1; // } // function dec() external { // count -= 1; // } // } /* 使用call低级调用来调用另一个合约 */ 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); } } // 定义合约,来调用test合约函数 contract Call { // 定义状态变量,用于装载返回的数据 bytes public data; // 合约如果没有主币需要使用payable外部传送 function callFoo(address _test) external payable { // 使用abi编码传递参数,函数名称以字符串形式传递,这里uint必须写uint256类型, 后面跟上参数值 // 调用call同时可以主币的数量用大括号表示,大括号value表示发送多少币,gas表示携带多少gas 修改两个变量5000 gas费用不够 // 有两个返回值,返回值1用来标记是否调用成功,返回值2用来装载被调用的函数的所有返回值 (bool success, bytes memory _data) = _test.call{value:111}( abi.encodeWithSignature("foo(string, uint256)","call foo",123) ); // 判断调用是否成功,如果失败,报错提示 require(success, "callFoo failed"); data = _data; } // 调用测试合约不存在的函数,测试合约会触发fallback函数调用。如果测试合约没有fallback回退函数,再调用时这个函数会直接报错 function callDoesNotExit(address _test) external { (bool success,) = _test.call(abi.encodeWithSignature("doesNotExit()")); require(success, "call failed"); } } /* 委托调用 调用DelegateCall合约的setVars函数,传入值,该函数虽然调用了测试合约的setVars函数,但是实际测试合约的变量值不会改变,只改变了当前合约的变量值。 升级合约,需要在测试合约改变代码,比如num = 2 * _num; 就会改变DelegateCall变量值 同时需要注意DelegateCall合约和测试合约的变量要保持一致,否则会发出变量查找错误,导致赋值错误。 */ contract TestDelegateCall { uint public num; address public sender; uint public value; 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 { // 委托调用,和call低级调用相似,abi也可是使用encodeWithSelect选择 两种实现效果一样 // _test.delegateCall(abi.encodeWithSignature("setVars(uint256)", _num)); (bool success, bytes memory data) = _test.delegatecall( abi.encodeWithSelector(TestDelegateCall.setVars.selector, _num) ); require(success , "delegateCall failed"); } } /* 工厂合约 采用new创建 创建Account合约,只需部署AccountFactory合约即可,需在一个文件中,如果不在一个文件中,需使用import导入进来 将创建好Account合约,使用地址方式加载出来。 输入Account合约地址,点击At Address */ 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, uint _amount) external payable { // Account合约构造有payable修饰,这里使用大括号修饰 Account account = new Account{value: _amount}(_owner); accounts.push(account); } } /************************ 合约调用其他合约结束 ***********************************/ /* 库合约 库合约也是一种节约代码量的做法 */ // 定义库合约 使用library开头 library Math { // 一般库合约都是在合约内部使用,所以定义成internal 定义成外部可视和私有可视完全没有意义 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 { // 传入的数组是状态变量,使用storage修饰 参数1数组,参数2要寻找的数字,返回值:参数2在数组所在索引 function find(uint[] storage arr, uint x) internal view returns (uint) { for (uint i=0; i