Solidity基础语法代码

// 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 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. 部署助手合约、代理合约

Solidity基础语法代码_第1张图片

2. 通过助手合约获取Test1合约十六进制机器码

Solidity基础语法代码_第2张图片

3. 将Test1合约十六进制机器码拷贝下来当做代理合约参数,生成合约地址,找到声明的Deploy事件,找到地址

Solidity基础语法代码_第3张图片

4. 拷贝地址,找到Test1合约,使用At address 将Test1合约加载出来

Solidity基础语法代码_第4张图片

5. 可以看到加载出来的Test1合约,不是部署出来的,点击获取管理员,这个时候管理员地址就是代理合约地址

Solidity基础语法代码_第5张图片

6. 把代理合约地址设置成自己的地址,但是Test1 setOwner方法判断了修改人是不是管理员,用自己地址调用会报错,因为调用者并不是当前管理员,当前管理员是代理合约,必须通过代理合约把管理员换成自己的地址

Solidity基础语法代码_第6张图片

7. 通过代理合约把管理员换成自己的地址,调用代理合约的execute方法,然后设置目标地址,就是测试1的合约地址,data就是调用setOwner加参数打包之后的十六进制编码,使用助手合约生成,在助手合约填入自己地址作为参数,调用getCalldata方法获取机器码,拷贝出来

Solidity基础语法代码_第7张图片

8.调用代理合约execute方法,参数1为Test1的合约地址,参数2为上图地址

Solidity基础语法代码_第8张图片

9. 调用成功过之后,重新查看Test1合约的管理员地址,已经改成了自己的地址

Solidity基础语法代码_第9张图片Solidity基础语法代码_第10张图片

10.部署测试合约2,测试合约2有两个构造参数,并且构造函数带有payable,可以在部署的时候发送主币,使用助手合约部署测试2合约,生成测试2的机器码,拷贝出来

Solidity基础语法代码_第11张图片

11. 将拷贝出来的机器码放在代理合约的部署方法里,部署生成合约2的地址,同时可以先填写一定数量的主币

Solidity基础语法代码_第12张图片Solidity基础语法代码_第13张图片

12. 部署成之后找到事件,找到测试合约2的地址,拷贝出来

Solidity基础语法代码_第14张图片

13.加载测试合约2的地址,找到测试合约2的代码,填写测试合约2地址,点击At Address,此时测试合约2就加载出来了

Solidity基础语法代码_第15张图片

13.可以看到测试合约2的管理员地址就是代理地址,同时value就是发送的123个主币的数量,还有x,y的值和我们输入的是一样的

Solidity基础语法代码_第16张图片

/***************************** 代理合约部署结束 **********************************/

/**
    数据存储位置 在智能合约中数据存储在: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 
  


 

你可能感兴趣的:(区块链,Solidity)