Solidity入门(6)-合约实战2

提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档

文章目录


简单代币合约 (使用ERC-20)

什么是ERC-20

初学者可以简单的理解为这个是一个同质化代币的标准,ERC-20 提出了一个同质化代币的标准,换句话说,它们具有一种属性,使得每个代币都与另一个代币(在类型和价值上)完全相同。
以太坊文档
官方定义文档

简单代币合约

上一篇文章我们已经在WETH案例实现了简单的代表合约

ERC20核心函数

  • 3 个查询
    • balanceOf: 查询指定地址的 Token 数量
    • allowance: 查询指定地址对另外一个地址的剩余授权额度
    • totalSupply: 查询当前合约的 Token 总量
  • 2 个交易
    • transfer: 从当前调用者地址发送指定数量的 Token 到指定地址。(直接转账)
      这是一个写入方法,所以还会抛出一个 Transfer 事件。
    • transferFrom: 当向另外一个合约地址存款时,对方合约必须调用 transferFrom 才可以把 Token 拿到它自己的合约中。( 授权转账)
  • 2 个事件
    • Transfer
    • Approval
  • 1 个授权
    • approve: 授权指定地址可以操作调用者的最大 Token 数量。

授权机制详解

授权机制的工作原理

为什么需要授权机制?

问题场景:

Alice想在Uniswap上用USDT购买ETH。这个过程中,Uniswap合约需要获取Alice的USDT。

为什么不能用transfer?

bash 复制代码
// transfer只能由代币持有者自己调用
function transfer(address to, uint256 amount) public returns (bool) {
    // msg.sender必须是代币持有者
    balanceOf[msg.sender] -= amount;
    // ...
}

// Uniswap合约无法调用Alice的transfer
// 因为msg.sender会是Uniswap合约地址,不是Alice

问题的核心:

智能合约无法主动获取用户的代币。如果没有授权机制,合约就无法代表用户操作代币。

授权机制的解决方案:

用户主动授权合约

合约代表用户操作

用户通过控制授权额度保持控制权

这是一种委托代理模式。

授权流程详解

让我们通过一个完整的场景来理解授权机制。

场景:Alice在Uniswap用USDT购买ETH

步骤1:Alice授权Uniswap

bash 复制代码
// Alice调用USDT合约的approve函数
usdt.approve(uniswapAddress, 1000);

执行过程:
1. allowance[Alice][Uniswap] = 1000
2. 触发Approval事件
3. 返回true

状态变化:
- Alice的USDT余额:不变(仍然是2000)
- Uniswap的授权额度:1000
- 代币位置:仍在Alice账户中

关键点:approve只是设置授权额度,并不转移代币!

步骤2:Uniswap使用授权

bash 复制代码
// Uniswap合约调用transferFrom
usdt.transferFrom(Alice, Pool, 500);

执行过程:
1. 检查:Alice余额 >= 500? ✓(2000 >= 500)
2. 检查:allowance[Alice][Uniswap] >= 500? ✓(1000 >= 500)
3. Alice余额 -= 500(2000 → 1500)
4. Pool余额 += 500
5. allowance[Alice][Uniswap] -= 500(1000 → 500)
6. 触发Transfer事件
7. 返回true

最终状态:
- Alice的USDT余额:1500(减少了500)
- Pool的USDT余额:500(增加了500)
- 剩余授权额度:500(被消耗了500)

关键点:授权额度会被消耗,不是一次性使用全部!

完整流程图

bash 复制代码
初始状态:
Alice余额:2000 USDT
Pool余额:0 USDT
授权额度:0

    ↓ approve(Uniswap, 1000)

状态1:
Alice余额:2000 USDT
Pool余额:0 USDT
授权额度:1000 ←(已授权)

    ↓ transferFrom(Alice, Pool, 500)

最终状态:
Alice余额:1500 USDT ←(减少500)
Pool余额:500 USDT ←(增加500)
授权额度:500 ←(消耗500)

授权机制的实际应用

  • 应用场景1:去中心化交易所(Uniswap)
  • 应用场景2:流动性挖矿
  • 应用场景3:NFT购买
  • 应用场景4:借贷协议(Compound/Aave)

授权安全注意事项

bash 复制代码
// 危险:授权最大值
token.approve(contract, type(uint256).max);
// 相当于把全部代币的控制权交给了合约

问题:

  • 如果合约有漏洞,所有代币都可能被盗
  • 授权一次永久有效,风险持续存在
  • 恶意合约可以随时转走全部代币
bash 复制代码
// 安全:只授权需要的数量
token.approve(uniswap, 100);  // 只授权本次交易需要的100个

// 使用后撤销授权
token.approve(uniswap, 0);    // 撤销授权

授权安全原则:

  • 最小授权:只授权实际需要的数量
  • 使用后撤销:完成操作后立即撤销授权
  • 只授权可信合约:只对经过审计的知名合约授权
  • 定期检查:定期检查并撤销不再需要的授权
  • 使用授权管理工具:使用Revoke.cash等工具管理授权
    真实案例:

许多用户因为无限授权损失了资金:

  • 2021年某DeFi协议被攻击,用户损失数百万美元
  • 攻击者利用用户的无限授权转走代币
  • 只有撤销授权的用户幸免

教训:永远不要给不熟悉的合约无限授权!

openzeppelin 实现ERC20

简单理解openzeppelin是一个标准化实现的组织,提供一些标准化的包。下面是ERC20实现的一个例子,官网有其他demo也可以自己试试

bash 复制代码
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.19;
import {ERC20}  from"@openzeppelin/contracts/token/ERC20/ERC20.sol";
import {ERC20Permit} from "@openzeppelin/contracts/token/ERC20/extensions/draft-ERC20Permit.sol";

contract Mytoken is ERC20,ERC20Permit{
    constructor () ERC20("Mytoken","MTK") ERC20Permit("Mytoken"){
        _mint(msg.sender,100000);
    }
}

这里还涉及两个之前没有提到内容,

  • 导包 import
  • 继承 contract Mytoken is ERC20,ERC20Permit

和其他语言实现类似,看一下基本就理解了

上面的例子实现了给自己铸造代币,我们可以按下面的步骤,导入自己的钱包看下

部署合约

部署合约,可以选择sepolia测试网,然后复制合约地址

在钱包添加代币

使用上面负责的合约地址,添加代币

等一下显示后点击导入即可,成功后如下图所示

尝试使用代币其他功能

bash 复制代码
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.19;

// 导入 OpenZeppelin 官方合约
import "@openzeppelin/contracts/token/ERC20/ERC20.sol";
import "@openzeppelin/contracts/access/Ownable.sol";

/**
 * @title MyToken2
 * @dev 带权限控制的 ERC20 代币:
 * - 部署时向部署者铸造初始供应量(自动乘以 18 位小数)
 * - 仅合约拥有者可增发代币
 * - 任意用户可销毁自己的代币
 */
contract MyToken2 is ERC20, Ownable {
    // 代币小数位(显式声明,默认 18,可按需修改)
    uint8 public constant DECIMALS = 18;

    /**
     * @dev 构造函数
     * @param initialSupply 初始供应量("枚"数,自动转换为最小单位)
     */
    constructor(uint256 initialSupply) 
        ERC20("My Token", "MTK") 
        Ownable(msg.sender) // 显式指定初始拥有者(OpenZeppelin v5+ 必需)
    {
        // 初始铸造:将"枚"数转换为最小单位(18 位小数)
        _mint(msg.sender, initialSupply * 10 ** DECIMALS);
    }

    /**
     * @dev 增发代币(仅拥有者可调用)
     * @param to 接收增发代币的地址
     * @param amount 增发数量("枚"数,自动转换为最小单位)
     */
    function mint(address to, uint256 amount) public onlyOwner {
        // 校验:增发数量不能为 0
        require(amount > 0, "Mint amount cannot be zero");
        // 转换为最小单位后铸造
        _mint(to, amount * 10 ** DECIMALS);
    }

    /**
     * @dev 销毁自己的代币
     * @param amount 销毁数量("枚"数,自动转换为最小单位)
     */
    function burn(uint256 amount) public {
        // 校验:销毁数量不能为 0
        require(amount > 0, "Burn amount cannot be zero");
        // 校验:余额足够(ERC20 的 _burn 会自动校验,此处显式提示更友好)
        require(balanceOf(msg.sender) >= amount * 10 ** DECIMALS, "Insufficient balance to burn");
        // 转换为最小单位后销毁
        _burn(msg.sender, amount * 10 ** DECIMALS);
    }

    // 可选:重写 decimals 函数(若需修改小数位,比如 6 位)
    // function decimals() public view override returns (uint8) {
    //     return DECIMALS;
    // }
}
相关推荐
公链开发8 小时前
开发一条公链大概得多少钱?
去中心化·区块链
MicroTech20258 小时前
微算法科技(NASDAQ MLGO)采用分层实用拜占庭容错(H-PBFT)共识算法,提高区块链模型的共识效率、安全性和可扩展性
科技·区块链·共识算法
古城小栈8 小时前
Go语言原生智能合约开发与部署完全指南
golang·区块链·智能合约
区块链小八歌8 小时前
Pi Network Ventures首笔投资落地OpenMind,探索区块链+AI真实应用
区块链
YSGZJJ20 小时前
股指期货交割日前后应当如何应对?
区块链
友莘居士1 天前
solidity中数据位置storage、memory、calldata的区别
区块链·memory·solidity·storage·calldata·数据位置
公链开发1 天前
交易所系统平台开发流程及达普韦伯核心优势
区块链
一点 内容1 天前
深入理解分布式共识算法 Raft:从原理到实践
分布式·区块链·共识算法
ZFJ_张福杰1 天前
【区块链】区块链智能合约:从原理到应用的完整入门指南
区块链·智能合约