本站域名已变更为https://blog.67v.cn,全新首页即将发布,尽情期待!!!

第四天:Solidity 核心语法一次过——mapping、struct、modifier 与错误处理

Hello World 跑通了,今天把 Solidity 最常用的四块核心语法一次讲透。这些内容是读任何一份开源合约的地基,花一天换后面读代码的速度,值。

mapping 和 struct:链上的键值对与结构体

code
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.24;

contract Votes {
    struct Candidate {
        string name;
        uint256 voteCount;
    }

    mapping(address => uint256) public votedFor;   // 谁投给了谁
    mapping(uint256 => Candidate) public candidates; // 编号 => 候选人
    uint256 public candidateCount;

    function addCandidate(string calldata _name) external {
        candidates[candidateCount++] = Candidate(_name, 0);
    }

    function vote(uint256 _id) external {
        require(votedFor[msg.sender] == 0, "already voted");
        votedFor[msg.sender] = _id;
        candidates[_id].voteCount += 1;
    }
}

mapping(address => uint256) 等价于一个只能按 key 读写的哈希表:所有 key 默认存在且值为 0,所以没法遍历也没法”判空”,需要”是否投过票”这种布尔判断就得像上面那样借值本身编码(0 = 没投)。另外 msg.sender 是内置变量,永远等于”当前调用这笔交易的钱包地址”,权限判断全靠它。

数据位置:storage / memory / calldata

引用类型(string、数组、结构体)必须显式声明数据位置,这也是新手编译报错最集中的地方:

  • storage:链上持久存储,读写都贵;状态变量默认在这里;
  • memory:函数内的临时内存,函数结束即销毁;
  • calldata:外部调用的参数只读区,最便宜,外部函数的引用类型参数尽量用它。

上面的 string calldata _name 就是这个用意:参数只读不改,用最省 gas 的位置。

modifier 与错误处理

code
contract Owned {
    address public owner;

    error NotOwner(address caller);

    modifier onlyOwner() {
        if (msg.sender != owner) {
            revert NotOwner(msg.sender);
        }
        _;
    }

    constructor() {
        owner = msg.sender;
    }

    function transferOwnership(address _new) external onlyOwner {
        owner = _new;
    }
}

modifier 是函数的前置拦截器,_; 的位置就是被修饰函数体执行的位置——权限控制的标准写法。错误处理上,0.8 之后的最佳实践是自定义 error + revert,比 require("字符串") 省 gas,也比裸字符串好维护;调用失败时链下能拿到错误码和参数,前端报错可以做得非常精准。

今天的作业:把投票合约补一个”撤销投票”功能,要求用 modifier 限制只有已投票的人能调用,且撤销后候选人票数要减回去。写不出来就回去重读 mapping 那段——明天见。

读完了点一下,作者能得积分

赞赏

如果这篇内容帮到了你,不妨请作者喝杯咖啡

分享 微博 QQ

发表评论

发表评论