Solidity

跟踪 uint256 到結構映射的正確方法是什麼?

  • August 11, 2022

下面的智能合約是使用者可以出售物品的市場。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

編輯

我重構了整個契約。

我重命名priceInEtherpriceInWei以使其更清晰。我添加了一個檢查,_name所以你不允許和空字元串作為報價名稱。我重構了acceptOffer函式。

映射的要點是鍵必須始終是唯一的——你永遠不可能有兩個具有相同 ID。您是對的,儘管您可能會遇到這樣的情況,即使用 Id 創建條目,然後很快被具有相同 id 鍵的另一個條目覆蓋。

我建議為您的 uint256 條目創建一個 uuid,而不是增加一個值。您目前無法輕鬆生成 uuid,但您可以安全地使用 offer 對像中的值和 keccak256 散列來生成對 uuid 非常安全的東西。您可以添加一個額外的檢查,例如“如果映射鍵已經具有值 - 然後生成並嘗試一個新的 uuid”。現在我想到了,你也可以為你的標準值增加做,哈哈。只需事先添加一個檢查它不存在。

引用自:https://ethereum.stackexchange.com/questions/133369