Solidity 8.0 高阶-实战用法
验证签名
正常签名流程:
- hash message
- 链下签名:
sign (消息+私钥签名)
- 链上校验:恢复验证
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
}
}
权限控制合约
- 有多种角色身份
- 功能:给地址升级降级出角色 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;
}
}
存钱罐
- 任何人可以向合约发送主币
- 存钱罐拥有者才能取钱
- 取完后自动销毁合约
// 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 协议 ,转载请注明出处!