跟踪 uint256 到結構映射的正確方法是什麼?
下面的智能合約是使用者可以出售物品的市場。struct Offer,包含特定項目的所有資訊。為了跟踪報價,我使用了一個帶有 uint256 鍵的映射,並創建了一個計數器變數,每次創建報價時遞增一。我的問題是這是正確的方法嗎?例如,如果有機會同時創建了兩個報價,它們是否具有相同的 ID?這是程式碼範例:
//SPDX-License-Identifier: MIT pragma solidity >=0.4.22 <0.9.0; contract MarketPlace { uint256 public offerCount = 0; struct Offer { uint256 id; uint256 priceInEther; uint256 createdAt; address seller; address buyer; string itemName; bool isActive; } mapping(uint256 => Offer) public offers; event OfferCreated(uint256 id, uint256 price, uint256 createdAt); event OfferBought(uint256 id, address buyer, uint256 boughtAt); function createOffer(uint256 _priceInEther, string memory _name) external { require(_priceInEther > 0, "Amount must be positive"); uint256 id = offerCount++; offers[id].id = id; offers[id].priceInEther = _priceInEther; offers[id].createdAt = block.timestamp; offers[id].seller = msg.sender; offers[id].itemName = _name; offers[id].isActive = true; emit OfferCreated(id, _priceInEther, block.timestamp); } function acceptOffer(uint256 _offerId) external payable { address buyer = msg.sender; require(offers[_offerId].isActive == true, "Offer is not active."); require(offers[_offerId].seller != buyer, "You can not buy your own product."); require(msg.value == offers[_offerId].priceInEther, "Please pay the item's price."); offers[_offerId].buyer = buyer; offers[_offerId].isActive = false; emit OfferBought(_offerId, buyer, block.timestamp); } }
任何幫助或見解將不勝感激。
創建增加的 id 不是一個好主意,即使在正常軟體開發中也是如此。這樣做將使您的所有數據易於被攻擊者索引和利用。
在正常軟體開發中,我們會生成像 UUID 或其他格式的隨機 id。但在智能合約中,獲得隨機 id 並不是那麼容易。
我建議您創建一個從數據本身派生的 id,從您知道應該且不會更改的數據中創建。在您的情況下,您可以使用您知道永遠不會更改的報價欄位,對其進行編碼並創建一個
keccak256
雜湊作為其 id,這樣 id 實際上與數據相關並且可以輕鬆地從中派生,就像如何交易和區塊的雜湊值來自交易和區塊本身。我重構了你的程式碼來處理這個:
contract MarketPlace { struct Offer { bytes32 id; uint256 priceInWei; uint256 createdAt; address seller; address buyer; string itemName; bool isActive; } mapping(bytes32 => Offer) public offers; event OfferCreated(bytes32 id, uint256 price, uint256 createdAt); event OfferBought(bytes32 id, address buyer, uint256 boughtAt); function createOffer(uint256 _priceInWei, string memory _name) external returns(bytes32 offerHash) { require(_priceInWei > 0, "Amount must be positive"); require(keccak256(abi.encodePacked(_name)) != keccak256(""), "Offer should have a valid name"); offerHash = generateOfferHash(_priceInWei, _name); Offer memory offer = Offer(offerHash, _priceInWei, block.timestamp, msg.sender, address(0), _name, true); offers[offerHash] = offer; emit OfferCreated(offerHash, _priceInWei, block.timestamp); } function generateOfferHash(uint256 _priceInWei, string memory _name) internal view returns(bytes32) { // Using values from the offer that we know should and will not change, to create the offer hash (offer id). // If there is a value that should and will change, then we should not use it to create the offer's hash. return keccak256(abi.encodePacked( _priceInWei, block.timestamp, msg.sender, _name )); } function acceptOffer(bytes32 _offerId) external payable { address buyer = msg.sender; Offer storage offer = offers[_offerId]; require(offer.isActive, "Offer is not active."); require(offer.seller != buyer, "You can not buy your own product."); require(msg.value == offer.priceInWei, "Please pay the item's price."); offer.buyer = buyer; offer.isActive = false; emit OfferBought(_offerId, buyer, block.timestamp); } }
現在請注意,我建議
Offer
首先在記憶體中創建實例,而不是像您的程式碼那樣多次訪問儲存。在記憶體中創建它,然後在將其保存在映射中的同時將其寫入儲存,這樣更省氣,因為每次offers[id].property = value
從儲存中讀取都比從記憶體中讀取要貴一些。檢查操作碼列表和它們消耗的氣體量,並註意
MLOAD
(記憶體負載)消耗的氣體比SLOAD
(儲存負載)少:https ://github.com/crytic/evm-opcodes編輯
我重構了整個契約。
我重命名
priceInEther
為priceInWei
以使其更清晰。我添加了一個檢查,_name
所以你不允許和空字元串作為報價名稱。我重構了acceptOffer
函式。
映射的要點是鍵必須始終是唯一的——你永遠不可能有兩個具有相同 ID。您是對的,儘管您可能會遇到這樣的情況,即使用 Id 創建條目,然後很快被具有相同 id 鍵的另一個條目覆蓋。
我建議為您的 uint256 條目創建一個 uuid,而不是增加一個值。您目前無法輕鬆生成 uuid,但您可以安全地使用 offer 對像中的值和 keccak256 散列來生成對 uuid 非常安全的東西。您可以添加一個額外的檢查,例如“如果映射鍵已經具有值 - 然後生成並嘗試一個新的 uuid”。現在我想到了,你也可以為你的標準值增加做,哈哈。只需事先添加一個檢查它不存在。