全问题?
回答要点:
关于参考OpenSea方案: 是的,我们在设计阶段深入研究了OpenSea的方案,特别是他们的Wyvern协议(OpenSea早期使用的协议)和后 来的Seaport协议。但我们并没有完全照搬,而是根据自己的需求和目标用户群体做了简化和优化。 OpenSea的Seaport协议非常复杂和灵活,支持各种复杂的订单类型:部分成交、批量购买、荷兰拍卖、英式拍 卖、打包交易等。这种灵活性很强大,但也带来了复杂度,合约代码量很大,Gas消耗也较高。 我们的平台定位是在Polygon链上服务中小型NFT项目和用户,不需要那么复杂的功能。所以我们只实现了最核心 的固定价格买卖功能,这使得我们的合约代码更简洁(约800行Solidity代码,而Seaport有数千行),更容易审 计,Gas消耗也更低(我们的平均Gas消耗约15万,而OpenSea在以太坊主网上可能需要30万以上)。 我们借鉴了OpenSea的几个核心设计理念:链下订单签名、链上原子撮合、版税自动分配、nonce批量取消机制。 但在具体实现上,我们做了适合自己的优化。
安全是我们最重视的方面,我们实现了多层次的安全防护机制。 防范重入攻击: 重入攻击是智能合约最常见也最危险的攻击方式之一。2016年的The DAO攻击就是利用重入漏洞盗取了价值5000 万美元的以太币,直接导致了以太坊的硬分叉。 重入攻击的原理是:当合约A调用合约B(或向外部地址转账)时,合约B可以在回调函数中再次调用合约A。如果 合约A在外部调用之前没有更新状态,就会被重复利用。 在我们的Exchange合约中,最容易受到重入攻击的是资金转移环节。我们采用了三重防护: 第一重防护:使用OpenZeppelin的ReentrancyGuard。 这是一个经过广泛验证的防重入库,原理是使用一个状态变量作为锁。在函数开始时检查锁是否被占用,如果没有 就加锁,函数结束时解锁。如果在函数执行过程中再次调用这个函数,会发现锁已被占用,直接revert。 我们在所有涉及资金转移的函数上都加了nonReentrant修饰器,包括matchOrder、cancelOrder、withdraw等。 这确保了这些函数不可能被重入调用。 第二重防护:严格遵循Checks-Effects-Interactions模式。 这是Solidity开发的最佳实践,要求函数按照特定顺序执行: Checks:检查所有条件(余额是否足够、授权是否有效等) Effects:更新合约状态(标记订单已完成、更新余额等) Interactions:与外部合约交互(转移资金、转移NFT) 在我们的matchOrder函数中,严格遵循了这个顺序。我们先验证签名、检查订单状态、验证资产;然后更新订单 状态为已完成、记录交易信息;最后才执行资金转移和NFT转移。这样即使外部调用触发了重入,合约状态已经更 新,不会被重复利用。
第三重防护:使用transfer而非call进行ETH转账。 在早期版本中,我们使用transfer函数转账ETH,因为transfer只会转发2300 gas,这个gas量只够接收方记录一个 事件,不足以执行复杂的重入攻击逻辑。 但后来我们发现transfer有一个问题:如果接收方是一个合约,且其receive或fallback函数消耗的gas超过2300, 转账会失败。这在某些情况下会影响用户体验。 所以我们改用了call方法,但限制了转发的gas量,并且在调用前已经更新了所有状态,即使发生重入也不会有安全 问题。同时我们保留了ReentrancyGuard作为额外保障。
撤单是一个看似简单但实际上有很多安全细节的操作。 链下订单的撤单: 对于链下订单(只有签名,没有上链),撤单很简单,用户在前端点击取消,后端数据库标记订单为已取消即可。 但这里有一个安全问题:如果有人在撤单前就保存了签名订单,他们仍然可以尝试在链上撮合这个订单。 我们的解决方案是在合约中维护一个已取消订单的映射。用户可以调用cancelOrder函数,在链上记录某个订单已 被取消。matchOrder函数在执行前会检查这个映射,如果订单已被取消,交易会revert。 但这种方式需要用户为每个订单支付Gas费用来取消,成本较高。所以我们还提供了批量取消机制。 Nonce批量取消机制: 我们为每个用户维护一个nonce计数器。每个订单在创建时会包含当前的nonce值。用户可以调用 incrementNonce函数,将自己的nonce加1。这样,所有包含旧nonce的订单都会失效。 这种方式的好处是用户只需要一次链上交易,就可以取消所有旧订单,非常高效。缺点是无法选择性取消某些订 单,只能全部取消。所以我们同时提供了单个取消和批量取消两种方式,用户可以根据需要选择。 在撤单函数中,我们也使用了ReentrancyGuard,虽然撤单通常不涉及资金转移,但为了保险起见,我们对所有状 态变更操作都加了防重入保护。 其他安全措施: 防止签名重放攻击: 每个订单签名都包含以下信息来防止重放: chainId:防止跨链重放(比如在以太坊主网的签名不能在BSC上使用) 合约地址:防止跨合约重放 nonce:防止同一订单被重复使用 有效期:过期订单自动失效 防止整数溢出: 虽然Solidity 0.8.0以后默认开启了溢出检查,但我们仍然在关键计算中使用SafeMath库(在0.8.0之前)或者显式 检查,确保不会出现溢出问题。 权限控制: 合约中有一些管理员功能,比如设置手续费率、暂停合约、提取手续费等。这些功能都有严格的权限控制,只有 owner可以调用。我们使用OpenZeppelin的Ownable合约来管理权限,并且计划在未来升级为多签钱包控制,进 一步去中心化。
紧急暂停机制: 我们实现了Pausable合约,在发现安全问题时可以立即暂停所有交易。暂停期间,用户无法创建新订单或撮合订 单,但可以取消订单和提取资金。这个机制在紧急情况下可以最大限度地保护用户资产。 安全审计: 合约在上线前经过了XX安全公司的专业审计,审计过程持续了两周。审计发现了2个中危漏洞和5个低危问题,我们 都进行了修复。审计报告是公开的,用户可以在我们的网站上查看。 审计后,我们还进行了Bug Bounty计划,邀请白帽黑客测试我们的合约,发现漏洞可以获得奖励。这个计划持续 了一个月,收到了十几份报告,大多数是低危问题或者建议性的优化,我们都进行了处理。 实际运营中的安全记录: 在9个月的运营期间,我们没有发生任何安全事故。没有用户因为合约漏洞损失资金,没有发生重入攻击或其他智 能合约攻击。这证明了我们的安全措施是有效的。 我们也建立了安全监控系统,实时监控合约的异常行为。比如如果检测到大额资金转移、频繁的失败交易、或者异 常的Gas消耗,系统会立即告警,我们可以快速响应。