
WTF Solidity 合约安全: S08. 绕过合约检查
我最近在重新学solidity,巩固一下细节,也写一个“WTF Solidity极简入门”,供小白们使用(编程大佬可以另找教程),每周更新1-3讲。 推特:@0xAA_Science|@WTFAcademy_ 社区:Discord|微信群|官网 wtf.academy 所有代码和教程开源在github: github.com/AmazingAng/WTFSolidity这一讲,我们将介绍绕过合约长度检查,并介绍预防的方法。绕过合约检查很多 freemint 的项目为了限制科学家(程序员)会用到 isContract() 方法,希望将调用者 msg.sender 限制为外部账户(EOA),而非合约。这个函数利用 extcodesize 获取该地址所存储的 bytecode 长度(runtime),若大于0,则判断为合约,否则就是EOA(用户)。 // 利用 extcodesize 检查是否为合约 function isContract(address account) public view returns (bool) { // extcodesize > 0 的地址一定是合约...

WTF Solidity 合约安全: S09. 拒绝服务
我最近在重新学solidity,巩固一下细节,也写一个“WTF Solidity极简入门”,供小白们使用(编程大佬可以另找教程),每周更新1-3讲。 推特:@0xAA_Science|@WTFAcademy_ 社区:Discord|微信群|官网 wtf.academy 所有代码和教程开源在github: github.com/AmazingAng/WTFSolidity这一讲,我们将介绍智能合约的拒绝服务(Denial of Service, DoS)漏洞,并介绍预防的方法。NFT项目 Akutar 曾因为 DoS 漏洞损失 11,539 ETH,当时价值 3400 万美元。DoS在 Web2 中,拒绝服务攻击(DoS)是指通过向服务器发送大量垃圾信息或干扰信息的方式,导致服务器无法向正常用户提供服务的现象。而在 Web3,它指的是利用漏洞使得智能合约无法正常提供服务。 在2022年4月,一个很火的 NFT 项目名为 Akutar,他们使用荷兰拍卖进行公开发行,筹集了 11,539.5 ETH,非常成功。之前持有他们社区Pass的参与者会得到 0.5 ETH的退款,但是他们处理...

WTF Solidity 合约安全 S06. 签名重放
我最近在重新学solidity,巩固一下细节,也写一个“WTF Solidity极简入门”,供小白们使用(编程大佬可以另找教程),每周更新1-3讲。 推特:@0xAA_Science|@WTFAcademy_ 社区:Discord|微信群|官网 wtf.academy 所有代码和教程开源在github: github.com/AmazingAng/WTFSolidity这一讲,我们将介绍智能合约的签名重放(Signature Replay)攻击和预防方法,它曾间接导致了著名做市商 Wintermute 被盗2000万枚 $OP。签名重放上学的时候,老师经常会让家长签字,有时候家长很忙,我就会很“贴心”照着以前的签字抄一遍。某种意义上来说,这就是签名重放。 在区块链中,数字签名可以用于识别数据签名者和验证数据完整性。发送交易时,用户使用私钥签名交易,使得其他人可以验证交易是由相应账户发出的。智能合约也能利用 ECDSA 算法验证用户将在链下创建的签名,然后执行铸造或转账等逻辑。更多关于数字签名的介绍请见WTF Solidity第37讲:数字签名。 数字签名一般有两种常见的重放攻击...
WTF Academy: wtf.academy



WTF Solidity 合约安全: S08. 绕过合约检查
我最近在重新学solidity,巩固一下细节,也写一个“WTF Solidity极简入门”,供小白们使用(编程大佬可以另找教程),每周更新1-3讲。 推特:@0xAA_Science|@WTFAcademy_ 社区:Discord|微信群|官网 wtf.academy 所有代码和教程开源在github: github.com/AmazingAng/WTFSolidity这一讲,我们将介绍绕过合约长度检查,并介绍预防的方法。绕过合约检查很多 freemint 的项目为了限制科学家(程序员)会用到 isContract() 方法,希望将调用者 msg.sender 限制为外部账户(EOA),而非合约。这个函数利用 extcodesize 获取该地址所存储的 bytecode 长度(runtime),若大于0,则判断为合约,否则就是EOA(用户)。 // 利用 extcodesize 检查是否为合约 function isContract(address account) public view returns (bool) { // extcodesize > 0 的地址一定是合约...

WTF Solidity 合约安全: S09. 拒绝服务
我最近在重新学solidity,巩固一下细节,也写一个“WTF Solidity极简入门”,供小白们使用(编程大佬可以另找教程),每周更新1-3讲。 推特:@0xAA_Science|@WTFAcademy_ 社区:Discord|微信群|官网 wtf.academy 所有代码和教程开源在github: github.com/AmazingAng/WTFSolidity这一讲,我们将介绍智能合约的拒绝服务(Denial of Service, DoS)漏洞,并介绍预防的方法。NFT项目 Akutar 曾因为 DoS 漏洞损失 11,539 ETH,当时价值 3400 万美元。DoS在 Web2 中,拒绝服务攻击(DoS)是指通过向服务器发送大量垃圾信息或干扰信息的方式,导致服务器无法向正常用户提供服务的现象。而在 Web3,它指的是利用漏洞使得智能合约无法正常提供服务。 在2022年4月,一个很火的 NFT 项目名为 Akutar,他们使用荷兰拍卖进行公开发行,筹集了 11,539.5 ETH,非常成功。之前持有他们社区Pass的参与者会得到 0.5 ETH的退款,但是他们处理...

WTF Solidity 合约安全 S06. 签名重放
我最近在重新学solidity,巩固一下细节,也写一个“WTF Solidity极简入门”,供小白们使用(编程大佬可以另找教程),每周更新1-3讲。 推特:@0xAA_Science|@WTFAcademy_ 社区:Discord|微信群|官网 wtf.academy 所有代码和教程开源在github: github.com/AmazingAng/WTFSolidity这一讲,我们将介绍智能合约的签名重放(Signature Replay)攻击和预防方法,它曾间接导致了著名做市商 Wintermute 被盗2000万枚 $OP。签名重放上学的时候,老师经常会让家长签字,有时候家长很忙,我就会很“贴心”照着以前的签字抄一遍。某种意义上来说,这就是签名重放。 在区块链中,数字签名可以用于识别数据签名者和验证数据完整性。发送交易时,用户使用私钥签名交易,使得其他人可以验证交易是由相应账户发出的。智能合约也能利用 ECDSA 算法验证用户将在链下创建的签名,然后执行铸造或转账等逻辑。更多关于数字签名的介绍请见WTF Solidity第37讲:数字签名。 数字签名一般有两种常见的重放攻击...
WTF Academy: wtf.academy
Share Dialog
Share Dialog

Subscribe to 0xAA

Subscribe to 0xAA
>100 subscribers
>100 subscribers
我最近在重新学solidity,巩固一下细节,也写一个“Solidity极简入门”,供小白们使用(编程大佬可以另找教程),每周更新1-3讲。
欢迎关注我的推特:@0xAA_Science
WTF技术社群discord,内有加微信群方法:链接
所有代码和教程开源在github(1024个star发课程认证,2048个star发社群NFT): github.com/AmazingAng/WTFSolidity

Loot是以太坊链上的实验性NFT项目,发行于21年8月,共有8000个,前7777个免费mint,后233个项目方预留,最高时地板价突破20 ETH,目前稳定在1 ETH左右。与充斥市场的图片NFT不同,Loot是文字类NFT,所有元数据都保存在链上,保证了去中心化,没人能篡改。
它的内容比较简单,就是用文字描述了玩家的一套装备,包括武器、头盔、戒指共8类物品。“金指环”,“双子之剑”,“硬皮手套”,复古的名字让我回忆起小时候玩的《暗黑破坏神》。
Loot是一个开放的生态,项目方希望有更多团队能加入到Loot元宇宙的建设中。这一讲,我将介绍Loot是怎么用智能合约生成的文字,又是怎么把它放上链的。
Loot代码在etherscan上开源,地址:链接
主合约Loot从1291行开始,继承了ERC721Enumerable,ReentrancyGuard和Ownable,这些合约都是oppenzepplin标准库中的。
contract Loot is ERC721Enumerable, ReentrancyGuard, Ownable {
Loot在状态变量中定义了11个String数组用于生成装备描述的基本词组:
其中8个是装备列表,用于描述不同部位的装备,包括weapon, chestArmor, headArmor, waistArmor, footArmor, handArmor, necklaces, rings。拿weapon举例,它的String数组包含战锤, 木棒等内容:
string[] private weapons = [
"Warhammer",
"Quarterstaff",
"Maul",
"Mace",
"Club",
"Katana",
"Falchion",
"Scimitar",
"Long Sword",
"Short Sword",
"Ghost Wand",
"Grave Wand",
"Bone Wand",
"Wand",
"Grimoire",
"Chronicle",
"Tome",
"Book"
];
剩余3个是修饰装备的前缀和后缀,包括suffixes, namePrefixes和nameSuffixes。前缀后缀可以让装备看起来更牛逼,例如:龙的完美之冠,鹰吼-华丽的巨人胸甲。拿装备后缀举例,suffixes包括:
string[] private suffixes = [
"of Power",
"of Giants",
"of Titans",
"of Skill",
"of Perfection",
"of Brilliance",
"of Enlightenment",
"of Protection",
"of Anger",
"of Rage",
"of Fury",
"of Vitriol",
"of the Fox",
"of Detection",
"of Reflection",
"of the Twins"
];
装备列表和修饰词随机结合,就能生成出Loot的文本NFT。
为了区分装备的稀有度,Loot利用链上伪随机数生成函数random()为装备描述文本提供随机性:
function random(string memory input) internal pure returns (uint256) {
return uint256(keccak256(abi.encodePacked(input)));
}
random()函数参数为input字符串,它计算input的哈希,再转换为uint256,将不同的input均匀的映射到不同的数字上。之后将得到数字映射到稀有度,就可以了,Loot定义了pluck()函数来做这一点。
function pluck(uint256 tokenId, string memory keyPrefix, string[] memory sourceArray) internal view returns (string memory) {
uint256 rand = random(string(abi.encodePacked(keyPrefix, toString(tokenId))));
string memory output = sourceArray[rand % sourceArray.length];
uint256 greatness = rand % 21;
if (greatness > 14) {
output = string(abi.encodePacked(output, " ", suffixes[rand % suffixes.length]));
}
if (greatness >= 19) {
string[2] memory name;
name[0] = namePrefixes[rand % namePrefixes.length];
name[1] = nameSuffixes[rand % nameSuffixes.length];
if (greatness == 19) {
output = string(abi.encodePacked('"', name[0], ' ', name[1], '" ', output));
} else {
output = string(abi.encodePacked('"', name[0], ' ', name[1], '" ', output, " +1"));
}
}
return output;
}
pluck()函数的作用就是在给定tokenId, keyPrefix (装备部位)和sourceArray (装备列表),来生成特定部位装备的描述。一个装备有33.3%的概率拥有后缀,其中有9.5%的概率拥有特殊名称。因此,Loot中每件装备有66.7%为普通,23.8%为稀有,9.5%为史诗。
Loot包含8个get()函数来获取8个部位的装备,拿getWeapon()举例,这个函数获得武器描述:它调用了pluck函数,装备部位为"WEAPON",装备列表为状态变量weapons。
function getWeapon(uint256 tokenId) public view returns (string memory) {
return pluck(tokenId, "WEAPON", weapons);
}
由于keyPrefix和sourceArray都是复用的,因此Loot的装备描述 /稀有度完全由tokenId决定:给定tokenId,总会得到同一组装备。因此,Loot没有"保存"所有装备描述。每次用户查询元数据的时候,合约会生成一份装备描述。这个方法非常创新非常聪明,显著减少了存储占用,让元数据上链成为可能。
我们看一下它的tokenURI()函数:
function tokenURI(uint256 tokenId) override public view returns (string memory) {
string[17] memory parts;
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">';
parts[1] = getWeapon(tokenId);
parts[2] = '</text><text x="10" y="40" class="base">';
parts[3] = getChest(tokenId);
parts[4] = '</text><text x="10" y="60" class="base">';
parts[5] = getHead(tokenId);
parts[6] = '</text><text x="10" y="80" class="base">';
parts[7] = getWaist(tokenId);
parts[8] = '</text><text x="10" y="100" class="base">';
parts[9] = getFoot(tokenId);
parts[10] = '</text><text x="10" y="120" class="base">';
parts[11] = getHand(tokenId);
parts[12] = '</text><text x="10" y="140" class="base">';
parts[13] = getNeck(tokenId);
parts[14] = '</text><text x="10" y="160" class="base">';
parts[15] = getRing(tokenId);
parts[16] = '</text></svg>';
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]));
string memory json = Base64.encode(bytes(string(abi.encodePacked('{"name": "Bag #', toString(tokenId), '", "description": "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.", "image": "data:image/svg+xml;base64,', Base64.encode(bytes(output)), '"}'))));
output = string(abi.encodePacked('data:application/json;base64,', json));
return output;
}
一般pfp NFT的tokenURI()函数是直接返回一个带有元数据json的网址;而Loot的则是直接返回一个json。它定义了parts变量,然后通过8个get()函数拼接出含有装备描述的svg文件,作为元数据的image,方便展示。最后,它把name,description和image一起打包成一个Base64编码的json,作为tokenURI()查询的返回值。
下面是一个tokenURI()的返回值例子:
data:application/json;base64,eyJuYW1lIjogIkJhZyAjNSIsICJkZXNjcmlwdGlvbiI6ICJMb290IGlzIHJhbmRvbWl6ZWQgYWR2ZW50dXJlciBnZWFyIGdlbmVyYXRlZCBhbmQgc3RvcmVkIG9uIGNoYWluLiBTdGF0cywgaW1hZ2VzLCBhbmQgb3RoZXIgZnVuY3Rpb25hbGl0eSBhcmUgaW50ZW50aW9uYWxseSBvbWl0dGVkIGZvciBvdGhlcnMgdG8gaW50ZXJwcmV0LiBGZWVsIGZyZWUgdG8gdXNlIExvb3QgaW4gYW55IHdheSB5b3Ugd2FudC4iLCAiaW1hZ2UiOiAiZGF0YTppbWFnZS9zdmcreG1sO2Jhc2U2NCxQSE4yWnlCNGJXeHVjejBpYUhSMGNEb3ZMM2QzZHk1M015NXZjbWN2TWpBd01DOXpkbWNpSUhCeVpYTmxjblpsUVhOd1pXTjBVbUYwYVc4OUluaE5hVzVaVFdsdUlHMWxaWFFpSUhacFpYZENiM2c5SWpBZ01DQXpOVEFnTXpVd0lqNDhjM1I1YkdVK0xtSmhjMlVnZXlCbWFXeHNPaUIzYUdsMFpUc2dabTl1ZEMxbVlXMXBiSGs2SUhObGNtbG1PeUJtYjI1MExYTnBlbVU2SURFMGNIZzdJSDA4TDNOMGVXeGxQanh5WldOMElIZHBaSFJvUFNJeE1EQWxJaUJvWldsbmFIUTlJakV3TUNVaUlHWnBiR3c5SW1Kc1lXTnJJaUF2UGp4MFpYaDBJSGc5SWpFd0lpQjVQU0l5TUNJZ1kyeGhjM005SW1KaGMyVWlQazFoZFd3Z2IyWWdVbVZtYkdWamRHbHZiand2ZEdWNGRENDhkR1Y0ZENCNFBTSXhNQ0lnZVQwaU5EQWlJR05zWVhOelBTSmlZWE5sSWo1UWJHRjBaU0JOWVdsc1BDOTBaWGgwUGp4MFpYaDBJSGc5SWpFd0lpQjVQU0kyTUNJZ1kyeGhjM005SW1KaGMyVWlQa1J5WVdkdmJpZHpJRU55YjNkdUlHOW1JRkJsY21abFkzUnBiMjQ4TDNSbGVIUStQSFJsZUhRZ2VEMGlNVEFpSUhrOUlqZ3dJaUJqYkdGemN6MGlZbUZ6WlNJK1UyRnphRHd2ZEdWNGRENDhkR1Y0ZENCNFBTSXhNQ0lnZVQwaU1UQXdJaUJqYkdGemN6MGlZbUZ6WlNJK1NHOXNlU0JIY21WaGRtVnpQQzkwWlhoMFBqeDBaWGgwSUhnOUlqRXdJaUI1UFNJeE1qQWlJR05zWVhOelBTSmlZWE5sSWo1SVlYSmtJRXhsWVhSb1pYSWdSMnh2ZG1WelBDOTBaWGgwUGp4MFpYaDBJSGc5SWpFd0lpQjVQU0l4TkRBaUlHTnNZWE56UFNKaVlYTmxJajVRWlc1a1lXNTBQQzkwWlhoMFBqeDBaWGgwSUhnOUlqRXdJaUI1UFNJeE5qQWlJR05zWVhOelBTSmlZWE5sSWo1VWFYUmhibWwxYlNCU2FXNW5QQzkwWlhoMFBqd3ZjM1puUGc9PSJ9
把它复制到浏览器打开,可以直接获取Loot的元数据,挺神奇的:

下面是Loot生成的文字描述svg图片的例子
data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHByZXNlcnZlQXNwZWN0UmF0aW89InhNaW5ZTWluIG1lZXQiIHZpZXdCb3g9IjAgMCAzNTAgMzUwIj48c3R5bGU+LmJhc2UgeyBmaWxsOiB3aGl0ZTsgZm9udC1mYW1pbHk6IHNlcmlmOyBmb250LXNpemU6IDE0cHg7IH08L3N0eWxlPjxyZWN0IHdpZHRoPSIxMDAlIiBoZWlnaHQ9IjEwMCUiIGZpbGw9ImJsYWNrIiAvPjx0ZXh0IHg9IjEwIiB5PSIyMCIgY2xhc3M9ImJhc2UiPk1hdWwgb2YgUmVmbGVjdGlvbjwvdGV4dD48dGV4dCB4PSIxMCIgeT0iNDAiIGNsYXNzPSJiYXNlIj5QbGF0ZSBNYWlsPC90ZXh0Pjx0ZXh0IHg9IjEwIiB5PSI2MCIgY2xhc3M9ImJhc2UiPkRyYWdvbidzIENyb3duIG9mIFBlcmZlY3Rpb248L3RleHQ+PHRleHQgeD0iMTAiIHk9IjgwIiBjbGFzcz0iYmFzZSI+U2FzaDwvdGV4dD48dGV4dCB4PSIxMCIgeT0iMTAwIiBjbGFzcz0iYmFzZSI+SG9seSBHcmVhdmVzPC90ZXh0Pjx0ZXh0IHg9IjEwIiB5PSIxMjAiIGNsYXNzPSJiYXNlIj5IYXJkIExlYXRoZXIgR2xvdmVzPC90ZXh0Pjx0ZXh0IHg9IjEwIiB5PSIxNDAiIGNsYXNzPSJiYXNlIj5QZW5kYW50PC90ZXh0Pjx0ZXh0IHg9IjEwIiB5PSIxNjAiIGNsYXNzPSJiYXNlIj5UaXRhbml1bSBSaW5nPC90ZXh0Pjwvc3ZnPg==
把它复制到浏览器打开,得到下面的图片:

由于tokenId对应的稀有度在mint前已经决定,黑客可以写一个合约来mint稀有的NFT。
具体方法:计算每个tokenURI对应在pluck()函数中greatness,当greatness%21 >= 19,装备必是史诗。优先加高gas来mint这类稀有的Loot NFT。
Loot是我知道的第一个将元数据全部上链的文字NFT项目,非常有开创性和可拓展性。它并不是把元数据直接存到链上(太费链上存储空间),而是每次都通过智能合约重新生成一份元数据返回。如果大家想fork这个项目,需要认真看下random(), pluck()和tokenURI()这三个函数。
我最近在重新学solidity,巩固一下细节,也写一个“Solidity极简入门”,供小白们使用(编程大佬可以另找教程),每周更新1-3讲。
欢迎关注我的推特:@0xAA_Science
WTF技术社群discord,内有加微信群方法:链接
所有代码和教程开源在github(1024个star发课程认证,2048个star发社群NFT): github.com/AmazingAng/WTFSolidity

Loot是以太坊链上的实验性NFT项目,发行于21年8月,共有8000个,前7777个免费mint,后233个项目方预留,最高时地板价突破20 ETH,目前稳定在1 ETH左右。与充斥市场的图片NFT不同,Loot是文字类NFT,所有元数据都保存在链上,保证了去中心化,没人能篡改。
它的内容比较简单,就是用文字描述了玩家的一套装备,包括武器、头盔、戒指共8类物品。“金指环”,“双子之剑”,“硬皮手套”,复古的名字让我回忆起小时候玩的《暗黑破坏神》。
Loot是一个开放的生态,项目方希望有更多团队能加入到Loot元宇宙的建设中。这一讲,我将介绍Loot是怎么用智能合约生成的文字,又是怎么把它放上链的。
Loot代码在etherscan上开源,地址:链接
主合约Loot从1291行开始,继承了ERC721Enumerable,ReentrancyGuard和Ownable,这些合约都是oppenzepplin标准库中的。
contract Loot is ERC721Enumerable, ReentrancyGuard, Ownable {
Loot在状态变量中定义了11个String数组用于生成装备描述的基本词组:
其中8个是装备列表,用于描述不同部位的装备,包括weapon, chestArmor, headArmor, waistArmor, footArmor, handArmor, necklaces, rings。拿weapon举例,它的String数组包含战锤, 木棒等内容:
string[] private weapons = [
"Warhammer",
"Quarterstaff",
"Maul",
"Mace",
"Club",
"Katana",
"Falchion",
"Scimitar",
"Long Sword",
"Short Sword",
"Ghost Wand",
"Grave Wand",
"Bone Wand",
"Wand",
"Grimoire",
"Chronicle",
"Tome",
"Book"
];
剩余3个是修饰装备的前缀和后缀,包括suffixes, namePrefixes和nameSuffixes。前缀后缀可以让装备看起来更牛逼,例如:龙的完美之冠,鹰吼-华丽的巨人胸甲。拿装备后缀举例,suffixes包括:
string[] private suffixes = [
"of Power",
"of Giants",
"of Titans",
"of Skill",
"of Perfection",
"of Brilliance",
"of Enlightenment",
"of Protection",
"of Anger",
"of Rage",
"of Fury",
"of Vitriol",
"of the Fox",
"of Detection",
"of Reflection",
"of the Twins"
];
装备列表和修饰词随机结合,就能生成出Loot的文本NFT。
为了区分装备的稀有度,Loot利用链上伪随机数生成函数random()为装备描述文本提供随机性:
function random(string memory input) internal pure returns (uint256) {
return uint256(keccak256(abi.encodePacked(input)));
}
random()函数参数为input字符串,它计算input的哈希,再转换为uint256,将不同的input均匀的映射到不同的数字上。之后将得到数字映射到稀有度,就可以了,Loot定义了pluck()函数来做这一点。
function pluck(uint256 tokenId, string memory keyPrefix, string[] memory sourceArray) internal view returns (string memory) {
uint256 rand = random(string(abi.encodePacked(keyPrefix, toString(tokenId))));
string memory output = sourceArray[rand % sourceArray.length];
uint256 greatness = rand % 21;
if (greatness > 14) {
output = string(abi.encodePacked(output, " ", suffixes[rand % suffixes.length]));
}
if (greatness >= 19) {
string[2] memory name;
name[0] = namePrefixes[rand % namePrefixes.length];
name[1] = nameSuffixes[rand % nameSuffixes.length];
if (greatness == 19) {
output = string(abi.encodePacked('"', name[0], ' ', name[1], '" ', output));
} else {
output = string(abi.encodePacked('"', name[0], ' ', name[1], '" ', output, " +1"));
}
}
return output;
}
pluck()函数的作用就是在给定tokenId, keyPrefix (装备部位)和sourceArray (装备列表),来生成特定部位装备的描述。一个装备有33.3%的概率拥有后缀,其中有9.5%的概率拥有特殊名称。因此,Loot中每件装备有66.7%为普通,23.8%为稀有,9.5%为史诗。
Loot包含8个get()函数来获取8个部位的装备,拿getWeapon()举例,这个函数获得武器描述:它调用了pluck函数,装备部位为"WEAPON",装备列表为状态变量weapons。
function getWeapon(uint256 tokenId) public view returns (string memory) {
return pluck(tokenId, "WEAPON", weapons);
}
由于keyPrefix和sourceArray都是复用的,因此Loot的装备描述 /稀有度完全由tokenId决定:给定tokenId,总会得到同一组装备。因此,Loot没有"保存"所有装备描述。每次用户查询元数据的时候,合约会生成一份装备描述。这个方法非常创新非常聪明,显著减少了存储占用,让元数据上链成为可能。
我们看一下它的tokenURI()函数:
function tokenURI(uint256 tokenId) override public view returns (string memory) {
string[17] memory parts;
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">';
parts[1] = getWeapon(tokenId);
parts[2] = '</text><text x="10" y="40" class="base">';
parts[3] = getChest(tokenId);
parts[4] = '</text><text x="10" y="60" class="base">';
parts[5] = getHead(tokenId);
parts[6] = '</text><text x="10" y="80" class="base">';
parts[7] = getWaist(tokenId);
parts[8] = '</text><text x="10" y="100" class="base">';
parts[9] = getFoot(tokenId);
parts[10] = '</text><text x="10" y="120" class="base">';
parts[11] = getHand(tokenId);
parts[12] = '</text><text x="10" y="140" class="base">';
parts[13] = getNeck(tokenId);
parts[14] = '</text><text x="10" y="160" class="base">';
parts[15] = getRing(tokenId);
parts[16] = '</text></svg>';
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]));
string memory json = Base64.encode(bytes(string(abi.encodePacked('{"name": "Bag #', toString(tokenId), '", "description": "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.", "image": "data:image/svg+xml;base64,', Base64.encode(bytes(output)), '"}'))));
output = string(abi.encodePacked('data:application/json;base64,', json));
return output;
}
一般pfp NFT的tokenURI()函数是直接返回一个带有元数据json的网址;而Loot的则是直接返回一个json。它定义了parts变量,然后通过8个get()函数拼接出含有装备描述的svg文件,作为元数据的image,方便展示。最后,它把name,description和image一起打包成一个Base64编码的json,作为tokenURI()查询的返回值。
下面是一个tokenURI()的返回值例子:
data:application/json;base64,eyJuYW1lIjogIkJhZyAjNSIsICJkZXNjcmlwdGlvbiI6ICJMb290IGlzIHJhbmRvbWl6ZWQgYWR2ZW50dXJlciBnZWFyIGdlbmVyYXRlZCBhbmQgc3RvcmVkIG9uIGNoYWluLiBTdGF0cywgaW1hZ2VzLCBhbmQgb3RoZXIgZnVuY3Rpb25hbGl0eSBhcmUgaW50ZW50aW9uYWxseSBvbWl0dGVkIGZvciBvdGhlcnMgdG8gaW50ZXJwcmV0LiBGZWVsIGZyZWUgdG8gdXNlIExvb3QgaW4gYW55IHdheSB5b3Ugd2FudC4iLCAiaW1hZ2UiOiAiZGF0YTppbWFnZS9zdmcreG1sO2Jhc2U2NCxQSE4yWnlCNGJXeHVjejBpYUhSMGNEb3ZMM2QzZHk1M015NXZjbWN2TWpBd01DOXpkbWNpSUhCeVpYTmxjblpsUVhOd1pXTjBVbUYwYVc4OUluaE5hVzVaVFdsdUlHMWxaWFFpSUhacFpYZENiM2c5SWpBZ01DQXpOVEFnTXpVd0lqNDhjM1I1YkdVK0xtSmhjMlVnZXlCbWFXeHNPaUIzYUdsMFpUc2dabTl1ZEMxbVlXMXBiSGs2SUhObGNtbG1PeUJtYjI1MExYTnBlbVU2SURFMGNIZzdJSDA4TDNOMGVXeGxQanh5WldOMElIZHBaSFJvUFNJeE1EQWxJaUJvWldsbmFIUTlJakV3TUNVaUlHWnBiR3c5SW1Kc1lXTnJJaUF2UGp4MFpYaDBJSGc5SWpFd0lpQjVQU0l5TUNJZ1kyeGhjM005SW1KaGMyVWlQazFoZFd3Z2IyWWdVbVZtYkdWamRHbHZiand2ZEdWNGRENDhkR1Y0ZENCNFBTSXhNQ0lnZVQwaU5EQWlJR05zWVhOelBTSmlZWE5sSWo1UWJHRjBaU0JOWVdsc1BDOTBaWGgwUGp4MFpYaDBJSGc5SWpFd0lpQjVQU0kyTUNJZ1kyeGhjM005SW1KaGMyVWlQa1J5WVdkdmJpZHpJRU55YjNkdUlHOW1JRkJsY21abFkzUnBiMjQ4TDNSbGVIUStQSFJsZUhRZ2VEMGlNVEFpSUhrOUlqZ3dJaUJqYkdGemN6MGlZbUZ6WlNJK1UyRnphRHd2ZEdWNGRENDhkR1Y0ZENCNFBTSXhNQ0lnZVQwaU1UQXdJaUJqYkdGemN6MGlZbUZ6WlNJK1NHOXNlU0JIY21WaGRtVnpQQzkwWlhoMFBqeDBaWGgwSUhnOUlqRXdJaUI1UFNJeE1qQWlJR05zWVhOelBTSmlZWE5sSWo1SVlYSmtJRXhsWVhSb1pYSWdSMnh2ZG1WelBDOTBaWGgwUGp4MFpYaDBJSGc5SWpFd0lpQjVQU0l4TkRBaUlHTnNZWE56UFNKaVlYTmxJajVRWlc1a1lXNTBQQzkwWlhoMFBqeDBaWGgwSUhnOUlqRXdJaUI1UFNJeE5qQWlJR05zWVhOelBTSmlZWE5sSWo1VWFYUmhibWwxYlNCU2FXNW5QQzkwWlhoMFBqd3ZjM1puUGc9PSJ9
把它复制到浏览器打开,可以直接获取Loot的元数据,挺神奇的:

下面是Loot生成的文字描述svg图片的例子
data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHByZXNlcnZlQXNwZWN0UmF0aW89InhNaW5ZTWluIG1lZXQiIHZpZXdCb3g9IjAgMCAzNTAgMzUwIj48c3R5bGU+LmJhc2UgeyBmaWxsOiB3aGl0ZTsgZm9udC1mYW1pbHk6IHNlcmlmOyBmb250LXNpemU6IDE0cHg7IH08L3N0eWxlPjxyZWN0IHdpZHRoPSIxMDAlIiBoZWlnaHQ9IjEwMCUiIGZpbGw9ImJsYWNrIiAvPjx0ZXh0IHg9IjEwIiB5PSIyMCIgY2xhc3M9ImJhc2UiPk1hdWwgb2YgUmVmbGVjdGlvbjwvdGV4dD48dGV4dCB4PSIxMCIgeT0iNDAiIGNsYXNzPSJiYXNlIj5QbGF0ZSBNYWlsPC90ZXh0Pjx0ZXh0IHg9IjEwIiB5PSI2MCIgY2xhc3M9ImJhc2UiPkRyYWdvbidzIENyb3duIG9mIFBlcmZlY3Rpb248L3RleHQ+PHRleHQgeD0iMTAiIHk9IjgwIiBjbGFzcz0iYmFzZSI+U2FzaDwvdGV4dD48dGV4dCB4PSIxMCIgeT0iMTAwIiBjbGFzcz0iYmFzZSI+SG9seSBHcmVhdmVzPC90ZXh0Pjx0ZXh0IHg9IjEwIiB5PSIxMjAiIGNsYXNzPSJiYXNlIj5IYXJkIExlYXRoZXIgR2xvdmVzPC90ZXh0Pjx0ZXh0IHg9IjEwIiB5PSIxNDAiIGNsYXNzPSJiYXNlIj5QZW5kYW50PC90ZXh0Pjx0ZXh0IHg9IjEwIiB5PSIxNjAiIGNsYXNzPSJiYXNlIj5UaXRhbml1bSBSaW5nPC90ZXh0Pjwvc3ZnPg==
把它复制到浏览器打开,得到下面的图片:

由于tokenId对应的稀有度在mint前已经决定,黑客可以写一个合约来mint稀有的NFT。
具体方法:计算每个tokenURI对应在pluck()函数中greatness,当greatness%21 >= 19,装备必是史诗。优先加高gas来mint这类稀有的Loot NFT。
Loot是我知道的第一个将元数据全部上链的文字NFT项目,非常有开创性和可拓展性。它并不是把元数据直接存到链上(太费链上存储空间),而是每次都通过智能合约重新生成一份元数据返回。如果大家想fork这个项目,需要认真看下random(), pluck()和tokenURI()这三个函数。
No activity yet