黃冠學
    • Create new note
    • Create a note from template
      • Sharing URL Link copied
      • /edit
      • View mode
        • Edit mode
        • View mode
        • Book mode
        • Slide mode
        Edit mode View mode Book mode Slide mode
      • Customize slides
      • Note Permission
      • Read
        • Only me
        • Signed-in users
        • Everyone
        Only me Signed-in users Everyone
      • Write
        • Only me
        • Signed-in users
        • Everyone
        Only me Signed-in users Everyone
      • Engagement control Commenting, Suggest edit, Emoji Reply
    • Invite by email
      Invitee

      This note has no invitees

    • Publish Note

      Share your work with the world Congratulations! 🎉 Your note is out in the world Publish Note

      Your note will be visible on your profile and discoverable by anyone.
      Your note is now live.
      This note is visible on your profile and discoverable online.
      Everyone on the web can find and read all notes of this public team.
      See published notes
      Unpublish note
      Please check the box to agree to the Community Guidelines.
      View profile
    • Commenting
      Permission
      Disabled Forbidden Owners Signed-in users Everyone
    • Enable
    • Permission
      • Forbidden
      • Owners
      • Signed-in users
      • Everyone
    • Suggest edit
      Permission
      Disabled Forbidden Owners Signed-in users Everyone
    • Enable
    • Permission
      • Forbidden
      • Owners
      • Signed-in users
    • Emoji Reply
    • Enable
    • Versions and GitHub Sync
    • Note settings
    • Note Insights
    • Engagement control
    • Transfer ownership
    • Delete this note
    • Save as template
    • Insert from template
    • Import from
      • Dropbox
      • Google Drive
      • Gist
      • Clipboard
    • Export to
      • Dropbox
      • Google Drive
      • Gist
    • Download
      • Markdown
      • HTML
      • Raw HTML
Menu Note settings Versions and GitHub Sync Note Insights Sharing URL Create Help
Create Create new note Create a note from template
Menu
Options
Engagement control Transfer ownership Delete this note
Import from
Dropbox Google Drive Gist Clipboard
Export to
Dropbox Google Drive Gist
Download
Markdown HTML Raw HTML
Back
Sharing URL Link copied
/edit
View mode
  • Edit mode
  • View mode
  • Book mode
  • Slide mode
Edit mode View mode Book mode Slide mode
Customize slides
Note Permission
Read
Only me
  • Only me
  • Signed-in users
  • Everyone
Only me Signed-in users Everyone
Write
Only me
  • Only me
  • Signed-in users
  • Everyone
Only me Signed-in users Everyone
Engagement control Commenting, Suggest edit, Emoji Reply
  • Invite by email
    Invitee

    This note has no invitees

  • Publish Note

    Share your work with the world Congratulations! 🎉 Your note is out in the world Publish Note

    Your note will be visible on your profile and discoverable by anyone.
    Your note is now live.
    This note is visible on your profile and discoverable online.
    Everyone on the web can find and read all notes of this public team.
    See published notes
    Unpublish note
    Please check the box to agree to the Community Guidelines.
    View profile
    Engagement control
    Commenting
    Permission
    Disabled Forbidden Owners Signed-in users Everyone
    Enable
    Permission
    • Forbidden
    • Owners
    • Signed-in users
    • Everyone
    Suggest edit
    Permission
    Disabled Forbidden Owners Signed-in users Everyone
    Enable
    Permission
    • Forbidden
    • Owners
    • Signed-in users
    Emoji Reply
    Enable
    Import from Dropbox Google Drive Gist Clipboard
       owned this note    owned this note      
    Published Linked with GitHub
    Subscribed
    • Any changes
      Be notified of any changes
    • Mention me
      Be notified of mention me
    • Unsubscribe
    Subscribe
    # 復盤 We are What We Eat NFT 專案持續火熱,由黃心健、江振誠與張逸軍共創之 NFT 專案 [We are What We Eat](https://www.tatlerasia.com/dining/journeys/we-are-what-we-eat-seed-nfts-presale?fbclid=IwAR1tgZ0xCJFSPIdAle5sFayF78f-PCz3nZbpEfzcC1MSveUyVLaVJ0oXN7Y) 2022.1.10 11:00 優先釋出的 200 個 Seed NFTs 在 2 分鐘內全數鑄造完成,市場反應熱烈之下,項目方 EchoX 隔天(1/11)11:00 啟動第二波活動 Public Sale 預計再賣出 50 個。但第二波活動發生超賣和提前購買等問題。 項目方遭發現合約漏洞導致之超額鑄造,當時先行公告未經正常流程鑄造的 NFT 將不享有原本福利並要求退還,而後宣布於二級市場買賣之 NFT 皆可享有其原本福利補償購買客戶損失。 本次專案超賣原訂數量 200個 NFT,項目方要求未經正常鑄造流程上架到 OpenSea 平台銷售之 NFT 應主動退回,造成已在平台搶購之粉絲錯愕與損失,智能合約在 NFT 專案中更顯重要,小型專案百萬起跳,大型專案 100 個 NFT 市值動輒超過 1 億,我們將從智能合約分析,再談該如何避免 NFT 超賣事件重演。 此次3位頂尖創作者與 ExchoX 的事件,我們先從項目方後續處理討論,再分析合約與討論解決方案。 #### 一、EchoX 事件之後續處理 #### 二、從智能合約角度分析為何會超賣與偷跑 1. 找出合約 Mint Function 1. 合約如何更新與設定? 1. 為什麼會發生**偷跑**(提早了半小時) 1. 為什麼會發生**超賣**(多賣了200個) 1. 總結 ![](https://i.imgur.com/qwjk8N0.jpg) --- # 一、EchoX 事件之後續處理 2022.1.11 放棄現有合約,原本透過官方渠道鑄造 Seed NFT 的買家,會重新空投原購買量的 Seed NFT 到你的錢包。在OpenSea 之二級市場買到「非官方 Seed NFT」的買家,EchoX 會直接空投新合約的 Seed NFT 至買家錢包,但需於 1/12 6pm(UTC+8)前 Email:service@echox.io 回報。需附上 (1.) 錢包地址 (2.) 你的 OpenSea NFT 連結。 --- # 二、從智能合約分析為何會超賣 ### 1. 找出合約 Mint Function 在 [etherscan](https://etherscan.io/) 從合約地址去看[交易紀錄](https://etherscan.io/address/0x52afa21bb061bb8a434102802c3d1625a86e9870) ![](https://i.imgur.com/qaXTJbY.jpg) 首先我們到 Transactions 看1/10, 1/11的交易紀錄,可以看到`Presale`這個 function被執行多次,presale 需要傳送 ETH,且傳送的 ETH 也是 mint 的價格 0.095 ETH 的倍數,所以它就是 mint function。 ![](https://i.imgur.com/RubB7Oo.jpg) ![](https://i.imgur.com/uNAMzir.jpg) "Write Contract" 跟 "Transactions" 找出 mint function 是 presale ![](https://i.imgur.com/Ysb6J1u.png) 下面再去 "Code" 裡面仔細看 "presale" function 是怎麼作用的 --- ### presale function ![](https://i.imgur.com/CMHqcOk.png) presale 的功能為購買 NFT,這裡有 2 個規定 : * 規定截止時間(沒有規定開始時間) * 不能超過最大發行數量 接著我們來看一下合約要如何更新和設定。 --- ### 2. 合約如何更新和設定 透過 `setActivity` function 來設定每一波活動 https://etherscan.io/address/0x52afa21bb061bb8a434102802c3d1625a86e9870#writeContract ![](https://i.imgur.com/IRCvfwF.png =x510) setActivity 參數: ![](https://i.imgur.com/WHWFxmu.png =x110) (1.) 第一波活動的合約設定 https://etherscan.io/tx/0x719ab9537f6d210f6308f22a0adf7ab78cbce11d93c0526b8f391a754162b2e4 ![](https://i.imgur.com/k3chq6B.png) ![](https://i.imgur.com/ZycrcC0.png) 把輸入資料(Input Data)放大來看: ![](https://i.imgur.com/IoPqEle.png) 從 etherscan 的活動紀錄我們可以明顯看出 第一次的設定是在 Jan-07-2022 10:54:28 AM +UTC (台北時間要 +8 小時) **setActivity** 設定內容為 : **活動 ID**(activityID) : 3 **價格**(charges.price) : 0.095 ETH **截止時間**(period) : 1672272000 (Thursday, December 29, 2022 12:00:00 AM GMT) **最大發行數量**(maxCirculation) : 200 (2.)第一波購買 第一波活動設定完成後就可以等人購買了,這裡 NFT 最大發行數量設定為 200 個,和官網宣布的一樣,這裡沒有問題。 但時間的設定會有點問題,因為 1672272000 的時間是 2022/12/29,這樣等同於沒有限制,但因為沒人發現,所以第一波沒有人提前購買。 ![](https://i.imgur.com/P8Dg12p.png) 訂單成立時間:2022-01-10 11:17:52 - 2022-01-10 11:21:09 (台北時間) 得到 19 eth,共賣出 200 個(每個 NFT 定價 0.095 ETH) (3.)第二波活動的合約設定 https://etherscan.io/tx/0xf1a55b20727cbf7ee53bf18425e3cd64987337cc0239ed6e3a2982622c6eae03 ![](https://i.imgur.com/SwvPtlQ.png) ![](https://i.imgur.com/s3EOmDp.png) 我們把輸入資料(input data)放大來看: ![](https://i.imgur.com/J4K5t8F.png) 從 etherscan 的活動紀錄我們可以明顯看出 第二次的設定是在 Jan-11-2022 02:24:32 AM +UTC (台北時間要 +8 小時) **setActivity** 設定內容為 : **活動 ID**(activityID) : 3 **價格**(charges.price) : 0.095 ETH **截止時間**(period) : 1672272000 (Thursday, December 29, 2022 12:00:00 AM GMT) **最大發行數量**(maxCirculation) : 250 (4.) 第二波購買 訂單成立時間:2022-01-11 10:25:32 - 2022-01-11 10:30:55 (台北時間) 得到 23.75 eth,共賣出 250個(每個 NFT 定價 0.095 ETH) 以上是兩波發行的數據,至於為什麼會發生超賣和提前購買的問題,需要從程式碼去解析。 ### 3. 為什麼會發生**偷跑**(提早了半小時) NFT 合約的**部署**和**設定**會在銷售活動開始前,通常程式碼會寫出一個未來的開啟時間,等到區塊鏈時間大於開啟時間才能購買。 **通常我們會這麼寫:** ```=solidity require(block.timestamp >= activity.period, "MSActivityCenter: This activity is out of date") ``` 意思是活動會在設定的時間後才開啟。 註: `block.timestamp` 為區塊鏈時間,`activity.period` 為設定的開始時間 ---- ##### 但這份合約的邏輯是相反的 ![](https://i.imgur.com/yGGEujZ.jpg) 時間條件(畫紅線處): ``` activity.period >= block.timestamp ``` 變成在指定的時間前才能購買,也就是這份合約**只設定截止時間**。 #### 所以第二波偷跑是怎麼造成的? ![](https://i.imgur.com/hweXXIo.png) 第二波項目方設定活動時,區塊鏈時間(`block.timestamp`)是 2022/1/11 10:24 (換算秒數:**1641896672**) activity.period 被設定為 **1672272000** 從`presale`函式可以看到時間條件: ``` activity.period >= block.timestamp ``` > `1672272000 >= 1641896672` # 時間條件通過 所以項目方早上 10:24 設定活動,(下圖綠框)時間條件有通過,加上最大發行量(maxCirculation)跟本次發行量(circulation) 被設定為 250 跟 0 個,所以數量限制(下圖紅框)也通過了。才造成原本預計販售時間 11 點,卻在合約一設定就立刻變成可以購買,提早了 36 分鐘。 ![](https://i.imgur.com/yuLZIRt.png) #### 合約優化建議: * 合約的時間條件可以改成 ``` block.timestamp >= activity.startTime ``` 將原本的 period 命名改成 startTime(開賣時間),這樣程式碼的可讀性會更高 --- ### 4.為什麼會發生**超賣**(多賣了200個) **超賣** ![](https://i.imgur.com/YkxWXFN.png) 主要是人為操作的問題。因為每一波活動都需設定 `setActivity` 函式(上圖),這個函式會設定 **NFT價格,活動截止時間,最大發行量,並將此次活動目前發行量設置為 0**。 在第一波結束後,已售出量為 200。設定第二波活動時,**第二波活動**初始發行量被設定為 0(下圖 172 行),所以**最大發行量(maxCirculation) 應該設定 50**,但是第二波設定時把最大發行量設定成 250,多了 200 個。 此合約設計每次活動是獨立的,最大發行量是每次活動的發行量。但是設定活動的人,把**第二波的最大發行量**當作本項目預計釋出的總發行量,才會有 200 落差。 ![](https://i.imgur.com/ncrhLbY.jpg) [第一波活動鏈上設定](https://etherscan.io/tx/0x719ab9537f6d210f6308f22a0adf7ab78cbce11d93c0526b8f391a754162b2e4) [第二波活動鏈上設定](https://etherscan.io/tx/0xf1a55b20727cbf7ee53bf18425e3cd64987337cc0239ed6e3a2982622c6eae03) #### 最大發行量的優化建議 如果只有兩波發行活動,且已確定最大發行量,可以在一開始就設定好 maxSupply,並讓總發行量(totalSupply)不要超過這個數值。 例如 seed NFT如果一開始就打算賣 250 個,可以在 constructor 階段就設定 ``` uint public maxSupply = 250; ``` --- ### 總結 整起 EchoX 事件中我們發現到,合約的設計以及人為操作,確實都需要很謹慎。畢竟合約就是法律條文,從部署上鏈以後,就不能夠輕易的更改。因此合約部署之前,團隊成員彼此間及活動的執行者,都需要進行充分的溝通以及討論。如此一來,才能避免兵荒馬亂,讓團隊在應對不管是好的突發狀況,又或者是壞的突發狀況,都可以很從容地去應對,也可以降低人為操作發生問題的可能性,就像是這次 EchoX 設定數量與官網公告的有3倍以上的差距。 另外也很值得探討的一點是 NFT 項目方與去中心化平台的角色,像這次 EchoX 中間一度不願意承認在二級市場 Opensea 上買到的 NFT。 從中我們也可以進一步感受到去中心化世界的運作,同時對於 NFT 的擁有權及 NFT 項目方給予社群回饋,中間是存在著不確定性的,像是這次 EchoX 所承諾的 RAW 優先訂位權以及加購享用專屬的沉浸式「We Are What We Eat」VR 與料理體驗,EchoX 如果拒絕承認,就算持有NFT,到時候也沒辦法在 RAW 使用優先訂位權的。

    Import from clipboard

    Paste your markdown or webpage here...

    Advanced permission required

    Your current role can only read. Ask the system administrator to acquire write and comment permission.

    This team is disabled

    Sorry, this team is disabled. You can't edit this note.

    This note is locked

    Sorry, only owner can edit this note.

    Reach the limit

    Sorry, you've reached the max length this note can be.
    Please reduce the content or divide it to more notes, thank you!

    Import from Gist

    Import from Snippet

    or

    Export to Snippet

    Are you sure?

    Do you really want to delete this note?
    All users will lose their connection.

    Create a note from template

    Create a note from template

    Oops...
    This template has been removed or transferred.
    Upgrade
    All
    • All
    • Team
    No template.

    Create a template

    Upgrade

    Delete template

    Do you really want to delete this template?
    Turn this template into a regular note and keep its content, versions, and comments.

    This page need refresh

    You have an incompatible client version.
    Refresh to update.
    New version available!
    See releases notes here
    Refresh to enjoy new features.
    Your user state has changed.
    Refresh to load new user state.

    Sign in

    Forgot password

    or

    By clicking below, you agree to our terms of service.

    Sign in via Facebook Sign in via Twitter Sign in via GitHub Sign in via Dropbox Sign in with Wallet
    Wallet ( )
    Connect another wallet

    New to HackMD? Sign up

    Help

    • English
    • 中文
    • Français
    • Deutsch
    • 日本語
    • Español
    • Català
    • Ελληνικά
    • Português
    • italiano
    • Türkçe
    • Русский
    • Nederlands
    • hrvatski jezik
    • język polski
    • Українська
    • हिन्दी
    • svenska
    • Esperanto
    • dansk

    Documents

    Help & Tutorial

    How to use Book mode

    Slide Example

    API Docs

    Edit in VSCode

    Install browser extension

    Contacts

    Feedback

    Discord

    Send us email

    Resources

    Releases

    Pricing

    Blog

    Policy

    Terms

    Privacy

    Cheatsheet

    Syntax Example Reference
    # Header Header 基本排版
    - Unordered List
    • Unordered List
    1. Ordered List
    1. Ordered List
    - [ ] Todo List
    • Todo List
    > Blockquote
    Blockquote
    **Bold font** Bold font
    *Italics font* Italics font
    ~~Strikethrough~~ Strikethrough
    19^th^ 19th
    H~2~O H2O
    ++Inserted text++ Inserted text
    ==Marked text== Marked text
    [link text](https:// "title") Link
    ![image alt](https:// "title") Image
    `Code` Code 在筆記中貼入程式碼
    ```javascript
    var i = 0;
    ```
    var i = 0;
    :smile: :smile: Emoji list
    {%youtube youtube_id %} Externals
    $L^aT_eX$ LaTeX
    :::info
    This is a alert area.
    :::

    This is a alert area.

    Versions and GitHub Sync
    Get Full History Access

    • Edit version name
    • Delete

    revision author avatar     named on  

    More Less

    Note content is identical to the latest version.
    Compare
      Choose a version
      No search result
      Version not found
    Sign in to link this note to GitHub
    Learn more
    This note is not linked with GitHub
     

    Feedback

    Submission failed, please try again

    Thanks for your support.

    On a scale of 0-10, how likely is it that you would recommend HackMD to your friends, family or business associates?

    Please give us some advice and help us improve HackMD.

     

    Thanks for your feedback

    Remove version name

    Do you want to remove this version name and description?

    Transfer ownership

    Transfer to
      Warning: is a public team. If you transfer note to this team, everyone on the web can find and read this note.

        Link with GitHub

        Please authorize HackMD on GitHub
        • Please sign in to GitHub and install the HackMD app on your GitHub repo.
        • HackMD links with GitHub through a GitHub App. You can choose which repo to install our App.
        Learn more  Sign in to GitHub

        Push the note to GitHub Push to GitHub Pull a file from GitHub

          Authorize again
         

        Choose which file to push to

        Select repo
        Refresh Authorize more repos
        Select branch
        Select file
        Select branch
        Choose version(s) to push
        • Save a new version and push
        • Choose from existing versions
        Include title and tags
        Available push count

        Pull from GitHub

         
        File from GitHub
        File from HackMD

        GitHub Link Settings

        File linked

        Linked by
        File path
        Last synced branch
        Available push count

        Danger Zone

        Unlink
        You will no longer receive notification when GitHub file changes after unlink.

        Syncing

        Push failed

        Push successfully