Solidity8 高级 
合约部署 
通过合约部署合约。
JavaScript
// SPDX-License-Identifier:MIT
  
pragma solidity 0.8.7;
  
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 {
    function deploy(bytes memory _code) external payable {
        new TestContract1();
    }
}但是我们希望直接通过在_code 处输入机器码就可以直接部署合约,接下来实现一个新的代理合约:
JavaScript
// SPDX-License-Identifier:MIT
  
pragma solidity 0.8.7;
  
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);
  
    function deploy(bytes memory _code) external payable returns (address addr) {
        // 添加内联汇编
        assembly {
            // create(v, p, n)
            // v = amount of ETH to send
            // p = pointer in memory o start of code
            // n = size of code
             addr := create(callvalue(), add(_code, 0x20), mload(_code))  // 隐式返回
            // 这里用 callvalue()替换平常使用的 msg.sender 来获取这次消息发送的主例
            // add(_code, 0x20)获取 pointer,mload(_code)获取大小
        }
        // 返回的地址使零地址,说明这次合约失败
        require(addr != address(0), "deploy failed");
  
        // 之后触发这个事件,这样我们就可以在交易中看到这个地址是多少了
        emit Deploy(addr);
    }
    // 使代理合约将这个地址改为我们的
    function execute(address _target, bytes memory _data) external payable {
        (bool success, ) = _target.call{value: msg.value}(_data);
        require(success, "failed");
    }
}
// 获取机器码_code
contract Helper {
    function getBytecode1() external pure returns (bytes memory) {
        bytes memory bytecode = type(TestContract1).creationCode;
        return bytecode;
    }
    // 测试合约 2 它有构造函数,有参数,所以不能直接像上述操作一样,
    function getBytecode2(uint _x, uint _y) external pure returns (bytes memory) {
        bytes memory bytecode = type(TestContract2).creationCode;
        // 所以我们将 xy 通过打包的形式连接在其之后形成新的 bytecode 这样就能形成新的 bytecode
        return abi.encodePacked(bytecode, abi.encode(_x, _y));
    }
    // 这是设置管理员那个方法的 bytecode
    function getCalldata(address _owner) external pure returns (bytes memory) {
        return abi.encodeWithSignature("setOwner(address)" _owner);
    }
}存储位置 
- storage:使用该定义修改后,状态变量的值就直接发生了修改
- memory:使用该定义修改后,是处于局部变量的位置,函数调用结束后就消失了,并不会对状态变量产生影响。
- calldata:和 memory 类似,但只能用在输入的参数中,相比于 memroy 参数,calldata 在多函数传递参数时,可以直接传递,不需要像 memory 一样复制,从而可以节省 gas。 当参数或返回值是数组、字符串、结构体这类的,都需要加上 memory 关键字 简单存储例子:
JavaScript
// SPDX-License-Identifier:MIT
  
pragma solidity 0.8.7;
contract SimpleStorage {
    string public text;
  
    // aaaaaaaaaaaaaa...
    // calldata 89626 gas
    // memory 90114 gas
    function set(string calldata _text) external {
        text = _text;  // 修改状态变量
    }
    // 相当于智能合约将状态变量拷贝到了内存中然后返回回来
    function get() external view returns (string memory) {
        return text;
    }
}阶段例子--TodoList 
JavaScript
// SPDX-License-Identifier:MIT
  
pragma solidity 0.8.7;
  
// insert, update, read  from array of structs
contract TodoList {
    struct Todo {
        string text;
        bool completed;
    }
  
    Todo[] public todos;
  
    function create(string calldata _text) external {
        todos.push(Todo({
            text: _text,
            completed: false
        }));
    }
    // 根据索引进行更新
    function updateText() external {
        // method1: 如果只更新其中一部分内容,这种方法会更节约 gas
        todos[_index].text = _text;
  
        // method2:先装入到 storage 中,再进行更新。这种方法对于更新全部数据会更节约 gas
        Todo storage todo = todos[_index];
        todo.text = _text;
        // other data...
    }
  
    function get(uint _index) external view returns (string memory, bool) {
        Todo memory todo = todos[_index];
        return (todo.text, todo.completed);
    }
  
    function toggleCompleted(uint _index) external {
        // 改变完成状态,反转即可。
        todos[_index].completed = !todos[_index].completed;
    }
}事件 
事件是一种记录当前智能合约运行状态的方法,但是它并不会记录在状态变量中,而是会体现在区块浏览器上,或是出现在交易记录中的Log中。
JavaScript
// SPDX-License-Identifier:MIT
  
pragma solidity 0.8.7;
  
contract Event {
    event Log(string message. uint val);
    // 注意:就是在一个事件中,可以让他汇报很多很多变量,但是在索引总,最多只能有三个变量。
    event IndexedLog(address indexed sender, uint val);
  
    function example() external {
        // 调用这个函数就会触发这个事件,这个事件就会记录在交易记录中的 Logs 里,也会体现在区块链浏览器上
        emit Log("Foo", 1234);
        emit IndexedLog(msg.sender, 789);
        // 然后我们在链外用 web3 或者其他就可以查出该地址所有的事件  
    }
    // 索引的三个变量例子
    event Message(address indexed _from, address indexed _to, string message);
    function sendMessage(address _to, string calldata message) external{
        // 然后同上,写个函数触发这个事件
        emit Message(msg.sender, _to, message);
    }
}继承 
基础 
- 在继承前,首先要用virtual标记哪些函数是可以被重写的
- 然后后面来进行重写操作的函数也要通过override标记函数来证明它是覆盖调之前的函数
JavaScript
// SPDX-License-Identifier:MIT
  
pragma solidity 0.8.7;
  
contract A {
    function foo() public pure virtual returns (string memory) {
        return "A";
    }
  
    function bar() public pure virtual returns (string memory) {
        return "A";
    }
  
    // more code here
    function baz() public pure returns (string memory) {
        return "A";
    }
}
  
contract B is A {
    function foo() public pure override returns (string memory) {
        return "B";
    }
  
    function bar() public pure override returns (string memory) {
        return "B";
    }
  
    // more code here
}多重继承: 
原则:我们需要把继承最少,越基础的合约放在最前面,按层数从上往下来,不然编译的时候就会报错!
JavaScript
contract X{//...}
contract Y is X{//...}
contract Z is X,Y{
	function foo() public pure override(X, Y) returns (string memory) {//...}
}注意:这里第三行就需要先写 X,再写 Y。
运行父合约中构造函数 
JavaScript
// SPDX-License-Identifier:MIT
  
pragma solidity 0.8.7;
  
contract S{
    string public name;
  
    constructor(string memory _name) {
        name = _name;
    }
}
  
contract T {
    string public text;
  
    constructor(string memory _text) {
        text = _text;
    }
}
  
// method1:在已知构造函数所需参数的时候可以使用这种方法
contract U is S("s"), T("t") {
  
}
// method2:这样可以在部署的时候由调用者自己使用
contract V is S, T {
    constructor(string memory _name, string memory _text) S(_name) T(_text) {
  
    }
}
// 混合使用:一个继承的时候传,一个部署的时候传
contract UV is S("s"), T {
    constructor(string memory _text) T(_text) {
  
    }
}构造函数初始化的运行顺序:会按照继承的顺序进行初始化,即使改变了构造函数上的S(_name) T(_text)的顺序也不会影响,始终是按继承的顺序进行初始化。
JavaScript
// Order of execution
// 1. T
// 2. S
// 3. V3
contract V3 is T, S {
    constructor(string memory _name. string memory _text) S(_name) T(_text) {}
}调用父级合约函数 
JavaScript
// SPDX-License-Identifier:MIT
  
pragma solidity 0.8.7;
  
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");
        // method1:直接调用
        E.foo();
    }
  
    function bar() public virtual override {
        emit Log("F.bar");
        // method2:使用 super,这个关键字会自动寻找父级合约
        super.bar();  // 如果是多个父级合约都含有该方法,会栈式调用,并且调用且只调用一次所有相关合约
    }
}可视范围 
- private:合约的内部可见
- internal:合约内部和继承其的子合约
- public:公开
- external:只有外部的合约可见
不可变量 
有些常量的值在部署合约之前是不知道的。 关键词:immutable 也会节约gas费
JavaScript
// SPDX-License-Identifier:MIT
  
pragma solidity 0.8.7;
  
  
contract Immutable {
    // 45718 gas
    // address public owner = msg.sender;
  
    // 43585 gas
    address public immutable owner;
  
    constructor() {
        owner = msg.sender;
    }
  
    uint public x;
    function foo() external {
        require(msg.sender == owner);
        x += 1;
    }
}支付 Eth 
如果你在函数中标记一个 payable 关键词,你就可以接收以太坊铸币的参数。
JavaScript
// SPDX-License-Identifier:MIT
  
pragma solidity 0.8.7;
  
contract Payable {
    // 标记地址变量,然后这个地址就可以发送以太坊铸币了
    address payable public owner;
  
    constructor() {
        owner = payable(msg.sender);  // 这里因为之前定义的 owner 具有 payable 关键字,所以这里也需要加上,否则报错!
    }
  
    function deposit() external payable{}  // 这里可以接收铸币了,别人发的会存在当前合约地址上
  
    function getBalance() external view returns (uint) {
        return address(this).balance;
    }
}回退函数 
功能:
- 当你调用函数在合约中不存在的时候
- 向合约中直接发送以太坊铸币的时候
JavaScript
// SPDX-License-Identifier:MIT
  
pragma solidity 0.8.7;
  
contract Fallback {
    // 当你调用合约中不存在的函数的时候,就会转入到回退函数中,触发里面的逻辑。
    // 而要接收铸币的发送,还需要加上 payable
    fallback() external payable {}
    // solidity8.0 进行了细化,对铸币的回退单独出了一个函数
    receive() external payable {}  // 有这个函数,发送铸币回退时就只进入该函数,没有这个函数,就会进入 fallback 函数
    // 同时 receive 函数时不接收任何数据的
}发送 Eth 
- transfer -- 2300gas,reverts(失败)
- send -- 2300gas,return bool
- call -- all gas,returns bool and data
JavaScript
// SPDX-License-Identifier:MIT
  
pragma solidity 0.8.7;
  
contract SendEther {
    constructor() payable {
        function sendViaTransfer(address payable _to) external payable {
            _to.transfer(123);
        }
        // 发送铸币的时候,只带 2300gas,如果 gas 被消耗完,或者是发送的时候对方拒收等其他原因,就会报出异常 revert
        function sendViaSend(address payable _to) external payable {
            _to.transfer(123);  // 123wei+2300gas
        }
        // 遇到错误情况并不会报出异常,而是返回一个布尔值
        function sendViaSend(address payable _to) external payable {
            bool sent = _to.send(123);
            // 确认
            require(sent, "send failed");
        }
  
        function sendViaCall(address payable _to) external payable {
            // 两个返回值,1:是否成功;2:bytes memory data:如果这次调用遇到的是智能合约,它有可能返回一个 data 数据
           (bool success, ) =  _to.call{value: 123}("");
           require(success, "call failed");
        }
    }
}阶段例子--钱包合约 
通过这个钱包,我们可以向合约中存入一定数量的以太坊铸币,并且可以随时从我们的合约中取出我们的以太坊铸币。 我们还需要规定管理员的身份。
JavaScript
// SPDX-License-Identifier:MIT
  
pragma solidity 0.8.7;
  
contract EtherWallet {
    address payable public owner;
  
    constructor() {
        owner = payable(msg.sender);
    }
  
    receive() external payable {}
  
    // 合约的拥有者可以随时取出里面的铸币,发送当然不需要了,谁都可以发送铸币给该合约。
    function withdraw(uint _amount) external {
        require(msg.sender == owner, "caller is not owner");
        // owner.transfer(_amount);
        // 节约 gas 可以这样写,因为 owner 是从状态变量中读取出来的
        payable(msg.sender).transfer(_amount);
    }
  
    function getBalance() external view returns (uint) {
        return address(this).balance;
    }
}调用其他合约 
JavaScript
// SPDX-License-Identifier:MIT
  
pragma solidity 0.8.7;
  
contract CallTestContract {
    // method1
    function setX1(address _test, uint _x) external {
        // 只需要把另一个合约当作类型,然后传入其地址,就可以调用其方法了。
        TestContract(_test).setX(_x);
    }
    // method2
    function setX2(TestContract _test, uint _x) external {
        _test.setX(_x);
    }
    // 更复杂的情况
    function setXandSendEther(address _test, uint _x) external payable {
        TestContract(_test).setXandReceiveEther{value: msg.value}(_x);
    }
}
  
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);
    }
}接口合约 
JavaScript
// SPDX-License-Identifier:MIT
  
pragma solidity 0.8.7;
  
interface ICounter {
    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();
    }
}
// 然后你这里就可以部署了
// 其他地方实现对应的 Counter 合约
// 然后通过地址调用就可以了低级 call 
JavaScript
// SPDX-License-Identifier:MIT
  
pragma solidity 0.8.7;
  
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);
    }
}
// 去调用上面的合约
contract Call {
    bytes public data;
  
    function callFoo(address _test) external payable{
        // 传入的是 abi 编码
        (bool success, bytes memory data)_test.call{value: 111, gas: 5000}(
            abi.encodeWithSignature(
                "foo(string, uint256)", "call foo", 123
            )
        );
        require(success, "call failed");
        data = _data;
    }
  
    function callDoesNotExit(address _test) external {
        (bool success, ) = _test.call(abi.encodeWithSignature("doesNotExist()"));
        require(success, "call failed");
    }
}委托调用 
 C 是委托调用的 B,所以值和铸币都是保存在 B 的,C 可以看到但不可以修改,同时看到的也是 A 的。
 C 是委托调用的 B,所以值和铸币都是保存在 B 的,C 可以看到但不可以修改,同时看到的也是 A 的。
JavaScript
// SPDX-License-Identifier:MIT
  
pragma solidity 0.8.7;
  
contract TestDelegateCall {
    uint public num;
    address public sender;
    uint public value;
  
    function setVars(uint _num) external payable {
        num = _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 {
        // 这里进行委托调用
        // _test.delegatecall(
        //     abi.encodeWithSignature("setVars(uint56)", _num)
        // );
        // 上述写法是使用签名进行编码
        // 另一种写法,这里使用 Select 进行编码
        (bool success, bytes memory data) = _test.delegatecall(
            abi.encodeWithSelect(TestDelegateCall.setVars.selector, _num);
        );
        require(success, "delegatecall failed");
    }
}- 就是可以让我们得调用委托到下一个合约中,底层相当于把被调用合约代码拿到调用合约中使用,类似调用库合约。
- 被调用的合约的值不能被改变,我们只是使用被调用合约的逻辑来改变当前合约的状态变量的值
- 虽然不用被调用合约的状态变量值,但必须要设置,因为要使他们的变量结构是一样的。
工厂合约 
用合约部署合约(之前讲过一种通过内联汇编部署新合约的方法) 通过 new 语句新建合约的方法
JavaScript
// SPDX-License-Identifier:MIT
  
pragma solidity 0.8.7;
  
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) external payable{
        // 返回的是账户合约的地址,可以用账户合约类型去接收它。
        Account account = new Account{value: 111}(_owner);  // 创建的时候也是可以接收铸币的,这里同样可以使用大括号进行接收
        accounts.push(account);
    }
}库合约 
我们可以把常用的算法抽象成为库合约。
JavaScript
// SPDX-License-Identifier:MIT
  
pragma solidity 0.8.7;
  
library Math {
    // 这里得定义为内部可视,因为库合约一般都是在合约内部使用的,定义为外部可视是没有任何意义的
    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 {
    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);
    }
}哈希运算 
JavaScript
// SPDX-License-Identifier:MIT
  
pragma solidity 0.8.7;
  
contract HashFunc {
    function hash(string memory text, uint num, address addr) external pure returns (bytes32) {
        // 哈希算法的一种特定内部函数,传入这三个参数前先进行打包,打包之后会形成一个 bytes 的返回值,这个返回值是不定长的,然后再通过哈希
        return keccak256(abi.encodePacked(text, num, addr));
  
        // 测试 encode 与 encodePacked 的区别
        function encode(string memory text0, string memory text1) external pure returns (memory bytes){  // 这里不定长的需要加上 memory
            return abi.encode(text0. text1);
        }
  
        function encodePacked(string memory text0, string memory text1) external pure returns (memory bytes){
            return abi.encodePacked(text0, text1);
        }
    }
}- encode:进行了补零
- encodePacked:直接返回得 16 进制  
不进行补零会容易出现一些错误,比如("AAAA","BBB")和("AAA","ABBB")的 encodePacked 返回值是没有任何变化的。这样就有可能造成哈希错误(碰撞) 所以以后在需要哈希的时候尽量使用 encode 进行打包
验证签名 
- 消息签名
- 消息进行哈希
- 再把私钥和哈希后的消息进行签名,在链下完成
- 恢复签名
JavaScript
// SPDX-License-Identifier:MIT
  
pragma solidity 0.8.7;
  
contract VerifySig {
    // 最后我们要验证恢复后的地址和签名人的地址是否是相等的
    function vertify(address _signer, string memory _message, bytes memory _sig)
        external pure returns (bool)
    {
        bytes32 messageHash = getMessageHash(_message);  // 1.
        bytes32 ethSignedMessageHash = getEthSinedMessageHash(messageHash);  // 2.
        // 3.4.
        return recover(ethSignedMessageHash, _sig) == _signer;
    }
    function getMessageHash(string memory _message) public pure returns (bytes32) {
        return keccak256(abi.encodePacked(_message));
    }
  
    function getEthSinedMessageHash(bytes32 _messageHash) public pure returns (bytes32) {
        return keccak256(abi.encodePacked("\x19Ethereum Signed Message:\n32", _messageHash));
    }
  
    function recover(bytes32 _ethSignedMessageHash, bytes memory _sig)
        public pure returns (address)
    {
        (bytes32 r, bytes32 s, uint8 v) = _split(_sig);
        return ecrecover(_ethSignedMessageHash, v, r, s);
    }
  
    function _split(bytes memory _sig) internal pure returns (bytes32 r, bytes32 s, uint8 v){
        require(_sig.length == 65, "invalid signature length");
  
        assembly {
            r := mload(add(_sig, 32))
            s := mload(add(_sig, 64))
            v := byte(0, mload(add(_sig, 96)))
        }
    }
}阶段例子--权限控制合约 
JavaScript
// SPDX-License-Identifier:MIT
  
pragma solidity 0.8.7;
  
contract AccessControl {
    event GrantRole(bytes32 indexed role, address indexed account);
    event RevokeRole(bytes32 indexed role, address indexed account);
  
    // role => account => bool
    mapping(bytes32 => mapping(address => bool)) public roles;
    // 我们采用哈希值做名称是因为 string 比 bytes32 消耗的 gas 要多得多
    bytes32 private constant ADMIN = keccak256(abi.encodePacked("ADMIN"));
    bytes32 private constant USER = keccak256(abi.encodePacked("USER"));
  
    modifier onlyRole(bytes32 _role) {
        require(roles[_role][msg.sender]. "not authorized");
        _;
    }
  
    // 初始时,将管理员权限赋给合约的部署者
    constructor() {
        _grantRole(ADMIN, msg.sender);
    }
  
    // 升级角色的函数,内部的不做权限检查
    function _grantRole(bytes32 _role, address _account) internal {
        roles[_role][_account] = true;  // 这里我们修改了状态变量的值,按照智能合约的编写习惯来说,我们修改值后就一定要报出一个事件
        emit GrantRole(_role, _account);
    }
    // 外部的只能由管理员才能调用,所以这里设计并加上函数修改器
    function grantrole(bytes32 _role, address _account) external onlyRole(ADMIN) {
        _grantRole(_role, _account);
    }
    // 撤销权限的函数
    // ... -->false
}合约自毁 
selfdestruct删除合约,强制发送铸币到一个地址。
JavaScript
// SPDX-License-Identifier:MIT
  
pragma solidity 0.8.7;
  
contract Kill {
    constructor() payable {}
    function kill() external {
        // 强制删除,就算对方合约拒绝接受,也会收到
        selfdesctruct(payable(msg.sender));
    }
  
    function testCall() external pure returns (uint) {
        return 123;
    }
}阶段例子--小猪存钱罐 
可以通过任何人地址发送以太坊铸币,存钱罐的拥有者才可以取出,取出之后,它就会像真正的小猪存钱罐一样,会被打碎,就是自毁掉。
JavaScript
// SPDX-License-Identifier:MIT
  
pragma solidity 0.8.7;
  
contract PiggyBank {
    event Deposit(uint amount);
    event Withdraw(uint amount);
  
    address public owner = msg.sender;
  
    receive() external payable {
        emit Deposit(msg.value);
    }
  
    function withdraw() external {
        require(msg.sender == owner, "not owner");
        emit Withdraw(address(this).balance);
        selfdesctruct(payable(msg.sender));
    }
}