
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
我最近在重新学solidity,巩固一下细节,也写一个“Solidity极简入门”,供小白们使用(编程大佬可以另找教程),每周更新1-3讲。
欢迎关注我的推特:@0xAA_Science
欢迎加入WTF科学家社区,内有加微信群方法:链接
所有代码和教程开源在github(1024个star发课程认证,2048个star发社群NFT): github.com/AmazingAng/WTFSolidity
这一讲,我将介绍荷兰拍卖,并通过简化版Azuki荷兰拍卖代码,讲解如何通过荷兰拍卖发售ERC721标准的NFT。
荷兰拍卖(Dutch Auction)是一种特殊的拍卖形式。 亦称“减价拍卖”,它是指拍卖标的的竞价由高到低依次递减直到第一个竞买人应价(达到或超过底价)时击槌成交的一种拍卖。

在币圈,很多NFT通过荷兰拍卖发售,其中包括Azuki和World of Women,其中Azuki通过荷兰拍卖筹集了超过8000枚ETH。
项目方非常喜欢这种拍卖形式,主要有两个原因
荷兰拍卖的价格由最高慢慢下降,能让项目方获得最大的收入。
拍卖持续较长时间(通常6小时以上),可以避免gas war。
代码基于Azuki的代码简化而成。DucthAuction合约继承了之前介绍的ERC721和Ownable合约:
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.4;
import "@openzeppelin/contracts/access/Ownable.sol";
import "https://github.com/AmazingAng/WTFSolidity/blob/main/34_ERC721/ERC721.sol";
contract DutchAuction is Ownable, ERC721 {
合约中一共有9个状态变量,其中有6个和拍卖相关,他们是:
COLLECTOIN_SIZE:NFT总量
AUCTION_START_PRICE:荷兰拍卖起拍价,也是最高价。
AUCTION_END_PRICE:荷兰拍卖结束价,也是地板价。
AUCTION_TIME:拍卖持续时长。
AUCTION_DROP_INTERVAL:每过多久时间,价格衰减一次。
auctionStartTime:拍卖起始时间(区块链时间戳,block.timestamp)。
uint256 public constant COLLECTOIN_SIZE = 10000; // NFT总数
uint256 public constant AUCTION_START_PRICE = 1 ether; // 起拍价
uint256 public constant AUCTION_END_PRICE = 0.1 ether; // 结束价(最低价)
uint256 public constant AUCTION_TIME = 10 minutes; // 拍卖时间,为了测试方便设为10分钟
uint256 public constant AUCTION_DROP_INTERVAL = 1 minutes; // 每过多久时间,价格衰减一次
uint256 public constant AUCTION_DROP_PER_STEP =
(AUCTION_START_PRICE - AUCTION_END_PRICE) /
(AUCTION_TIME / AUCTION_DROP_INTERVAL); // 每次价格衰减步长
uint256 public auctionStartTime; // 拍卖开始时间戳
string private _baseTokenURI; // metadata URI
uint256[] private _allTokens; // 记录所有存在的tokenId
荷兰拍卖合约中共有9个函数,与ERC721相关的函数我们这里不再重复介绍,只介绍和拍卖相关的。
设定拍卖起始时间:我们在构造函数中会声明当前区块时间为起始时间,项目方也可以通过setAuctionStartTime()函数来调整:
constructor() ERC721("WTF Dutch Auctoin", "WTF Dutch Auctoin") {
auctionStartTime = block.timestamp;
}
// auctionStartTime setter函数,onlyOwner
function setAuctionStartTime(uint32 timestamp) external onlyOwner {
auctionStartTime = timestamp;
}
获取拍卖实时价格:getAuctionPrice()函数通过当前区块时间以及拍卖相关的状态变量来计算实时拍卖价格。当block.timestamp小于起始时间,价格为最高价AUCTION_START_PRICE;当block.timestamp大于结束时间,价格为最低价AUCTION_END_PRICE;当block.timestamp处于两者之间时,则计算出当前的衰减价格。
// 获取拍卖实时价格
function getAuctionPrice(uint256 _auctionStartTime)
public
view
returns (uint256)
{
if (block.timestamp < _auctionStartTime) {
return AUCTION_START_PRICE;
}else if (block.timestamp - _auctionStartTime >= AUCTION_TIME) {
return AUCTION_END_PRICE;
} else {
uint256 steps = (block.timestamp - _auctionStartTime) /
AUCTION_DROP_INTERVAL;
return AUCTION_START_PRICE - (steps * AUCTION_DROP_PER_STEP);
}
}
用户拍卖并铸造NFT:用户通过调用auctionMint()函数,支付ETH参加荷兰拍卖并铸造NFT。该函数首先检查拍卖是否开始/铸造是否超出NFT总量。接着,合约通过getAuctionPrice()和铸造数量计算拍卖成本,并检查用户支付的ETH是否足够:如果足够,则将NFT铸造给用户,并退回超额的ETH;反之,则回退交易。
// 拍卖mint函数
function auctionMint(uint256 quantity) external payable{
uint256 _saleStartTime = uint256(auctionStartTime); // 建立local变量,减少gas花费
require(
_saleStartTime != 0 && block.timestamp >= _saleStartTime,
"sale has not started yet"
); // 检查拍卖是否开始
require(
totalSupply() + quantity <= COLLECTOIN_SIZE,
"not enough remaining reserved for auction to support desired mint amount"
); // 检查是否超过NFT上限
// Mint NFT
for(uint i = 0; i < quantity; i++) {
uint mintIndex = totalSupply();
_mint(msg.sender, mintIndex);
_addTokenToAllTokensEnumeration(mintIndex);
}
uint256 totalCost = getAuctionPrice(auctionStartTime) * quantity; // 计算mint成本
require(msg.value >= totalCost, "Need to send more ETH."); // 检查用户是否支付足够ETH
// 多余ETH退款
if (msg.value > totalCost) {
payable(msg.sender).transfer(msg.value - totalCost);
}
}
项目方取出筹集的ETH:项目方可以通过withdrawMoney()函数提走拍卖筹集的ETH。
// 提款函数,onlyOwner
function withdrawMoney() external onlyOwner {
(bool success, ) = msg.sender.call{value: address(this).balance}("");
require(success, "Transfer failed.");
}
这一讲,我们介绍了荷兰拍卖,并通过简化版Azuki荷兰拍卖代码,讲解如何通过荷兰拍卖发售ERC721标准的NFT。我拍卖到的最贵的NFT是音乐家Jonathan Mann的一首音乐NFT,你呢?
我最近在重新学solidity,巩固一下细节,也写一个“Solidity极简入门”,供小白们使用(编程大佬可以另找教程),每周更新1-3讲。
欢迎关注我的推特:@0xAA_Science
欢迎加入WTF科学家社区,内有加微信群方法:链接
所有代码和教程开源在github(1024个star发课程认证,2048个star发社群NFT): github.com/AmazingAng/WTFSolidity
这一讲,我将介绍荷兰拍卖,并通过简化版Azuki荷兰拍卖代码,讲解如何通过荷兰拍卖发售ERC721标准的NFT。
荷兰拍卖(Dutch Auction)是一种特殊的拍卖形式。 亦称“减价拍卖”,它是指拍卖标的的竞价由高到低依次递减直到第一个竞买人应价(达到或超过底价)时击槌成交的一种拍卖。

在币圈,很多NFT通过荷兰拍卖发售,其中包括Azuki和World of Women,其中Azuki通过荷兰拍卖筹集了超过8000枚ETH。
项目方非常喜欢这种拍卖形式,主要有两个原因
荷兰拍卖的价格由最高慢慢下降,能让项目方获得最大的收入。
拍卖持续较长时间(通常6小时以上),可以避免gas war。
代码基于Azuki的代码简化而成。DucthAuction合约继承了之前介绍的ERC721和Ownable合约:
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.4;
import "@openzeppelin/contracts/access/Ownable.sol";
import "https://github.com/AmazingAng/WTFSolidity/blob/main/34_ERC721/ERC721.sol";
contract DutchAuction is Ownable, ERC721 {
合约中一共有9个状态变量,其中有6个和拍卖相关,他们是:
COLLECTOIN_SIZE:NFT总量
AUCTION_START_PRICE:荷兰拍卖起拍价,也是最高价。
AUCTION_END_PRICE:荷兰拍卖结束价,也是地板价。
AUCTION_TIME:拍卖持续时长。
AUCTION_DROP_INTERVAL:每过多久时间,价格衰减一次。
auctionStartTime:拍卖起始时间(区块链时间戳,block.timestamp)。
uint256 public constant COLLECTOIN_SIZE = 10000; // NFT总数
uint256 public constant AUCTION_START_PRICE = 1 ether; // 起拍价
uint256 public constant AUCTION_END_PRICE = 0.1 ether; // 结束价(最低价)
uint256 public constant AUCTION_TIME = 10 minutes; // 拍卖时间,为了测试方便设为10分钟
uint256 public constant AUCTION_DROP_INTERVAL = 1 minutes; // 每过多久时间,价格衰减一次
uint256 public constant AUCTION_DROP_PER_STEP =
(AUCTION_START_PRICE - AUCTION_END_PRICE) /
(AUCTION_TIME / AUCTION_DROP_INTERVAL); // 每次价格衰减步长
uint256 public auctionStartTime; // 拍卖开始时间戳
string private _baseTokenURI; // metadata URI
uint256[] private _allTokens; // 记录所有存在的tokenId
荷兰拍卖合约中共有9个函数,与ERC721相关的函数我们这里不再重复介绍,只介绍和拍卖相关的。
设定拍卖起始时间:我们在构造函数中会声明当前区块时间为起始时间,项目方也可以通过setAuctionStartTime()函数来调整:
constructor() ERC721("WTF Dutch Auctoin", "WTF Dutch Auctoin") {
auctionStartTime = block.timestamp;
}
// auctionStartTime setter函数,onlyOwner
function setAuctionStartTime(uint32 timestamp) external onlyOwner {
auctionStartTime = timestamp;
}
获取拍卖实时价格:getAuctionPrice()函数通过当前区块时间以及拍卖相关的状态变量来计算实时拍卖价格。当block.timestamp小于起始时间,价格为最高价AUCTION_START_PRICE;当block.timestamp大于结束时间,价格为最低价AUCTION_END_PRICE;当block.timestamp处于两者之间时,则计算出当前的衰减价格。
// 获取拍卖实时价格
function getAuctionPrice(uint256 _auctionStartTime)
public
view
returns (uint256)
{
if (block.timestamp < _auctionStartTime) {
return AUCTION_START_PRICE;
}else if (block.timestamp - _auctionStartTime >= AUCTION_TIME) {
return AUCTION_END_PRICE;
} else {
uint256 steps = (block.timestamp - _auctionStartTime) /
AUCTION_DROP_INTERVAL;
return AUCTION_START_PRICE - (steps * AUCTION_DROP_PER_STEP);
}
}
用户拍卖并铸造NFT:用户通过调用auctionMint()函数,支付ETH参加荷兰拍卖并铸造NFT。该函数首先检查拍卖是否开始/铸造是否超出NFT总量。接着,合约通过getAuctionPrice()和铸造数量计算拍卖成本,并检查用户支付的ETH是否足够:如果足够,则将NFT铸造给用户,并退回超额的ETH;反之,则回退交易。
// 拍卖mint函数
function auctionMint(uint256 quantity) external payable{
uint256 _saleStartTime = uint256(auctionStartTime); // 建立local变量,减少gas花费
require(
_saleStartTime != 0 && block.timestamp >= _saleStartTime,
"sale has not started yet"
); // 检查拍卖是否开始
require(
totalSupply() + quantity <= COLLECTOIN_SIZE,
"not enough remaining reserved for auction to support desired mint amount"
); // 检查是否超过NFT上限
// Mint NFT
for(uint i = 0; i < quantity; i++) {
uint mintIndex = totalSupply();
_mint(msg.sender, mintIndex);
_addTokenToAllTokensEnumeration(mintIndex);
}
uint256 totalCost = getAuctionPrice(auctionStartTime) * quantity; // 计算mint成本
require(msg.value >= totalCost, "Need to send more ETH."); // 检查用户是否支付足够ETH
// 多余ETH退款
if (msg.value > totalCost) {
payable(msg.sender).transfer(msg.value - totalCost);
}
}
项目方取出筹集的ETH:项目方可以通过withdrawMoney()函数提走拍卖筹集的ETH。
// 提款函数,onlyOwner
function withdrawMoney() external onlyOwner {
(bool success, ) = msg.sender.call{value: address(this).balance}("");
require(success, "Transfer failed.");
}
这一讲,我们介绍了荷兰拍卖,并通过简化版Azuki荷兰拍卖代码,讲解如何通过荷兰拍卖发售ERC721标准的NFT。我拍卖到的最贵的NFT是音乐家Jonathan Mann的一首音乐NFT,你呢?
>100 subscribers
>100 subscribers
No activity yet