# 从代码层面聊聊 PartyDao

By [Smithereens](https://paragraph.com/@soyoung) · 2022-07-08

---

今天来聊聊一个神奇的项目 `PartyDAO`，这个项目最近融资1640万美元，估值两亿美金，其来源于推特的一番对话，具体可以参考超哥的一篇文章：[PartyDAO的故事](https://mp.weixin.qq.com/s?__biz=Mzk0OTIzODQ0OA==&mid=2247483889&idx=1&sn=3383bbac39a5a3cfa6427960605eb0e0&chksm=c35a2e2ef42da7386db5c80a718411b693f37acaf75cec087915fea50e58203ec8ebd63dd96e&mpshare=1&scene=1&srcid=0706HeIddon5ZpafAZiJVmkC&sharer_sharetime=1657172603621&sharer_shareid=2d7e88f7df3e640d307eabea28d912dd#rd)，对话的后续是 Mirror 的创始人 Denis Nazarov 在推特上提出了一个想法并讲了基本的流程，我把整个逻辑翻译下如下： 如果部署自动一个 DAO 来竞标任何 NFT 拍卖，那不是很酷吗？给它起名 PartyBid，它包含以下功能：

*   构造函数：auction\_id
    
*   任何人存入 ETH，取回 ERC20
    
*   合同自动投标拍卖
    
*   如果获胜，资助者将获得 NFT 的部分所有权
    
*   如果输了，资金退回
    

这个想法被一个程序员大佬 [\_anishagnihotri](https://twitter.com/_anishagnihotri) 看到了，于是他就在一个周末把这个功能实现了，在这里膜拜大佬，我们这里就看看整个代码是怎样实现的，首先大佬先把 Denis Nazarov 提出的想法扩展了下，想法毕竟是想法不是完整的 PM 需求，日常开发如果 PM 提出了这么个不完整的需求肯定是会挨程序员的骂的，但这里 anishagnihotri 显然是对这个想法感兴趣的，所以就把需求完善了下并描述了下该合约是如何工作的：

1.  首先部署一个 PartyBidRA.sol 合约，构造函数有四个参数 (\_ReserveAuctionV3Address, \_auctionID, \_bidAmount, \_exitTimeout)，其中 \_ReserveAuctionV3Address 我们随后再讲，\_auctionID 对应的是某 NFT 的 TokenId，bidAmount 对应的是出价，这里应该是 ETH 计价，exitTimeout 过期时间。
    
2.  接下来，个人可以调用 join() 方法成为 DAO 的成员，发送他们的 ETH 来积累后续投标拍卖的资金。
    

于是后续会产生三种可能性：

1.  这个 DAO 在 exitTimeout 时间之前参与人过少，筹集的 ETH 没有达到预订的 bidAmount, 此前参与的用户可以调用合约的 exit() 方法来提取他们投资的 ETH。
    
2.  如果达到了预订的 bidAmount，DAO 成员可以调用 placeBid() 进行出价。如果 DAO 未能成功赢得 NFT，DAO 成员可以调用 exit() 来提取他们的资金。
    
3.  筹集的资金达到了预订的 bidAmount，并且最终出价成功买入了该 NFT，后续会有如下的可能操作：
    
    1.  DAO 成员可以调用 DAOProposeZoraBid() 方法发起一个新的提案：在 Zora 市场将该 NFT 进行出价卖出。
        
    2.  DAO 成员可以调用 DAOVoteForZoraBidProposal() 对上述已提出的提案进行投票，提案需要超过 50% 的选票才能被执行。
        
    3.  DAO 成员可以调用 DAOExecuteZoraBid() 来执行一个提案，该提案：(1) 拥有超过 50% 的 DAO 投票权，并且 (2) 自投票发起以来没有改变（以防止不良行为者在投票期间改变他们的出价） 投票过程）。 这会将 NFT 转移给出价者，并接受他们的资金到合约中。
        
    4.  最后，一旦成功转售，DAO 成员可以调用 exit() 来收回他们在投标金额中的份额。
        

至此，整个功能的描述就完成了，当然代码也是如此完成的。上述整个流程对于不在这个圈子里面的人来说有点难以理解，这里我举个例子去描述下： 小明想买一套房子，该房子房主目前出价 100 万，小明买不起，于是发起了一个众筹，一伙人看到了也觉得这个房子100万价格合适，于是纷纷加入，最终10个人加入了，每人出资10万元，成功将该房子买入了，但这个区域是学区房，房价过了一段时间还在涨，一直涨到了150左右，其中小张坐不住了，说咱们把房子卖出去吧，于是就发起了一个提案说咱们以 150 万的价格卖出，众人觉得可以，纷纷投票赞同，投票数超过5个人的时候，小张就执行了该提议，把房子卖给了出价 150万 的那个人，于是最后每个人欢欢喜喜的拿走了自己的15万。

整个流程其实和上述描述的大差不差，唯一和现实不同的是整个操作流程是在链上完成的，都是公开透明的，资金也都是按照个人初始投资进行均分的。

OK，基本流程已经说完了，我们看下代码：

首先说下 \_ReserveAuctionV3Address 这个是拍卖合约的地址，`placeBid()` 方法其实针对的是某一个拍卖发起的报价，所以这个流程执行的过程依赖拍卖方的，创建拍卖的方法如下：

     function createAuction(
            uint256 tokenId,
            uint256 duration,
            uint256 reservePrice,
            uint8 curatorFeePercent,
            address curator,
            address payable fundsRecipient
        ) external nonReentrant whenNotPaused auctionNonExistant(tokenId) {
            // Check basic input requirements are reasonable.
            require(curator != address(0));
            require(fundsRecipient != address(0));
            require(curatorFeePercent < 100, "Curator fee should be < 100");
            // 初始化拍卖结构体信息
            auctions[tokenId] = Auction({
                duration: duration,
                reservePrice: reservePrice,
                curatorFeePercent: curatorFeePercent,
                curator: curator,
                fundsRecipient: fundsRecipient,
                amount: 0,
                firstBidTime: 0,
                bidder: address(0)
            });
            // Transfer the NFT into this auction contract, from whoever owns it.
            // 将要拍卖的 NFT 转移到拍卖合约
            IERC721(nftContract).transferFrom(
                IERC721(nftContract).ownerOf(tokenId),
                address(this),
                tokenId
            );
        }
    

当一个拍卖发起后，才可以执行 `placeBid` 进行叫价，最终到达拍卖结束时间后叫价最高的账户将赢得拍卖，结束拍卖的方法如下：

       function endAuction(uint256 tokenId)
            external
            nonReentrant
            whenNotPaused
            auctionComplete(tokenId)
        {
            // Store relevant auction data in memory for the life of this function.
            address winner = auctions[tokenId].bidder;
            uint256 amount = auctions[tokenId].amount;
            address curator = auctions[tokenId].curator;
            uint8 curatorFeePercent = auctions[tokenId].curatorFeePercent;
            address payable fundsRecipient = auctions[tokenId].fundsRecipient;
            // Remove all auction data for this token from storage.
            delete auctions[tokenId];
            // We don't use safeTransferFrom, to prevent reverts at this point,
            // which would break the auction.
            // 赢的拍卖的获取到该 NFT
            IERC721(nftContract).transferFrom(address(this), winner, tokenId);
            // First handle the curator's fee.
            if (curatorFeePercent > 0) {
                // Determine the curator amount, which is some percent of the total.
                uint256 curatorAmount = amount.mul(curatorFeePercent).div(100);
                // Send it to the curator.
                transferETHOrWETH(curator, curatorAmount);
                // Subtract the curator amount from the total funds available
                // to send to the funds recipient and original NFT creator.
                amount = amount.sub(curatorAmount);
                // Emit the details of the transfer as an event.
                emit CuratorFeePercentTransfer(tokenId, curator, curatorAmount);
            }
            // Get the address of the original creator, so that we can split shares
            // if appropriate.
            address payable nftCreator =
                payable(
                    address(IMediaModified(nftContract).tokenCreators(tokenId))
                );
            // If the creator and the recipient of the funds are the same
            // (and we expect this to be common), we can just do one transaction.
            if (nftCreator == fundsRecipient) {
                transferETHOrWETH(nftCreator, amount);
            } else {
                // Otherwise, we should determine the percent that goes to the creator.
                // Collect share data from Zora.
                uint256 creatorAmount =
                    // Call the splitShare function on the market contract, which
                    // takes in a Decimal and an amount.
                    IMarket(IMediaModified(nftContract).marketContract())
                        .splitShare(
                        // Fetch the decimal from the BidShares data on the market.
                        IMarket(IMediaModified(nftContract).marketContract())
                            .bidSharesForToken(tokenId)
                            .creator,
                        // Specify the amount.
                        amount
                    );
                // 分红给 nftCreator
                transferETHOrWETH(nftCreator, creatorAmount);
                // 转移剩下的资金给拍卖方
                transferETHOrWETH(fundsRecipient, amount.sub(creatorAmount));
            }
        }
    

最核心的代码就是上面两部分了，完整的代码可以这里查看[partybid初始代码](https://github.com/Anish-Agnihotri/partybid)。但你会发现和官网的操作并不一样，因为这份代码是最原始的版本，随着项目的进展，他们后续引入了 PM 以及有合约开发自荐进行迭代开发，[新合约代码地址](https://github.com/PartyDAO/partybid)。所以你会发现，现在 [partybid官网](https://www.partybid.app/) 的操作是没有拍卖的，因为后续是接入了类似 Opensea 市场直接进行买入，简化了拍卖的这一步，侧重点放到了众筹买入这个功能，当然有时间也可以积极的去官网进行参与下，说不定后面有空投惊喜呢。

---

*Originally published on [Smithereens](https://paragraph.com/@soyoung/partydao)*
