// 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; i<arr.length-1; i++) {
arr[i] = arr[i+1];
}
arr.pop();
}
function testRemoveFun() external {
arr = [1,2,3,4,5];
remove(2); // [1,2,4,5]
assert(arr[0] ==1);
assert(arr[1] ==2);
assert(arr[2] ==4);
assert(arr[3] ==5);
assert(arr.length == 4);
arr = [1];
remove(0); // []
assert(arr.length == 0);
}
}
// 数组删除,原理替换,将要删除的下标赋值为数组最后一个元素值,再pop最后一位,缺点会打乱数组顺序
contract ArrayReplace {
uint[] public arr;
function remove(uint _index) private {
arr[_index] = arr[arr.length -1];
arr.pop();
}
function test() external {
arr = [1,2,3,4];
remove(1); // [1,4,3]
assert(arr.length == 3);
assert(arr[2] == 3);
remove(2); // [1,4]
assert(arr.length == 2);
assert(arr[1] == 4);
}
function getArr() external view returns(uint[] memory){
return arr;
}
}
// Mapping映射
contract Mapping {
// 数组["alice","bob","charlie"] 数组查询tom需要遍历数组
// 映射["alice":true,"bob":true,"charlie":true] 映射查询tom只需看是否返回true
mapping(address => uint) 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<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.find(2);
}
}
本文介绍了区块链合约的部署流程,包括部署助手合约、代理合约,获取Test1合约十六进制机器码并加载合约,设置管理员地址,还涉及测试合约2的部署,包括处理构造参数、发送主币等操作,最终完成代理合约部署。
849

被折叠的 条评论
为什么被折叠?



