规则即代码:人话解读加密朋克智能合约

Posted vigor2323

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了规则即代码:人话解读加密朋克智能合约相关的知识,希望对你有一定的参考价值。

我当年猛写论文的时候,心想,现在学术期刊上发表的这些论文,大多都是不及格的(很多故弄玄虚,连符号都不交代清楚,分明是不想让人看懂),如果有编译器,估计10个有10个无法通过编译。

心想,以后的世界,写论文应该都用专门的形式化语言,质量怎么样,编译运行一下就知道,先不说里面结论怎么样,首先逻辑得通顺完整吧。

后来又发现,不光论文,很多规章制度一样是说得含含糊糊,语焉不详,似乎故意要说的那么抽象、含糊和没有细节,心想,以后要都代码化了,不知道会是什么景象。

geek理想的世界,一切规则,一切法律、规章、合同、协议、流程都是代码写成的,一切都清清楚楚、明明白白。能运行就是能运行,能通过就是能通过,没有那么多人为解释、人工干预、黑箱操作、徇私舞弊。

区块链之所以被很多人推崇,就是因为它有可能做到这点。以太坊就像一台分布式的大计算机,智能合约就是在其上自动运行的代码,全世界的人都可以调用其代码,查看其公开的数据和日志。

“规则即代码”,正在逐步成为现实。

而且,智能合约编程非常简单,代码量很少(一共只有200多行),我觉得这才是低代码的典范,比起那些以“拖拉拽”为噱头的垃圾产品不知道强到哪里去了。

注:不管是小白还是hacker,如果你能硬着头皮看完本文,你就算区块链编程入门了。

什么是加密朋克

如果你还不了解什么是加密朋克(Cryptopunks),这里简单花一分钟了解一下:

CryptoPunks是Larva Labs的产品,创制于2017年6月。Larva Labs原本打算做一批手机app或游戏的头像,于是根据算法生成了1万个24 x 24像素的8-bit头像,每个都有自己随机生成的独特朋克外观和特征。但在出售时,无人问津。于是他们改变主意,自留其中1000个头像,再把余下9000个免费发放,任何拥有以太坊钱包的用户都可以自行获取,所有头像很快被抢夺一空。

Larva Labs说:““我们编写了存在于区块链上的代码,任何人都可以使用它与世界上任何另一个人买卖朋克(punk)。这个系统一个有趣的地方是,我们不再控制运行加密朋克的代码!一旦我们将其发布到区块链上,它就永久嵌入在那里,任何人都无法再修改。”

注:“智能合约”、“代码”在本文中等义。

若果你现在还是一头雾水,建议先看一下《用直观抓住NFT是什么》。

直观感受一下合约

加密朋克有10000个朋克(punk),每个punk有一个编号(从0到9999),punk的拥有者(主人)在本文中称为“punk主”。

加密朋克合约部署在以太坊上,任何人都可以通过其接口(ABI)调用加密朋克的代码,完成一个punk的获取、转移、出售、购买、竞价。这些都是自动通过合约完成的,都是公开透明的,规则就写在代码里。

比如有一个接口是“查询punk主地址”,给它一个punk编号,它就会给你返回一个以太坊地址,告诉你punk主是谁,比如0号punk主的地址为:

0xE08c32737c021C7d05d116b00a68a02F2d144AC0

这是怎么查到的呢?

在etherscan.io网站上,搜索加密朋克的合约地址:

0xb47e3cd837dDF8e4c57F05d70Ab865de6e193BBB

然后在Contract -> Read contract中,选择punkIndexToAddress这个接口,填入0,点击Query按钮即可得到0号punk主的地址。

 

加密朋克的具体规则

加密朋克的规则全都体现在它的代码里,并没有什么其他文本。

下面的管理规则是我从代码抽象出来的,仅仅是为了便于读者理解代码。


《加密朋克管理规则》

为规范加密朋克的发行、出售、购买、竞价、转让等活动,特制定本规则。

第一条 关于加密朋克

本加密朋克有10000个,每个称为一个朋克(punk),不可再拆分。

本加密朋克的符号为Ͼ。

任何人可以在Larva Labs官网验证一个punk形象的真伪(和官网展示的punk形象对比即可)。

任何人可以将官网上展示的punk合集做hash,验证是否和区块链上的hash一致。

注:该合集图片的链接为:

https://www.larvalabs.com/public/images/cryptopunks/punks.png

注:该合集图片的hash为:

ac39af4793119ee46bbff351d8cb6b5f23da60222126add4268e261199a2921b

注:如果你完全不知道hash是什么,建议阅读《弄明白HASH,你就弄明白区块链的一大半

第二条 关于特权

合约的创始人即特权人,特权人有免费赠送他人punk的权利。

在特权人行使特权完毕后,本加密朋克才正式面向公众开放。

在行使特权期间,特权人可以将任意编号的punk赠送给任何人,即便这个punk已经有主,也可以送给他人。具体来说,就是把punk的编号和受赠人的地址相绑定。

完成赠送后,公告这个punk分配事件。

特权人可自行决定何时结束特权派送,并宣告特权结束,面向公众的非特权功能开始生效。

一旦面向公众开放,特权人就不再有特权。

第三条 免费获取punk

任何人可以通过getpunk接口,免费获取其指定编号的punk,前提是这个punk无主。

获取成功后,这个punk分配事件会予以公告。

第四条 账户资金

本合约接受资金。

本合约给每个用户都建立了账户(注意此账户不是以太坊地址账户,而是合约内部建立的账户)。

用户竞标时,如果是最高出价,竞标资金从用户的以太坊地址进入合约。

用户出售punk成功后,买家付的钱进入卖家的账户。

用户接受投标竞价后,竞标者付的钱进入卖家的账户。

用户退出竞标时,投标资金将返还用户的以太坊地址。

用户可以随时将自己在账户中的钱取出到自己的以太坊地址上。

第五条 转移punk

任何人可以把自己拥有的punk免费转赠给他人。

如果这个punk正在售卖中,那么系统会自动取消售卖状态。

如果被赠人是这个punk的当前最高价投标者,其投标金额会退还给他的账户。

punk转移事件会予以公告。

第六条 公开出售punk

punk主可以把自己拥有的punk标一个价格,并公告想要卖出。

punk主可以取消售卖,这会发出一个取消售卖公告。

punk主可以指定卖给一个特定的地址。如果不指定(该地址初始为0),则表示可以卖给任何人。

第七条 购买punk

用户可以购买那些正在售卖的punk

如果该punk售卖指定了买家,那么只有该买家才能买。

买家出价必须大于或等于卖家标出的价格。

售卖成功后,转移punk给买家,将买家出的钱打入卖家的账户。

售卖成功后,取消该punk的售卖信息(也即清空售卖信息),如果买家是该punk的当前最高价投标者,将其投标金额退还给他的账户。

公告相应的punk转移事件;公告售卖成功事件;公告取消售卖事件。

第八条 竞标punk

任何人可以对某个punk出价进行竞标(bid),系统仅记录最高出价及出价人地址,低于最高价的出价无效。

出价同时要付钱,如果你的出价不再是最高价,你出的钱会打回给你。

当前的最高出价者,可以主动退出竞标,投标资金将返还该人以太坊地址。

公告所有的竞标和放弃竞标事件。

第九条 接受竞价

punk主如果满意,可以接受当前的最高出价,完成punk转移。

公告punk售卖成功事件。

第十条 附则

本规则由代码做最终解释。

本规则自合约中部署成功后生效。

规则全文结束。


以上这些是我用自然语言写的,显然还是缺乏细节,显然还是不够具体和明确的。

最终还是要看代码。

合约的设计

其实编码思路很简单,完成主要数据的设计,基本上就好写了。

1、建立几张映射表,把主要数据记录下来。

比如需要三张表,记录punk编号和地址的对应关系,记录每个地址底下有几个punk,记录每个用户账户的资金。

这三张表就是:punkIndexToAddress表,balanceOf表,pendingWithdrawals表。

1.1 punkIndexToAddress表,记录punk编号和该punk主地址的对应,比如0号punk对应哪个地址,1号punk对应哪个地址,……9999号punk对应哪个地址。对于还没有分配主人的punk,对应的地址为0。

注意:在Solidity语言的实现中,所有未赋值的变量,缺省值都是0,这些值也不占以太坊空间。

1.2 balanceOf表,记录了某个地址拥有几个punk,在这个接口下,输入地址,会返回该地址下拥有的punk数。

比如我的地址下,拥有0个。而查询下面这个地址:

0x279679e6b44ec547c6e0466e5efd7d47ef3a400f

能看到有9个。

据信这是余文乐的以太坊地址。

在Crypto官网上可以看到,他拥有的9个punk是:

1.3 pendingWithdrawals表,记录一个地址下有多少钱,一个人在购买和竞价punk时,都需要花钱(也即调用合约时带上ETH),如果购买或竞标成功,钱就会进入卖家账户,如果失败,也会退回买家账户。用户还可随时根据需要,调用withdraw接口将账户资金提取到以太坊地址。

2、关于特权行使的设计

这个其实简单,只需要一个变量即可,这个变量在代码中是allPunksAssigned,其值为false的时候,合约拥有者(owner)可以行使特权,公众无法使用,当这个值为true的时候,特权停止,面向公众开放。而这个值可以由owner调用allInitialOwnersAssigned接口设置为true。

谁是合约拥有者?在合约部署后,首先执行的是构造函数,构造函数中规定部署合约的人为owner。

合约中有些接口是只能owner调用的,做到这点也很简单,在接口代码一开始的地方写一行即可:

if (msg.sender != owner) throw; 

这句的意思是,如果(if)调用者(msg.sender)不是(!=)拥有者(owner),就退出(throw)

不过在新版的solidity语言中,已经不用throw这种写法了,大家更喜欢使用require语句。

3、售卖和竞价的数据保存

还有就是在售卖和竞价punk时,需要保存一些数据。

合约中设计了两个数据结构:Offer(用于售卖)和Bid(用于竞价)。

Offer数据结构记录了:punk编号、卖家地址、卖家设置的最低售价、指定卖给哪个地址(为0则表明卖给谁都行)。

Bid数据结构记录了:punk编号、目前最高出价、最高出价者的地址。

和这两个数据结构相关的两个表为:

punksOfferedForSale表,punkBids表。

前者你给它一个punk编号,他告诉你该punk的Offer信息。

后者你给它一个punk编号,他告诉你该punk的Bid信息。

行了,可以看代码了。

(以下对小白来说需要费点劲,hacker读起来会比较轻松,小白读我写的注释即可)

代码全文及注释

注:凡是以//开头的,都是注释。

注:可以左右滑动看到完整代码

pragma solidity ^0.4.8;  // 对编译器版本的要求,^0.4.8表示本合约在0.4.8 ~ 0.5.0(不包含0.5.0)的版本都可以进行编译

contract CryptoPunksMarket {   // 合约名:CryptoPunksMarket

    // punk合集图片的SHA-256 hash值,用于验证图片的正确性
    string public imageHash = "ac39af4793119ee46bbff351d8cb6b5f23da60222126add4268e261199a2921b";

    // 合约拥有者变量声明
    address owner;

    // NFT的名称、代号、是否可以分割等变量声明
    string public standard = 'CryptoPunks';
    string public name;
    string public symbol;
    uint8 public decimals;

    // 记录punk总量的变量声明
    uint256 public totalSupply;

    // 下一个即将被分配的punk的编号,一开始是从0开始
    // 笔者注:事实上,这个变量没有用到,属于编程人的过度设计。
    uint public nextPunkIndexToAssign = 0;

    // 特权派送punk是否已经结束的开关
    bool public allPunksAssigned = false;
    
    // 还剩下多少无主的punk
    uint public punksRemainingToAssign = 0;

    // punk拥有者记录表:记录每个punk及其拥有者地址(缺省为0),通过punk编号可查地址。
    mapping (uint => address) public punkIndexToAddress;

    // 拥有数量表:记录某地址拥有punk的数量 (缺省都为0)
    mapping (address => uint256) public balanceOf;

    // punk售卖信息数据结构
    struct Offer {
        bool isForSale;    // 此变量如果为true,即punk正在售卖;false则相反
        uint punkIndex;    // punk编号
        address seller;    // 卖方(即punk主)
        uint minValue;     // 最低价(可以高于此价成交)
        address onlySellTo;  // 可以设置只卖给谁,不设此变量(为0)则卖给谁都可以
    }

    // punk竞标信息数据结构
    struct Bid {
        bool hasBid;      // 是否正在竞标。笔者注:事实上这个变量没有用到,属于编程人的过度设计。
        uint punkIndex;   // punk编号
        address bidder;   // 最高出价人
        uint value;       // 最高出价
    }

    // punk售卖信息表:通过punk编号来查看该punk的售卖信息
    mapping (uint => Offer) public punksOfferedForSale;

    // punk竞标信息表:通过punk编号来查看该punk的竞标信息
    mapping (uint => Bid) public punkBids;

    // 用户账户表:通过地址查看用户在本合约中账户里有多少钱
    mapping (address => uint) public pendingWithdrawals;

    //事件记录:合约调用以下函数,可以记录已经发生了的事件,可理解为日志,事实上就是一个对外的“公告”。
    event Assign(address indexed to, uint256 punkIndex);
    event Transfer(address indexed from, address indexed to, uint256 value);
    event PunkTransfer(address indexed from, address indexed to, uint256 punkIndex);
    event PunkOffered(uint indexed punkIndex, uint minValue, address indexed toAddress);
    event PunkBidEntered(uint indexed punkIndex, uint value, address indexed fromAddress);
    event PunkBidWithdrawn(uint indexed punkIndex, uint value, address indexed fromAddress);
    event PunkBought(uint indexed punkIndex, uint value, address indexed fromAddress, address indexed toAddress);
    event PunkNoLongerForSale(uint indexed punkIndex);

    // 合约部署时执行的动作,只执行一次。(此即构造函数)
    function CryptoPunksMarket() payable {     
        owner = msg.sender;    // 注意这句明确了特权者,谁部署此合约,谁就是合约拥有者(特权者)了
        totalSupply = 10000;   // punk总数为10000
        punksRemainingToAssign = totalSupply;   // 未分配余量为10000
        name = "CRYPTOPUNKS";  // 设置本NFT的名字为CRYPTOPUNKS
        symbol = "Ͼ";          // 设置本NFT的符号为Ͼ
        decimals = 0;          // 1个punk就是1个punk,不再可分
    }

    // 通过调用此函数,特权者可以将punk免费派送给某些用户,参数:派送给谁(to),派送哪个编号的punk
    // 笔者注:里面经常出现的throw,意思就是不满足就回滚退出。throw在较新的Solidity版本中已经弃用,改为assert、require和revert等方式。
    function setInitialOwner(address to, uint punkIndex) {
        if (msg.sender != owner) throw;    // 合约owner(特权者)才能调用此函数
        if (allPunksAssigned) throw;       // 如果特权派送已经结束,就退出。
        if (punkIndex >= 10000) throw;     // 编号不能大于等于10000
        if (punkIndexToAddress[punkIndex] != to) {   //如果这个punk已经有主而且主人就是to,就算了
            if (punkIndexToAddress[punkIndex] != 0x0) {//如果此punk已经有主(比如一开始给了自己,现在想送人)
                balanceOf[punkIndexToAddress[punkIndex]]--; //那么夺过来,让他拥有的punk少一个
            } else {                                //如果该punk无主
                punksRemainingToAssign—-;    //无主punk数减1
            }
            punkIndexToAddress[punkIndex] = to;   // 分配该punk的拥有者地址为to
            balanceOf[to]++;   // to用户的punk拥有数加1
            Assign(to, punkIndex);   // 在以太坊上记录此分配事件,也即记录地址to和punk编号
        }
    }

    // 特权者调用,可同时将多个punk派送给多个用户
    function setInitialOwners(address[] addresses, uint[] indices) {
        if (msg.sender != owner) throw;  //只有合约owner才能调用此函数
        uint n = addresses.length;
        for (uint i = 0; i < n; i++) {
            setInitialOwner(addresses[i], indices[i]);  //在循环中一一派送
        }
    }

    // 关闭特权派送,从此开始面向公众
    function allInitialOwnersAssigned() {
        if (msg.sender != owner) throw;  //合约owner才能调用此函数
        allPunksAssigned = true;      // 特权派送关闭,意思是主人不想再送了,派送结束。
    }

    //任何用户都可以通过该接口来申请获得无主的punk ,参数是punk编号 (可惜现在都有主了,笔者注)
    function getPunk(uint punkIndex) {
        if (!allPunksAssigned) throw;  // 如果特权派送还未结束,退出
        if (punksRemainingToAssign == 0) throw;   // 剩下无主punk数要大于0
        if (punkIndexToAddress[punkIndex] != 0x0) throw;  // 只能得到无主的punk
        if (punkIndex >= 10000) throw;   // 所申请punk的编号要小于10000
        punkIndexToAddress[punkIndex] = msg.sender;   // 以上条件都满足的话,记录该编号punk归申请人
        balanceOf[msg.sender]++;   // 申请人拥有punk数加一
        punksRemainingToAssign--;  // 无主punk数减一
        Assign(msg.sender, punkIndex);   // 在以太坊上记录此分配事件,也即记录地址to和punk编号
    }


    // 转移punk给他人(不涉及资金,是免费转移),参数:转给谁(to),punk的编号
    function transferPunk(address to, uint punkIndex) {
        if (!allPunksAssigned) throw;   // 必须是特权派送结束才行
        if (punkIndexToAddress[punkIndex] != msg.sender) throw;  //函数调用者必须是punk的主人
        if (punkIndex >= 10000) throw;  // punk编号要大于10000
        if (punksOfferedForSale[punkIndex].isForSale) {  //如果之前主人正在售卖这个punk
            punkNoLongerForSale(punkIndex);  //停止售卖
        }
        punkIndexToAddress[punkIndex] = to;  // 记录这个punk归to了
        balanceOf[msg.sender]--;  // 主人拥有数减一
        balanceOf[to]++;   // to的拥有数加一
        Transfer(msg.sender, to, 1);  // 记录punk转移事件,记录转移人、被转移人
        PunkTransfer(msg.sender, to, punkIndex); // 记录punk赠送事件(其他的转移是花钱的)
        // Check for the case where there is a bid from the new owner and refund it.
        // Any other bid can stay in place.
        //查看被转移方是否之前有对该punk进行投标,有则取消,并将其投标的金额放进其临时账户中
        Bid bid = punkBids[punkIndex];
        if (bid.bidder == to) {
            pendingWithdrawals[to] += bid.value;  // 归还投标者在合约中锁定的金额
            punkBids[punkIndex] = Bid(false, punkIndex, 0x0, 0); //清空投标信息
                }
    }

    // 取消之前的punk售卖公告,参数是punk编号
    function punkNoLongerForSale(uint punkIndex) {
        if (!allPunksAssigned) throw;   // 特权派送结束后才可以生效
        if (punkIndexToAddress[punkIndex] != msg.sender) throw; // 调用者必须是punk的主人
        if (punkIndex >= 10000) throw;  // punk编号应小于10000
         // 更新该punk的售卖信息为:停止售卖、编号、售卖人、价格为0,不指定买家
        punksOfferedForSale[punkIndex] = Offer(false, punkIndex, msg.sender, 0, 0x0);
        PunkNoLongerForSale(punkIndex);  //记录一下,相当于公告售卖取消
    }

    // 设置价格,公告某punk开始出售。参数:punk编号,最低价。
    function offerPunkForSale(uint punkIndex, uint minSalePriceInWei) {
        if (!allPunksAssigned) throw;   // 特权派送结束后才可以生效
        if (punkIndexToAddress[punkIndex] != msg.sender) throw;  // 调用者必须是该punk的主人
        if (punkIndex >= 10000) throw;   //punk编号应小于10000
        // 更新该punk的售卖信息:正在售卖、编号、售卖人、最低售价、不指定买家
        punksOfferedForSale[punkIndex] = Offer(true, punkIndex, msg.sender, minSalePriceInWei, 0x0);
        PunkOffered(punkIndex, minSalePriceInWei, 0x0);// 记录这个事件,相当于公告售卖信息
    }

    // 公告某punk想要卖给某个特定人。参数:punk编号,最低价,指定买家。
    function offerPunkForSaleToAddress(uint punkIndex, uint minSalePriceInWei, address toAddress) {
        if (!allPunksAssigned) throw; // 特权派送结束后才可以生效
        if (punkIndexToAddress[punkIndex] != msg.sender) throw; //调用者必须是punk的主人
        if (punkIndex >= 10000) throw;    //punk编号应小于10000
        // 更新该punk的售卖信息:正在售卖、编号、售卖人、最低售价、指定买家
        punksOfferedForSale[punkIndex] = Offer(true, punkIndex, msg.sender, minSalePriceInWei, toAddress);
        PunkOffered(punkIndex, minSalePriceInWei, toAddress); //公告该信息
    }

    //购买某个正在售卖的punk,参数是punk编号。此函数接受ETH
    function buyPunk(uint punkIndex) payable {
        if (!allPunksAssigned) throw;   // 特权派送结束后才可以生效
        Offer offer = punksOfferedForSale[punkIndex];  // 获取该punk的售卖信息
        if (punkIndex >= 10000) throw;  // punk编号应小于10000
        if (!offer.isForSale) throw;    // punk必须确实处于正在售卖状态
        // 要么该售卖不指定买家,要么指定了买家而且调用者确实是买家
        if (offer.onlySellTo != 0x0 && offer.onlySellTo != msg.sender) throw;
        if (msg.value < offer.minValue) throw;    // 买家出价必须大于或等于卖家标出的价格
        if (offer.seller != punkIndexToAddress[punkIndex]) throw; //再次确认卖方必须是punk的主人

        address seller = offer.seller;

        punkIndexToAddress[punkIndex] = msg.sender; // punk所绑定的地址改为买家地址
        balanceOf[seller]--;// 卖家拥有punk数减一
        balanceOf[msg.sender]++;// 买家拥有punk数加一
        Transfer(seller, msg.sender, 1); // 公告punk转移事件

        punkNoLongerForSale(punkIndex);  // 已经卖出,所以取消售卖
        pendingWithdrawals[seller] += msg.value;  // 将买家出的钱写入卖家的账户

        PunkBought(punkIndex, msg.value, seller, msg.sender); //公告售卖成功事件

       //如果该买家同时是该punk的当前竞标人(最高出价人),并将投标的钱放入他的账户中,并清空竞标信息
        Bid bid = punkBids[punkIndex];
        if (bid.bidder == msg.sender) {
            // Kill bid and refund value
            pendingWithdrawals[msg.sender] += bid.value;  // 返还其竞标资金
            punkBids[punkIndex] = Bid(false, punkIndex, 0x0, 0);// 清空竞标信息
        }
    }

    //用户从账户中取钱,其实就是合约把钱send回用户
    function withdraw() {
        if (!allPunksAssigned) throw;   // 特权派送结束后才能干这事
        uint amount = pendingWithdrawals[msg.sender];// 记下用户账户余额
        // Remember to zero the pending refund before
        // sending to prevent re-entrancy attacks
        pendingWithdrawals[msg.sender] = 0;  // 先清空账户再转钱,防止重入攻击
        msg.sender.transfer(amount);  // 转账啦!账户余额从合约发送到调用者的以太坊地址
    }

    //用户对某个punk开始投标,参数是punk编号,调用时要带钱的。函数接受Ether。
    function enterBidForPunk(uint punkIndex) payable {
        if (punkIndex >= 10000) throw;  //punk编号应小于10000
        if (!allPunksAssigned) throw;       //特权派送结束后才可以
        if (punkIndexToAddress[punkIndex] == 0x0) throw;  //该punk必须是有主的,而不是无主的
        if (punkIndexToAddress[punkIndex] == msg.sender) throw;  //该punk的主人不能参与竞标
        if (msg.value == 0) throw;   //投标价格一定要大于0
        Bid existing = punkBids[punkIndex];   //获取目前的投标信息,其实主要是获取目前的最高竞标价
        if (msg.value <= existing.value) throw;   //出的投标价高于之前的最高价时,该投标才成功
        if (existing.value > 0) {//之前投标的人的投标价会返回到它的临时账户中
            // Refund the failing bid
            pendingWithdrawals[existing.bidder] += existing.value;
        }
        punkBids[punkIndex] = Bid(true, punkIndex, msg.sender, msg.value); //更新该punk的投标信息
        PunkBidEntered(punkIndex, msg.value, msg.sender); //记录有人以最高价竞标这个事件
    }

    //接受目前价格的投标(完成交易),参数:punk编号,最低价
    function acceptBidForPunk(uint punkIndex, uint minPrice) {
        if (punkIndex >= 10000) throw;  // punk编号要小于10000
        if (!allPunksAssigned) throw;    // 特权派送结束后才行
        if (punkIndexToAddress[punkIndex] != msg.sender) throw; // 只有punk主才能接受投标
        address seller = msg.sender;  // 获取punk主地址
        Bid bid = punkBids[punkIndex]; // 获取投标信息
        if (bid.value == 0) throw;  // 如果投标价是0,说明目前压根没人投标,退出
        if (bid.value < minPrice) throw;  // 投标价要大于函数调用者指定的最低格

        punkIndexToAddress[punkIndex] = bid.bidder; // 设定该punk对应的地址为当前投标人地址
        balanceOf[seller]--;  // 卖家punk数减一
        balanceOf[bid.bidder]++;  // 买家punk数加一
        Transfer(seller, bid.bidder, 1); // 公告punk转移事件
        punksOfferedForSale[punkIndex] = Offer(false, punkIndex, bid.bidder, 0, 0x0); // 清空售卖信息
        uint amount = bid.value;  // 获得投标价
        punkBids[punkIndex] = Bid(false, punkIndex, 0x0, 0); // 清空竞标信息
        pendingWithdrawals[seller] += amount;  // 把投标的钱放入卖家的账户
        PunkBought(punkIndex, bid.value, seller, bid.bidder); //公告买punk成功事件
    }

    // 投标人可以自行放弃对某个punk的投标,参数:punk编号
    function withdrawBidForPunk(uint punkIndex) {
        if (punkIndex >= 10000) throw;  // punk编号应小于10000
        if (!allPunksAssigned) throw;       // 特权派送结束后才可以做这事         
        if (punkIndexToAddress[punkIndex] == 0x0) throw;   // 该punk应该是有主的
        if (punkIndexToAddress[punkIndex] == msg.sender) throw;  //该punk的主人不能干这事
        Bid bid = punkBids[punkIndex];     // 获取当前的投标信息
        if (bid.bidder != msg.sender) throw;  // 如果调用者不是当前最高出价人,退出。
        PunkBidWithdrawn(punkIndex, bid.value, msg.sender); // 公告这个放弃投标事件
        uint amount = bid.value;  // 获取当前投标价(也即最高投标价)
        punkBids[punkIndex] = Bid(false, punkIndex, 0x0, 0); // 清空投标信息
        msg.sender.transfer(amount);  // 将投标资金返还给投标者的以太坊地址
    }

}

后注

我最感兴趣的是里面的getPunk接口,如果我当年先知先觉,也调用一下这个接口,我就也是有punk的人了。现在当然已经分配完了,调用这个接口是不会成功的。

我用Ethererum Studio,编译Cryptopunks的代码,一开始总报错,后来在Truffle的config.json中设置Solc的版本为0.4.26,EVM版本为拜占庭,编译就顺利通过了。

我将其部署在Rinkeby网络上,合约地址为:

0x1BFb1B295B23473e2CEBC1BC74339be2325947BB

在这个地方,你可以get一个punk试试,虽然这仅仅是个测试。

有没有Bug?

这个代码有没有问题?

有人对加密朋克代码做了安全审计,还真发现有一个问题。

限有篇幅,本篇就不说了。

关注本号,下篇看看这个bug。

致谢:本文参考了 https://www.cnblogs.com/wanghui-garcia/p/9506390.html

建议阅读:

用人话说说最近大火的NFT

用直观抓住NFT是什么

NFT这么香,到底解决了什么问题

支付宝、腾讯都发了NFT?监管是怎么看的?

像nerd一样把玩NFT

文|卫剑钒

以上是关于规则即代码:人话解读加密朋克智能合约的主要内容,如果未能解决你的问题,请参考以下文章

EOS 智能合约源代码解读 合约开发示例

EOS 智能合约源代码解读 总体说明

智能合约实战 solidity 语法学习 10 [ BNB合约解读 ] 附代码

智能合约实战 solidity 语法学习 10 [ BNB合约解读 ] 附代码

智能合约实战 solidity 语法学习 10 [ BNB合约解读 ] 附代码

EOS 智能合约源代码解读 bios合约