在NFT交易平台的合约开发中,我主要遇到了三个具体的安全挑战,并针对性地解决了它们。 挑战一:签名重放攻击(Signature Replay Attack) 问题描述: 我们采用了链下签名、链上撮合的模式。最初设计时,我意识到如果签名没有足够的唯一性标识,可能会被滥用。 比如,卖家在Polygon上对一个订单进行了签名,如果这个签名不包含链ID(Chain ID),攻击者可能拿着这个签 名去以太坊主网(如果我们在主网也部署了相同地址的合约)重放这个订单。虽然卖家在主网可能没有这个NFT, 但如果是一个ERC20代币的批准签名,后果就很严重。 这就好比你签了一张支票,本来是给A公司的,结果B公司拿去也能兑现;或者你签了一次,对方复印了几份去兑 现多次。 解决方案: 为了彻底防止重放攻击,我们严格遵循EIP-712标准(以太坊类型化数据签名标准)。在签名数据中,我们包含了 DOMAIN_SEPARATOR,它由以下几部分组成: name:应用名称(比如"NFT Exchange") version:版本号("1.0") chainId:当前链的ID(Polygon是137) verifyingContract:合约地址 这意味着,即使是完全相同的订单内容,在不同的链上、不同的合约地址下,生成的签名也是完全不同的。 此外,我们在订单结构中加入了nonce(随机数)。每个用户都有一个独立的nonce计数器。一旦某个nonce的订 单被执行或取消,这个nonce就失效了。这防止了同一个签名被多次使用。 挑战二:恶意ERC20代币的攻击 问题描述: 我们的平台支持用户使用任意ERC20代币进行支付。但我发现,有些恶意的ERC20代币实现是不标准的,或者是有 后门的。 比如,有些代币在转账时会收取"转账税"(Transfer Tax),你转账100个币,接收方实际只收到90个。如果我们的 合约没有处理这种情况,就会导致资金分配错误。比如卖家应得90个币,但因为扣了税,合约里只有80个,导致转 账给卖家时失败。 还有些代币采用了重入钩子(Reentrancy Hooks),在转账时会回调发送者或接收者,这可能导致重入攻击。 解决方案: 针对"转账税"问题,我们在代码中并不假设接收到的金额等于发送的金额。在调用transferFrom将代币从买家转入 合约后,我们会检查合约余额的变化量: uint256 balanceBefore = token.balanceOf(address(this)); token.transferFrom(buyer, address(this), amount); uint256 balanceAfter = token.balanceOf(address(this)); uint256 actualAmount = balanceAfter - balanceBefore; 我们后续的资金分配(手续费、版税、卖家收入)都是基于这个actualAmount来计算的,而不是订单原本的 amount。这样即使代币有转账税,也不会导致合约资金不足。 针对恶意代币的重入风险,我们使用了OpenZeppelin的ReentrancyGuard,并严格遵循Checks-Effects- Interactions模式,确保所有状态更新都在外部调用之前完成。 挑战三:舍入误差导致的资金残留(Dust) 问题描述: 在计算版税和手续费时,由于Solidity不支持浮点数,除法运算会向下取整。这会导致微小的资金误差。 比如,总金额是100 wei,需要分成三份:33.3%、33.3%、33.3%。在整数运算下,每份都是33 wei,总共99 wei,剩下了1 wei在合约里。 虽然1 wei价值微乎其微,但如果不处理,长年累月下来,合约里会积累大量的"灰尘"(Dust),而且这会让财务 对账变得困难。更严重的是,如果逻辑不严密,可能导致最后一笔转账因为余额不足1 wei而失败(虽然在这个案 例中是剩下了,但在某些反向计算中可能变少)。 解决方案: 我们采用了"剩余全部给最后一方"的策略。具体来说,我们先计算平台手续费和版税: uint256 platformFee = amount * platformFeeBps / 10000; uint256 royaltyFee = amount * royaltyFeeBps / 10000; uint256 sellerIncome = amount - platformFee - royaltyFee; 这里关键是卖家收入不是通过百分比计算的,而是用总额减去其他费用得到的。这样就保证了 platformFee + royaltyFee + sellerIncome 永远严格等于 amount,不会多出也不会少于1 wei。 所有计算出的金额加起来完美匹配输入的金额,彻底解决了舍入误差导致的资金残留问题。