# 从合约考量项目价值-兔子

By [sen](https://paragraph.com/@sen-3) · 2022-06-08

---

我比较喜欢的项目[兔子](https://www.eurekarabbit.io/)，最近开盒了，玩法还是挺不错的，可惜中奖绝缘体的我并没有第一时间获得带有塔罗牌属性的兔子。今天抽时间从技术角度浅分析下项目玩法如何实现的。

玩法介绍
----

每只兔子除了拥有基础的部件属性外（body、ears等），还额外拥有一个“隐藏属性”（因为有些开了，有些没开，所以我们这里暂且称之为隐藏属性），根据兔子项目白皮书描述，隐藏属性有78只最稀有的NFT在开盒时就会开启，其余的也可以通过一些特殊途径开启（具体方式没有细说，估计后续会公布）。里面有一点比较吸引我：隐藏属性每天可变，而且是表现在图片上，这点我开始没想明白，今天就从合约代码看看能否找到一些线索。

合约代码-盲盒
-------

项目方采用盲盒（ERC-1155）与NFT（ERC-721）分开的方式实现，可能是为了尽可能减少发售时用户的铸造费用，因为1155对于批量铸造的支持非常友好，批量铸造10个和铸造1个气费相同。

盲盒中代码没有特别要说的，这里看下开盒代码：

![开盒方法](https://storage.googleapis.com/papyrus_images/ae4f9672a8c65baef3d20d2417848bcf174c47d216702963fbc0fb0988d9474d.png)

开盒方法

只有这一个单个开盒的方法，先销毁1155盲盒，再调用NFT的mintTransfer方法下发NFT。

合约代码-NFT
--------

### 开盒开启隐藏属性

通过这段代码，可以看到3261 - 3184 = 77，由于是>= 所以刚好有78只NFT在开盒的时候就会解锁luckyCard属性

![NFT合约-开盒](https://storage.googleapis.com/papyrus_images/e9d25d7667979d7a55ce02ca674f029ae357fb5851625c2fe52f7c533dd9d2f7.png)

NFT合约-开盒

### 开启隐藏属性扩展口

![主动开启隐藏属性入口](https://storage.googleapis.com/papyrus_images/08649a70c7ee5958f12336b4da5763b247287a8a3f21f82ff525b8cdb4a30ec0.png)

主动开启隐藏属性入口

通过代码或注释已经可以比较清楚的看到项目方多方面布局，不仅可以通过做各种各样的任务来自动解锁 unlock()，也可以自己获取授权来解锁 unlockBySelf() 。

有趣的是，居然还有能开启隐藏属性的地方：

![](https://storage.googleapis.com/papyrus_images/7ac0710d61bdce5f4d9776acefa62d39764aa59d6bd3a2dd5f13154d6184a7bd.png)

不过这里有点疑惑，为什么仅仅发了个event，并没有真的修改luckyCardStatus？于是有道了下这句注释：

![](https://storage.googleapis.com/papyrus_images/5c9948089cbc924f260da91bdfc813c4c070838db84deb341ee6d6ac56668469.png)

金额合理这个方法会在transfer时调用，也就是在转手时会触发这段逻辑，也就是在交易所上以高于特定价格的交易来触发，估计是借助中心化服务完成的金额校验。

再看白皮书，并没有这个关于特定价格说明，看项目方后续的动作吧，也算是一个开启隐藏属性的方式。

### 隐藏属性每日可变

我刚看到这个的时候就在思考怎么实现？如果是我的话，可能会采用拼接svg的方式，但拼接svg的方式需要把图片部件数据全部代码化并写到合约上，像素风还行，这种非像素类型的作品图片较大且难压缩，那在这个合约里面：

![tokenURI()](https://storage.googleapis.com/papyrus_images/7779c771b88fb0e5b051ae4b4dd62134b2690a195c589bf757753c76e9c27bf1.png)

tokenURI()

好吧，还是比较简单粗暴的，根据当天luckyCard来返回对应的图片。不过这种方案也是很大缺点，即整个系列的图要准备78套，为了实现这个功能也是挺费劲的了。

其中最核心的方法是luckyCardValue()，这个方法为什么能保持一天内返回值不变？撸代码：

![luckyCardValue()](https://storage.googleapis.com/papyrus_images/8eba1a3a1fbddc5a50235c54c1deec1b9e0cde2a0fa891e1a308fdff0c4e1aef.png)

luckyCardValue()

这个方法我看了半天，可以看到已开启隐藏属性的NFT获取今天的luckyCard时，是随机的，取（1970.1.1 00:00:00 到现在的天数）、（盐evenSalt/ollSalt）、（tokenId）的hash值，其中天数在一天之内都不会变，tokenId一直都不会变，中间这个盐在每次交易时都会重新计算：

![盐](https://storage.googleapis.com/papyrus_images/31485dac1813cc1c89375b67838e6edb9028badfdc7272c8fe10fae14571c1df.png)

盐

这里设计比较有意思，为了保证修改盐不会改变当天的luckyCard，他这里分了“奇数盐”和“偶数盐”，当天是奇数天修改偶数天的盐，当天是偶数天修改奇数天的盐。

同时也在某种程度上保证了第二天的属性不可预测。

扩展
--

支持升级，但不清楚具体是以什么方式展开，留了一个悬念。

![预留升级](https://storage.googleapis.com/papyrus_images/4f3408f16d6bbfd2ff9f28500920a7970031941b84aea8ffdb1793aa8d1e6503.png)

预留升级

总结
--

关于随机数这块，应该是考虑到用户体验，他这里并没有选择VRF，但也没有太大问题，因为这种实现方式也已经规避了预测属性值带来的风险。

最后广告下，我会持续分析NFT合约代码，从合约出发考量项目价值，可以关注[twitter](https://twitter.com/wangsen_lcc)。

---

*Originally published on [sen](https://paragraph.com/@sen-3/12jTHGEp4T1e1GrMlKwo)*
