Solidity 8.0 高阶-实战用法

验证签名

正常签名流程:

  1. hash message
  2. 链下签名: sign (消息+私钥签名)
  3. 链上校验:恢复验证 ecrecover(ethHash(message), signature) == signer 恢复签名 参数1:hash后的消息原文, 参数2:链下签名
// SPDX-License-Identifier: MIT
pragma solidity >=0.8.0 <0.9.0;


/*
正常签名步骤
1. sing 签名
2. hash(message) 消息 hash
3. sign(hash(message), priveate key) 消息和私钥签名(链下完成)
4. ecrecover(ethHash(message), signature) == signer 恢复签名   参数1:hash后的消息原文, 参数2:链下签名
*/


// 签名验证合约, 签名\校验\恢复
contract Verifysig {
    // 校验签名是否正确
    // 参数1:签名人的地址
    // 参数2:消息原文
    // 参数3:签名结果
    function verify(address _signer, string memory _message, bytes memory _sign) external pure returns(bool){
        bytes32 messageHash = getMessageHash(_message);  // 消息 hash
        bytes32 ethSignedMessageHash = getEthSignedMessageHash(messageHash); // 进行结果eth hash
        return recover(ethSignedMessageHash, _sign) == _signer;  // 恢复地址,进行比对
    }

    function getMessageHash(string memory _message) public pure returns(bytes32){
        return keccak256(abi.encodePacked(_message));
    }

    function getEthSignedMessageHash(bytes32 _messageHash) public pure returns(bytes32){
        return keccak256(abi.encodePacked("\x19Ethereum Signed Message:\n32" , _messageHash));
    }

    function recover(bytes32 _ethSignedMessageHash, bytes memory _sign) public pure returns(address){
        // 非对称加密,开始解密
        (bytes32 r, bytes32 s, uint8 v) = _split(_sign);
        // 内部函数 ecrecover
        return ecrecover(_ethSignedMessageHash, v, r, s);
    }

    // 通过切割拿到
    function _split(bytes memory _sign) public pure returns (bytes32 r, bytes32 s, uint8 v){
        require(_sign.length == 65, "invalid signature length");
        // 只能通过内联汇编进行分割, 前32位,中间32位,最后1位
        assembly {
            r := mload(add(_sign, 32))
            s := mload(add(_sign, 64))
            v := byte(0, mload(add(_sign, 96)))
        }
    }
}

ERC20 合约

包含 IERC20 接口的标准都叫 ERC20

// SPDX-License-Identifier: MIT
pragma solidity >=0.8.0 <0.9.0;

// EIP中定义的ERC20标准接口
interface IERC20 {
    // 返回存在的代币数量
    function totalSupply() external view returns (uint256);

    // 返回 account 拥有的代币数量
    function balanceOf(address account) external view returns (uint256);

    // 将 amount 代币从调用者账户移动到 recipient
    // 返回一个布尔值表示操作是否成功
    // 发出 {Transfer} 事件
    function transfer(address recipient, uint256 amount)
        external
        returns (bool);

    // 返回 spender 允许 owner 通过 {transferFrom}消费剩余的代币数量
    function allowance(address owner, address spender)
        external
        view
        returns (uint256);

    // 调用者设置 spender 消费自己amount数量的代币
    function approve(address spender, uint256 amount) external returns (bool);

    // 将amount数量的代币从 sender 移动到 recipient ,从调用者的账户扣除 amount
    function transferFrom(
        address sender,
        address recipient,
        uint256 amount
    ) external returns (bool);

    // 当value数量的代币从一个form账户移动到另一个to账户
    event Transfer(address indexed from, address indexed to, uint256 value);
    // 当调用{approve}时,触发该事件
    event Approval(
        address indexed owner,
        address indexed spender,
        uint256 value
    );
}

// ERC20 标准中可选元数据功能的接口
interface IERC20Metadata is IERC20 {
    // 返回代币名称
    function name() external view returns (string memory);

    // 返回代币符号
    function symbol() external view returns (string memory);

    // 返回代币的精度(小数位数)
    function decimals() external view returns (uint8);
}

// 提供有关当前执行上下文的信息,包括事务的发送者及其数据。 虽然这些通常可以通过 msg.sender 和 msg.data 获得,但不应以这种直接方式访问它们,因为在处理元交易时,发送和支付执行的帐户可能不是实际的发送者(就应用而言)。
// 只有中间的、类似程序集的合约才需要这个合约。
abstract contract Context {
    function _msgSender() internal view virtual returns (address) {
        return msg.sender;
    }

    function _msgData() internal view virtual returns (bytes calldata) {
        return msg.data;
    }
}

// 实现{IERC20}接口
contract ERC20 is Context, IERC20, IERC20Metadata {
    mapping(address => uint256) private _balances;

    mapping(address => mapping(address => uint256)) private _allowances;

    uint256 private _totalSupply;

    string private _name;
    string private _symbol;

    // 设置 {name} 和 {symbol} 的值
    constructor(string memory name_, string memory symbol_) {
        _name = name_;
        _symbol = symbol_;
    }

    // 返回代币的名称
    function name() public view virtual override returns (string memory) {
        return _name;
    }

    // 返回代币的符号
    function symbol() public view virtual override returns (string memory) {
        return _symbol;
    }

    // 返回代币的精度(小数位数)
    function decimals() public view virtual override returns (uint8) {
        return 18;
    }

    // 返回存在的代币数量
    function totalSupply() public view virtual override returns (uint256) {
        return _totalSupply;
    }

    // 返回 account 拥有的代币数量
    function balanceOf(address account)
        public
        view
        virtual
        override
        returns (uint256)
    {
        return _balances[account];
    }

    // 将 amount 代币从调用者账户移动到 recipient
    // 返回一个布尔值表示操作是否成功
    // 发出 {Transfer} 事件
    function transfer(address recipient, uint256 amount)
        public
        virtual
        override
        returns (bool)
    {
        _transfer(_msgSender(), recipient, amount);
        return true;
    }

    // 返回 spender 允许 owner 通过 {transferFrom}消费剩余的代币数量
    function allowance(address owner, address spender)
        public
        view
        virtual
        override
        returns (uint256)
    {
        return _allowances[owner][spender];
    }

    // 调用者设置 spender 消费自己amount数量的代币
    function approve(address spender, uint256 amount)
        public
        virtual
        override
        returns (bool)
    {
        _approve(_msgSender(), spender, amount);
        return true;
    }

    // 将amount数量的代币从 sender 移动到 recipient ,从调用者的账户扣除 amount
    function transferFrom(
        address sender,
        address recipient,
        uint256 amount
    ) public virtual override returns (bool) {
        _transfer(sender, recipient, amount);

        uint256 currentAllowance = _allowances[sender][_msgSender()];
        require(
            currentAllowance >= amount,
            "ERC20: transfer amount exceeds allowance"
        );
        unchecked {
            _approve(sender, _msgSender(), currentAllowance - amount);
        }

        return true;
    }

    // 增加调用者授予 spender 的可消费数额
    function increaseAllowance(address spender, uint256 addedValue)
        public
        virtual
        returns (bool)
    {
        _approve(
            _msgSender(),
            spender,
            _allowances[_msgSender()][spender] + addedValue
        );
        return true;
    }

    // 减少调用者授予 spender 的可消费数额
    function decreaseAllowance(address spender, uint256 subtractedValue)
        public
        virtual
        returns (bool)
    {
        uint256 currentAllowance = _allowances[_msgSender()][spender];
        require(
            currentAllowance >= subtractedValue,
            "ERC20: decreased allowance below zero"
        );
        unchecked {
            _approve(_msgSender(), spender, currentAllowance - subtractedValue);
        }

        return true;
    }

    // 将amount数量的代币从 sender 移动到 recipient
    function _transfer(
        address sender,
        address recipient,
        uint256 amount
    ) internal virtual {
        require(sender != address(0), "ERC20: transfer from the zero address");
        require(recipient != address(0), "ERC20: transfer to the zero address");

        _beforeTokenTransfer(sender, recipient, amount);

        uint256 senderBalance = _balances[sender];
        require(
            senderBalance >= amount,
            "ERC20: transfer amount exceeds balance"
        );
        unchecked {
            _balances[sender] = senderBalance - amount;
        }
        _balances[recipient] += amount;

        emit Transfer(sender, recipient, amount);

        _afterTokenTransfer(sender, recipient, amount);
    }

    // 给account账户创建amount数量的代币,同时增加总供应量
    function _mint(address account, uint256 amount) internal virtual {
        require(account != address(0), "ERC20: mint to the zero address");

        _beforeTokenTransfer(address(0), account, amount);

        _totalSupply += amount;
        _balances[account] += amount;
        emit Transfer(address(0), account, amount);

        _afterTokenTransfer(address(0), account, amount);
    }

    // 给account账户减少amount数量的代币,同时减少总供应量
    function _burn(address account, uint256 amount) internal virtual {
        require(account != address(0), "ERC20: burn from the zero address");

        _beforeTokenTransfer(account, address(0), amount);

        uint256 accountBalance = _balances[account];
        require(accountBalance >= amount, "ERC20: burn amount exceeds balance");
        unchecked {
            _balances[account] = accountBalance - amount;
        }
        _totalSupply -= amount;

        emit Transfer(account, address(0), amount);

        _afterTokenTransfer(account, address(0), amount);
    }

    // 将 `amount` 设置为 `spender` 对 `owner` 的代币的津贴
    function _approve(
        address owner,
        address spender,
        uint256 amount
    ) internal virtual {
        require(owner != address(0), "ERC20: approve from the zero address");
        require(spender != address(0), "ERC20: approve to the zero address");

        _allowances[owner][spender] = amount;
        emit Approval(owner, spender, amount);
    }

    // 在任何代币转移之前调用的钩子, 包括铸币和销币
    function _beforeTokenTransfer(
        address from,
        address to,
        uint256 amount
    ) internal virtual {}

    // 在任何代币转移之后调用的钩子, 包括铸币和销币
    function _afterTokenTransfer(
        address from,
        address to,
        uint256 amount
    ) internal virtual {}
}

多钱包签名

:::success
需在合约中多人同意情况下才能转出主币
:::
所需事件

  • Deposit 存款事件
  • Submit 提交交易的申请事件
  • Approve 签名人批准事件(多人)
  • Revoke 撤销批准事件
  • Execute 执行事件

设计

  • 数组 owners 装合约所属者
  • 映射 isOwner 获取地址是否是管理员
  • required 最少签名通过人数
  • 结构体 Transtions 包含交易详情
  • 结构体数组 transtions 索引作为交易ID
  • 构造函数 初始化合约拥有者,最少签名通过人数
// SPDX-License-Identifier: GPL-3.0

pragma solidity >=0.8.0 <0.9.0;

// 多地址签名同意才发起交易主币

contract MultiSigWallet{
    event Deposit(address indexed sender, uint amount);  // 存款事件
    event Submit(uint indexed txId);  // 提交申请
    event Approve(address indexed owner, uint indexed txId);  // 批准
    event Revoke(address indexed owner, uint indexed txId);  // 撤销批准
    event Execute(uint indexed txId);

    address[] public owners;  // 合约拥有者, 再制作一个 mapping 映射来方便查询
    mapping(address => bool) public isOwners;
    uint public required;  // 最少签名通过人数

    struct Transaction {  // 交易结构体,包含每次交易数据
        uint value;
        address to;
        bytes data;
        bool executed;
    }

    Transaction[] public transactions; // 记录交易   交易ID = 数组索引
    mapping(uint => mapping(address => bool)) public approved;  // {交易ID:{地址1:是否批准,地址2:是否批准}...}

    constructor(address[] memory _owners, uint _required){
        require(_owners.length > 0, "owners mast > 0");
        require(_required > 0 && _required <= _owners.length, "_required error");

        // 检测地址有效性,且不能重复
        for (uint i; i < _owners.length; i++){
            address owner = _owners[i];
            require(owner != address(0), "owners 0 error");
            require(!isOwners[owner], "owner is not unique")
            isOwners[owner] = true;
            owners.push(owner); // 推入合约拥有者签名人表
        }

        required = _required;
    }
}

权限管理

创建的时候赋予所有者,鉴权操作,并能够转移权限

// SPDX-License-Identifier: MIT
pragma solidity >=0.8.0 <0.9.0;

// Ownable 管理合约

contract Ownable{
    address public woner;

    // 初始化时将创建者设为拥有者
    constructor(){
        woner = msg.sender;
    }

    // 函数修改器,父类: 校验权限
    modifier onlyOwner(){
        require(msg.sender == woner, "not woner");
        _;
    }

    // 临时替换 owner 所有者方法
    function setOwner(address _newOwner) external onlyOwner{
        require(_newOwner != address(0), "invalid address");  // 新地址不能是 0 地址,以免锁死
        woner = _newOwner;
    }

    // 测试代码
    function onlyOwnerCanCall() external onlyOwner{
        // code
    }

    function anyOneCanCall() external {
        // code
    }
}

权限控制合约

  1. 有多种角色身份
  2. 功能:给地址升级降级出角色  GrantRole  RevokeRole
// SPDX-License-Identifier: MIT
pragma solidity >=0.8.0 <0.9.0;


/*
涵盖板块:
constructor 构造函数,创建者有管理权限
event 触发器,状态变量改变需要通报
mapping 为该角色 {角色:{地址:true}}
modifier 函数修改器,鉴别权限再操作
角色不用string 用 常量 bytes32 节省 gas
升级降级权限函数分为内部和外部函数(内部函数可以初始化权限给创建者)
*/

contract AccessControl {
    event GrantRole(address indexed account, bytes32 indexed role);
    event RevokeRole(address indexed account, bytes32 indexed role);

    mapping(bytes32 => mapping(address => bool)) public roles;
    // 定义角色(用 bytes32 节省gas)
    bytes32 private constant ADMIN = keccak256(abi.encodePacked("ADMIN"));  // 0xdf8b4c520ffe197c5343c6f5aec59570151ef9a492f2c624fd45ddde6135ec42
    bytes32 private constant USER = keccak256(abi.encodePacked("USER"));  // 0x2db9fd3d099848027c2383d0a083396f6c41510d7acfd92adc99b6cffcf31e96

    // 初始化给创建者权限
    constructor() {
        // roles[ADMIN][msg.sender] = true;
        _grantRole(msg.sender, ADMIN);
    }

    modifier onlyRole (bytes32 _role) {
        require(roles[_role][msg.sender], "not admin");
        _;
    }

    // 权限升级
    function _grantRole(address _account, bytes32 _role) internal {
        roles[_role][_account] = true;
        emit GrantRole(_account, _role);
    }
    function grantRole(address _account, bytes32 _role) public onlyRole(ADMIN){
        _grantRole(_account, _role);
    }

    // 权限取消
    function revokeRole(address _account, bytes32 _role) public {
        roles[_role][_account] = false;
        emit RevokeRole(_account, _role);
    }
}

ToDoList

// SPDX-License-Identifier: MIT
pragma solidity >=0.8.0 <0.9.0;

// 示例:创建代办事件列表
contract toDoList{
    event Jion(address indexed sender, string message);

    struct Todo {
        string text;
        bool completed;
    }

    Todo[] public todos;

    function create(string calldata _text) external {
        todos.push(Todo({ text: _text, completed: false}));
    }

    function update(uint _index, string calldata _text) external {
        todos[_index].text = _text;

    }

    function get(uint _index) external view returns (string memory, bool){
        Todo memory todo = todos[_index];
        return (todo.text, todo.completed);
    }

    function changeCompleted(uint _index) external {
        todos[_index].completed = !todos[_index].completed; // 反转状态 加个感叹号
    }
}

合约钱包

// SPDX-License-Identifier: MIT
pragma solidity >=0.8.0 <0.9.0;

/*
例子 eth 钱包
1. 可以存、取 ETH 主币
2. 需要管理员才能取
*/ 
contract EtherWallet{
    address payable public owner;
    event Log(string func, address sender, uint value);


    constructor(uint){
        owner=payable(msg.sender); // 强行给 msg.sender 加上 payable 属性
    }

    // 通过回退函数接受 主币(调用 CALLDATA 填入value)
    receive() external payable{
        emit Log("receive", msg.sender, msg.value);
    }
    // 修饰器
    modifier checkOwner(){
        require(owner == msg.sender, "auth errer");
        _; 
    }

    // 取款 
    function withdraw(uint _amount) external checkOwner{
        // owner.transfer(_amount); 这样写稍微浪费 gas,可以用下面方法
        payable(msg.sender).transfer(_amount);
    }

    // 查当前合约余额
    function getBalance() external view returns(uint){
        return address(this).balance;
    }
}

存钱罐

  1. 任何人可以向合约发送主币
  2. 存钱罐拥有者才能取钱
  3. 取完后自动销毁合约
// SPDX-License-Identifier: MIT
pragma solidity >=0.8.0 <0.9.0;


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);
        selfdestruct(payable(msg.sender));
    }
}

本博客所有文章除特别声明外,均采用 CC BY-SA 4.0 协议 ,转载请注明出处!