From bc97f34ffa460a0f4d36619b6f4d9b9f7cd02ef6 Mon Sep 17 00:00:00 2001 From: zajck Date: Tue, 24 Oct 2023 11:12:30 +0200 Subject: [PATCH 1/5] Uncomment slither GA --- .github/workflows/ci.yaml | 62 +++++++++++++++++++-------------------- 1 file changed, 31 insertions(+), 31 deletions(-) diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 43937a1e2..9056d1495 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -194,34 +194,34 @@ jobs: if: failure() uses: andymckay/cancel-action@0.2 -# analyze: -# needs: setup -# runs-on: ubuntu-latest -# permissions: -# contents: read -# security-events: write -# steps: -# - name: Checkout repository -# uses: actions/checkout@v3.1.0 -# - name: Setup node -# uses: actions/setup-node@v3.5.1 -# with: -# node-version: 16.14.x -# cache: "npm" -# - name: Install Dependencies -# run: npm install -# - name: Prepare Environment -# shell: bash -# run: | -# cp .env.example .env -# - name: Slither analyzer -# uses: crytic/slither-action@v0.3.0 -# id: slither -# with: -# node-version: 16 -# sarif: results.sarif -# fail-on: none -# - name: Upload SARIF file -# uses: github/codeql-action/upload-sarif@v2 -# with: -# sarif_file: ${{ steps.slither.outputs.sarif }} + analyze: + needs: setup + runs-on: ubuntu-latest + permissions: + contents: read + security-events: write + steps: + - name: Checkout repository + uses: actions/checkout@v3.1.0 + - name: Setup node + uses: actions/setup-node@v3.5.1 + with: + node-version: 16.14.x + cache: "npm" + - name: Install Dependencies + run: npm install + - name: Prepare Environment + shell: bash + run: | + cp .env.example .env + - name: Slither analyzer + uses: crytic/slither-action@v0.3.0 + id: slither + with: + node-version: 16 + sarif: results.sarif + fail-on: none + - name: Upload SARIF file + uses: github/codeql-action/upload-sarif@v2 + with: + sarif_file: ${{ steps.slither.outputs.sarif }} From d7a3cb0f8d91abf2f4aa0d25741805d514b92063 Mon Sep 17 00:00:00 2001 From: zajck Date: Tue, 24 Oct 2023 12:08:42 +0200 Subject: [PATCH 2/5] Extend commitToOffer metatx --- contracts/domain/BosonConstants.sol | 6 ++-- contracts/domain/BosonTypes.sol | 2 +- .../facets/MetaTransactionsHandlerFacet.sol | 25 +++++++++++++-- test/protocol/MetaTransactionsHandlerTest.js | 31 +++++++++++++++++-- 4 files changed, 57 insertions(+), 7 deletions(-) diff --git a/contracts/domain/BosonConstants.sol b/contracts/domain/BosonConstants.sol index b918f7e56..b0ef082d2 100644 --- a/contracts/domain/BosonConstants.sol +++ b/contracts/domain/BosonConstants.sol @@ -212,9 +212,11 @@ bytes32 constant META_TRANSACTION_TYPEHASH = keccak256( "MetaTransaction(uint256 nonce,address from,address contractAddress,string functionName,bytes functionSignature)" ) ); -bytes32 constant OFFER_DETAILS_TYPEHASH = keccak256("MetaTxOfferDetails(address buyer,uint256 offerId)"); +bytes32 constant OFFER_DETAILS_TYPEHASH = keccak256( + "MetaTxOfferDetails(address buyer,uint256 offerId,address exchangeToken,uint256 price,uint256 sellerDeposit,uint256 buyerCancelPenalty,uint256 voucherRedeemableFrom,uint256 disputePeriod,uint256 resolutionPeriod)" +); bytes32 constant META_TX_COMMIT_TO_OFFER_TYPEHASH = keccak256( - "MetaTxCommitToOffer(uint256 nonce,address from,address contractAddress,string functionName,MetaTxOfferDetails offerDetails)MetaTxOfferDetails(address buyer,uint256 offerId)" + "MetaTxCommitToOffer(uint256 nonce,address from,address contractAddress,string functionName,MetaTxOfferDetails offerDetails)MetaTxOfferDetails(address buyer,uint256 offerId,address exchangeToken,uint256 price,uint256 sellerDeposit,uint256 buyerCancelPenalty,uint256 voucherRedeemableFrom,uint256 disputePeriod,uint256 resolutionPeriod)" ); bytes32 constant CONDITIONAL_OFFER_DETAILS_TYPEHASH = keccak256( "MetaTxConditionalOfferDetails(address buyer,uint256 offerId,uint256 tokenId)" diff --git a/contracts/domain/BosonTypes.sol b/contracts/domain/BosonTypes.sol index c0e222f2d..0b24588cf 100644 --- a/contracts/domain/BosonTypes.sol +++ b/contracts/domain/BosonTypes.sol @@ -282,7 +282,7 @@ contract BosonTypes { struct HashInfo { bytes32 typeHash; - function(bytes memory) internal pure returns (bytes32) hashFunction; + function(bytes memory) internal view returns (bytes32) hashFunction; } struct OfferFees { diff --git a/contracts/protocol/facets/MetaTransactionsHandlerFacet.sol b/contracts/protocol/facets/MetaTransactionsHandlerFacet.sol index 72de932b0..5134a9aea 100644 --- a/contracts/protocol/facets/MetaTransactionsHandlerFacet.sol +++ b/contracts/protocol/facets/MetaTransactionsHandlerFacet.sol @@ -111,9 +111,30 @@ contract MetaTransactionsHandlerFacet is IBosonMetaTransactionsHandler, Protocol * @param _offerDetails - the offer details * @return the hashed representation of the offer details struct */ - function hashOfferDetails(bytes memory _offerDetails) internal pure returns (bytes32) { + function hashOfferDetails(bytes memory _offerDetails) internal view returns (bytes32) { + // The buyer and offerId are part of calldata (address buyer, uint256 offerId) = abi.decode(_offerDetails, (address, uint256)); - return keccak256(abi.encode(OFFER_DETAILS_TYPEHASH, buyer, offerId)); + + // Get other offer details from the protocol + Offer storage offer = getValidOffer(offerId); + OfferDates storage offerDates = fetchOfferDates(offerId); + OfferDurations storage offerDurations = fetchOfferDurations(offerId); + + return + keccak256( + abi.encode( + OFFER_DETAILS_TYPEHASH, + buyer, + offerId, + offer.exchangeToken, + offer.price, + offer.sellerDeposit, + offer.buyerCancelPenalty, + offerDates.voucherRedeemableFrom, + offerDurations.disputePeriod, + offerDurations.resolutionPeriod + ) + ); } /** diff --git a/test/protocol/MetaTransactionsHandlerTest.js b/test/protocol/MetaTransactionsHandlerTest.js index db5723d27..b7c10d987 100644 --- a/test/protocol/MetaTransactionsHandlerTest.js +++ b/test/protocol/MetaTransactionsHandlerTest.js @@ -1648,6 +1648,13 @@ describe("IBosonMetaTransactionsHandler", function () { offerType = [ { name: "buyer", type: "address" }, { name: "offerId", type: "uint256" }, + { name: "exchangeToken", type: "address" }, + { name: "price", type: "uint256" }, + { name: "sellerDeposit", type: "uint256" }, + { name: "buyerCancelPenalty", type: "uint256" }, + { name: "voucherRedeemableFrom", type: "uint256" }, + { name: "disputePeriod", type: "uint256" }, + { name: "resolutionPeriod", type: "uint256" }, ]; // Set the message Type @@ -1671,8 +1678,19 @@ describe("IBosonMetaTransactionsHandler", function () { offerId: offer.id, }; + const extendedOfferDetails = { + ...validOfferDetails, + exchangeToken: offer.exchangeToken, + price: offer.price, + sellerDeposit: offer.sellerDeposit, + buyerCancelPenalty: offer.buyerCancelPenalty, + voucherRedeemableFrom: offerDates.voucherRedeemableFrom, + disputePeriod: offerDurations.disputePeriod, + resolutionPeriod: offerDurations.resolutionPeriod, + }; + // Prepare the message - message.offerDetails = validOfferDetails; + message.offerDetails = extendedOfferDetails; // Deposit native currency to the same seller id await fundsHandler @@ -1733,7 +1751,16 @@ describe("IBosonMetaTransactionsHandler", function () { validOfferDetails.offerId = offerId; // Prepare the message - message.offerDetails = validOfferDetails; + message.offerDetails = { + ...validOfferDetails, + exchangeToken: offer.exchangeToken, + price: offer.price, + sellerDeposit: offer.sellerDeposit, + buyerCancelPenalty: offer.buyerCancelPenalty, + voucherRedeemableFrom: offerDates.voucherRedeemableFrom, + disputePeriod: offerDurations.disputePeriod, + resolutionPeriod: offerDurations.resolutionPeriod, + }; // Collect the signature components let { r, s, v } = await prepareDataSignatureParameters( From 41a07e4b58ca221079631c96f8da1c9b7d30ff8c Mon Sep 17 00:00:00 2001 From: zajck Date: Fri, 27 Oct 2023 11:17:48 +0200 Subject: [PATCH 3/5] Format dates and durations --- contracts/domain/BosonConstants.sol | 4 +- .../facets/MetaTransactionsHandlerFacet.sol | 40 ++++++++++-- package-lock.json | 13 +++- package.json | 3 +- test/protocol/MetaTransactionsHandlerTest.js | 62 +++++++++++++++---- 5 files changed, 101 insertions(+), 21 deletions(-) diff --git a/contracts/domain/BosonConstants.sol b/contracts/domain/BosonConstants.sol index b0ef082d2..6c1411b9d 100644 --- a/contracts/domain/BosonConstants.sol +++ b/contracts/domain/BosonConstants.sol @@ -213,10 +213,10 @@ bytes32 constant META_TRANSACTION_TYPEHASH = keccak256( ) ); bytes32 constant OFFER_DETAILS_TYPEHASH = keccak256( - "MetaTxOfferDetails(address buyer,uint256 offerId,address exchangeToken,uint256 price,uint256 sellerDeposit,uint256 buyerCancelPenalty,uint256 voucherRedeemableFrom,uint256 disputePeriod,uint256 resolutionPeriod)" + "MetaTxOfferDetails(address buyer,uint256 offerId,address exchangeToken,uint256 price,uint256 sellerDeposit,uint256 buyerCancelPenalty,string voucherRedeemableFrom,string disputePeriod,string resolutionPeriod)" ); bytes32 constant META_TX_COMMIT_TO_OFFER_TYPEHASH = keccak256( - "MetaTxCommitToOffer(uint256 nonce,address from,address contractAddress,string functionName,MetaTxOfferDetails offerDetails)MetaTxOfferDetails(address buyer,uint256 offerId,address exchangeToken,uint256 price,uint256 sellerDeposit,uint256 buyerCancelPenalty,uint256 voucherRedeemableFrom,uint256 disputePeriod,uint256 resolutionPeriod)" + "MetaTxCommitToOffer(uint256 nonce,address from,address contractAddress,string functionName,MetaTxOfferDetails offerDetails)MetaTxOfferDetails(address buyer,uint256 offerId,address exchangeToken,uint256 price,uint256 sellerDeposit,uint256 buyerCancelPenalty,string voucherRedeemableFrom,string disputePeriod,string resolutionPeriod)" ); bytes32 constant CONDITIONAL_OFFER_DETAILS_TYPEHASH = keccak256( "MetaTxConditionalOfferDetails(address buyer,uint256 offerId,uint256 tokenId)" diff --git a/contracts/protocol/facets/MetaTransactionsHandlerFacet.sol b/contracts/protocol/facets/MetaTransactionsHandlerFacet.sol index 5134a9aea..bbb6d19ef 100644 --- a/contracts/protocol/facets/MetaTransactionsHandlerFacet.sol +++ b/contracts/protocol/facets/MetaTransactionsHandlerFacet.sol @@ -10,6 +10,8 @@ import { DiamondLib } from "../../diamond/DiamondLib.sol"; import { ProtocolLib } from "../libs/ProtocolLib.sol"; import { ProtocolBase } from "../bases/ProtocolBase.sol"; import { EIP712Lib } from "../libs/EIP712Lib.sol"; +import { DateTime } from "@quant-finance/solidity-datetime/contracts/DateTime.sol"; +import { Strings } from "@openzeppelin/contracts/utils/Strings.sol"; /** * @title MetaTransactionsHandlerFacet @@ -117,7 +119,27 @@ contract MetaTransactionsHandlerFacet is IBosonMetaTransactionsHandler, Protocol // Get other offer details from the protocol Offer storage offer = getValidOffer(offerId); - OfferDates storage offerDates = fetchOfferDates(offerId); + bytes memory redeemableFrom; + { + OfferDates storage offerDates = fetchOfferDates(offerId); + + (uint256 year, uint256 month, uint256 day, uint256 hour, uint256 minute, uint256 second) = DateTime + .timestampToDateTime(offerDates.voucherRedeemableFrom); + redeemableFrom = abi.encodePacked( + Strings.toString(year), + "/", + Strings.toString(month), + "/", + Strings.toString(day), + " ", + Strings.toString(hour), + ":", + Strings.toString(minute), + ":", + Strings.toString(second) + ); + } + OfferDurations storage offerDurations = fetchOfferDurations(offerId); return @@ -130,9 +152,19 @@ contract MetaTransactionsHandlerFacet is IBosonMetaTransactionsHandler, Protocol offer.price, offer.sellerDeposit, offer.buyerCancelPenalty, - offerDates.voucherRedeemableFrom, - offerDurations.disputePeriod, - offerDurations.resolutionPeriod + keccak256(redeemableFrom), + keccak256( + abi.encodePacked( + Strings.toString(offerDurations.disputePeriod / DateTime.SECONDS_PER_DAY), + " days" + ) + ), + keccak256( + abi.encodePacked( + Strings.toString(offerDurations.resolutionPeriod / DateTime.SECONDS_PER_DAY), + " days" + ) + ) ) ); } diff --git a/package-lock.json b/package-lock.json index e0ac94776..1c2c9b538 100644 --- a/package-lock.json +++ b/package-lock.json @@ -10,7 +10,8 @@ "license": "GPL-3.0-or-later", "dependencies": { "@openzeppelin/contracts": "^4.9.0", - "@openzeppelin/contracts-upgradeable": "4.9.3" + "@openzeppelin/contracts-upgradeable": "4.9.3", + "@quant-finance/solidity-datetime": "^2.2.0" }, "devDependencies": { "@bosonprotocol/solidoc": "3.0.3", @@ -2430,6 +2431,11 @@ "dev": true, "optional": true }, + "node_modules/@quant-finance/solidity-datetime": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@quant-finance/solidity-datetime/-/solidity-datetime-2.2.0.tgz", + "integrity": "sha512-iO0EnqPKTzGCgQOkI9lerpJc0XKUhMNurSjHcA7p7nlP2K2z3U4kk9OC9eQkZUrdBtltft+kIibiDdIOYWuQMg==" + }, "node_modules/@redux-saga/core": { "version": "1.2.3", "resolved": "https://registry.npmjs.org/@redux-saga/core/-/core-1.2.3.tgz", @@ -21348,6 +21354,11 @@ "dev": true, "optional": true }, + "@quant-finance/solidity-datetime": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@quant-finance/solidity-datetime/-/solidity-datetime-2.2.0.tgz", + "integrity": "sha512-iO0EnqPKTzGCgQOkI9lerpJc0XKUhMNurSjHcA7p7nlP2K2z3U4kk9OC9eQkZUrdBtltft+kIibiDdIOYWuQMg==" + }, "@redux-saga/core": { "version": "1.2.3", "resolved": "https://registry.npmjs.org/@redux-saga/core/-/core-1.2.3.tgz", diff --git a/package.json b/package.json index 597b510c0..243048c69 100644 --- a/package.json +++ b/package.json @@ -77,7 +77,8 @@ }, "dependencies": { "@openzeppelin/contracts": "^4.9.0", - "@openzeppelin/contracts-upgradeable": "4.9.3" + "@openzeppelin/contracts-upgradeable": "4.9.3", + "@quant-finance/solidity-datetime": "^2.2.0" }, "devDependencies": { "@bosonprotocol/solidoc": "3.0.3", diff --git a/test/protocol/MetaTransactionsHandlerTest.js b/test/protocol/MetaTransactionsHandlerTest.js index b7c10d987..581d31799 100644 --- a/test/protocol/MetaTransactionsHandlerTest.js +++ b/test/protocol/MetaTransactionsHandlerTest.js @@ -1652,9 +1652,9 @@ describe("IBosonMetaTransactionsHandler", function () { { name: "price", type: "uint256" }, { name: "sellerDeposit", type: "uint256" }, { name: "buyerCancelPenalty", type: "uint256" }, - { name: "voucherRedeemableFrom", type: "uint256" }, - { name: "disputePeriod", type: "uint256" }, - { name: "resolutionPeriod", type: "uint256" }, + { name: "voucherRedeemableFrom", type: "string" }, + { name: "disputePeriod", type: "string" }, + { name: "resolutionPeriod", type: "string" }, ]; // Set the message Type @@ -1678,15 +1678,57 @@ describe("IBosonMetaTransactionsHandler", function () { offerId: offer.id, }; + const SECONDS_PER_DAY = 24n * 60n * 60n; + const SECONDS_PER_HOUR = 60n * 60n; + const SECONDS_PER_MINUTE = 60n; + const OFFSET19700101 = 2440588n; + + function timestampToDateTime(timestamp) { + timestamp = BigInt(timestamp); + let [year, month, day] = _daysToDate(timestamp / SECONDS_PER_DAY); + let secs = timestamp % SECONDS_PER_DAY; + let hour = secs / SECONDS_PER_HOUR; + secs = secs % SECONDS_PER_HOUR; + let minute = secs / SECONDS_PER_MINUTE; + let second = secs % SECONDS_PER_MINUTE; + + return [year, month, day, hour, minute, second]; + } + + function _daysToDate(_days) { + let __days = _days; + + let L = __days + 68569n + OFFSET19700101; + let N = (4n * L) / 146097n; + L = L - (146097n * N + 3n) / 4n; + let _year = (4000n * (L + 1n)) / 1461001n; + L = L - (1461n * _year) / 4n + 31n; + let _month = (80n * L) / 2447n; + let _day = L - (2447n * _month) / 80n; + L = _month / 11n; + _month = _month + 2n - 12n * L; + _year = 100n * (N - 49n) + _year + L; + + let year = _year; + let month = _month; + let day = _day; + return [year, month, day]; + } + + let [year, month, day, hour, minute, second] = timestampToDateTime( + Number(offerDates.voucherRedeemableFrom) + ); + let voucherRedeemableFrom = `${year}/${month}/${day} ${hour}:${minute}:${second}`; + const extendedOfferDetails = { ...validOfferDetails, exchangeToken: offer.exchangeToken, price: offer.price, sellerDeposit: offer.sellerDeposit, buyerCancelPenalty: offer.buyerCancelPenalty, - voucherRedeemableFrom: offerDates.voucherRedeemableFrom, - disputePeriod: offerDurations.disputePeriod, - resolutionPeriod: offerDurations.resolutionPeriod, + voucherRedeemableFrom: voucherRedeemableFrom, + disputePeriod: `${BigInt(offerDurations.disputePeriod) / SECONDS_PER_DAY} days`, + resolutionPeriod: `${BigInt(offerDurations.resolutionPeriod) / SECONDS_PER_DAY} days`, }; // Prepare the message @@ -1752,14 +1794,8 @@ describe("IBosonMetaTransactionsHandler", function () { // Prepare the message message.offerDetails = { + ...message.offerDetails, ...validOfferDetails, - exchangeToken: offer.exchangeToken, - price: offer.price, - sellerDeposit: offer.sellerDeposit, - buyerCancelPenalty: offer.buyerCancelPenalty, - voucherRedeemableFrom: offerDates.voucherRedeemableFrom, - disputePeriod: offerDurations.disputePeriod, - resolutionPeriod: offerDurations.resolutionPeriod, }; // Collect the signature components From 19fe3a5caad4492c381080c178d7b039d574e15e Mon Sep 17 00:00:00 2001 From: zajck Date: Fri, 27 Oct 2023 12:02:13 +0200 Subject: [PATCH 4/5] Same format for conditional offers --- contracts/domain/BosonConstants.sol | 11 +- .../facets/MetaTransactionsHandlerFacet.sol | 44 ++++--- test/protocol/MetaTransactionsHandlerTest.js | 120 +++++++++--------- test/util/constants.js | 6 + test/util/utils.js | 44 ++++++- 5 files changed, 144 insertions(+), 81 deletions(-) diff --git a/contracts/domain/BosonConstants.sol b/contracts/domain/BosonConstants.sol index 6c1411b9d..ff86ac4d2 100644 --- a/contracts/domain/BosonConstants.sol +++ b/contracts/domain/BosonConstants.sol @@ -212,17 +212,20 @@ bytes32 constant META_TRANSACTION_TYPEHASH = keccak256( "MetaTransaction(uint256 nonce,address from,address contractAddress,string functionName,bytes functionSignature)" ) ); +bytes32 constant OFFER_PARAMETERS_TYPEHASH = keccak256( + "MetaTxOfferParameters(uint256 offerId,address exchangeToken,uint256 price,uint256 sellerDeposit,uint256 buyerCancelPenalty,string voucherRedeemableFrom,string disputePeriod,string resolutionPeriod)" +); bytes32 constant OFFER_DETAILS_TYPEHASH = keccak256( - "MetaTxOfferDetails(address buyer,uint256 offerId,address exchangeToken,uint256 price,uint256 sellerDeposit,uint256 buyerCancelPenalty,string voucherRedeemableFrom,string disputePeriod,string resolutionPeriod)" + "MetaTxOfferDetails(address buyer,MetaTxOfferParameters offerParameters)MetaTxOfferParameters(uint256 offerId,address exchangeToken,uint256 price,uint256 sellerDeposit,uint256 buyerCancelPenalty,string voucherRedeemableFrom,string disputePeriod,string resolutionPeriod)" ); bytes32 constant META_TX_COMMIT_TO_OFFER_TYPEHASH = keccak256( - "MetaTxCommitToOffer(uint256 nonce,address from,address contractAddress,string functionName,MetaTxOfferDetails offerDetails)MetaTxOfferDetails(address buyer,uint256 offerId,address exchangeToken,uint256 price,uint256 sellerDeposit,uint256 buyerCancelPenalty,string voucherRedeemableFrom,string disputePeriod,string resolutionPeriod)" + "MetaTxCommitToOffer(uint256 nonce,address from,address contractAddress,string functionName,MetaTxOfferDetails offerDetails)MetaTxOfferDetails(address buyer,MetaTxOfferParameters offerParameters)MetaTxOfferParameters(uint256 offerId,address exchangeToken,uint256 price,uint256 sellerDeposit,uint256 buyerCancelPenalty,string voucherRedeemableFrom,string disputePeriod,string resolutionPeriod)" ); bytes32 constant CONDITIONAL_OFFER_DETAILS_TYPEHASH = keccak256( - "MetaTxConditionalOfferDetails(address buyer,uint256 offerId,uint256 tokenId)" + "MetaTxConditionalOfferDetails(address buyer,MetaTxOfferParameters offerParameters,uint256 tokenId)MetaTxOfferParameters(uint256 offerId,address exchangeToken,uint256 price,uint256 sellerDeposit,uint256 buyerCancelPenalty,string voucherRedeemableFrom,string disputePeriod,string resolutionPeriod)" ); bytes32 constant META_TX_COMMIT_TO_CONDITIONAL_OFFER_TYPEHASH = keccak256( - "MetaTxCommitToConditionalOffer(uint256 nonce,address from,address contractAddress,string functionName,MetaTxConditionalOfferDetails offerDetails)MetaTxConditionalOfferDetails(address buyer,uint256 offerId,uint256 tokenId)" + "MetaTxCommitToConditionalOffer(uint256 nonce,address from,address contractAddress,string functionName,MetaTxConditionalOfferDetails offerDetails)MetaTxConditionalOfferDetails(address buyer,MetaTxOfferParameters offerParameters,uint256 tokenId)MetaTxOfferParameters(uint256 offerId,address exchangeToken,uint256 price,uint256 sellerDeposit,uint256 buyerCancelPenalty,string voucherRedeemableFrom,string disputePeriod,string resolutionPeriod)" ); bytes32 constant EXCHANGE_DETAILS_TYPEHASH = keccak256("MetaTxExchangeDetails(uint256 exchangeId)"); bytes32 constant META_TX_EXCHANGE_TYPEHASH = keccak256( diff --git a/contracts/protocol/facets/MetaTransactionsHandlerFacet.sol b/contracts/protocol/facets/MetaTransactionsHandlerFacet.sol index bbb6d19ef..25b521f00 100644 --- a/contracts/protocol/facets/MetaTransactionsHandlerFacet.sol +++ b/contracts/protocol/facets/MetaTransactionsHandlerFacet.sol @@ -117,11 +117,33 @@ contract MetaTransactionsHandlerFacet is IBosonMetaTransactionsHandler, Protocol // The buyer and offerId are part of calldata (address buyer, uint256 offerId) = abi.decode(_offerDetails, (address, uint256)); + return keccak256(abi.encode(OFFER_DETAILS_TYPEHASH, buyer, hashOfferParameters(offerId))); + } + + /** + * @notice Returns hashed representation of the conditional offer details struct. + * + * @param _offerDetails - the conditional offer details + * @return the hashed representation of the conditional offer details struct + */ + function hashConditionalOfferDetails(bytes memory _offerDetails) internal view returns (bytes32) { + (address buyer, uint256 offerId, uint256 tokenId) = abi.decode(_offerDetails, (address, uint256, uint256)); + return keccak256(abi.encode(CONDITIONAL_OFFER_DETAILS_TYPEHASH, buyer, hashOfferParameters(offerId), tokenId)); + } + + /** + * @notice Returns hashed representation of the offer parameters struct. + * This is reused for both the offer and conditional offer. + * + * @param _offerId - the offer id + * @return the hashed representation of the offer details struct + */ + function hashOfferParameters(uint256 _offerId) internal view returns (bytes32) { // Get other offer details from the protocol - Offer storage offer = getValidOffer(offerId); + Offer storage offer = getValidOffer(_offerId); bytes memory redeemableFrom; { - OfferDates storage offerDates = fetchOfferDates(offerId); + OfferDates storage offerDates = fetchOfferDates(_offerId); (uint256 year, uint256 month, uint256 day, uint256 hour, uint256 minute, uint256 second) = DateTime .timestampToDateTime(offerDates.voucherRedeemableFrom); @@ -140,14 +162,13 @@ contract MetaTransactionsHandlerFacet is IBosonMetaTransactionsHandler, Protocol ); } - OfferDurations storage offerDurations = fetchOfferDurations(offerId); + OfferDurations storage offerDurations = fetchOfferDurations(_offerId); return keccak256( abi.encode( - OFFER_DETAILS_TYPEHASH, - buyer, - offerId, + OFFER_PARAMETERS_TYPEHASH, + _offerId, offer.exchangeToken, offer.price, offer.sellerDeposit, @@ -169,17 +190,6 @@ contract MetaTransactionsHandlerFacet is IBosonMetaTransactionsHandler, Protocol ); } - /** - * @notice Returns hashed representation of the conditional offer details struct. - * - * @param _offerDetails - the conditional offer details - * @return the hashed representation of the conditional offer details struct - */ - function hashConditionalOfferDetails(bytes memory _offerDetails) internal pure returns (bytes32) { - (address buyer, uint256 offerId, uint256 tokenId) = abi.decode(_offerDetails, (address, uint256, uint256)); - return keccak256(abi.encode(CONDITIONAL_OFFER_DETAILS_TYPEHASH, buyer, offerId, tokenId)); - } - /** * @notice Returns hashed representation of the exchange details struct. * diff --git a/test/protocol/MetaTransactionsHandlerTest.js b/test/protocol/MetaTransactionsHandlerTest.js index 581d31799..8053ba9fc 100644 --- a/test/protocol/MetaTransactionsHandlerTest.js +++ b/test/protocol/MetaTransactionsHandlerTest.js @@ -30,6 +30,7 @@ const { setupTestEnvironment, getSnapshot, revertToSnapshot, + timestampToDateTime, } = require("../util/utils.js"); const { mockOffer, @@ -42,7 +43,7 @@ const { mockExchange, mockCondition, } = require("../util/mock"); -const { oneMonth } = require("../util/constants"); +const { oneMonth, SECONDS_PER_DAY } = require("../util/constants"); const { getSelectors, FacetCutAction, @@ -74,7 +75,8 @@ describe("IBosonMetaTransactionsHandler", function () { let metaTransactionsHandler, nonce, functionSignature; let seller, offerId, buyerId; let validOfferDetails, - offerType, + offerDetailsType, + offerParametersType, metaTransactionType, metaTxExchangeType, customTransactionType, @@ -1645,8 +1647,7 @@ describe("IBosonMetaTransactionsHandler", function () { .createOffer(offer, offerDates, offerDurations, disputeResolver.id, agentId); // Set the offer Type - offerType = [ - { name: "buyer", type: "address" }, + offerParametersType = [ { name: "offerId", type: "uint256" }, { name: "exchangeToken", type: "address" }, { name: "price", type: "uint256" }, @@ -1657,6 +1658,11 @@ describe("IBosonMetaTransactionsHandler", function () { { name: "resolutionPeriod", type: "string" }, ]; + offerDetailsType = [ + { name: "buyer", type: "address" }, + { name: "offerParameters", type: "MetaTxOfferParameters" }, + ]; + // Set the message Type metaTransactionType = [ { name: "nonce", type: "uint256" }, @@ -1669,7 +1675,8 @@ describe("IBosonMetaTransactionsHandler", function () { customTransactionType = { MetaTxCommitToOffer: metaTransactionType, - MetaTxOfferDetails: offerType, + MetaTxOfferDetails: offerDetailsType, + MetaTxOfferParameters: offerParametersType, }; // prepare validOfferDetails @@ -1678,57 +1685,23 @@ describe("IBosonMetaTransactionsHandler", function () { offerId: offer.id, }; - const SECONDS_PER_DAY = 24n * 60n * 60n; - const SECONDS_PER_HOUR = 60n * 60n; - const SECONDS_PER_MINUTE = 60n; - const OFFSET19700101 = 2440588n; - - function timestampToDateTime(timestamp) { - timestamp = BigInt(timestamp); - let [year, month, day] = _daysToDate(timestamp / SECONDS_PER_DAY); - let secs = timestamp % SECONDS_PER_DAY; - let hour = secs / SECONDS_PER_HOUR; - secs = secs % SECONDS_PER_HOUR; - let minute = secs / SECONDS_PER_MINUTE; - let second = secs % SECONDS_PER_MINUTE; - - return [year, month, day, hour, minute, second]; - } - - function _daysToDate(_days) { - let __days = _days; - - let L = __days + 68569n + OFFSET19700101; - let N = (4n * L) / 146097n; - L = L - (146097n * N + 3n) / 4n; - let _year = (4000n * (L + 1n)) / 1461001n; - L = L - (1461n * _year) / 4n + 31n; - let _month = (80n * L) / 2447n; - let _day = L - (2447n * _month) / 80n; - L = _month / 11n; - _month = _month + 2n - 12n * L; - _year = 100n * (N - 49n) + _year + L; - - let year = _year; - let month = _month; - let day = _day; - return [year, month, day]; - } - let [year, month, day, hour, minute, second] = timestampToDateTime( Number(offerDates.voucherRedeemableFrom) ); let voucherRedeemableFrom = `${year}/${month}/${day} ${hour}:${minute}:${second}`; const extendedOfferDetails = { - ...validOfferDetails, - exchangeToken: offer.exchangeToken, - price: offer.price, - sellerDeposit: offer.sellerDeposit, - buyerCancelPenalty: offer.buyerCancelPenalty, - voucherRedeemableFrom: voucherRedeemableFrom, - disputePeriod: `${BigInt(offerDurations.disputePeriod) / SECONDS_PER_DAY} days`, - resolutionPeriod: `${BigInt(offerDurations.resolutionPeriod) / SECONDS_PER_DAY} days`, + buyer: validOfferDetails.buyer, + offerParameters: { + offerId: validOfferDetails.offerId, + exchangeToken: offer.exchangeToken, + price: offer.price, + sellerDeposit: offer.sellerDeposit, + buyerCancelPenalty: offer.buyerCancelPenalty, + voucherRedeemableFrom: voucherRedeemableFrom, + disputePeriod: `${BigInt(offerDurations.disputePeriod) / SECONDS_PER_DAY} days`, + resolutionPeriod: `${BigInt(offerDurations.resolutionPeriod) / SECONDS_PER_DAY} days`, + }, }; // Prepare the message @@ -1793,10 +1766,7 @@ describe("IBosonMetaTransactionsHandler", function () { validOfferDetails.offerId = offerId; // Prepare the message - message.offerDetails = { - ...message.offerDetails, - ...validOfferDetails, - }; + message.offerDetails.offerParameters.offerId = offerId; // Collect the signature components let { r, s, v } = await prepareDataSignatureParameters( @@ -1933,9 +1903,20 @@ describe("IBosonMetaTransactionsHandler", function () { .createOfferWithCondition(offer, offerDates, offerDurations, disputeResolver.id, condition, agentId); // Set the offer Type - offerType = [ - { name: "buyer", type: "address" }, + offerParametersType = [ { name: "offerId", type: "uint256" }, + { name: "exchangeToken", type: "address" }, + { name: "price", type: "uint256" }, + { name: "sellerDeposit", type: "uint256" }, + { name: "buyerCancelPenalty", type: "uint256" }, + { name: "voucherRedeemableFrom", type: "string" }, + { name: "disputePeriod", type: "string" }, + { name: "resolutionPeriod", type: "string" }, + ]; + + offerDetailsType = [ + { name: "buyer", type: "address" }, + { name: "offerParameters", type: "MetaTxOfferParameters" }, { name: "tokenId", type: "uint256" }, ]; @@ -1951,7 +1932,8 @@ describe("IBosonMetaTransactionsHandler", function () { customTransactionType = { MetaTxCommitToConditionalOffer: metaTransactionType, - MetaTxConditionalOfferDetails: offerType, + MetaTxConditionalOfferDetails: offerDetailsType, + MetaTxOfferParameters: offerParametersType, }; // prepare validOfferDetails @@ -1961,8 +1943,28 @@ describe("IBosonMetaTransactionsHandler", function () { tokenId: "0", }; + let [year, month, day, hour, minute, second] = timestampToDateTime( + Number(offerDates.voucherRedeemableFrom) + ); + let voucherRedeemableFrom = `${year}/${month}/${day} ${hour}:${minute}:${second}`; + + const extendedOfferDetails = { + buyer: validOfferDetails.buyer, + offerParameters: { + offerId: validOfferDetails.offerId, + exchangeToken: offer.exchangeToken, + price: offer.price, + sellerDeposit: offer.sellerDeposit, + buyerCancelPenalty: offer.buyerCancelPenalty, + voucherRedeemableFrom: voucherRedeemableFrom, + disputePeriod: `${BigInt(offerDurations.disputePeriod) / SECONDS_PER_DAY} days`, + resolutionPeriod: `${BigInt(offerDurations.resolutionPeriod) / SECONDS_PER_DAY} days`, + }, + tokenId: validOfferDetails.tokenId, + }; + // Prepare the message - message.offerDetails = validOfferDetails; + message.offerDetails = extendedOfferDetails; // Deposit native currency to the same seller id await fundsHandler @@ -2023,7 +2025,7 @@ describe("IBosonMetaTransactionsHandler", function () { validOfferDetails.offerId = offerId; // Prepare the message - message.offerDetails = validOfferDetails; + message.offerDetails.offerParameters.offerId = offerId; // Collect the signature components let { r, s, v } = await prepareDataSignatureParameters( diff --git a/test/util/constants.js b/test/util/constants.js index 738eb72a1..d7c442e92 100644 --- a/test/util/constants.js +++ b/test/util/constants.js @@ -3,6 +3,9 @@ const oneDay = 86400n; // 1 day in seconds const ninetyDays = oneDay * 90n; // 90 days in seconds const oneWeek = oneDay * 7n; // 7 days in seconds const oneMonth = oneDay * 31n; // 31 days in seconds +const SECONDS_PER_DAY = 24n * 60n * 60n; +const SECONDS_PER_HOUR = 60n * 60n; +const SECONDS_PER_MINUTE = 60n; const VOUCHER_NAME = "Boson Voucher (rNFT)"; const VOUCHER_SYMBOL = "BOSON_VOUCHER_RNFT"; const SEAPORT_ADDRESS = "0x00000000000001ad428e4906aE43D8F9852d0dD6"; // 1.4 @@ -18,3 +21,6 @@ exports.VOUCHER_NAME = VOUCHER_NAME; exports.VOUCHER_SYMBOL = VOUCHER_SYMBOL; exports.maxPriorityFeePerGas = maxPriorityFeePerGas; exports.SEAPORT_ADDRESS = SEAPORT_ADDRESS; +exports.SECONDS_PER_DAY = SECONDS_PER_DAY; +exports.SECONDS_PER_HOUR = SECONDS_PER_HOUR; +exports.SECONDS_PER_MINUTE = SECONDS_PER_MINUTE; diff --git a/test/util/utils.js b/test/util/utils.js index 3c343c7fd..19736944a 100644 --- a/test/util/utils.js +++ b/test/util/utils.js @@ -16,7 +16,14 @@ const { ZeroAddress, } = ethers; const { getFacets } = require("../../scripts/config/facet-deploy.js"); -const { oneWeek, oneMonth, maxPriorityFeePerGas } = require("./constants"); +const { + oneWeek, + oneMonth, + maxPriorityFeePerGas, + SECONDS_PER_DAY, + SECONDS_PER_HOUR, + SECONDS_PER_MINUTE, +} = require("./constants"); const Role = require("../../scripts/domain/Role"); const { toHexString } = require("../../scripts/util/utils.js"); const { expect } = require("chai"); @@ -484,6 +491,40 @@ function deriveTokenId(offerId, exchangeId) { return (BigInt(offerId) << 128n) + BigInt(exchangeId); } +function timestampToDateTime(timestamp) { + timestamp = BigInt(timestamp); + let [year, month, day] = _daysToDate(timestamp / SECONDS_PER_DAY); + let secs = timestamp % SECONDS_PER_DAY; + let hour = secs / SECONDS_PER_HOUR; + secs = secs % SECONDS_PER_HOUR; + let minute = secs / SECONDS_PER_MINUTE; + let second = secs % SECONDS_PER_MINUTE; + + return [year, month, day, hour, minute, second]; +} + +function _daysToDate(_days) { + const OFFSET19700101 = 2440588n; + + let __days = _days; + + let L = __days + 68569n + OFFSET19700101; + let N = (4n * L) / 146097n; + L = L - (146097n * N + 3n) / 4n; + let _year = (4000n * (L + 1n)) / 1461001n; + L = L - (1461n * _year) / 4n + 31n; + let _month = (80n * L) / 2447n; + let _day = L - (2447n * _month) / 80n; + L = _month / 11n; + _month = _month + 2n - 12n * L; + _year = 100n * (N - 49n) + _year + L; + + let year = _year; + let month = _month; + let day = _day; + return [year, month, day]; +} + exports.setNextBlockTimestamp = setNextBlockTimestamp; exports.getEvent = getEvent; exports.eventEmittedWithArgs = eventEmittedWithArgs; @@ -503,3 +544,4 @@ exports.getSnapshot = getSnapshot; exports.revertToSnapshot = revertToSnapshot; exports.deriveTokenId = deriveTokenId; exports.getSellerSalt = getSellerSalt; +exports.timestampToDateTime = timestampToDateTime; From fa2c48dd017d3ab4c33f244004bf506c2f03899c Mon Sep 17 00:00:00 2001 From: zajck Date: Mon, 30 Oct 2023 08:56:31 +0100 Subject: [PATCH 5/5] Fix failing unit test --- test/protocol/MetaTransactionsHandlerTest.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/protocol/MetaTransactionsHandlerTest.js b/test/protocol/MetaTransactionsHandlerTest.js index 8053ba9fc..6aa782945 100644 --- a/test/protocol/MetaTransactionsHandlerTest.js +++ b/test/protocol/MetaTransactionsHandlerTest.js @@ -2064,7 +2064,7 @@ describe("IBosonMetaTransactionsHandler", function () { validOfferDetails.tokenId = tokenId; // Prepare the message - message.offerDetails = validOfferDetails; + message.offerDetails.tokenId = tokenId; // Collect the signature components let { r, s, v } = await prepareDataSignatureParameters(