在我們嘗試著創造一個新的收藏品的時候,發現gas費比NFT本身還要貴!
本文旨在解決上面的問題。
接下來我們將看到的是,NFT智能合約的工程團隊去尋找降低gas費用的方法時,會發生什么。
本指南是我們精心研究和實驗的結果。
在本文中,我們將通過不同的方法來提高鑄造的成本效益:
我們真的需要ERC721Enumerable嗎?
使用映射而不是數組
ERC721A標準
從代幣Id1開始
白名單的Merkle樹
打包變量
使用未經檢查的
為什么第一個鑄造的更貴,我們能做什么?
使用優化器
將'if語句'轉換為單獨的函數
本文中提到的所有代碼都可以在我們的Github上找到:
https://github.com/WallStFam/gas-optimization
我們真的需要ERC721Enumerable嗎?
在編寫mint函數時,需要確保該函數使用了所需的最少代碼。
有時,在合約中添加更多的函數以備將來使用,或者使鏈下查詢更容易。問題是,我們添加的任何額外函數都會增加gas成本。
使用昂貴的Mint函數最常見的情況之一是讓我們的合約繼承自ERC721Enumerable。
這個擴展的問題是,它為轉賬增加了大量的開銷。
ERC721Enumerable使用4個映射和一個數組來跟蹤每個用戶的代幣id。在每次轉賬中寫入這些結構要花費大量的gas。
以下是兩個智能合約制造一個代幣的gas成本的比較。一個繼承自ERC721Enumerable,另一個不繼承:
ERC721Enumerable的價格是普通ERC721的2倍!
如果我們看看排在第一次鑄造之后的鑄造,我們就會發現兩者的差異更明顯:
ERC721Enumerable在第一次鑄造后的成本幾乎比ERC721高出3倍!
注意:在solidity中,將變量從0設置為非0比從非0設置為非0更昂貴。這就是為什么在ERC721中的第一個鑄造會更昂貴,因為用戶的余額從0變化到1。
但有趣的是,ERC721中的第一次鑄造更貴,ERC721Enumerable中的第一次鑄造更便宜。如果我們有興趣并且想知道為什么會這樣,請查看OpenZepplin的ERC721Enumerable的98行。
第一次鑄造的是更昂貴的,因為_addtokentownerenumeration(address,uint256)在第一次鑄造將映射中的值設置為零,并將值從0設置為0是沒有成本。
因此,在添加ERC721Enumerable之前,請自問:“我的合約中真的需要這個函數嗎?”
如果我們只打算從合約之外查詢每個用戶的代幣id,那么有一些方法可以做到這一點,而無需使用ERC721Enumerable。
這里有兩種方法:
為每個代幣調用ownerOf(uinttokenId)。
查詢來自ERC721的Transfer事件并處理它們以獲得每個代幣的所有者
可以在我們的Github庫中找到這兩種方法的腳本:
Binance NFT市場推出NFT鑄造功能:6月30日消息,Binance NFT市場宣布推出NFT鑄造功能,允許用戶在BNB Smart Chain和以太坊網絡上創建NFT系列和鑄造NFT。目前該功能開放給在Binance NFT上擁有至少五個關注者且經過驗證的用戶。[2022/6/30 1:42:46]
https://github.com/WallStFam/gas-optimization/blob/master/scripts/getAllOwners/getAllOwners_ownerOf.js
https://github.com/WallStFam/gas-optimization/blob/master/scripts/getAllOwners/getAllOwners_Transfer_event.js
這些腳本查詢區塊鏈以獲得ERC721合約的每個代幣的所有者。
我們在這些腳本中使用了WallStreetDads合約作為示例,但是我們可以自由地將該代碼用于任何其他合約。我們只需要替換abi和合約地址。
使用映射而不是數組
有時可以用映射替代數組的函數。映射的優點是,我們可以訪問任何值,而不必像通常使用數組那樣進行迭代。
例如,在NFT集合中使用白名單是很常見的。被添加到白名單的用戶有優先權,通常可以得到比公開銷售更低的價格。
我們可以做一個白名單使用數組如下:
盡管這段代碼有用,但它有一個大問題:隨著越來越多的用戶被添加到whitelistedUsers數組,調用mintWhitelist()的變得越來越昂貴。這是因為數組越大,需要迭代的次數就越多,以確定是否添加了用戶。
通常情況下,Solidity中的循環可能不是正確的解決方案。在某些情況下使用數組是可以的,但要確保循環是有界的。這意味著循環具有已知的最大迭代次數,并且迭代次數相對較少。在這個例子中,循環沒有邊界,這就是問題所在。
如果循環是無界的,則需要嘗試不同的方法。也許可以將東西移出鏈或使用不同的代碼結構。
讓我們用映射而不是數組來重寫代碼:
使用whitelistedUsers映射允許智能合約在一條指令中檢查一個用戶是否被列入白名單,而不是通過循環迭代。
這使得代碼的執行成本更低,并且當添加更多用戶到白名單時,它不會變得更昂貴(白名單用戶的成本是不變的)。
這是針對不同數量的用戶調用mintWhitelist()的平均成本:
這些值是模擬位于白名單不同位置的350個用戶來計算的。
使用WhitelistArray獲得的值將取決于用戶(msg.sender)在數組中的位置。
如果它在開頭,對mintWhitelist()的調用將比在末尾調用要便宜得多。
對于WhitelistMapping調用mintWhitelist()的gas成本是相同的(min=max=avg),與用戶數無關。
對于WhitelistArray,本例中的最小值和最大值范圍為60.884(對于白名單開頭的用戶)到990.516(對于列表末尾的用戶)。
智能合約的完整源代碼可以在這里找到:
Doodles將于今日正式發布Space Doodle NFT:2月28日消息,NFT 項目 Doodles 官方在社交媒體發文宣布將于今日正式發布 Space Doodle NFT。
此前消息,Space Doodle NFT 采用 NFT 打包技術,持有者可以通過 Doodle Bot 智能合約將自己的 Doodle NFT 打包,隨機生成的宇宙飛船將與原始 Doodle 合成為 Space Doodle NFT,用戶的原始 Doodle NFT 將托管于官方的 Space Doodles 金庫中。[2022/2/28 10:21:06]
https://github.com/WallStFam/gas-optimization/blob/master/contracts/WhitelistArray721.sol
https://github.com/WallStFam/gas-optimization/blob/master/contracts/WhitelistMapping721.sol
下面是用于計算每種方法的gas成本的腳本:
https://github.com/WallStFam/gas-optimization/blob/master/scripts/mintWhitelisted.js
ERC721A標準
來自AzukiNFT(https://www.azuki.com/)的團隊發布了ERC721的新標準ERC721A。
這個新標準允許用戶制造多個代幣,其成本接近制造一個代幣的成本。
Azuki團隊在https://www.erc721a.org/上分享了ERC721A的一個很好的解釋。
我們將重新審視這個新標準,讓它更容易被理解,并展示如何將他們提出的解決方案應用到智能合約的其他方面。
值得一提的是,鑄造過程中節省的一些成本,之后會用于交易。
這取決于我們希望我們的用戶在哪里節省gas。不過,ERC721A的一個好處是,鑄造過程中節省的gas可能比用戶需要為交易支付的額外gas多得多。
出于營銷目的,Azuki團隊比較了使用他們的標準鑄造和使用ERC721Enumerable鑄造的gas成本。
但公平地說,他們應該比較ERC721A和ERC721。當然,將ERC721A與ERC721進行比較的影響不像與ERC721Enumerable進行比較那樣大,正如我們在本文中已經看到的那樣,它增加了大量的開銷。
但幸運的是,即使與ERC721相比,如果我們鑄造多個代幣,ERC721A也會使鑄造成本更低,如下圖所示:
那么,他們是如何實現如此低的gas費的呢?
他們的做法很簡單:
當用戶生成多個代幣時,ERC721A只更新一次鑄造者的余額,并且還將這批代幣的所有者設置為一個整體,而不是每個代幣。
為批次而不是每個代幣設置變量,如果我們希望生成許多代幣,則生成多個代幣的成本會低得多。
正如我們前面提到的,ERC721A的問題是,由于這種鑄造優化,當用戶想要轉移代幣時,將產生更多的gas成本。
下面是通過模擬20個用戶以隨機順序鑄造和轉移不同數量的代幣100次而生成的圖表:
在平均轉賬中,使用ERC721A的代幣要貴55%。
NFT平臺Ethernity與The Sandbox達成戰略合作:1月22日消息,NFT平臺Ethernity宣布與區塊鏈游戲和元宇宙項目The Sandbox達成戰略合作。Ethernety將把其世界級許可證庫帶到The Sandbox元宇宙,允許玩家為其虛擬形象穿上經過認證和完全授權的服裝,這些服裝來自知名明星、球員、品牌、團隊、聯賽等。[2022/1/22 9:06:10]
要決定是否使用ERC721A,請考慮轉移代幣的額外成本,并考慮用戶是否會鑄造大量代幣。
如果我們想自己檢查這些值,或者模擬更高數量的用戶或轉賬,下面是用來計算這些值的腳本:
https://github.com/WallStFam/gas-optimization/blob/master/scripts/vs721A_transfer.js
從代幣ID1開始
許多合約的代幣id從0開始(例如Azuki,BAYC等)。
如果我們,作為創造者,要做第一個鑄造,這是可以的。因為正如我們之前在“我們真的需要ERC721Enumerable嗎?”一節中提到的那樣:在Solidity中,將變量從0設置為非0是很昂貴的。
將代幣id從1開始是一個很好的技巧。這樣我們的第一個鑄造就會便宜得多。
下面是使用初始化為0和1的代幣Id的ERC721A合約的第一個鑄幣廠的比較:
因此,如果我們的某個用戶打算要鑄造第一個,那么通過將代幣初始化為1來降低他們的成本。
白名單的Merkle樹
在前一節“使用映射而不是數組”中,我們展示了實現白名單的合約的示例。
這些示例使用數組或映射來存儲白名單地址。盡管映射比使用數組便宜,但如果我們計劃有1000個(或更多)白名單用戶,那么映射仍然是一個非常昂貴的解決方案。
下面是使用數組和映射將用戶列入白名單的成本:
使用數組的代價非常高,主要是因為每次向白名單添加新用戶時,都需要檢查該用戶是否還未被添加,當更多用戶已經被添加到白名單時,檢查將越來越昂貴。
注意:我們可以對WhitelistArray使用另一種方法,而不檢查用戶是否已經在白名單中,但這仍然會很昂貴,因為它至少與WhitelistMapping一樣昂貴,后者也非常昂貴。
將500、1000甚至2000或3000用戶列入白名單并不罕見。
按照目前的gas價格,白名單上的500名用戶相當于要花費5648美元!
考慮到我們可能不想花那么多錢將用戶添加到我們的白名單中,解決方案和最便宜的方法是使用Merkle樹。
注:我們將解釋Merkle樹的工作原理,但這里有一個視頻可以很好地解釋它——https://www.youtube.com/watch?v=YIc6MNfv5iQ
Merkle樹是一種存儲哈希的二叉樹。樹中的每個葉節點都是一個哈希,父節點是子節點的哈希。
在本例中,我們將使用它來存儲白名單地址,因此樹的每個葉節點都是一個地址的哈希值。
一個包含4個地址的簡單樹看起來像這樣:
哈希H7是Merkle樹的根。為列入白名單用戶而使用的Merkle樹的優點是,我們需要寫入智能合約的唯一數據是Merkle樹的根。
人工智能NFT項目YetAi將在Solana上推出:12月28日消息,YetAi宣布將在Solana上發行其NFT。YetAi是一個100%由AI生成的NFT項目,共包含8888個獨一無二的NFT。
YetAi將尋求為持有者和社區成員提供一系列公用設施,其中包括社區會議、研討會和私人活動的獨家訪問權,其目標是讓更多的人進入加密貨幣和NFT市場。(Globe Newswire)[2021/12/28 8:09:04]
因此,不需要在我們的智能合約中寫入數千個地址,我們只需要寫入一個只有32字節的哈希。
當然,這使得向智能合約編寫白名單的成本變低,而且它與白名單的大小無關(不管白名單的大小是10或10,000,成本將是相同的)。
但也有一些缺點:
使用Merkle樹使白名單的Mint函數更復雜,這導致成本會高一些(進一步我們將看到多少)
從客戶端調用白名單Mint函數需要做更多的工作
要檢查地址是否在Merkle樹中,我們需要提供所謂的Merkle證明。
Merkle證明是一個哈希數組,我們的智能合約將使用它來檢查用戶的地址是否在Merkle樹中。它用于從葉子到根的迭代哈希,然后檢查存儲在智能合約中的根是否與使用證明計算的根相匹配。
如果我們回到這個例子,想象一下智能合約已經存儲了根(H7),并且地址為4的用戶調用了白名單Mint函數。
前端將計算證明并將其傳遞給mint函數。在本例中,證明將是一個包含元素H3和H5的數組。這是因為它首先會計算H4=Hash(地址4),然后計算H6=Hash(H3+H4),最后計算H7=Hash(H5+H6)。
所以它只需要H3和H5從葉子到根計算每一步的哈希值。
在下表中,我們比較了一個使用映射的合約和另一個使用Merkle樹的合約的白名單Mint函數:
幸運的是,使用Merkle樹生成白名單的開銷非常小(大約多15%的gas)。這是因為在solidity中計算哈希的成本很低:
Calculatingahashinsoliditycosts30gas+6gasperbyte.Foratreeofheight10,atotalof10hashesneedtobecalculated:(30+6*4)*10=540gas
為了決定我們是否應該在我們的智能合約中使用Merkle樹,確保我們了解利弊。
gas的成本并不高,但我們需要設置前端,以便它能夠創建Merkle樹的實例,并使用它來計算證明。
下面是用于計算gas成本的腳本(我們還將看到如何使用地址列表創建Merkle樹,以及如何計算證明并傳遞給智能合約):
https://github.com/WallStFam/gas-optimization/blob/master/scripts/vsMerkle.js
我們在腳本中使用了以下智能合約(WhitelistMerkle721.sol),它實現了使用Merkle樹生成白名單的函數:
https://github.com/WallStFam/gas-optimization/blob/master/contracts/WhitelistMerkle721.sol
打包變量
Solidity將變量排列在32字節的槽中。
一些變量,如uint256占用一個完整的槽,但其他像uint8,uint16,bool等,只占用槽的一部分。
這意味著要讀取一個uint8,例如,我們需要讀取在同一槽的其他變量。
音樂家Grimes以超600萬美元出售NFT收藏品:音樂家和視覺藝術家Grimes以超過600萬美元的價格出售了一系列NFT收藏品。(Decrypt)[2021/3/2 18:05:40]
我們可以使用這個函數來優化gas成本:如果我們有一個函數,它使用的變量都在同一個槽中,那么讀取和寫入變量的成本會更低,因為我們只需要加載一次槽。
讓我們看看打包3個變量的兩種方法:
由于變量是按照它們在智能合約中輸入的順序打包的,在A)的情況下,這3個變量使用了3個槽,因為var2使用了一個完整的槽,使得其他變量各使用一個槽。
在B中,3個變量使用2個槽,因為var2和var3可以打包在一起。
當我們以這種方式打包變量時,我們可以節省部署合約和調用使用它們的函數的成本。
讓我們來看看調用以下函數的gas成本:
正如我們所看到的,調用foo()的gas成本增加了近5000個gas單位,這僅僅是因為變量的打包方式!
現在考慮一下在智能合約中調用和分配變量的所有不同位置。所有那些對未打包或未正確打包的變量的調用將累加。
另一件需要注意的事情是,選擇將哪些變量打包在一起也很重要,因為如果變量需要同時加載,我們更愿意將它們打包在一起。
如果一個函數將被調用多次,請確保可以將函數使用的所有變量放入盡可能小的槽中。
使用未經檢查的
算術操作可以打包在未檢查的區塊中,這樣編譯器就不會包含額外的操作代碼來檢查下溢/溢出。這可以提高代碼的成本效率。
讓我們來看一個例子:
checked和unchecked_函數執行相同的算術操作,但它們使用的gas量不同:
節省的空間并不大,但如果我們有很多不同的算術操作,例如,在for循環中修改迭代器的值,那么我們可以使用未經檢查的區塊為用戶節省一些gas。
為什么第一個鑄造更貴,我們能做什么?
第一次鑄造通常更貴,因為有一些變量會從零變為非零,這在Solidity中非常昂貴。
給定一個變量,這是將其設置為“0到非0”、“非0到非0”和“從非0到0”的成本:
設置一個從0到非0的變量的成本幾乎是設置一個從非0到非0的變量的兩倍。
所以,我們應該注意到這一點,如果可以將變量初始化為非0,而不是0,那么我們可以為用戶節省一些gas。
可以查看智能合約和用于計算gas成本的腳本:
https://github.com/WallStFam/gas-optimization/blob/master/contracts/SetVariables.sol
https://github.com/WallStFam/gas-optimization/blob/master/scripts/setVariables.js
使用優化器
Solidity編譯器帶有一個集成的優化器。
優化器應該能夠降低部署和函數調用的gas成本。我們要測試一下這是不是真的!
為了使用優化器,我們需要啟用它并設置“運行次數”。
從文檔中可以看出:“運行的次數(--optimize-runs)大致指定了在合約的生命周期內,部署代碼的每個操作碼執行的頻率”。
通俗地說,這意味著如果我們有需要多次調用的函數,那么我們應該設置較高的運行次數,以提示編譯器如何優化我們的代碼。
注意:默認行為取決于每個平臺。例如,在Hardhat中,優化器被禁用,默認運行200次。
優化器有許多不同類型的優化。關于它們的詳細解釋以及優化器是如何工作的,請參考Solidity團隊的AMA(SolidityOptimizer部分):https://blog.soliditylang.org/2020/11/04/solidity-ama-1-recap/。
為了評估優化器的有效性,我們測試了不同的mint函數,將run參數設置為1、200和5000。我們還在關閉優化器的情況下測試了代碼:
鑄造1個代幣:
鑄造10個代幣:
鑄造100個代幣:
我們注意到的第一件事是,關閉優化器總是會導致mint函數的成本更高。但不幸的是,使用優化器并沒有太多好處。
gas的價格是較低,但只是少了一點點。此外,盡管運行次數越多,效果越好,但對于所有mint類型和不同的運行次數,結果幾乎是相同的。
僅僅看這些結果,我們可能會得出這樣的結論:“也許它沒有那么好,但至少是更好了!”所以我可能會打開它,并設置運行的數量非常高。”
在得出早期結論之前,我們需要再看一下優化器的另一個方面。
優化器的工作方式是,做出一些犧牲,可能會增加生成的字節碼的大小,這將增加部署智能合約的成本。
讓我們看看通過改變運行量和設置優化器來部署每個合約需要多少成本:
第一個結論:最好設置優化器。將關閉優化器將使部署和調用函數的成本更高。
真正令人驚訝的是,當關閉優化器時,部署的成本會高得多,幾乎是原來的兩倍。
第二個結論:“運行次數”對部署成本的影響最大。運行設置為5000時,部署成本比運行設置為1或200時高出20%左右。
希望這能讓我們大致了解,在將優化器應用于代碼時,它會帶來什么。
但請務必理解,我們給出的值只適用于ERC721的不同變體的mint函數,如果我們的合約執行完全不同的代碼,我們可能會發現不同的結果。
因此,請確保以類似于我們介紹的方式測試代碼,以找到針對特定情況的最便宜配置。
如果我們想自己測試這些函數或查看我們是如何計算這些值的,請參閱存儲庫中的scripts/testOptimizer.js文件。
我們可以將hardhat.config.js中的優化器運行參數更改為我們想要測試的任何值。我們還可以在那里啟用和禁用優化器。在進行任何更改后,請確保重新編譯代碼。
將'if語句'轉換為單獨的函數
WallStMoms的智能合約使用了我們稱為“鑄造階段”的東西。
有3個階段:經典,現代和元,每個階段都有不同的要求和鑄造限制。
最初,我們認為在所有階段使用一個Mint函數是一個好主意。在內部,該函數將使用“if語句”檢查當前階段,并相應地進行操作。
這樣,智能合約接口將只是一個函數,前端客戶端將不需要知道合約當前處于哪個階段。
這種方法可能適用于其他環境,但對于智能合約,它不是被推薦的模式。問題在于,檢查智能合約中的階段增加了復雜性,降低了可讀性,并增加了gas成本。
讓我們看一個簡單的例子:
如果仔細觀察這兩個函數,就會發現調用mint()與調用mintPhases(1)的效果完全相同。這是兩個通信的gas費用:
使用if語句添加300個gas。
注:這段代碼只是一個示例,用于說明這個概念。
mintphase函數可以被分成兩個不同的函數來節省gas成本:
為每個階段使用單獨的函數將'if語句'的成本傳遞給前端。
現在,我們可能認為300美元不是什么大問題(實際上在當前的價格中大約是5美分),但要意識到,我們在這里只是提出了一個非常簡單的例子,同樣的模式可以應用到更復雜的情況。
此外,所有的gas成本都加起來了,在這種情況下,使用該模式沒有缺點,我們只需要在前端添加一些邏輯,以讀取智能合約處于哪個階段,并調用相應的mintPhases_x函數。
自己做測試
推動區塊鏈技術向前發展的最佳方法之一是為最終用戶創建更好的用戶體驗。
降低gas成本是創造更好用戶體驗的好方法。
在本文中,我們探索了可以應用于智能合約的不同通用技術。
但是如何處理自己的自定義代碼呢?
在Solidity中優化代碼的最佳方法是測試函數的gas成本。
這個想法很簡單,我們計算一個函數消耗了多少gas,對代碼進行更改,然后再次計算,看看是否減少了gas成本。
我們可以這樣計算任何函數的gas成本:
我們可以在任何環境中運行這個代碼,我們總是會得到相同的gasUsed(hardhat,rinkeby,mainnet等)。
當我們在測試我們的函數時,要特別注意那些我們的用戶會調用最多的函數(例如mint函數)。
通過降低gas費,我們不僅可以幫助我們的項目,也可以幫助整個生態系統。
注意:我們在本文開頭共享的存儲庫中有與每個部分相關的智能合約和腳本。我們可以看到如何計算這些腳本中所有函數的gas成本。
受歡迎的合約
在本文的最后,我們想看看流行的NFT集合的智能合約的gas成本。
我們選擇了BAYC,Doodles和CoolCats。
讓我們看看每個合約的mint函數成本是多少:
令人驚訝的是,這三份合約的鑄造成本都非常相似,而且成本很高。
使用本文介紹的技術,我們可以將鑄造1個代幣的成本設定為60,000個gas左右,鑄造5個代幣的成本設定為70,000個gas左右(如果我們使用ERC721A來利用多個代幣鑄造)。
這3個合約之所以如此昂貴,是因為它們都使用了ERC721Enumerable,正如我們在“我們真的需要ERC721Enumerable嗎?”所說,大多數時候是可以避免的。
鑄造5個代幣的成本非常高,如果他們實施ERC721A或類似的解決方案,他們會讓他們的用戶大受青睞。
總結
我們仍處于Web3生態系統的早期階段。不利的一面意味著我們都將面對糟糕的用戶體驗和昂貴的運營。
好處是有機會排除所有可能的解決方法,以確保獲得良好的體驗。
Source:https://medium.com/@WallStFam/the-ultimate-guide-to-nft-gas-optimization-7e9289e2d88f
Tags:GASMERNFTINTOntology GasxFarmerMilady Vault (NFTX)MyPoints E-Commerce
相信喜歡打游戲的伙伴對“代練”并不陌生,所謂“代練”是指在游戲中有償幫助他人提升等級級、打裝備、攢素材、肝活動、通過特定關卡、提升游戲段位等的行為.
1900/1/1 0:00:00元宇宙的概念引發了熱烈的討論,但已經有人提出了關于安全性的問題;虛擬世界可能會為網絡釣魚、身份盜竊甚至間諜活動提供新機會;我們必須在元宇宙早期就建立核心安全原則.
1900/1/1 0:00:00注:這是我的一篇有關Web3時代創作者經濟的文章,主要從技術結構的角度介紹我對Web3時代創作者經濟的一些思考。對于所有創作者而言,這是最好的時代.
1900/1/1 0:00:00DeFi數據 1.DeFi代幣總市值:1223.21億美元 DeFi總市值數據來源:coingecko2.過去24小時去中心化交易所的交易量:40.
1900/1/1 0:00:00本次討論主要關于潤的2個事情,一個是身法,關于出海方法詳情;一個是心法,就是心理建設、如何克服心魔。大家每個人在web3行業情況不同,各位嘉賓從不同的心路歷程進行分享,給大家參考.
1900/1/1 0:00:00前言 圍繞創作者經濟的炒作可能在一年前達到頂峰,但我相信,現在是決定創作者經濟命運的最重要時刻。這是過去18個月中出現的兩股強大但對立的力量之間拉鋸戰的結果,這兩股力量都應該對創作者有利.
1900/1/1 0:00:00