# PXN 合约分析

By [Jack_Zhang](https://paragraph.com/@jack-zhang-2) · 2022-05-07

---

### 初学者对 PXN 项目的合约分析

> OpenSea : [https://opensea.io/collection/pxnghostdivision](https://opensea.io/collection/pxnghostdivision)
> 
> 合约地址：[https://etherscan.io/address/0x160c404b2b49cbc3240055ceaee026df1e8497a0#code](https://etherscan.io/address/0x160c404b2b49cbc3240055ceaee026df1e8497a0#code)

**Tips：**

自学有一段时间了，尝试看了一些项目的合约，这是第一次写总结，有不足或者有问题的地方欢迎在推特上私信或留言，一起讨论学习成长。

[https://twitter.com/Bowei52748580](https://twitter.com/Bowei52748580)

### 合约类型

继承了 ERC721A；Ownable

![](https://storage.googleapis.com/papyrus_images/b9f5b527c2d8915820273d8cd9c7c905ac314c5f5b1a9ab4a4629566ee2ec621.png)

### 关键变量

    //荷兰拍的数量 4k
    DA_QUANTITY = 4000; 
    
    //白单可以mint 6k.
    WL_QUANTITY = 6000;
    
    //WL Price 白单地板价
    WLprice = 0.35 ether; 
    
    //合拍开始价格 2e
    DA_STARTING_PRICE = 2 ether;
    
    //合拍结束价格 0.1
    DA_ENDING_PRICE = 0.1 ether;
    
    //合拍递减的价格 0.05
    DA_DECREMENT = 0.05 ether;
    
    //每15分钟降价一次
    DA_DECREMENT_FREQUENCY = 900;
    
    //荷兰拍开始的时间
    DA_STARTING_TIMESTAMP = 1651719600
    

总结：

该项目共计 10k 个nft，荷兰拍起拍价 2e，之后每隔15分钟降价 0.05e，白单mint价格 0.35e

### 合约概览分析

1.  使用 Ownable 对函数调用的权限做了限制；
    
2.  mint函数中，均采用了\_safemint；
    
3.  对关键性信息做了 require校验：每个钱包mint数量，项目总mint数量，钱包余额，mint地址，荷兰拍和不同种类 mint 时间的起始判断；
    
4.  以上判断可防止「超发」「科学家非法手段mint」「Reentrancy 重入攻击」「一个钱包多次mint」
    
5.  使用 callerIsUser() 防止合约调用；
    
        modifier callerIsUser() {
          require(tx.origin == msg.sender, "The caller is another contract");
          _;
        }
        
    

业务逻辑分析
------

### 荷兰拍 -→ mintDutchAuction

**基础逻辑：**

*   荷兰拍共提供 4000 个，起拍价 2e，然后每隔15分钟降价 0.05e。
    
*   荷兰拍开始时间是：
    

![起拍时间的时间戳](https://storage.googleapis.com/papyrus_images/6485deb371eb024c78ca1ce786e00b6d9132980a50a06389434b9ae3a41abb68.png)

起拍时间的时间戳

*   时间戳转换之后如下：
    

![时间戳转换之后的时间](https://storage.googleapis.com/papyrus_images/746a8e8d42214d90732f663913a6c4f34a628ca97e2cc6451877d492a127cec5.png)

时间戳转换之后的时间

*   随着时间的推移，荷兰拍的价格会逐渐下降，当荷兰拍价格小于 0.7 时那么此时 WL mint的价格就是：「当前荷兰拍价格/ 2」。若当前荷兰拍结束后其价格是0.6 那么白单的mint价格从初始的 0.35e 变为 0.3e。所以荷兰拍的最终价格可能会影响到 WL 的mint 价格。
    

![荷兰拍最终价格影响 WL 价格](https://storage.googleapis.com/papyrus_images/007e1fcc46a6680f75d636ffb9aaebb3613f6925bdd66da21f337eb69c3793b0.png)

荷兰拍最终价格影响 WL 价格

**条件校验：**

*   判断荷兰拍开始。DA\_ACTIVE == TRUE 【合约状态校验】
    
*   判断前端页面传过来的的加密签名是不是和后端加密之后的一样，这样可以防止合约机器人调用合约。【校验合约调用者】
    
*   判断荷兰拍是否到4000个【防止超发】
    
*   通过获取当前区块时间来判断是否到荷兰拍的开始时间【荷兰拍时间校验】
    
*   若当前区块时间在白单开始时间之前那么此时荷兰拍尚未结束【荷兰拍结束时间校验】
    
    *   白单mint 开始时间是荷兰拍进行24小时之后，换句话说，荷兰拍持续时间是24小时。
        
*   校验当前钱包mint的数量，要求数量最多 **2** 个【钱包 mint 数量校验】
    
*   判断当前钱包余额是否够支付【金额校验】
    
    **当上边的校验都通过之后开始执行 \_safeMint()**
    
*   第一个成功通过荷兰拍 mint 的人共花了 4k多刀的 gas fee。所以下次如果参加 荷兰拍想要提前确认交易，就尽可能提高你的 gas 设置吧。至于为什么这和 POW 共识机制有关，请自行Google。
    
*   整个荷兰拍在10分钟左右就结束了。所以说这个项目非常火。最后一个人mint 的gas fee 只有55刀，可见时机的把握可以节省不少 gas fee。
    

![荷兰拍第一个成功确认交易的人](https://storage.googleapis.com/papyrus_images/9734ee4d9e189708fbb6db5c87d18bf16f0ae7c8d09ad57d7fc9a2c04704245c.png)

荷兰拍第一个成功确认交易的人

### 白单→mintWL

**条件校验：**

*   签名校验与荷兰拍的签名校验方法一样【校验合约调用者】
    
*   判断mint 数量不超过 6000 个【防止超发】
    
*   校验当前 mint 地址是否已经 mint 过，一个地址只能 mint 一次，mint 数量也是 1.【防止单个地址多次 mint 】
    
*   获取当前区块时间应大于白单开始 mint 的时间，否则白单 mint 尚未开始【校验mint 开始时间】
    
*   校验当前 msg.value 应该足够支付【看是否有足够的金额支付交易】
    
*   执行 \_safeMint() 完成交易
    

### 开发团队→ devMint

*   白单mint 一天「24h」之后，dev 团队开始mint，dev团队mint 的数量就是项目总提供量 10k - 已经被mint的。
    
*   dev mint 的方法可以理解为将尚未mint 的全部打到dve 的地址中。他们mint的方式是10个10个的mint，这样应该是为了节省 gas fee。
    

![](https://storage.googleapis.com/papyrus_images/dd2b282212cc6064be031b8872b06d35b2a8c3aab51b7d9a28d9d4755b528b24.png)

### team → teamMint

**条件校验：**

*   team mint 时间和白单 mint 开始时间相同【mint 时间校验】
    
*   team 的mint 地址不能超过限定的数量，这个根据贡献度不同 mint 的数量不同【规则校验】
    
*   校验钱包的余额是否够支付此次交易【余额校验】
    
*   然后执行 \_safeMint()
    

**以上就是 PXN 项目的合约分析，如果有不懂的或者有问题的欢迎讨论在我的推特下留言。**

补充知识
----

**知识点1:**

在修改器 modifier 中我们看到 tx.origin == msg.sender，这段代码是什么意思呢？请看图

    modifier callerIsUser() {
      require(tx.origin == msg.sender, "The caller is another contract");
      _;
    }
    

![原文地址：https://davidkathoh.medium.com/tx-origin-vs-msg-sender-93db7f234cb9](https://storage.googleapis.com/papyrus_images/ed72bd2810ecc26562b0378815b47058867af962221e37a606005160d25eb641.png)

原文地址：https://davidkathoh.medium.com/tx-origin-vs-msg-sender-93db7f234cb9

**msg.sender:** 指直接调用智能合约函数的账户或智能合约地址;

\*\*tx.origin：\*\*指调用智能合约函数的账户地址，只有账户地址可以是tx.origin.

因此可以通过 callerIsUser() 这个修改器判断当前合约调用者是不是合约地址。

**知识点2**

如何具体查看Transaction失败的原因？

首先到 etherscan 上我们可以看到某些 transcation 是失败的，此时我们可以将这个失败的 Transaction Hash 粘贴到 tenderly 上去查看具体原因。如图所示，还可以看到那些改变链上状态的函数消耗了多少 gas fee，用来调试优化合约时候可以用到。毕竟每次修改链上状态都是要花真金白银的。

![](https://storage.googleapis.com/papyrus_images/2520539708e2e0f9889702af761a316cde4f42c0ebd4f0c69716a2087619516f.jpg)

区块链的特点是匿名、公开、透明、不可更改，每一笔交易所有的细节都能在链上看到，从最开始尝试看懂链上数据，到后来决定走科学家这条路从中收获很多，虽才刚刚开始，但持之以恒总会有满意的收获。

以后遇到火爆的项目会持续解读欢迎关注我的推特

> 在成为科学家的道路上一起成长，在赚钱的道路上并肩前行，你好我是Jack\_Zhang.

[https://twitter.com/Bowei52748580](https://twitter.com/Bowei52748580)

---

*Originally published on [Jack_Zhang](https://paragraph.com/@jack-zhang-2/pxn)*
