N合约分析

本文深入剖析了一个基于Loot概念的简单NFT合约,展示了如何通过智能合约随机生成独特的冒险者装备属性。合约中的关键函数random、pluck和tokenURI分别负责随机数生成、数字选择和SVG图片创建。通过对合约的分析,揭示了NFT的稀有度可以通过算法预测,可能导致信息不对称的问题。此外,还介绍了用于评估NFT稀有度的工具和流程。
摘要由CSDN通过智能技术生成

N合约分析

什么是LOOT?

在官网,只用了两行简短的英语对其进行介绍:

Loot is randomized adventurer gear generated and stored on chain. Stats, images, and other functionality are intentionally omitted for others to interpret.Feel free to use Loot in any way you want.

Loot是随机生成的冒险者装备,并存储在区块链上。统计数字、图像和其他功能被有意省略,供他人解释。

请自由地以任何方式使用Loot。

通俗一点,Loot 是一种黑色背景,只包含文本的链上 NFT,任何人都可以参与铸造,将会随机获得一组奇幻冒险家装备,当然是以文本的形式,这些装备具有随机分布的稀缺特征。Loot是一个几乎空白的画布,却赋有巨大的吸引力来让人共同创作、建设和传播。

基于此,本文想对一个最简单的loot合约代码以及每个loot发行出来的价值进行分析,为学习loot提供参考。

代码地址:https://github.com/WeLightProject/tai-shang-nft-contracts/blob/feat/basic_n/N.sol

该loot合约发行的NFT是包含0-14的8行数字。下面来分析一下主要的函数功能:

1.random

传进一个string类型的参数,然后对其a bi编码,在对其keccak256哈希运算,最后转成int256返回。

function random(string memory input) internal pure returns (uint256) {
        return uint256(keccak256(abi.encodePacked(input)));
    }

2.pluck

可以看到,获取每行的数字内容,关键是调用pluck函数,需要给他传进三个参数,tokenId, keyPrefix,sourceArray,一二两个参数可以来构造生成随机,这样可以确保每个NFT的8行数字里面没有相同的数字,同时保证生成的8888个NFT的唯一性,sourceArray则是为数字提供数据集,后面会对从数据集里面选出的数字output,下一步进行if匹配条件再进行相应的加工,例如+1,+2等,最后返回出去就是每行得到的最终数字

		function getFirst(uint256 tokenId) public view returns (uint256) {
        return pluck(tokenId, "FIRST", units);
    }
    function getSecond(uint256 tokenId) public view returns (uint256) {
        return pluck(tokenId, "SECOND", units);
    }
		.......
		function pluck(
        uint256 tokenId,
        string memory keyPrefix,
        uint8[] memory sourceArray
    ) internal view returns (uint256) {
       //传进tokenId和例如"FIRST"这样的字符,然后返回一个随机数
        uint256 rand = random(string(abi.encodePacked(keyPrefix, toString(tokenId))));
        //对rand % sourceArray.length取余,获取sourceArray里面的一个值
        uint256 output = sourceArray[rand % sourceArray.length];
        //对随机数进行取余
        uint256 luck = rand % 21;
        if (luck > 14) {
        		//output+1或output+2
            output += suffixes[rand % suffixes.length];
        }
        if (luck >= 19) {
            if (luck == 19) {
            		//(output*1或output*0)再+1或+2
                output = (output * multipliers[rand % multipliers.length]) + suffixes[rand % suffixes.length];
            } else {
            		//output*1或output*0
                output = (output * multipliers[rand % multipliers.length]);
            }
        }
        return output;
    }

3.tokenURI

此函数的作用是返回一个将8个数字以一种黑底白字的svg格式的图片,主要是通过拼接字符串的方式

function tokenURI(uint256 tokenId) public view override returns (string memory) {
        string[17] memory parts;
        //svg图片格式的前缀
        parts[
        0
        ] = '<svg xmlns="http://www.w3.org/2000/svg" preserveAspectRatio="xMinYMin meet" viewBox="0 0 350 350"><style>.base { fill: white; font-family: serif; font-size: 14px; }</style><rect width="100%" height="100%" fill="black" /><text x="10" y="20" class="base">';
        //数字1
        parts[1] = toString(getFirst(tokenId));
        parts[2] = '</text><text x="10" y="40" class="base">';
				//数字2
        parts[3] = toString(getSecond(tokenId));

        parts[4] = '</text><text x="10" y="60" class="base">';
				//数字3
        parts[5] = toString(getThird(tokenId));

        parts[6] = '</text><text x="10" y="80" class="base">';
				//数字4
        parts[7] = toString(getFourth(tokenId));

        parts[8] = '</text><text x="10" y="100" class="base">';
				//数字5
        parts[9] = toString(getFifth(tokenId));

        parts[10] = '</text><text x="10" y="120" class="base">';
				//数字6
        parts[11] = toString(getSixth(tokenId));
			
        parts[12] = '</text><text x="10" y="140" class="base">';
				//数字7
        parts[13] = toString(getSeventh(tokenId));

        parts[14] = '</text><text x="10" y="160" class="base">';
				//数字8
        parts[15] = toString(getEight(tokenId));

        parts[16] = "</text></svg>";
				//接下来就是对上面的各部分进行拼接,9个一组
        string memory output = string(
            abi.encodePacked(parts[0], parts[1], parts[2], parts[3], parts[4], parts[5], parts[6], parts[7], parts[8])
        );
        output = string(
            abi.encodePacked(
                output,
                parts[9],
                parts[10],
                parts[11],
                parts[12],
                parts[13],
                parts[14],
                parts[15],
                parts[16]
            )
        );
        //拼接完毕,使用Base64编码库函数进行整体编码,方便传输
        string memory json = Base64.encode(
            bytes(
                string(
                    abi.encodePacked(
                        '{"name": "N #',
                        toString(tokenId),
                        '", "description": "N is just numbers.", "image": "data:image/svg+xml;base64,',
                        //这里对于图片的内容单独进行了一次Base64编码
                        Base64.encode(bytes(output)),
                        '"}'
                    )
                )
            )
        );
        output = string(abi.encodePacked("data:application/json;base64,", json));
        return output;
    }
 

这里我们可以使用remix传入参数1调用一下看一下具体返回格式究竟是什么样子:

data:application/json;base64,eyJuYW1lIjogIk4gIzEiLCAiZGVzY3JpcHRpb24iOiAiTiBpcyBqdXN0IG51bWJlcnMuIiwgImltYWdlIjogImRhdGE6aW1hZ2Uvc3ZnK3htbDtiYXNlNjQsUEhOMlp5QjRiV3h1Y3owaWFIUjBjRG92TDNkM2R5NTNNeTV2Y21jdk1qQXdNQzl6ZG1jaUlIQnlaWE5sY25abFFYTndaV04wVW1GMGFXODlJbmhOYVc1WlRXbHVJRzFsWlhRaUlIWnBaWGRDYjNnOUlqQWdNQ0F6TlRBZ016VXdJajQ4YzNSNWJHVStMbUpoYzJVZ2V5Qm1hV3hzT2lCM2FHbDBaVHNnWm05dWRDMW1ZVzFwYkhrNklITmxjbWxtT3lCbWIyNTBMWE5wZW1VNklERTBjSGc3SUgwOEwzTjBlV3hsUGp4eVpXTjBJSGRwWkhSb1BTSXhNREFsSWlCb1pXbG5hSFE5SWpFd01DVWlJR1pwYkd3OUltSnNZV05ySWlBdlBqeDBaWGgwSUhnOUlqRXdJaUI1UFNJeU1DSWdZMnhoYzNNOUltSmhjMlVpUGpVOEwzUmxlSFErUEhSbGVIUWdlRDBpTVRBaUlIazlJalF3SWlCamJHRnpjejBpWW1GelpTSStORHd2ZEdWNGRENDhkR1Y0ZENCNFBTSXhNQ0lnZVQwaU5qQWlJR05zWVhOelBTSmlZWE5sSWo0M1BDOTBaWGgwUGp4MFpYaDBJSGc5SWpFd0lpQjVQU0k0TUNJZ1kyeGhjM005SW1KaGMyVWlQak04TDNSbGVIUStQSFJsZUhRZ2VEMGlNVEFpSUhrOUlqRXdNQ0lnWTJ4aGMzTTlJbUpoYzJVaVBqazhMM1JsZUhRK1BIUmxlSFFnZUQwaU1UQWlJSGs5SWpFeU1DSWdZMnhoYzNNOUltSmhjMlVpUGpnOEwzUmxlSFErUEhSbGVIUWdlRDBpTVRBaUlIazlJakUwTUNJZ1kyeGhjM005SW1KaGMyVWlQalk4TDNSbGVIUStQSFJsZUhRZ2VEMGlNVEFpSUhrOUlqRTJNQ0lnWTJ4aGMzTTlJbUpoYzJVaVBqTThMM1JsZUhRK1BDOXpkbWMrIn0=

我们通过在线网站进行解码:https://tool.ip138.com/base64

将data:application/json;base64,之后的内容复制进去,得到一次解码后的内容:

{"name": "N #1", "description": "N is just numbers.", "image": "data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHByZXNlcnZlQXNwZWN0UmF0aW89InhNaW5ZTWluIG1lZXQiIHZpZXdCb3g9IjAgMCAzNTAgMzUwIj48c3R5bGU+LmJhc2UgeyBmaWxsOiB3aGl0ZTsgZm9udC1mYW1pbHk6IHNlcmlmOyBmb250LXNpemU6IDE0cHg7IH08L3N0eWxlPjxyZWN0IHdpZHRoPSIxMDAlIiBoZWlnaHQ9IjEwMCUiIGZpbGw9ImJsYWNrIiAvPjx0ZXh0IHg9IjEwIiB5PSIyMCIgY2xhc3M9ImJhc2UiPjU8L3RleHQ+PHRleHQgeD0iMTAiIHk9IjQwIiBjbGFzcz0iYmFzZSI+NDwvdGV4dD48dGV4dCB4PSIxMCIgeT0iNjAiIGNsYXNzPSJiYXNlIj43PC90ZXh0Pjx0ZXh0IHg9IjEwIiB5PSI4MCIgY2xhc3M9ImJhc2UiPjM8L3RleHQ+PHRleHQgeD0iMTAiIHk9IjEwMCIgY2xhc3M9ImJhc2UiPjk8L3RleHQ+PHRleHQgeD0iMTAiIHk9IjEyMCIgY2xhc3M9ImJhc2UiPjg8L3RleHQ+PHRleHQgeD0iMTAiIHk9IjE0MCIgY2xhc3M9ImJhc2UiPjY8L3RleHQ+PHRleHQgeD0iMTAiIHk9IjE2MCIgY2xhc3M9ImJhc2UiPjM8L3RleHQ+PC9zdmc+"}

此时我们就可以看到真的内容,还有base64格式的图片,让我接着再一次解析data:image/svg+xml;base64,之后的内容:

<svg xmlns="http://www.w3.org/2000/svg" preserveAspectRatio="xMinYMin meet" viewBox="0 0 350 350"><style>.base { fill: white; font-family: serif; font-size: 14px; }</style><rect width="100%" height="100%" fill="black" /><text x="10" y="20" class="base">5</text><text x="10" y="40" class="base">4</text><text x="10" y="60" class="base">7</text><text x="10" y="80" class="base">3</text><text x="10" y="100" class="base">9</text><text x="10" y="120" class="base">8</text><text x="10" y="140" class="base">6</text><text x="10" y="160" class="base">3</text></svg>

最终我们得到了,最后s v g图片的代码,展示图如下:

image-20211114162423096

4.claim

发行一个NFT,总量不超过8889个

  function claim(uint256 tokenId) public nonReentrant {
        require(tokenId > 0 && tokenId < 8889, "Token ID invalid");
        _safeMint(_msgSender(), tokenId);
    }

至于toString,还有Base64这里就不过多介绍了。

分析完合约,我们来对此合约生成的每个NFT进行分析

分析库代码地址:https://github.com/Anish-Agnihotri/dhof-loot

通过上面这个工具我们可以算出每个NFT的稀有度分数以及他的排名,然后通过一个脚本将json内容写进c s v文件中,如下部分截图:

image-20211114140333916

所以说NFT的内容是根据其token ID确定的——这意味着在最初的NFT发行之前,只要通过阅读智能合约,任何人都可以轻而易举地提前计算出每个NFT稀有度以及排名。由于 claim() 函数将代币 ID 作为一个参数,所以很容易从收藏品中挑选出最稀有的物品,并赶在其他人之前立即将其铸造完成。因为信息的不对称,对于每个玩家来说是极为不公平的,也与loot项目的初衷背道而驰,希望未来可以解决这个问题,让每个NFT的珍稀度变得真正随机。

总结:Loot是充满想象力的,它像一个给了你画笔的画布,赋有巨大的吸引力来让人共同创作、建设和传播,但是由于每个NFT稀缺性的有规可循使得信息不对称,很容易破坏NFT的公平竞争环境。

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值