⚙️ 第08课:函数与修饰符

智能合约 ✅ 验证通过

🎯 学习目标:深入函数重载、多返回值、修饰符模式,掌握错误处理(require/assert/revert/自定义错误),了解函数选择器与ABI编码。

📖 一、多返回值与解构赋值

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

contract MultiReturn {
    struct Order { uint256 id; address buyer; uint256 amount; bool done; }
    mapping(uint256 => Order) public orders;
    uint256 public count;

    // ✅ 多返回值
    function getInfo(uint256 id) public view
        returns (address buyer, uint256 amount, bool done) {
        Order storage o = orders[id];
        return (o.buyer, o.amount, o.done);
    }

    // ✅ 命名返回值(可直接赋值,无需return)
    function getInfo2(uint256 id) public view
        returns (address buyer, uint256 amount, bool done) {
        Order storage o = orders[id];
        buyer = o.buyer; amount = o.amount; done = o.done;
    }

    // ✅ 解构调用(跳过不需要的返回值)
    function getBuyer(uint256 id) public view returns (address) {
        (address b, , ) = getInfo(id);
        return b;
    }

    // ✅ 函数重载(同名不同参数)
    function create(uint256 amt) public returns (uint256) {
        count++; orders[count] = Order(count, msg.sender, amt, false);
        return count;
    }
    function create(address buyer, uint256 amt) public returns (uint256) {
        count++; orders[count] = Order(count, buyer, amt, false);
        return count;
    }
}
// ✅ 验证通过

📖 二、修饰符(Modifier)模式大全

contract Modifiers {
    address public owner;
    bool public paused;
    mapping(address => bool) public blacklist;

    modifier onlyOwner() { require(msg.sender == owner, "Not owner"); _; }
    modifier whenNotPaused() { require(!paused, "Paused"); _; }
    modifier notBlacklisted(address a) { require(!blacklist[a], "Blacklisted"); _; }
    modifier minAmt(uint256 n) { require(n >= 100, "Too small"); _; }

    // ✅ 多修饰符组合(按声明顺序执行)
    function withdraw(uint256 amt) public onlyOwner whenNotPaused
        notBlacklisted(msg.sender) minAmt(amt) {
        payable(msg.sender).transfer(amt);
    }

    // ✅ 重入锁(防重入攻击)
    bool private _locked;
    modifier nonReentrant() {
        require(!_locked, "ReentrancyGuard: reentrant call");
        _locked = true; _; _locked = false;
    }

    // ✅ 限流修饰符
    mapping(address => uint256) public lastCall;
    modifier rateLimit(uint256 cooldown) {
        require(block.timestamp >= lastCall[msg.sender] + cooldown, "Rate limit");
        lastCall[msg.sender] = block.timestamp; _;
    }

    // ✅ 修饰符中_;前后的代码都会执行
    modifier logGas() {
        uint256 start = gasleft(); _;
        require(start - gasleft() < 200000, "Too much gas");
    }
}
// ✅ 验证通过

📖 三、错误处理(require/assert/revert/自定义错误)

contract Errors {
    mapping(address => uint256) public balances;

    // ✅ 自定义错误(Solidity 0.8.4+) — 比require字符串省Gas!
    error InsufficientBalance(uint256 have, uint256 need);
    error TransferFailed(address to, uint256 amt);
    error Unauthorized(address caller);

    function withdraw(uint256 amt) public {
        uint256 bal = balances[msg.sender];
        if (bal < amt) revert InsufficientBalance(bal, amt);
        balances[msg.sender] -= amt;
        (bool s, ) = msg.sender.call{value: amt}("");
        if (!s) revert TransferFailed(msg.sender, amt);
    }

    function deposit(uint256 amt) public {
        require(amt > 0, "Amount must be positive");
        require(balances[msg.sender] + amt >= balances[msg.sender], "Overflow");
        balances[msg.sender] += amt;
    }

    function check(uint256 age, bool lic) public pure {
        if (age < 18) revert("Too young");
        if (!lic) revert("No license");
    }

    // assert — 检查内部不变量
    function safeDiv(uint256 a, uint256 b) public pure returns (uint256) {
        require(b != 0, "Div by zero");
        uint256 c = a / b;
        assert(a == c * b + a % b);
        return c;
    }
}
// ✅ 验证通过
方式用途Gas退还说明
require输入/外部条件验证✅ 退还最常用
revert复杂条件/自定义错误✅ 退还灵活
assert内部不变量检查✅ 退还防御性编程
自定义错误结构化错误信息✅ 退还最省Gas

📖 四、函数选择器与ABI编码

函数选择器机制 调用 contract.transfer(addr, 100): 1. keccak256("transfer(address,uint256)") = 0xa9059cbb2ab09eb2... 2. 取前4字节: selector = 0xa9059cbb 3. ABI编码参数: address(32B) + uint256(32B) 4. 完整calldata = 选择器 + 编码参数
contract ABIDemo {
    function encode(address to, uint256 amt) public pure returns (bytes memory) {
        return abi.encodeWithSignature("transfer(address,uint256)", to, amt);
    }

    function execute(address token, address to, uint256 amt) public returns (bool) {
        bytes memory d = abi.encodeWithSignature("transfer(address,uint256)", to, amt);
        (bool s, ) = token.call(d);
        require(s, "Call failed"); return s;
    }

    function decodeResult(bytes memory data) public pure returns (bool) {
        return abi.decode(data, (bool));
    }
}
// ✅ 验证通过
require vs assert? 自定义错误优势? 函数选择器? 多修饰符执行顺序? 命名返回值特点?

🔧 动手实验

  1. 部署Modifiers测试多修饰符组合
  2. 对比require和自定义错误的Gas差异
  3. 用abi.encodeWithSignature构造调用
  4. 测试rateLimit连续调用

📖 五、函数可见性详解

可见性合约内继承合约外部说明
public默认,最宽松
external仅外部,可calldata
internal默认状态变量
private最严格

📖 六、view vs pure vs payable

函数状态修饰符:
view: 读取状态但不修改(外部调用不消耗Gas)
pure: 既不读取也不修改状态
payable: 可接收ETH转账
• 无修饰符: 可以读写状态

⚠️ 内部调用view函数也会消耗Gas! 只有外部调用才免费。
⚠️ payable函数如果未标记,转入ETH会revert!

📖 七、常见陷阱与最佳实践

  1. ❌ 多修饰符顺序影响执行结果(仔细设计顺序)
  2. ❌ reentrancyGuard忘记在_后解锁(用try/finally)
  3. ❌ require字符串太长浪费Gas(用自定义错误)
  4. ❌ external函数内部不可直接调用(用this.fn())
  5. ❌ constructor中不能使用合约自身的函数
  6. ❌ 忽略函数选择器碰撞风险(4字节碰撞)
  7. ✅ 使用自定义错误替代require字符串
  8. ✅ 修饰符中避免复杂逻辑(保持简单)

📖 八、函数重载与解析

contract OverloadDemo {
    // ✅ 同名不同参数 → 不同selector
    function process(uint256 x) public pure returns (uint256) { return x * 2; }
    function process(address a) public pure returns (address) { return a; }
    function process(uint256 x, uint256 y) public pure returns (uint256) { return x + y; }

    // ⚠️ 重载与继承的冲突
    // 父合约和子合约同名函数不是重载,而是覆盖!
    // 如果需要重载,两个版本必须在同一合约中
}
// ✅ 验证通过

📖 九、抽象合约与接口

// 接口: 所有函数都是abstract,无实现,无构造函数,无状态变量
interface IToken {
    function transfer(address to, uint256 amount) external returns (bool);
    function balanceOf(address account) external view returns (uint256);
}

// 抽象合约: 至少一个未实现的函数,可以有状态变量和已实现函数
abstract contract BaseToken is IToken {
    mapping(address => uint256) public balances;
    function balanceOf(address account) external view returns (uint256) {
        return balances[account];  // ✅ 提供部分实现
    }
    // transfer仍为abstract,子合约必须实现
}
// ✅ 验证通过

📖 十、函数式编程模式

contract FunctionalPatterns {
    uint256[] public data;

    // ✅ 纯函数模式: 无副作用
    function _calcFee(uint256 amt) internal pure returns (uint256) {
        return amt > 1000 ? amt * 3 / 1000 : 0;
    }

    // ✅ 检查-效果-交互(CEI)在函数中的体现
    function safeWithdraw(uint256 amt) public {
        // Check
        require(balances[msg.sender] >= amt, "Insufficient");
        // Effect
        balances[msg.sender] -= amt;
        // Interaction
        (bool s, ) = msg.sender.call{value: amt}("");
        require(s);
    }

    // ✅ Guard模式: 早期返回/回滚
    function process(uint256 x) public pure returns (uint256) {
        require(x > 0, "Zero input");
        require(x < 1000, "Too large");
        uint256 result = x * 2 + 1;
        require(result > x, "Overflow");  // 不变量检查
        return result;
    }
    mapping(address => uint256) public balances;
}
// ✅ 验证通过

📖 十一、Solidity设计模式

常用设计模式:
访问控制: onlyOwner/Role-Based/Time-Lock
状态机: 用enum+modifier管理合约状态转换
工厂模式: 合约创建合约,管理子实例
代理模式: delegatecall实现可升级
Oracle模式: 外部数据注入
紧急暂停: Pausable模块应对突发事件
多签控制: 关键操作需多方确认
⚙️

函数大师

你已深入函数系统——多返回值/修饰符/错误处理/ABI编码!

✅ 多返回值 ✅ 修饰符模式 ✅ 自定义错误 ✅ ABI编码

📋 课程目录