资讯详情

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

当我写论文时,我想学术期刊上发表的大多数论文都失败了(很多都很神秘,甚至没有解释清楚符号,显然不想让人们理解)。如果有一个编译器,估计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是什么》。

直观感受合同

加密朋克有1万个朋克(punk),每个punk有一个编号(从0到999),punk在本文中,拥有者(主人)被称为punk主”。

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

例如,一个接口是查询punk给它一个主地址punk编号,它会给你一个以太坊地址,告诉你punk谁是主,比如0号punk主地址为:

0xE08c32737c021C7d05d116b00a68a02F2d144AC0

这是怎么发现的?

在etherscan.io搜索加密朋克的合同地址:

0xb47e3cd837dDF8e4c57F05d70Ab865de6e193BBB

然后在Contract -> Read contract中,选择punkIndexToAddress填入0,单击此接口Query按钮可获得0号punk主的地址。

?880876cb8abdcd8a9fceae7430fd171c.png

加密朋克的具体规则

加密朋克的规则都反映在它的代码中,没有其他文本。

我从代码中抽象出以下管理规则,只是为了方便读者理解代码。


加密朋克管理规则

为规范加密朋克的发行、销售、行、销售、购买、竞价、转让等活动。

第一条 关于加密朋克

本加密有1万个朋克,每个都叫一个朋克(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成功销售事件。

第十条 附则

该规则由代码最终解释。

本规则在合同成功部署后生效。

规则全文结束。


这些都是我用自然语言写的,显然缺乏细节,显然不够具体和清晰。

最后还是要看代码。

合约的设计

其实编码思路很简单,基本上很容易写完主要数据的设计。

建立几个映射表,记录主要数据。

例如,需要三张表来记录punk编号与地址的对应关系,记录每个地址下有多少punk,记录每个用户账户的资金。

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

1.1punkIndexToAddress表,记录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

文|卫剑钒

标签: 二极管b360b

锐单商城拥有海量元器件数据手册IC替代型号,打造 电子元器件IC百科大全!

锐单商城 - 一站式电子元器件采购平台