初探ETH智能合约 — Solidity 快速入门

ETH的智能合约现在应用有很多,比如许多山寨币,包括最近非常火的EOS(在没有上主网前)都是基于ETH的智能合约来发布代币(token)的。说起智能合约,不得不提 Solidity 这门专门写智能合约的语言。

快速过了一遍Solidity 的语法 ,发现这门语言和 JavaScript 非常像。这挺好的,我的js技能点基本点的差不多,感觉 Solidity 也十分顺手。

这种情况下最好的入门方式就是直接上实例了:

// 首先,注释和js一样,都支持"//"单行注释和"/*...*/"形式的多行注释

// 文件开头一般会有个 pragma 指令,用于限定编译器版本号
pragma solidity ^0.4.16; // 这里的版本号是语义化版本号,和npm的版本号意思一样,"^0.4.16"表示可以接受大于等于"0.4.0"而小于"0.5.0"的版本

// 协议就跟js的类差不多,有状态(成员变量)和方法
// 下面三个斜杠的是文档注释(Doxygen风格的)
/// @title 支持代理的投票系统
contract Ballot {
    // struct 就类似 C 语言的 struct,里面可以放各种东东,用来创建自定义的类型
    // 下面这个 Voter 用于代表一个投票者
    struct Voter {
        // struct 的字段都需要指定类型 -- 没错,Solidity是静态类型的
        // -- uint 顾名思义,代表 unsigned integer 无符号整数。
        // -- Solidity 里面的 uint 很大,默认是 256 bit的。
        uint weight; // 权重,代理别人的时候会累加别人的权重 

        // -- bool 即布尔值,和 JS 里面一样。不过不支持其他类型隐式转换为 bool 类型。
        bool voted;  // 是否已经投票了,或委托别人投票了

        // address 是一个基础类型,代表ETH的地址(20字节),不能加减,但能判断是否相等/大小
        //         还能直接通过 .balance 来获取余额,
        //         有 .send() 和 .transfer() 方法用于转账
        address delegate; // 委托给谁
        uint vote;   // 被投票的提案的编号
    }

    // 这是一个简单的提案的类型
    struct Proposal {
        // bytes32顾名思义是一个32字节长的字节数组,这里用来当字符串用
        // -- 字符串也就是UTF8编码的字节数组
        bytes32 name;   // 提案名称,最多32字节
        uint voteCount; // 累计被投数量
    }

    // 从字面意思上看 public 表示这个字段是公开的 
    // -- 而对于 Solidity 而言,public 修饰的字段会自动生成一个同名的getter方法
    // -- 即等价于自动加了个函数:function() public constant 
    //                                     returns address
    //                          { return chairperson }
    address public chairperson; // 主席

    // 这里声明了一个状态变量,它存储了地址到投票者(Voter)的映射
    // -- mapping就相当于 js 里面的 Object/Map,不过 key 可以是任何基础类型
    // -- 唯一的问题是区块链中根据 key 查 value 可以,但是枚举 mapping 中所有
    //    的 key 或 所有 value 是被禁止的。
    mapping(address => Voter) public voters;

    // 一个动态的提案数组。
    // -- 这个数组的语法就跟 Java 里面差不多,不过不需要初始化(自动就初始化了)
    Proposal[] public proposals;

    /// 构造函数,类似C++,名字必须和本合约名字一样
    function Ballot(bytes32[] proposalNames) public {
        // 下面`chairperson`即当前合约的`chairperson`字段,不用在前面加`this.`
        // (类似Java/C++)
        chairperson = msg.sender;
        voters[chairperson].weight = 1;

        // for 循环和其他语言一样。
        // 遍历所有的提案名字,创建提案信息结构体,追加到 proposals 这个数组里
        for (uint i = 0; i < proposalNames.length; i++) {
            // `Proposal({...})` 这就是创建结构体的实例的方法
            // `proposals.push(...)` 跟 js 里一样,用于把元素加到数组最后面
            proposals.push(Proposal({
                name: proposalNames[i],
                voteCount: 0
            }));
        }
    }

    // 给 `voter` 投票的权利。这个方法只能被主席调用。
    // -- 注意关键词function不能丢
    function giveRightToVote(address voter) public {
        // `require` 即打断言,参数如果非真则抛出异常
        // 异常会导致这个方法所在的事务终止并回退所有的修改(包括转账)
        // 合理地使用断言能有效地防止恶意用户以及未授权的访问。
        // 不过需要注意的是,事务回滚的时候燃料(gas)却不会退回(将来可能会改)
        // -- msg 是一个特殊的变量,代表当前调用上下文
        // -- msg.sender 的类型是 address,表示谁调用的这个方法
        require(
            (msg.sender == chairperson) &&
            !voters[voter].voted &&
            (voters[voter].weight == 0)
        );
        voters[voter].weight = 1;
    }

    /// 把你的投票权委托给投票者`to`
    function delegate(address to) public {
        // 赋值一个引用
        // -- 除了在内存里面,Solidity的变量还可以存放在 storage 里面(区块链上)
        // -- voters这种在合约内部直接定义的状态字段是放在 storage 里面的
        // -- 方法/函数内部的局部变量默认是在内存里面的
        // -- 从 storage 到内存里面是会导致拷贝的
        // -- 所以这里显式让 sender 变量放在 storage 里面则就不用拷贝的,而是一个引用
        Voter storage sender = voters[msg.sender];
        require(!sender.voted);

        // 不能自己委托给自己
        require(to != msg.sender);

        // 将代理投票权委托到底。
        // 这种循环很危险 -- 合约的燃料(gas)是按执行的指令数来算的
        // 如果循环得太多,则燃料(gas)可能会用尽而无法继续执行。
        while (voters[to].delegate != address(0)) {
            to = voters[to].delegate;

            // 不能循环代理
            require(to != msg.sender);
        }

        // `sender` 是一个引用,所以这里其实就是像直接修改了`voters[msg.sender].voted`
        sender.voted = true;
        sender.delegate = to;
        Voter storage delegate_ = voters[to];
        if (delegate_.voted) {
            // 如果代理人已经投过票了,则直接累加下票数
            proposals[delegate_.vote].voteCount += sender.weight;
        } else {
            // 如果代理人还没有投票,则增加其权重
            delegate_.weight += sender.weight;
        }
    }

    /// 投票
    function vote(uint proposal) public {
        Voter storage sender = voters[msg.sender];
        require(!sender.voted);
        sender.voted = true;
        sender.vote = proposal;

        // 数组越界的时候会自动抛出异常,从而导致事务终止&回滚
        proposals[proposal].voteCount += sender.weight;
    }

    // 计算哪个提案的票数最高
    function winningProposal() public view
            // 返回值可以命名 -- 类似golang -- 而且可以有多返回值
            returns (uint winningProposal_)
    {
        uint winningVoteCount = 0;
        for (uint p = 0; p < proposals.length; p++) {
            if (proposals[p].voteCount > winningVoteCount) {
                winningVoteCount = proposals[p].voteCount;
                winningProposal_ = p;
            }
        }
    }

    // 获取赢得投票的候选者的名字
    function winnerName() public view // view表示这个方法只读不改,方便优化
            returns (bytes32 winnerName_)
    {
        winnerName_ = proposals[winningProposal()].name;
    }
}
打赏

发表评论

电子邮件地址不会被公开。 必填项已用*标注