Tag: solidity

《纸上谈兵·solidity》课程小结

课程结构

第一部分:基础入门(第 0-2 课)

  • 第 0 课:搭建开发环境(Remix / Foundry / Hardhat)
  • 第 1 课:部署第一个合约
  • 第 2 课:合约交互(view/pure vs 状态修改)

第二部分:语言核心(第 3-12 课)

  • 第 3-4 课:事件与错误处理
  • 第 5 课:外部调用与重入防护
  • 第 6 课:存储布局(storage/memory/calldata)
  • 第 7-8 课:函数可见性、继承与接口
  • 第 9-12 课:事件日志、fallback、错误处理、ABI 编码

第三部分:高级特性(第 13-22 课)

  • 第 13-14 课:低级调用(call/delegatecall)、代理模式
  • 第 15-18 课:库、支付模式、权限管理、Diamond 标准
  • 第 19-20 课:安全专题(常见攻击与防御)
  • 第 21-22 课:Gas 优化、ERC20 实现

第四部分:实战项目(第 23-35 课)

  • 第 23 课:NFT(ERC721/ERC1155)
  • 第 24 课:众筹合约
  • 第 25-26 课:DEX、借贷合约
  • 第 27 课:DAO 治理
  • 第 28-31 课:安全审计案例(The DAO、Parity、Nomad)
  • 第 32-36 课:DeFi 基础、多签钱包、DEX 实战

第五部分:DeFi 进阶(第 37-48 课)

  • 第 37-39 课:资金池、利率模型、清算机制、aToken
  • 第 40-42 课:风险控制、协议费、多市场支持
  • 第 43-45 课:清算进阶、利率曲线、复利机制
  • 第 46-48 课:跨链借贷、治理代币、前端 DApp 集成

核心知识点

Solidity 基础

  • 数据类型、存储位置、函数类型、可见性、继承、错误处理

安全编程

  • 重入防护、权限控制、CEI 模式、Pull 支付、Gas 优化

设计模式

  • 代理模式(透明代理、UUPS、Diamond)、访问控制、可升级合约

标准协议

  • ERC20、ERC721、ERC1155、ERC165、EIP-2535

DeFi 核心

  • 资金池、利率模型、清算机制、价格预言机、DAO 治理

实战项目

  1. HelloWorld / Counter 合约
  2. ERC20 / ERC721 / ERC1155 代币
  3. 众筹合约 / DEX / 借贷池
  4. 多签钱包 / DAO 治理
  5. 完整 DeFi 协议(借贷、清算、治理)

工具链

  • 开发:Remix IDE、Foundry、Hardhat、VSCode
  • 测试:Foundry Test、Hardhat Test
  • 部署:forge script、hardhat deploy、Anvil
  • 前端:ethers.js、React、MetaMask

学习路径

  • 初学者:第 0-2 课 → 第 3-12 课 → 第 13-16 课 → 第 22-24 课
  • 进阶:第 17-21 课 → 第 25-27 课 → 第 28-31 课 → 第 32-36 课
  • 专业:第 37-48 课(完整 DeFi 协议开发)

安全原则

  1. 最小权限原则
  2. CEI 模式(Check-Effects-Interactions)
  3. Pull 支付模式
  4. 输入验证
  5. 重入防护(ReentrancyGuard)
  6. 避免时间依赖
  7. 外部调用谨慎处理
  8. 重要操作记录事件
  9. 上线前代码审计

常见陷阱

  • 开发:存储槽冲突、函数选择器碰撞、delegatecall 陷阱、Gas 限制
  • 安全:重入攻击、整数溢出、权限绕过、时间依赖
  • 部署:代理合约构造函数、初始化函数、升级兼容性、事件索引限制

推荐资源


孟斯特

声明:本作品采用署名-非商业性使用-相同方式共享 4.0 国际 (CC BY-NC-SA 4.0)进行许可,使用时请注明出处。
Author: mengbin
blog: mengbin
Github: mengbin92
腾讯云开发者社区:孟斯特

《纸上谈兵·solidity》第 46 课:DeFi 实战(10) -- 跨链借贷与流动性桥接

1、学习目标

  • 理解跨链借贷的几种常见体系结构(lock-mint、liquidity pool、atomic swap、debt-on-destination);
  • 了解主流跨链通信/桥的安全/设计要点(finality、reorg、replay、watchers、relayers、attestation);
  • 掌握跨链借贷带来的关键挑战:延迟、清算、oracle 一致性、双花、流动性碎片化、信任边界;
  • 实现一个教学级别的跨链借贷原型(MockBridge + ChainA 抵押管理 + ChainB 借贷/信用管理),并用 Foundry 测试关键流程(存抵押 → 跨链消息 → mint vToken → 借款 → 价格下跌 → 跨链清算);
  • 学会如何把教学实现替换成生产桥(LayerZero / Axelar / Wormhole)时需要注意的改动。

2、概念与设计详解

跨链借贷把「抵押在链 A、借款在链 B」或「在某链上开仓并在另一链上获得流动性」变为可能,但也带来了复杂性。下面尽量详尽列出设计要点与工程权衡。

《纸上谈兵·solidity》第 45 课:DeFi 实战(9) -- 利息累积与结算机制(可复利)

1、学习目标

  • 清楚区分 利息计算的几种方法(简单利息 / 离散复利 / 指数连续复利)及各自的利弊
  • 理解并掌握 index-based(借款/存款指数) 的利息会计方法(可扩展到多资产)
  • 从零实现:借款指数(borrowIndex)/ 存款指数(supplyIndex)、按秒累积利息、按比例分配协议储备(reserve)
  • 使用 Foundry 测试:模拟时间流逝、验证借款人负债、存款人收益、协议储备累积与还款行为

2、概念介绍

在现实的借贷协议里,利息并不是“每个用户单独按时间往账上写利息”,而是用**公用索引(index)**高效记录利息增长,然后按需用索引换算账户余额。这样做能极大节省 gas 并避免对每个用户频繁写状态。

《纸上谈兵·solidity》第 44 课:DeFi 实战(8) -- 利率曲线与资金池优化(动态利用率模型)

1、学习目标

  • 理解 资金利用率(Utilization Rate, U) 概念
  • 掌握 动态利率模型(线性 / 分段 / 曲线)
  • 通过代码实现利率自动调整机制
  • 编写 完整测试 验证资金利用率、借贷利率、存款收益随资金变化的联动
  • 对比 Compound / Aave 的利率曲线模型

2、资金利用率 (Utilization Rate)

借贷协议的核心指标:

《纸上谈兵·solidity》第 43 课:DeFi 实战(7) -- 清算机制进阶(多资产抵押清算路径、拍卖机制)

1、学习目标

  • 深入理解多资产抵押下的 清算路径 与策略选择(顺序清算 vs 拍卖)
  • 明确 清算参数closeFactorliquidationBonusliquidationThresholdmaxLiquidationSize 等的含义与取值权衡
  • 实现并测试:
    • 支持多资产、可选择性清算(指定要拿走哪种抵押物)
    • 基于折价直接清算(快速清算)
    • 一个简易拍卖(auction)示例,适合处理流动性差或高价值抵押品的场景
  • 掌握清算相关的 安全与经济风险(闪电贷清算、预言机操纵、清算人经济激励)及缓解措施

2、概念梳理

2.1 为什么需要「进阶清算」?

在单一资产、简单系统里,清算可以用“偿还债务 → 扣押抵押物(按折扣)”快速结束。但在多资产系统和真实市场中,会遇到多种复杂情形:

《纸上谈兵·solidity》第 42 课:DeFi 实战(6) -- 跨资产借贷与多市场支持

1. 学习目标

  • 理解为什么借贷协议必须支持多种资产而不是单一代币。
  • 掌握**市场(Market)**的概念:每个资产拥有独立的借贷池、利率模型、抵押参数。
  • 学习 跨资产借贷 的关键逻辑:抵押物与借款资产之间的价值评估。
  • 实现一个多市场借贷平台原型,允许用户用 A 代币抵押,借出 B 代币。

2. 背景与概念

2.1 为什么需要多资产支持?

单资产借贷(例如只能抵押 ETH 借出 ETH)意义不大。现实需求是:

《纸上谈兵·solidity》第 41 课:DeFi 实战(5) -- 协议费与治理

1、学习目标

  • 理解协议费(Protocol Fees)的来源、类型与记账方式
  • 理解协议金库(Treasury)的角色与取款分配模式
  • 掌握去中心化治理(On-chain Governance / DAO)的基本模型、风险与缓解手段
  • 从零实现一个简化版的 费用收集 → Treasury → 治理参数变更 流程(含 timelock)
  • 使用 Foundry 编写测试,验证费用累积与治理变更(例如修改 reserveFactor

2、概念详解(尽可能详细、贴近现实)

2.1 协议费(Protocol Fees)—— 来源与形式

协议费是 DeFi 协议获取收入、支持开发和治理的核心手段。常见来源有:

《纸上谈兵·solidity》第 40 课:DeFi 实战(4) -- 风险控制与防护

1. 学习目标

  • 理解借贷协议面临的核心安全风险
  • 掌握如何在 Solidity 中防御常见攻击(如重入攻击、预言机操纵)
  • 在资金安全与去中心化之间找到平衡

2. 核心知识点

  1. 重入攻击(Reentrancy Attack)
    • 攻击者通过合约回调,反复调用 withdraw() 等函数,导致重复转账。
    • 防御方法:
      • 使用 ReentrancyGuard(OpenZeppelin 提供)
      • 遵循 Checks-Effects-Interactions 模式
  2. 预言机操纵(Oracle Manipulation)
    • 攻击者通过闪电贷操纵交易对价格,导致借贷协议错误清算或套利。
    • 防御方法:
      • 使用去中心化预言机(如 Chainlink)
      • 设置价格更新延迟,避免瞬时波动影响
      • 采用多源价格聚合
  3. 利率与资金池风险
    • 资金池枯竭(借款率 100%)时,存款人无法提现。
    • 防御方法:
      • 设置借款上限(Reserve Factor)
      • 协议保留部分流动性

3. 合约实现:LendingPoolWithProtection.sol

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;

import "@openzeppelin/contracts/utils/ReentrancyGuard.sol";
import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
import "@openzeppelin/contracts/token/ERC20/ERC20.sol";

/**
 * @title 价格预言机接口
 * @notice 提供获取代币价格的功能
 */
interface IPriceOracle {
    /**
     * @notice 获取指定代币的当前价格
     * @param token 要查询价格的代币地址
     * @return 代币价格,以基础计价单位表示
     */
    function getPrice(address token) external view returns (uint256);
}

/**
 * @title 带保护机制的借贷池合约
 * @notice 允许用户存款、取款、借款和还款,包含重入保护和借款上限机制
 * @dev 使用ReentrancyGuard防止重入攻击,通过价格预言机获取资产价格
 */
contract LendingPoolWithProtection is ReentrancyGuard {
    using SafeERC20 for ERC20;

    /// @notice 存款事件,当用户存入资产时触发
    event Deposit(address indexed user, uint256 amount);
    /// @notice 取款事件,当用户取出资产时触发
    event Withdraw(address indexed user, uint256 amount);
    /// @notice 借款事件,当用户借出资产时触发
    event Borrow(address indexed user, uint256 amount);
    /// @notice 还款事件,当用户偿还借款时触发
    event Repay(address indexed user, uint256 amount);

    /// @notice 借贷池支持的ERC20资产
    ERC20 public immutable asset;
    /// @notice 价格预言机合约,用于获取资产价格
    IPriceOracle public immutable oracle;

    /// @notice 用户地址到存款金额的映射
    mapping(address => uint256) public deposits;
    /// @notice 用户地址到借款金额的映射
    mapping(address => uint256) public borrows;

    /// @notice 合约中总存款金额
    uint256 public totalDeposits;
    /// @notice 合约中总借款金额
    uint256 public totalBorrows;

    /// @notice 借款上限比例,基于总存款的百分比
    uint256 public constant BORROW_CAP = 80; // 最大 80% 资金可借出

    /**
     * @notice 构造函数,初始化借贷池
     * @param _asset 借贷池支持的ERC20代币地址
     * @param _oracle 价格预言机合约地址
     */
    constructor(address _asset, address _oracle) {
        asset = ERC20(_asset);
        oracle = IPriceOracle(_oracle);
    }

    /**
     * @notice 存款功能,用户将资产存入借贷池
     * @dev 使用nonReentrant修饰符防止重入攻击
     * @param amount 存款金额
     */
    function deposit(uint256 amount) external nonReentrant {
        require(amount > 0, "invalid amount");
        asset.safeTransferFrom(msg.sender, address(this), amount);

        deposits[msg.sender] += amount;
        totalDeposits += amount;
        emit Deposit(msg.sender, amount);
    }

    /**
     * @notice 借款功能,用户从借贷池借出资产
     * @dev 借款金额不能超过借款上限,使用nonReentrant修饰符防止重入攻击
     * @param amount 借款金额
     */
    function borrow(uint256 amount) external nonReentrant {
        require(amount > 0, "invalid amount");
        uint256 cap = (totalDeposits * BORROW_CAP) / 100;
        require(totalBorrows + amount <= cap, "borrow cap reached");

        borrows[msg.sender] += amount;
        totalBorrows += amount;

        asset.safeTransfer(msg.sender, amount);
        emit Borrow(msg.sender, amount);
    }

    /**
     * @notice 还款功能,用户偿还借款
     * @dev 还款金额不能超过用户的借款总额,使用nonReentrant修饰符防止重入攻击
     * @param amount 还款金额
     */
    function repay(uint256 amount) external nonReentrant {
        require(amount > 0, "invalid amount");
        require(borrows[msg.sender] >= amount, "repay too much");

        asset.safeTransferFrom(msg.sender, address(this), amount);

        borrows[msg.sender] -= amount;
        totalBorrows -= amount;
        
        emit Repay(msg.sender, amount);
    }

    /**
     * @notice 取款功能,用户从借贷池取出存款
     * @dev 取款金额不能超过用户存款和合约可用流动性,使用nonReentrant修饰符防止重入攻击
     * @param amount 取款金额
     */
    function withdraw(uint256 amount) external nonReentrant {
        require(deposits[msg.sender] >= amount, "not enough deposit");

        uint256 available = asset.balanceOf(address(this));
        require(amount <= available, "not enough liquidity");

        deposits[msg.sender] -= amount;
        totalDeposits -= amount;

        asset.safeTransfer(msg.sender, amount);
        
        emit Withdraw(msg.sender, amount);
    }

    /**
     * @notice 获取资产当前价格
     * @dev 通过价格预言机查询资产价格
     * @return 资产当前价格
     */
    function getAssetPrice() external view returns (uint256) {
        return oracle.getPrice(address(asset));
    }
}

4. 测试代码:LendingPoolWithProtection.t.sol

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;

import "forge-std/Test.sol";
import "../src/LendingPoolWithProtection.sol";

/**
 * @title MockERC20
 * @notice 用于测试的模拟ERC20代币合约
 * @dev 继承OpenZeppelin的ERC20实现,提供mint功能用于测试
 */
contract MockERC20 is ERC20 {
    /**
     * @notice 构造函数,初始化代币
     * @dev 铸造初始供应量给部署者
     */
    constructor() ERC20("MockToken", "MTK") {
        _mint(msg.sender, 1_000_000 ether);
    }

    /**
     * @notice 铸造代币
     * @dev 仅供测试使用,为指定地址铸造指定数量的代币
     * @param to 接收代币的地址
     * @param amount 铸造数量
     */
    function mint(address to, uint256 amount) external {
        _mint(to, amount);
    }
}

/**
 * @title MockOracle
 * @notice 用于测试的模拟价格预言机合约
 * @dev 实现IPriceOracle接口,允许手动设置价格
 */
contract MockOracle is IPriceOracle {
    /// @notice 当前价格
    uint256 public price = 1e18;

    /**
     * @notice 获取代币价格
     * @dev 忽略token参数,返回固定价格
     * @param token 代币地址(未使用)
     * @return 当前设置的价格
     */
    function getPrice(address token) external view returns (uint256) {
        return price;
    }

    /**
     * @notice 设置新的价格
     * @dev 仅供测试使用,更新预言机价格
     * @param newPrice 新的价格值
     */
    function setPrice(uint256 newPrice) external {
        price = newPrice;
    }
}

/**
 * @title LendingPoolWithProtectionTest
 * @notice 借贷池合约的完整测试套件
 * @dev 使用Forge测试框架测试LendingPoolWithProtection合约的所有功能
 */
contract LendingPoolWithProtectionTest is Test {
    /// @notice 测试用的ERC20代币
    MockERC20 public token;
    /// @notice 测试用的价格预言机
    MockOracle public oracle;
    /// @notice 被测试的借贷池合约
    LendingPoolWithProtection public pool;

    /// @notice 测试用户地址
    address owner = address(this);
    address user1 = address(0x123);
    address user2 = address(0x234);
    address user3 = address(0x345);

    /// @notice 借款上限常量
    uint256 constant BORROW_CAP = 80;

    /// @notice 测试事件声明
    event Deposit(address indexed user, uint256 amount);
    event Withdraw(address indexed user, uint256 amount);
    event Borrow(address indexed user, uint256 amount);
    event Repay(address indexed user, uint256 amount);

    /**
     * @notice 测试设置函数
     * @dev 在每个测试运行前执行,初始化测试环境
     */
    function setUp() public {
        // 部署测试合约
        token = new MockERC20();
        oracle = new MockOracle();
        pool = new LendingPoolWithProtection(address(token), address(oracle));

        // 分配代币给测试用户
        token.transfer(user1, 1000 ether);
        token.transfer(user2, 1000 ether);
        token.transfer(user3, 1000 ether);

        // 授权池子操作代币
        vm.startPrank(user1);
        token.approve(address(pool), type(uint256).max);
        vm.stopPrank();

        vm.startPrank(user2);
        token.approve(address(pool), type(uint256).max);
        vm.stopPrank();

        vm.startPrank(user3);
        token.approve(address(pool), type(uint256).max);
        vm.stopPrank();
    }

    // ============ 存款测试 ============

    /**
     * @notice 测试成功存款场景
     * @dev 验证存款后状态正确更新,事件正确触发
     */
    function test_Deposit_Success() public {
        vm.startPrank(user1);

        uint256 initialBalance = token.balanceOf(user1);
        uint256 depositAmount = 100 ether;

        // 验证事件
        vm.expectEmit(true, true, true, true);
        emit Deposit(user1, depositAmount);

        pool.deposit(depositAmount);

        // 验证状态更新
        assertEq(pool.deposits(user1), depositAmount);
        assertEq(pool.totalDeposits(), depositAmount);
        assertEq(token.balanceOf(user1), initialBalance - depositAmount);
        assertEq(token.balanceOf(address(pool)), depositAmount);

        vm.stopPrank();
    }

    /**
     * @notice 测试存款零金额时的回退
     * @dev 验证合约拒绝零金额存款
     */
    function test_RevertWhen_Deposit_ZeroAmount() public {
        vm.startPrank(user1);

        vm.expectRevert("invalid amount");
        pool.deposit(0);

        vm.stopPrank();
    }

    /**
     * @notice 测试多用户存款场景
     * @dev 验证多个用户存款时总存款和用户存款正确更新
     */
    function test_Deposit_MultipleUsers() public {
        // 用户1存款
        vm.prank(user1);
        pool.deposit(100 ether);
        assertEq(pool.deposits(user1), 100 ether);

        // 用户2存款
        vm.prank(user2);
        pool.deposit(200 ether);
        assertEq(pool.deposits(user2), 200 ether);

        // 验证总存款
        assertEq(pool.totalDeposits(), 300 ether);
        assertEq(token.balanceOf(address(pool)), 300 ether);
    }

    // ============ 取款测试 ============

    /**
     * @notice 测试成功取款场景
     * @dev 验证取款后状态正确更新,事件正确触发
     */
    function test_Withdraw_Success() public {
        vm.startPrank(user1);

        // 先存款
        pool.deposit(100 ether);

        uint256 initialPoolBalance = token.balanceOf(address(pool));
        uint256 withdrawAmount = 50 ether;

        // 验证事件
        vm.expectEmit(true, true, true, true);
        emit Withdraw(user1, withdrawAmount);

        pool.withdraw(withdrawAmount);

        // 验证状态更新
        assertEq(pool.deposits(user1), 50 ether);
        assertEq(pool.totalDeposits(), 50 ether);
        assertEq(
            token.balanceOf(address(pool)),
            initialPoolBalance - withdrawAmount
        );

        vm.stopPrank();
    }

    /**
     * @notice 测试取款超过存款金额时的回退
     * @dev 验证合约拒绝超额取款
     */
    function test_RevertWhen_Withdraw_InsufficientDeposit() public {
        vm.startPrank(user1);

        pool.deposit(100 ether);

        vm.expectRevert("not enough deposit");
        pool.withdraw(150 ether);

        vm.stopPrank();
    }

    /**
     * @notice 测试取款超过合约流动性时的回退
     * @dev 验证当合约流动性不足时拒绝取款
     */
    function test_RevertWhen_Withdraw_InsufficientLiquidity() public {
        vm.startPrank(user1);
        pool.deposit(100 ether);
        vm.stopPrank();

        // 用户2借款,消耗流动性
        vm.prank(user2);
        pool.borrow(80 ether);

        // 用户1尝试提取超过可用流动性的金额
        vm.prank(user1);
        vm.expectRevert("not enough liquidity");
        pool.withdraw(50 ether); // 池子只有20 ether流动性
    }

    /**
     * @notice 测试全额取款场景
     * @dev 验证用户可以取回全部存款
     */
    function test_Withdraw_AllDeposit() public {
        vm.startPrank(user1);

        pool.deposit(100 ether);
        pool.withdraw(100 ether);

        assertEq(pool.deposits(user1), 0);
        assertEq(pool.totalDeposits(), 0);
        assertEq(token.balanceOf(user1), 1000 ether); // 余额恢复

        vm.stopPrank();
    }

    // ============ 借款测试 ============

    /**
     * @notice 测试成功借款场景
     * @dev 验证借款后状态正确更新,事件正确触发
     */
    function test_Borrow_Success() public {
        // 用户1存款提供流动性
        vm.prank(user1);
        pool.deposit(100 ether);

        vm.startPrank(user2);

        uint256 borrowAmount = 50 ether;

        // 验证事件
        vm.expectEmit(true, true, true, true);
        emit Borrow(user2, borrowAmount);

        pool.borrow(borrowAmount);

        // 验证状态更新
        assertEq(pool.borrows(user2), borrowAmount);
        assertEq(pool.totalBorrows(), borrowAmount);
        assertEq(token.balanceOf(user2), 1000 ether + borrowAmount);

        vm.stopPrank();
    }

    /**
     * @notice 测试借款零金额时的回退
     * @dev 验证合约拒绝零金额借款
     */
    function test_RevertWhen_Borrow_ZeroAmount() public {
        vm.prank(user1);
        pool.deposit(100 ether);

        vm.prank(user2);
        vm.expectRevert("invalid amount");
        pool.borrow(0);
    }

    /**
     * @notice 测试超过借款上限时的回退
     * @dev 验证合约拒绝超过借款上限的借款请求
     */
    function test_RevertWhen_Borrow_CapLimit() public {
        vm.prank(user1);
        pool.deposit(100 ether);

        vm.startPrank(user2);

        // 借款达到上限 (80% of 100 = 80 ether)
        pool.borrow(80 ether);

        // 尝试再借1 wei,应该失败
        vm.expectRevert("borrow cap reached");
        pool.borrow(1);

        vm.stopPrank();
    }

    /**
     * @notice 测试多用户在借款上限内借款
     * @dev 验证多个用户可以共享借款额度
     */
    function test_Borrow_MultipleUsersUnderCap() public {
        vm.prank(user1);
        pool.deposit(100 ether);

        // 用户2借款
        vm.prank(user2);
        pool.borrow(40 ether);
        assertEq(pool.borrows(user2), 40 ether);

        // 用户3借款
        vm.prank(user3);
        pool.borrow(40 ether);
        assertEq(pool.borrows(user3), 40 ether);

        // 验证总借款
        assertEq(pool.totalBorrows(), 80 ether);
        assertEq(
            pool.totalBorrows(),
            (pool.totalDeposits() * BORROW_CAP) / 100
        );
    }

    /**
     * @notice 测试无流动性时的借款回退
     * @dev 验证当合约没有足够代币时借款失败
     */
    function test_RevertWhen_Borrow_NoLiquidity() public {
        // 没有存款,直接借款
        vm.prank(user1);
        vm.expectRevert(); // 由于余额不足,transfer会失败
        pool.borrow(10 ether);
    }

    // ============ 还款测试 ============

    /**
     * @notice 测试成功还款场景
     * @dev 验证还款后状态正确更新,事件正确触发
     */
    function test_Repay_Success() public {
        // 设置借款
        vm.prank(user1);
        pool.deposit(100 ether);

        vm.prank(user2);
        pool.borrow(50 ether);

        vm.startPrank(user2);

        uint256 repayAmount = 30 ether;

        // 验证事件
        vm.expectEmit(true, true, true, true);
        emit Repay(user2, repayAmount);

        pool.repay(repayAmount);

        // 验证状态更新
        assertEq(pool.borrows(user2), 20 ether);
        assertEq(pool.totalBorrows(), 20 ether);
        assertEq(token.balanceOf(user2), 1000 ether + 50 ether - repayAmount);

        vm.stopPrank();
    }

    /**
     * @notice 测试还款零金额时的回退
     * @dev 验证合约拒绝零金额还款
     */
    function test_RevertWhen_Repay_ZeroAmount() public {
        vm.prank(user1);
        pool.deposit(100 ether);

        vm.prank(user2);
        pool.borrow(50 ether);

        vm.prank(user2);
        vm.expectRevert("invalid amount");
        pool.repay(0);
    }

    /**
     * @notice 测试超额还款时的回退
     * @dev 验证合约拒绝超过借款金额的还款
     */
    function test_RevertWhen_Repay_ExcessAmount() public {
        vm.prank(user1);
        pool.deposit(100 ether);

        vm.prank(user2);
        pool.borrow(50 ether);

        vm.prank(user2);
        vm.expectRevert("repay too much");
        pool.repay(60 ether);
    }

    /**
     * @notice 测试全额还款场景
     * @dev 验证用户可以全额偿还借款
     */
    function test_Repay_FullRepayment() public {
        vm.prank(user1);
        pool.deposit(100 ether);

        vm.prank(user2);
        pool.borrow(50 ether);

        vm.prank(user2);
        pool.repay(50 ether);

        assertEq(pool.borrows(user2), 0);
        assertEq(pool.totalBorrows(), 0);
    }

    // ============ 借款上限逻辑测试 ============

    /**
     * @notice 测试借款上限计算
     * @dev 验证不同存款金额下的借款上限计算正确
     */
    function test_BorrowCap_Calculation() public {
        // 测试不同存款金额下的借款上限计算
        vm.prank(user1);
        pool.deposit(123.456 ether);

        uint256 expectedCap = (123.456 ether * BORROW_CAP) / 100;

        vm.prank(user2);
        pool.borrow(expectedCap);

        assertEq(pool.totalBorrows(), expectedCap);
    }

    /**
     * @notice 测试存款变化后的借款上限
     * @dev 验证新增存款后借款上限正确更新
     */
    function test_BorrowCap_AfterDepositChange() public {
        // 初始存款和借款
        vm.prank(user1);
        pool.deposit(100 ether);

        vm.prank(user2);
        pool.borrow(80 ether); // 达到上限

        // 增加存款,借款上限应该提高
        vm.prank(user3);
        pool.deposit(100 ether);

        // 现在可以借更多
        vm.prank(user2);
        pool.borrow(80 ether); // 再借80,总共160

        assertEq(pool.totalBorrows(), 160 ether);
        assertEq(pool.totalBorrows(), (200 ether * BORROW_CAP) / 100);
    }

    // ============ 价格预言机测试 ============

    /**
     * @notice 测试获取资产价格功能
     * @dev 验证价格预言机集成正常工作
     */
    function test_GetAssetPrice() public {
        uint256 price = pool.getAssetPrice();
        assertEq(price, 1e18);

        // 测试价格更新
        oracle.setPrice(1.5e18);
        price = pool.getAssetPrice();
        assertEq(price, 1.5e18);
    }

    // ============ 边缘情况测试 ============

    /**
     * @notice 测试复杂交互场景
     * @dev 模拟真实使用场景,验证合约在各种操作组合下的正确性
     */
    function test_Complex_Scenario() public {
        // 复杂场景:多个用户存款、借款、还款、取款

        // 用户1存款
        vm.prank(user1);
        pool.deposit(200 ether);

        // 用户2借款
        vm.prank(user2);
        pool.borrow(100 ether);

        // 用户3存款
        vm.prank(user3);
        pool.deposit(100 ether);

        // 用户2部分还款
        vm.prank(user2);
        pool.repay(50 ether);

        // 用户3借款
        vm.prank(user3);
        pool.borrow(40 ether);

        // 用户1取款
        vm.prank(user1);
        pool.withdraw(100 ether);

        // 验证最终状态
        assertEq(pool.deposits(user1), 100 ether);
        assertEq(pool.deposits(user3), 100 ether);
        assertEq(pool.borrows(user2), 50 ether);
        assertEq(pool.borrows(user3), 40 ether);
        assertEq(pool.totalDeposits(), 200 ether);
        assertEq(pool.totalBorrows(), 90 ether);

        // 验证借款上限
        uint256 currentCap = (pool.totalDeposits() * BORROW_CAP) / 100;
        assertTrue(pool.totalBorrows() <= currentCap);
    }

    /**
     * @notice 测试最大借款上限利用率
     * @dev 验证合约在达到最大借款上限时的行为
     */
    function test_Maximum_BorrowCap_Utilization() public {
        // 测试完全利用借款上限的情况
        vm.prank(user1);
        pool.deposit(1000 ether);

        vm.prank(user2);
        pool.borrow(800 ether);

        assertEq(pool.totalBorrows(), 800 ether);
        assertEq(
            pool.totalBorrows(),
            (pool.totalDeposits() * BORROW_CAP) / 100
        );
    }
}

执行测试

《纸上谈兵·solidity》番外1 -- ERC20Permit

在以太坊世界中,ERC-20代币标准无疑是最重要的标准之一。然而,传统ERC-20的授权机制存在一个明显的用户体验问题:需要先进行授权交易,然后才能进行实际操作,这不仅增加了Gas费用,还导致了糟糕的双重交易体验。OpenZeppelin的ERC20Permit扩展正是为了解决这一问题而生的创新方案。

Solidity Modifier 使用:平衡可读性与 Gas 效率

在 Solidity 中,modifier 是控制函数执行流程的强大工具。但使用不当会导致合约臃肿和 gas 开销增加。本文将介绍如何在 可读性执行效率 之间找到最佳平衡。

《纸上谈兵·solidity》第 39 课:DeFi 实战(3) -- 利息累积与 aToken 设计

1、学习目标

  • 理解为什么 DeFi 借贷平台需要 利息凭证代币(aToken / cToken)
  • 掌握 利息累计 的实现方式(借款人负债随时间增加,存款人资产随时间增加)
  • 从零实现一个简化版的 aToken 合约
  • 学会如何在测试中模拟“时间流逝”并验证利息计算

2、现实场景

在 Compound 和 Aave 这样的真实借贷平台:

《纸上谈兵·solidity》第 38 课:DeFi 实战(2) -- 清算机制与价格预言机

1、学习目标

  • 理解借贷平台中 抵押物、借款、清算 的核心关系
  • 掌握 健康因子(Health Factor) 的计算方式
  • 结合 价格预言机 获取资产价值
  • 编写一个简化的 清算合约,实现低抵押率下的清算流程
  • 使用 Foundry 测试完整流程:存款 → 抵押 → 借款 → 价格下跌 → 清算

2、知识点梳理

2.1 抵押与借款

  • 借款人必须先存入 抵押资产(如 ETH),再借出稳定币(如 USDC)。
  • 平台通过 抵押率(Collateral Factor) 来控制借款额度。
    例如:ETH 的抵押率 75%,存入 100 美元 ETH → 最多借 75 USDC。

2.2 健康因子(Health Factor)

$$ HF = \frac{抵押物价值 \times 抵押率}{借款价值} $$

《纸上谈兵·solidity》第 37 课:DeFi 实战 -- 资金池与利率模型

1、学习目标

  • 理解借贷平台的 资金池机制
  • 掌握 Aave / Compound 等平台的 动态利率模型
  • 编写一个简化的 带利率的借贷池合约
  • 使用 Foundry 编写测试,验证利率随资金利用率变化

2、知识点梳理

  1. 资金池(Lending Pool)
    • 所有存款用户的资产进入一个共享池子
    • 借款人从池子中提取资金
    • 池子内资金利用率决定利率水平
  2. 资金利用率(Utilization Rate, U)
    [ U = \frac{总借款}{总存款} ]
    • U 越高,说明池子资金越紧张,借款利率越高
    • U 越低,说明资金富余,借款利率越低
  3. 利率模型(Interest Rate Model)
    • 基础利率(Base Rate):当利用率接近 0 时的最低借款利率
    • 斜率(Slope):利用率上升时,利率增加的速度
    • 最优利用率(Optimal Utilization):一个转折点,超过该点后利率会陡增,防止资金池被借空
  4. 存款利率(Supply Rate)
    存款利率来自借款利息分配:
    [ 存款利率 = 借款利率 \times \frac{总借款}{总存款} \times (1 - 协议费率) ]

3、资金池合约实现

LendingPoolWithRate.sol

Foundry 实战:智能合约 Event 测试全攻略

在 Solidity 开发中,event 是智能合约与链下系统交互的重要桥梁。在单元测试中验证事件的触发和参数正确性,是保证合约逻辑正确的关键环节。本文将结合 Foundry,全面讲解事件的测试方法,包括严格顺序匹配、顺序忽略,以及解码非 indexed 参数。

《纸上谈兵·solidity》第 29 课:智能合约安全审计案例复盘 -- Parity Wallet Hack(2017)

  • 时间:2017 年 7 月(第一次) & 2017 年 11 月(第二次)
  • 事件:Parity 多签钱包合约存在严重漏洞,被攻击者利用,最终导致 约 51 万 ETH(当时价值 3 亿美元) 被盗/冻结。
  • 影响:继 The DAO Hack 之后又一次震惊整个以太坊社区的安全事故。

1. 背景

Parity Wallet 是由 Parity Technologies(Gavin Wood 创立的公司,以太坊联合创始人)开发的钱包,支持 多签机制(Multisig Wallet),广泛被 ICO 项目和机构投资人使用。

《纸上谈兵·solidity》第 28 课:智能合约安全审计案例复盘 -- The DAO Hack(2016)

The DAO Hack 简介

  • 时间:2016 年 6 月
  • 事件:一个基于以太坊的“去中心化投资基金”——The DAO,被黑客利用智能合约漏洞攻击,导致 360 万 ETH(当时约 5000 万美元)被盗。
  • 影响:直接导致以太坊社区分裂,产生了 ETH(Ethereum)与 ETC(Ethereum Classic) 两条链。

1. 背景

The DAO 是由 Slock.it 团队发起的一个智能合约,目标是让全球投资人通过 ETH 投资 DAO,然后社区投票决定投资哪些项目。

《纸上谈兵·solidity》第 27 课:DAO 治理合约(去中心化自治组织)

1、学习目标

  1. 理解 DAO 的核心理念:由代币持有人共同治理
  2. 学习实现 提案(Proposal)+ 投票(Voting)+ 执行(Execution) 流程
  3. 引入 治理代币(Governance Token),绑定投票权
  4. 学习 时间锁 Timelock,防止恶意提案被立即执行

2、DAO 合约设计要点

  • 治理代币:每个代币 = 1 票
  • 提案 Proposal:由用户提交,包含目标地址 + 执行数据
  • 投票 Voting:代币持有人按比例投票,投票期内可投
  • 执行 Execution:提案通过后,由合约调用目标合约
  • 时间锁 Timelock:执行需等待一段时间(例如 2 天)

3、示例合约 SimpleDAO.sol

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;

/// @title SimpleDAO - 简化版 DAO 治理合约
/// @notice 教学演示用,不可用于生产

interface IERC20 {
    function balanceOf(address account) external view returns (uint);
}

contract SimpleDAO {
    IERC20 public governanceToken;
    uint public proposalCount;
    uint public constant VOTING_PERIOD = 3 days;   // 投票期
    uint public constant TIMELOCK_DELAY = 2 days;  // 执行延迟
    uint public constant QUORUM = 100e18;          // 最低投票总数(100 票)

    enum ProposalState { Active, Defeated, Succeeded, Queued, Executed }

    struct Proposal {
        address proposer;
        address target;
        bytes data;
        string description;
        uint voteFor;
        uint voteAgainst;
        uint startTime;
        uint endTime;
        uint eta; // Estimated Time for execution
        ProposalState state;
    }

    mapping(uint => Proposal) public proposals;
    mapping(uint => mapping(address => bool)) public hasVoted;

    event ProposalCreated(uint id, address proposer, string description);
    event Voted(uint id, address voter, bool support, uint weight);
    event ProposalQueued(uint id, uint eta);
    event ProposalExecuted(uint id);

    constructor(address _token) {
        governanceToken = IERC20(_token);
    }

    /// @notice 创建提案
    function propose(address target, bytes calldata data, string calldata description) external {
        proposalCount++;
        proposals[proposalCount] = Proposal({
            proposer: msg.sender,
            target: target,
            data: data,
            description: description,
            voteFor: 0,
            voteAgainst: 0,
            startTime: block.timestamp,
            endTime: block.timestamp + VOTING_PERIOD,
            eta: 0,
            state: ProposalState.Active
        });

        emit ProposalCreated(proposalCount, msg.sender, description);
    }

    /// @notice 投票
    function vote(uint proposalId, bool support) external {
        Proposal storage proposal = proposals[proposalId];
        require(block.timestamp >= proposal.startTime, "voting not started");
        require(block.timestamp <= proposal.endTime, "voting ended");
        require(!hasVoted[proposalId][msg.sender], "already voted");

        uint weight = governanceToken.balanceOf(msg.sender);
        require(weight > 0, "no voting power");

        if (support) {
            proposal.voteFor += weight;
        } else {
            proposal.voteAgainst += weight;
        }

        hasVoted[proposalId][msg.sender] = true;
        emit Voted(proposalId, msg.sender, support, weight);
    }

    /// @notice 投票结果检查,并进入 Timelock 队列
    function queue(uint proposalId) external {
        Proposal storage proposal = proposals[proposalId];
        require(block.timestamp > proposal.endTime, "voting not ended");
        require(proposal.state == ProposalState.Active, "not active");

        if (proposal.voteFor <= proposal.voteAgainst || proposal.voteFor < QUORUM) {
            proposal.state = ProposalState.Defeated;
        } else {
            proposal.state = ProposalState.Queued;
            proposal.eta = block.timestamp + TIMELOCK_DELAY;
            emit ProposalQueued(proposalId, proposal.eta);
        }
    }

    /// @notice 执行提案
    function execute(uint proposalId) external {
        Proposal storage proposal = proposals[proposalId];
        require(proposal.state == ProposalState.Queued, "not queued");
        require(block.timestamp >= proposal.eta, "timelock not expired");

        (bool success, ) = proposal.target.call(proposal.data);
        require(success, "execution failed");

        proposal.state = ProposalState.Executed;
        emit ProposalExecuted(proposalId);
    }
}

4、测试文件 test/SimpleDAO.t.sol

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;

import "forge-std/Test.sol";
import "../src/SimpleDAO.sol";

/// @notice 简单的治理代币 (ERC20-like)
contract GovernanceToken is IERC20 {
    string public name = "GovToken";
    string public symbol = "GOV";
    uint8 public decimals = 18;
    uint public totalSupply;
    mapping(address => uint) public balanceOf;

    function mint(address to, uint amount) external {
        balanceOf[to] += amount;
        totalSupply += amount;
    }
}

/// @notice 被治理的目标合约(DAO 将控制它)
contract TargetContract {
    uint public value;

    function setValue(uint _value) external {
        value = _value;
    }
}

contract SimpleDAOTest is Test {
    GovernanceToken public gov;
    SimpleDAO public dao;
    TargetContract public target;

    address alice = address(0x123);
    address bob = address(0x234);

    function setUp() public {
        gov = new GovernanceToken();
        dao = new SimpleDAO(address(gov));
        target = new TargetContract();

        // 给 Alice 和 Bob 铸造治理代币
        gov.mint(alice, 100e18);
        gov.mint(bob, 50e18);
    }

    /// @notice 测试完整的提案生命周期
    function testProposalLifecycle() public {
        vm.startPrank(alice);

        // Alice 提出一个提案:调用 target.setValue(42)
        bytes memory data = abi.encodeWithSignature("setValue(uint256)", 42);
        dao.propose(address(target), data, "Set value to 42");

        vm.stopPrank();

        // Alice 投支持票
        vm.startPrank(alice);
        dao.vote(1, true);
        vm.stopPrank();

        // Bob 投反对票
        vm.startPrank(bob);
        dao.vote(1, false);
        vm.stopPrank();

        // 快进 3 天,投票结束
        vm.warp(block.timestamp + 3 days + 1);

        // 进入 Timelock 队列
        dao.queue(1);

        // 立即执行应失败(需要 timelock)
        vm.expectRevert();
        dao.execute(1);

        // 再快进 2 天
        vm.warp(block.timestamp + 2 days);

        // 执行提案
        dao.execute(1);

        // 验证目标合约的值已被修改
        assertEq(target.value(), 42);
    }
}

执行测试:

《纸上谈兵·solidity》第 26 课:借贷合约简化实现

1、学习目标

  1. 理解 借贷协议核心机制:存款、借款、还款、清算
  2. 掌握 抵押率(Collateral Factor) 的风险控制方法
  3. 学会实现一个最小版 Compound/Aave 借贷池

2、合约设计要点

  • 用户存入 ETH 作为抵押
  • 用户可以借出 ERC20 稳定币(如 DAI)
  • 设置 抵押率(Collateral Factor),保证抵押物 > 借款
  • 借款人债务随时间增长(利息按年化利率计算)
  • 当抵押不足时,可以被 清算(Liquidation)

3、合约实现 SimpleLending.sol

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;

/// @title SimpleLending - 带利息的简化版借贷池
/// @notice 仅用于教学演示,不能用于生产环境
/// @dev 该合约实现了基本的借贷功能,包括抵押、借款、还款和清算
interface IERC20 {
    /// @notice 转账函数
    /// @param to 接收地址
    /// @param amount 转账金额
    /// @return 是否成功
    function transfer(address to, uint amount) external returns (bool);

    /// @notice 从指定地址转账
    /// @param from 转出地址
    /// @param to 接收地址
    /// @param amount 转账金额
    /// @return 是否成功
    function transferFrom(address from, address to, uint amount) external returns (bool);

    /// @notice 查询余额
    /// @param account 查询地址
    /// @return 余额
    function balanceOf(address account) external view returns (uint);

    /// @notice 铸造代币
    /// @param to 接收地址
    /// @param amount 铸造金额
    function mint(address to, uint amount) external;
}

contract SimpleLending {
    /// @notice 稳定币合约地址
    IERC20 public stablecoin; 

    /// @notice 抵押率,75%
    uint public constant COLLATERAL_FACTOR = 75;

    /// @notice 清算阈值,80%
    uint public constant LIQUIDATION_THRESHOLD = 80;

    /// @notice 年化利率,5% (0.05 * 1e18)
    uint public constant INTEREST_RATE_PER_YEAR = 5e16;

    /// @notice 一年的秒数
    uint public constant SECONDS_PER_YEAR = 365 days;

    /// @notice 账户信息结构体
    struct Account {
        uint collateralETH; // 抵押 ETH
        uint debt;          // 借款本金 + 利息
        uint lastAccrued;   // 上次计息时间
    }

    /// @notice 用户账户映射
    mapping(address => Account) public accounts;

    // 事件
    /// @notice 抵押事件
    event Deposit(address indexed user, uint amount);

    /// @notice 借款事件
    event Borrow(address indexed user, uint amount);

    /// @notice 还款事件
    event Repay(address indexed user, uint amount);

    /// @notice 清算事件
    event Liquidate(address indexed liquidator, address indexed user, uint repayAmount);

    /// @notice 计息事件
    event AccrueInterest(address indexed user, uint newDebt);

    /// @notice 构造函数
    /// @param stablecoinAddr 稳定币合约地址
    constructor(address stablecoinAddr) {
        stablecoin = IERC20(stablecoinAddr);
    }

    /// @notice 内部函数:计息
    /// @dev 根据时间计算利息并更新债务
    /// @param user 用户地址
    function _accrueInterest(address user) internal {
        Account storage account = accounts[user];
        if (account.debt == 0) {
            account.lastAccrued = block.timestamp;
            return;
        }

        uint elapsed = block.timestamp - account.lastAccrued;
        if (elapsed == 0) return;

        uint interest = (account.debt * INTEREST_RATE_PER_YEAR * elapsed) / (SECONDS_PER_YEAR * 1e18);
        account.debt += interest;
        account.lastAccrued = block.timestamp;

        emit AccrueInterest(user, account.debt);
    }

    /// @notice 存入 ETH 作为抵押
    /// @dev 用户可以通过此函数存入 ETH 作为抵押
    function depositCollateral() external payable {
        accounts[msg.sender].collateralETH += msg.value;
        if (accounts[msg.sender].lastAccrued == 0) {
            accounts[msg.sender].lastAccrued = block.timestamp;
        }
        emit Deposit(msg.sender, msg.value);
    }

    /// @notice 借款
    /// @dev 用户可以通过此函数借款
    /// @param amount 借款金额
    function borrow(uint amount) external {
        _accrueInterest(msg.sender);

        Account storage account = accounts[msg.sender];
        require(account.collateralETH > 0, "no collateral");

        uint maxBorrow = (account.collateralETH * COLLATERAL_FACTOR) / 100;
        require(account.debt + amount <= maxBorrow, "exceeds borrow limit");

        account.debt += amount;
        stablecoin.mint(msg.sender, amount);

        emit Borrow(msg.sender, amount);
    }

    /// @notice 还款
    /// @dev 用户可以通过此函数还款
    /// @param amount 还款金额
    function repay(uint amount) external {
        _accrueInterest(msg.sender);

        Account storage account = accounts[msg.sender];
        require(account.debt >= amount, "repay too much");

        require(stablecoin.transferFrom(msg.sender, address(this), amount), "transfer failed");
        account.debt -= amount;

        emit Repay(msg.sender, amount);
    }

    /// @notice 清算
    /// @dev 清算人可以通过此函数清算用户的抵押
    /// @param user 被清算的用户地址
    function liquidate(address user) external {
        _accrueInterest(user);

        Account storage account = accounts[user];
        require(account.debt > 0, "no debt");

        uint collateralValue = account.collateralETH;
        uint threshold = (collateralValue * LIQUIDATION_THRESHOLD) / 100;
        require(account.debt > threshold, "healthy position");

        uint repayAmount = account.debt;
        require(stablecoin.transferFrom(msg.sender, address(this), repayAmount), "transfer failed");

        account.debt = 0;
        uint seizedETH = account.collateralETH;
        account.collateralETH = 0;

        payable(msg.sender).transfer(seizedETH);

        emit Liquidate(msg.sender, user, repayAmount);
    }

    /// @notice 查询抵押率
    /// @dev 返回用户的抵押率
    /// @param user 用户地址
    /// @return 抵押率
    function getCollateralRatio(address user) external view returns (uint) {
        Account memory account = accounts[user];
        if (account.debt == 0) return type(uint).max;
        return (account.collateralETH * 100) / account.debt;
    }
}

4、测试文件 test/SimpleLending.t.sol

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;

import "forge-std/Test.sol";
import "../src/SimpleLending.sol";

/// @title MockStablecoin - 模拟稳定币合约
/// @notice 用于测试 SimpleLending 合约的模拟稳定币
contract MockStablecoin {
    string public name = "Mock DAI";
    string public symbol = "mDAI";
    uint8 public decimals = 18;

    /// @notice 账户余额映射
    mapping(address => uint) public balanceOf;

    /// @notice 授权额度映射
    mapping(address => mapping(address => uint)) public allowance;

    /// @notice 转账事件
    event Transfer(address indexed from, address indexed to, uint value);

    /// @notice 授权事件
    event Approval(address indexed owner, address indexed spender, uint value);

    /// @notice 铸造代币
    /// @param to 接收地址
    /// @param amount 铸造金额
    function mint(address to, uint amount) external {
        balanceOf[to] += amount;
        emit Transfer(address(0), to, amount);
    }

    /// @notice 授权额度
    /// @param spender 授权地址
    /// @param amount 授权金额
    /// @return 是否成功
    function approve(address spender, uint amount) external returns (bool) {
        allowance[msg.sender][spender] = amount;
        emit Approval(msg.sender, spender, amount);
        return true;
    }

    /// @notice 转账
    /// @param to 接收地址
    /// @param amount 转账金额
    /// @return 是否成功
    function transfer(address to, uint amount) external returns (bool) {
        require(balanceOf[msg.sender] >= amount, "balance too low");
        balanceOf[msg.sender] -= amount;
        balanceOf[to] += amount;
        emit Transfer(msg.sender, to, amount);
        return true;
    }

    /// @notice 从授权地址转账
    /// @param from 转出地址
    /// @param to 接收地址
    /// @param amount 转账金额
    /// @return 是否成功
    function transferFrom(address from, address to, uint amount) external returns (bool) {
        require(balanceOf[from] >= amount, "balance too low");
        require(allowance[from][msg.sender] >= amount, "allowance too low");
        balanceOf[from] -= amount;
        allowance[from][msg.sender] -= amount;
        balanceOf[to] += amount;
        emit Transfer(from, to, amount);
        return true;
    }
}

/// @title SimpleLendingTest - SimpleLending 合约的测试
/// @notice 测试 SimpleLending 合约的功能
contract SimpleLendingTest is Test {
    /// @notice 模拟稳定币合约
    MockStablecoin dai;

    /// @notice SimpleLending 合约
    SimpleLending lending;

    /// @notice 测试用户 Alice
    address alice = address(0x123);

    /// @notice 测试用户 Bob
    address bob   = address(0x234);

    /// @notice 初始化测试环境
    function setUp() public {
        dai = new MockStablecoin();
        lending = new SimpleLending(address(dai));

        // 给 Bob 一些 mDAI 用于清算 + 授权
        dai.mint(bob, 1000 ether);
        vm.prank(bob);
        dai.approve(address(lending), type(uint).max);

        // 给 Alice/Bob 充值 ETH(Alice 要抵押 1 ETH)
        vm.deal(alice, 100 ether);
        vm.deal(bob, 100 ether);
    }

    /// @notice 测试抵押和借款功能
    function testDepositAndBorrow() public {
        vm.startPrank(alice);
        lending.depositCollateral{value: 1 ether}();
        lending.borrow(0.5 ether);
        uint ratio = lending.getCollateralRatio(alice);
        assertGt(ratio, 75);
        vm.stopPrank();
    }

    /// @notice 测试还款功能
    function testRepayDebt() public {
        vm.startPrank(alice);
        lending.depositCollateral{value: 1 ether}();
        lending.borrow(0.5 ether);
        dai.approve(address(lending), type(uint).max);
        lending.repay(0.5 ether);
        vm.stopPrank();

        ( , uint debtAfter, ) = lending.accounts(alice);
        assertEq(debtAfter, 0);
    }

    /// @notice 测试清算功能
    function testLiquidation() public {
        // 抵押 1 ETH,借到上限 0.75 ETH
        vm.startPrank(alice);
        lending.depositCollateral{value: 1 ether}();
        lending.borrow(0.75 ether);
        vm.stopPrank();

        // 利息 5%/年,2 年后:0.75 * (1 + 0.05*2) = 0.825 > 清算阈值 0.8
        vm.warp(block.timestamp + 2 * 365 days);

        // Bob 清算
        vm.prank(bob);
        lending.liquidate(alice);

        (uint collAfter, uint debtAfter, ) = lending.accounts(alice);
        assertEq(debtAfter, 0);
        assertEq(collAfter, 0);
    }

    /// @notice 测试利息计算功能
    function testInterestAccrual() public {
        vm.startPrank(alice);
        lending.depositCollateral{value: 1 ether}();
        lending.borrow(0.5 ether);
        vm.warp(block.timestamp + 365 days);
        // 触发一次计息(0 转账也会进 _accrueInterest)
        lending.repay(0);
        vm.stopPrank();

        (, uint debtWithInterest, ) = lending.accounts(alice);
        assertGt(debtWithInterest, 0.5 ether);
    }
}

执行测试:

《纸上谈兵·solidity》第 25 课:简化版的去中心化交易所(DEX)

1、学习目标

  1. 理解 恒定乘积公式 x * y = k 的原理
  2. 实现一个最小化的 DEX,支持 流动性提供 / 兑换 / 提取
  3. 引入 LP Token,模拟流动性凭证
  4. 探讨 AMM 的优缺点 & Gas 优化点

2、恒定乘积公式 (AMM)

  • 资金池:假设有 TokenATokenB,储备量分别为 xy
  • 公式x * y = k
  • 含义:只要有人兑换,必须保持乘积 k 不变
  • 结果:兑换时会自动形成滑点,越大的单笔兑换,价格偏移越大

3、简化版 DEX 合约

我们实现一个简化的 ETH ↔ ERC20 Token 交易对

《纸上谈兵·solidity》第 24 课:去中心化众筹合约(Crowdfunding)实战

1、本课学习目标

  • 理解去中心化众筹的业务模型与关键边界条件(目标、截止时间、退款/领取)
  • 能从零实现一个支持多 Campaign 的众筹合约(以太币版本)
  • 设计安全的资金流(pull-over-push、checks-effects-interactions、重入保护)
  • 用 Foundry 写完整测试(创建 Campaign、认购、退回、提取)

2、关键设计点

  1. Campaign 状态机
    • Active(正在进行,可 pledging)
    • Successful(达到目标,所有人可领取)
    • Failed(截止且未达到目标,支持退款)
    • Withdrawn(创建者已领取资金)
  2. 谁可以做什么
    • 任意地址可以创建 Campaign(或限定为合约 Owner)
    • 任意地址在活动期间可 pledge(支付 ETH)
    • 创建者在活动结束且目标达成后可 claim(提取所有资金)
    • 投资者在活动结束且目标未达成后可 refund(取回自己投入)
  3. 资金流安全原则
    • Pull over Push:优先把退款/领取弧度做成可提款模式(用户调用提取),不要把外部合约回调放在自动转账中
    • Checks-Effects-Interactions:先改变合约状态再进行外部调用
    • 重入防护:使用互斥锁或 OpenZeppelin 的 ReentrancyGuard
    • 检查零地址、金额、截止时间合理性
  4. 时间处理
    • 使用 block.timestamp;注意矿工可微调时间(可被操纵 ~900s),对大额攻击场景需谨慎
  5. Gas / DoS 风险
    • 不要在单笔函数里遍历大量数组(避免被 gas 限制 DOS)
    • 使用 mapping 存储出资详情,避免遍历退款列表

3、简单实现

src/SimpleCrowdfunding.sol

《纸上谈兵·solidity》第 23 课:NFT 合约(ERC721 / ERC1155)实战

1、学习目标

  • 理解 ERC721 与 ERC1155 的标准接口
  • 从零实现一个 最小化 ERC721(NFT)合约
  • 扩展功能:元数据管理(BaseURI)、批量铸造 / 批量转账
  • 对比 OpenZeppelin 实现
  • 使用 Foundry 测试

2、知识点梳理

  1. ERC721 核心接口
    • balanceOf(address)
    • ownerOf(uint256)
    • safeTransferFrom(address,address,uint256)
    • transferFrom(address,address,uint256)
    • approve(address,uint256) / setApprovalForAll(address,bool)
    • 事件:Transfer, Approval, ApprovalForAll
  2. ERC1155 核心接口
    • 支持 多代币标准(FT / NFT / SFT)
    • balanceOf(address,uint256)
    • safeTransferFrom(address,address,uint256,uint256,bytes)
    • safeBatchTransferFrom(...)
    • 事件:TransferSingle, TransferBatch, ApprovalForAll
  3. 应用场景差异
    • ERC721 → 独一无二的资产(头像、土地、艺术品)
    • ERC1155 → 大规模批量资产(游戏道具、门票、盲盒)

3、最小 ERC721 实现

MyERC721.sol

《纸上谈兵·solidity》第 20 课:Solidity 安全专题(二)—— 编译器特性与低级漏洞

课程目标

  • 理解 Solidity 编译器的存储布局机制
  • 学会识别 存储槽冲突、ABI 混淆攻击
  • 掌握 selfdestruct 等低级指令的风险
  • 通过 Foundry 测试模拟攻击与验证

1、存储槽冲突(Storage Slot Collision)

Solidity 使用 32 字节为一个存储槽(storage slot)。在继承或代理合约模式下,如果新旧合约的状态变量定义不一致,就可能发生槽冲突,导致关键数据被覆盖。

《纸上谈兵·solidity》第 14 课:Solidity 中的可升级合约模式 —— 从代理合约到透明代理、UUPS 与安全陷阱

1、可升级的必要性与问题

1. 区块链合约不可变的特性

  • 在区块链上部署的合约代码是永久存储的,不可直接更改或删除。
  • 这种不可变性保障了去中心化和安全性,但也意味着:
    • 一旦有 bug,无法直接修改。
    • 一旦需要新增功能,只能重新部署一个新版本。

2. 部署新合约迁移 vs 升级逻辑合约

  • 部署新合约迁移
    • 需要将旧合约中的状态数据(余额、映射等)迁移到新合约。
    • 迁移过程复杂、易出错、消耗大量 gas。
    • 用户需要更新交互地址,容易引起混乱。
  • 升级逻辑合约
    • 通过代理模式保留原有存储,替换逻辑实现。
    • 用户交互地址不变,数据原地保留。
    • 只需在升级时注意存储布局一致性。

2、可升级合约的核心思想

  • 问题:合约一旦部署,代码无法更改。
  • 解决方案:将合约分为 代理合约(Proxy)逻辑合约(Implementation)
    • 代理合约:存储状态变量,转发调用给逻辑合约。
    • 逻辑合约:包含可执行代码。
  • 关键技术delegatecall,在代理合约中使用 delegatecall 调用逻辑合约的函数,使得代码在代理的存储上下文中执行。

3、代理模式的工作原理

1. delegatecall 复习

(bool success, bytes memory data) = implementation.delegatecall(msg.data);
  • delegatecall 会在当前合约的存储和上下文中执行目标合约的代码。
  • 状态变量读写会影响代理合约,而不是逻辑合约。

2. 存储布局一致性

  • 代理合约和逻辑合约必须保持相同的状态变量声明顺序和类型,否则会出现数据错位。

4、常见可升级合约模式

1. 透明代理(Transparent Proxy)

  • EIP-1967 标准。
  • 普通用户调用逻辑合约函数;管理员调用代理的管理函数(升级逻辑合约地址)。
  • 优点:简单、被广泛支持(OpenZeppelin Proxy)。
  • 缺点:管理逻辑和业务逻辑混在同一个合约中,稍显冗余。

2. UUPS(Universal Upgradeable Proxy Standard)

  • EIP-1822 标准。
  • 升级逻辑放在逻辑合约自身,由 upgradeTo 函数完成。
  • 优点:代理合约更轻量,升级逻辑可定制。
  • 缺点:升级安全完全依赖逻辑合约实现,容易被错误实现破坏。

3. Beacon Proxy

  • 使用一个 Beacon 合约统一存储逻辑合约地址,多个代理共享升级源。
  • 适合多实例共享逻辑的场景。

5、可升级合约的安全陷阱

| 风险点 | 说明 | 解决方案 | | :-------------------- | :------------------------------------------------- | :-------------------------------------------------- | | 存储布局冲突 | 升级后逻辑合约的变量顺序、类型不一致,导致数据错位 | 遵循固定的变量追加规则,避免删除或更改类型 | | 初始化漏洞 | 新逻辑合约的构造函数不会被代理调用 | 使用 initializer 修饰的初始化函数,防止重复初始化 | | delegatecall 风险 | 调用外部不可信合约可能破坏存储 | 严格控制升级权限,禁止不可信代码执行 delegatecall | | 权限丢失 | 升级过程中可能被替换成恶意逻辑 | 使用多签或 Timelock 控制升级 |

《纸上谈兵·solidity》第 13 课:Solidity 低级调用 call/delegatecall/staticcall —— 直接和 EVM“对话”

1. 三种低级调用方式对比

| 调用方式 | 是否切换上下文(storage/msg.sender/msg.value) | 是否能改状态 | 特点与用途 | |:-------------- |:------------------------------------- |:------ |:----------------------- | | call | ✅ 切换到被调用合约 | ✅ | 最通用的外部调用,可带 ETH,可调用任意函数 | | delegatecall | ❌ 保持当前合约上下文 | ✅ | 代理模式核心,让当前合约执行别人的代码 | | staticcall | ✅ 切换到被调用合约 | ❌ | 安全读取外部数据,不改状态 |

《纸上谈兵·solidity》第 11 课:Solidity 错误处理与异常机制 —— 让合约优雅地失败

在 Solidity 智能合约开发中,失败并不可怕,可怕的是失败后状态不明确、资金不安全、调用方摸不着头脑。EVM 的一个重要特性是:当合约执行中发生错误时,会回滚所有状态更改,并退还未使用的 Gas。因此,正确使用错误处理机制,能够让合约在异常情况下安全地停止,而不是留下一地鸡毛。

《纸上谈兵·solidity》第 3 课:事件(Event)机制与链上日志——不是 print,是广播!

本课目标

  • 理解事件(event)的作用
  • 掌握事件的声明、触发和监听方法
  • 理解事件与交易日志(Transaction Logs)的关系
  • 明确事件和 console.log() 的区别
  • 学会在前端或脚本中监听链上事件

一、事件是什么?

在 Solidity 中,事件(event)不是控制台输出,也不是日志记录函数,而是链上广播机制

《纸上谈兵·solidity》第 1 课:部署你的第一个 Solidity 合约

本节目标

  • 创建一个最小的 Solidity 合约
  • 使用 Foundry 或 Hardhat 在本地部署它
  • 调用链上函数,确认部署成功

前置条件

  • 推荐已经完成 第 0 课,搭建好开发环境
  • 已安装:
    • Node.js(用于 Hardhat)
    • 或 Foundry(用于 forge
    • 或使用 Remix(可选 Web 环境)

实操方案一:使用 Foundry 部署 HelloWorld 合约

1. 创建项目

$ forge init hello-world
$ cd hello-world && tree -L 1
.
├── foundry.toml    # 项目的配置入口
├── lib/            # 依赖库目录,用于存放外部依赖(比如 openzeppelin-contracts)
├── README.md       # 项目说明文档
├── script/         # 脚本目录,部署或脚本自动化代码的位置
├── src/            # 主合约目录,所有生产环境要部署的合约都应该放在这里
└── test/           # 测试目录,所有测试合约放在这里

使用forge init 创建的项目中在 src、script和test目录下有项目的示例文件,在我们的 HelloWorld 合约中并不需要,所有需要将这些文件删除。

Foundry v1.0 迁移指南

Foundry v1.0 版本引入了多项破坏性变更,使用旧版本的项目可能需要相应调整。本指南记录了从旧版本迁移时的最佳实践建议。

深入了解Foundry配置文件

Foundry 作为 Solidity 开发的瑞士军刀,其配置文件 foundry.toml 是开发者实现高效工作流的核心枢纽。本文将从基础配置到高级优化,介绍 50+ 关键参数的配置逻辑,帮助我们更好地构建智能合约开发体系。

Foundry之测试部署后的合约

在之前文章中,我们介绍了如何使用Foundry将我们的合约部署到区块链上,现在简单介绍如何对已部署的合约进行测试。

Foundry之部署合约本地网络

在之前的文章里,我们介绍了如何使用Foundry来对我们的合约进行测试。现在合约测试完成后,我们需要将合约部署到本地网络上,以便其他用户可以调用。

Foundry之使用OpenZeppelin插件进行智能合约升级

合约的可升级性

智能合约在部署后,通常无法像传统软件一样直接进行修改或更新。这是因为区块链上的智能合约一旦被部署,就会被记录在区块链上,并且其代码是不可更改的。这种不可变性是区块链的一个重要特性,但也带来了一些问题,尤其是在智能合约的升级和维护方面。

Foundry单元测试之模拟地址

vm.startPrankvm.prank 都是 Foundry 中用于模拟(prank)不同地址执行合约操作的函数。它们的作用相似,但在使用场景和行为上有一些细微的区别。以下是这两个函数的对比。

Foundry单元测试之vm.expectRevert

vm.expectRevert 是 Foundry 中用于测试合约函数是否会抛出 revert 错误的一个非常有用的工具。它允许你验证在执行某个操作时,合约是否会按预期抛出特定的错误。这对编写安全和可靠的智能合约测试至关重要,尤其是在涉及访问控制、权限检查等场景时。

使用Foundry开发第一个ERC20合约

在之前的文章中,我们已经简单介绍了Foundry的基本概念和安装方法。本文将以一个简单的 ERC20 合约为例,介绍如何使用Foundry进行合约的编写。

在VScode中使用Foundry

在开发 Solidity 合约时,VSCode 提供了强大的支持,特别是通过安装 Solidity 扩展并与 Foundry 配合使用,可以显著提升开发效率。

Foundry 简介

随着区块链技术的飞速发展,开发者们对工具的需求也在不断变化。传统的开发工具常常面临着效率低、易用性差和难以维护等问题,而Foundry作为一个新兴的区块链开发框架,正逐步成为开发者青睐的选择。它不仅提高了开发效率,还通过创新的设计和简洁的工作流,帮助开发者更好地构建、测试和部署智能合约。本文将深入探讨Foundry框架的优势、核心特点以及如何在区块链开发中使用它。

Solidity:代理模式升级合约

在Solidity中,通过代理模式来升级智能合约是一种常见且有效的做法,它允许在不中断现有合约功能的情况下进行更新。这种模式的基本思路是将合约的状态和主要逻辑分离,使得可以在一个新的合约中部署更新的逻辑,然后通过一个代理合约来调用新的逻辑,从而达到升级的目的。

OpenZeppelin

OpenZeppelin 是一个开源框架,提供了一系列经过审计的智能合约库,帮助开发者在以太坊和其他 EVM 兼容区块链上构建安全的去中心化应用(dApps)。OpenZeppelin 的合约库涵盖了代币标准(如 ERC20 和 ERC721)、访问控制、支付、代理等多个方面,极大地简化了智能合约的开发过程。

Solidity:NFT

NFT,全称为非同质化代币(Non-Fungible Token),是一种基于区块链技术的数字资产。与比特币等传统加密货币不同,NFT是唯一的、不可替代的,每一个NFT都有其独特的标识信息。

Solidity:ERC721

ERC-721 是以太坊区块链上的一种智能合约标准,专门用于创建和管理不可替代代币(NFT)。这些代币与ERC-20代币不同,ERC-20代币是同质化代币,每个代币都是相同的,可以互换。而ERC-721代币则是独一无二的,每个代币都具有独特的属性和价值

Solidity:assembly

在Solidity中,assembly是一个内嵌的低级语言,它允许开发者直接编写EVM(以太坊虚拟机)字节码。这种能力使得开发者可以更精细地控制智能合约的行为,并且在某些情况下可以提高性能和减少gas费用。然而,使用assembly也增加了代码的复杂性和出错的可能性,因此应谨慎使用。

Solidity:ERC20

简介

ERC20是以太坊区块链上的一个标准,用于创建和发行智能合约,实现代币化的应用。它定义了一组通用的接口,使得开发者可以创建和使用代币,而不需要了解底层区块链技术的细节。以下是ERC20标准中的主要接口及其含义:

Solidity:chainlink

简介

Chainlink是一个去中心化的Oracle网络,它允许智能合约在链上与链下的数据进行安全和可靠的交互。Chainlink的目标是成为区块链和现实世界之间的桥梁,使得智能合约能够访问现实世界的数据,支付系统,API等,从而扩大其应用范围。

Solidity:多签钱包

什么是多签钱包

多签钱包,也称为多重签名钱包,是一种加密钱包,它需要多个私钥才能签署并发送交易。这种钱包的主要优点是提供了额外的安全层,因为即使一个私钥被盗,也无法进行交易,除非有其他私钥的授权。

Solidity:存钱罐合约

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.25;

contract CoinBank {
    // 声明一个变量来存储合约的所有者
    address immutable owner;

    // 在合约创建时,设置合约的所有者为创建者
    constructor() {
        owner = msg.sender;
    }

    // 装饰器函数,用于判断调用者是否是合约所有者
    modifier OnlyOwner() {
        require(msg.sender == owner, "Not owner");
        _;
    }

    // 创建一个名为deposit的函数,允许任何人向合约发送以太币
    function deposit() public payable {
        // 函数体为空,因为我们不需要做任何事情
        // 任何发送到这个函数的以太币都会自动添加到合约的余额中
    }

    // 创建一个名为withdraw的函数,只允许合约的所有者提取以太币
    function withdraw() external OnlyOwner {
        // require(msg.sender == owner, "caller is not owner");

        // 使用transfer函数将合约的所有余额发送给所有者
        (bool sent, bytes memory data) = address(msg.sender).call{
            value: address(this).balance
        }("");
        require(sent, "Failed to send Ether");
    }

    // 获取当前地址的余额
    function getBalance() external view returns (uint256) {
        return address(this).balance;
    }
}

上面的合约可以说是一个存钱罐合约,它允许任何人存入以太币,但只有合约的所有者才能取出:

Solidity:assembly和unchecked

unchecked

在 Solidity 0.8.0 及以上版本中,引入了一个新的关键字 unchecked。在 unchecked 块中,算术运算将不会进行溢出检查,这意味着如果结果超过了类型的最大值,它将回滚到类型的最小值,反之亦然。

Solidity:库函数

简介

在Solidity中,库(Libraries)是一种特殊类型的合约,它们包含了一些可重用的函数,可以在其他合约中调用。库函数可以帮助我们减少代码冗余,提高代码的可读性和可维护性。

Solidity:Gas 优化

在Solidity中,优化gas使用是非常重要的,因为每一笔交易都需要消耗gas。以下是一些可以帮助你优化gas使用的技巧:

Solidity:在合约中创建其它合约

在Solidity中,new关键字用于创建一个新的智能合约实例。当你使用new关键字创建一个新的合约实例时,Solidity会在区块链上部署一个新的合约,并返回新合约的地址。自0.8.0版本开始,new关键字通过指定salt选项支持create2特性。

Solidity:四种防止重入攻击的方法

在Solidity中,重入攻击是一种常见的安全问题。它发生在一个合约调用另一个合约的函数,然后被调用的合约再次调用原合约的函数,从而在原合约的函数完成之前改变其状态。以下是一些防止重入攻击的方法:

Solidity:合约调用方式

在Solidity中,合约之间的交互是通过调用进行的。以下是一些主要的合约调用方式:

Solidity:call 和 delegatecall

在Solidity中,calldelegatecall是两种用于在合约之间进行交互的低级函数。它们都可以调用合约的函数,但是它们的工作方式和用途有所不同。

Solidity:接收 ETH

payable

在Solidity中,payable是一个函数修饰符,它允许函数接收Ether(以太币)。如果一个函数被标记为payable,那么你可以在调用该函数时附带一定数量的Ether。如果一个函数没有被标记为payable,那么你不能在调用该函数时发送Ether,否则交易将被拒绝。

Solidity:发送 ETH

在Solidity中,transfersendcall是用于发送ETH的三种方法。以下是对这三种方法的详细介绍:

Solidity:接口

在Solidity中,接口是一种定义合约之间如何交互的方式。接口只能包含函数声明,不能包含函数实现。这意味着接口中的函数没有函数体。接口可以被其他合约实现(通过使用is关键字),这样那些合约就必须提供接口中所有函数的实现。

Solidity:合约继承

在Solidity中,继承是一种使一个合约可以获取另一个合约的属性和方法的方式。这是一种代码复用的方法,可以使你的代码更加模块化,更易于理解和维护。

Solidity:constructor函数

什么是 constructor

在Solidity中,constructor是一个特殊的函数,它在合约被部署到区块链时自动执行。这个函数在合约的生命周期中只会被调用一次,就是在合约创建时。

Solidity:修饰器

在Solidity中,修饰器是一种特殊的函数,可以用来修改其他函数的行为。修饰器可以用来检查函数的前置条件,修改函数的输入或输出,或者在函数执行前后执行一些额外的代码。

Solidity:错误

在Solidity中,错误处理是非常重要的一部分。合约中的错误可能会导致严重的安全问题,因此需要谨慎处理。Solidity提供了几种不同的方式来处理错误:

Solidity:事件

在 Solidity 中,事件(Event)是合约与区块链外部的接口。事件可以用来记录合约的状态变化,或者触发合约外部的响应。事件在合约中被触发,然后被区块链记录在交易日志中。

Solidity:函数

在 Solidity 中,函数是执行特定任务的代码块。函数可以接受参数,并且可以返回值。函数在智能合约中起着核心作用,用于实现合约的逻辑。

Solidity:数据存储位置

在 Solidity 中,有三种数据存储位置:storage、memory 和 calldata。这三者的差异主要在于它们的数据保存位置和存储周期。

Solidity:常用数据结构

在 Solidity 中,有各种各样的数据结构可用于组织和处理信息。以下是一些最常用的数据结构。

Solidity:控制语句

1. 条件语句

Solidity 中的条件判断和其他编程语言中的大致相同。它使用 ifelseelse if 语句来执行基于特定条件的代码块。这些语句在智能合约中经常使用,以便在满足某些条件时执行特定的操作。

Solidity:Gas

1. Gas 介绍

在 Solidity 和以太坊中,gas 是一个非常重要的概念。它是衡量在以太坊区块链上执行交易或运行智能合约所需的计算工作量的单位。基本上,每一次在以太坊网络中执行某种操作,如交易或合约调用,都需要消耗一定数量的 gas。

Solidity:变量与常量

1. 变量

在Solidity中,存在三种主要类型的变量:局部(Local)变量、状态(State)变量和全局(Global)变量。它们的作用范围和行为各不相同。需要注意的一点是,状态变量和全局变量的使用可能会耗费更多的gas,因为它们涉及到在区块链上存储和读取数据。因此,应该尽量减少这两种类型变量的使用,特别是在需要大量计算的函数中。

Hello World with solidity

1. Solidity 是什么?

Solidity 是一种面向对象的、静态类型的编程语言,专为在以太坊上编写智能合约而创建。由于以太坊上的智能合约可以处理真实世界的资产(比如加密货币),所以Solidity的设计非常关注安全性。

solidity 数据类型简介

1. solidity 简介

Solidity(中文名称:Solidity 语言)是一种面向智能合约(Smart Contracts)的高级编程语言,最初由以太坊(Ethereum)的团队开发并用于以太坊平台上的智能合约编写。Solidity 的设计目标是简化以太坊智能合约的开发,使开发者能够创建安全、可靠的去中心化应用程序(DApps)。

solidity 合约入门

入门合约1

下面是一个简单的 Solidity 合约示例,它实现了一个简单的数字存储合约,允许用户设置和获取一个整数值。这个合约将帮助你了解 Solidity 合约的基本结构和语法。