diff --git a/.gas-snapshot b/.gas-snapshot
index 427563fd..5b70304f 100644
--- a/.gas-snapshot
+++ b/.gas-snapshot
@@ -20,47 +20,49 @@ Certificate_supportsInterface:test() (gas: 5159)
Certificate_transferFrom:test() (gas: 48618)
Certificate_transferFrom:test_reverts_when_paused() (gas: 38088)
Certificate_transferFrom_reverts_ForbiddenTransferAfterMinting:test() (gas: 18960)
-Checkout_buyingFromOneRemoval:test() (gas: 650755)
-Checkout_buyingFromOneRemoval_byApproval:test() (gas: 586647)
-Checkout_buyingFromTenRemovals:test() (gas: 1974107)
-Checkout_buyingFromTenRemovals_singleSupplier:test() (gas: 1711817)
-Checkout_buyingFromTenRemovals_singleSupplier_withoutFee:test() (gas: 1707017)
-Checkout_buyingFromTenRemovals_withoutFee:test() (gas: 1719723)
-Checkout_buyingFromTenSuppliers:test() (gas: 2852393)
-Checkout_buyingWithAlternateERC20:test() (gas: 544701)
-Checkout_buyingWithAlternateERC20_floatingPointPriceMultiple:test() (gas: 510755)
-Checkout_swapWithDifferentPermitSignerAndMsgSender:test() (gas: 652749)
-Checkout_swapWithoutFeeSpecialOrder:test() (gas: 569077)
-Checkout_swapWithoutFeeSpecialOrder_specificSupplier:test() (gas: 563776)
-Checkout_swapWithoutFeeSpecialOrder_specificSupplier:test_revertsWhenSupplierDoesNotExistInMarket() (gas: 57679)
-Checkout_swapWithoutFeeSpecialOrder_specificVintages:test_basicFulfillment() (gas: 965356)
-Checkout_swapWithoutFeeSpecialOrder_specificVintages:test_revertsWhenNoRemovalsFromSpecifiedVintages() (gas: 90946)
-Checkout_swapWithoutFeeSpecialOrder_specificVintagesSpecificSupplier:test_basicFulfillment() (gas: 701713)
+Checkout_buyingFromOneRemoval:test() (gas: 652994)
+Checkout_buyingFromOneRemoval_byApproval:test() (gas: 589052)
+Checkout_buyingFromTenRemovals:test() (gas: 1976346)
+Checkout_buyingFromTenRemovals_singleSupplier:test() (gas: 1711916)
+Checkout_buyingFromTenRemovals_singleSupplier_withoutFee:test() (gas: 1707116)
+Checkout_buyingFromTenRemovals_withoutFee:test() (gas: 1719822)
+Checkout_buyingFromTenSuppliers:test() (gas: 2854632)
+Checkout_buyingWithAlternateERC20:test() (gas: 546928)
+Checkout_buyingWithAlternateERC20_floatingPointPriceMultiple:test() (gas: 512982)
+Checkout_swapRevertsWhenBuyerIsMissingSANCTION_ALLOWLIST_ROLE:test() (gas: 168598)
+Checkout_swapRevertsWithDifferentPermitSignerAndMsgSender:test() (gas: 168610)
+Checkout_swapWithoutFeeSpecialOrder:test() (gas: 569131)
+Checkout_swapWithoutFeeSpecialOrder_specificSupplier:test() (gas: 563830)
+Checkout_swapWithoutFeeSpecialOrder_specificSupplier:test_revertsWhenSupplierDoesNotExistInMarket() (gas: 57731)
+Checkout_swapWithoutFeeSpecialOrder_specificVintages:test_basicFulfillment() (gas: 965410)
+Checkout_swapWithoutFeeSpecialOrder_specificVintages:test_revertsWhenNoRemovalsFromSpecifiedVintages() (gas: 91000)
+Checkout_swapWithoutFeeSpecialOrder_specificVintagesSpecificSupplier:test_basicFulfillment() (gas: 701767)
LockedNORILib_availableAmount:test() (gas: 12371)
LockedNORITest:testBatchGrantCreation() (gas: 705179)
LockedNORITest:testNormalWithdrawal() (gas: 1102743)
LockedNORITest:testReentryTokensReceived() (gas: 1102887)
LockedNORITest:testReentryTokensToSend() (gas: 1104432)
LockedNORITest:testTokensReceivedReverts() (gas: 69026)
-MarketInvariantTest:invariant_callSummary() (runs: 400, calls: 6000, reverts: 4258)
-MarketInvariantTest:invariant_sumOfPurchaseAmounts() (runs: 400, calls: 6000, reverts: 4265)
-MarketSupplierSelectionNotUsingUpSuppliersLastRemoval:test() (gas: 921266)
-Market_ALLOWLIST_ROLE:test() (gas: 12821)
-Market_USDC_swap_respects_decimal_mismatch:test() (gas: 716031)
-Market_USDC_swap_withholds_restricted_nori:test() (gas: 891106)
+MarketInvariantTest:invariant_callSummary() (runs: 400, calls: 6000, reverts: 4269)
+MarketInvariantTest:invariant_sumOfPurchaseAmounts() (runs: 400, calls: 6000, reverts: 4292)
+MarketSupplierSelectionNotUsingUpSuppliersLastRemoval:test() (gas: 923444)
+Market_ALLOWLIST_ROLE:test() (gas: 12799)
+Market_SANCTION_ALLOWLIST_ROLE:test() (gas: 12897)
+Market_USDC_swap_respects_decimal_mismatch:test() (gas: 799139)
+Market_USDC_swap_withholds_restricted_nori:test() (gas: 974214)
Market__addActiveRemoval:test() (gas: 183344)
-Market__addActiveRemoval:test__lis2VintagesFor1SupplierFor2SubIdentifiers() (gas: 242879)
-Market__addActiveRemoval:test__list1VintageFor1Supplier() (gas: 188331)
+Market__addActiveRemoval:test__lis2VintagesFor1SupplierFor2SubIdentifiers() (gas: 242901)
+Market__addActiveRemoval:test__list1VintageFor1Supplier() (gas: 188309)
Market__addActiveRemoval:test__list1VintageFor2Suppliers() (gas: 360670)
Market__addActiveRemoval:test__list2VintagesFor1SupplierFor1SubIdentifier() (gas: 262754)
Market__isAuthorizedWithdrawal_false:test_returnsFalseWhenAllConditionsAreFalse() (gas: 7452)
-Market__isAuthorizedWithdrawal_true:test_returnsTrueWhenMsgSenderEqualsOwner() (gas: 421)
-Market__isAuthorizedWithdrawal_true:test_returnsTrueWhenMsgSenderHasDefaultAdminRole() (gas: 96555)
+Market__isAuthorizedWithdrawal_true:test_returnsTrueWhenMsgSenderEqualsOwner() (gas: 400)
+Market__isAuthorizedWithdrawal_true:test_returnsTrueWhenMsgSenderHasDefaultAdminRole() (gas: 96556)
Market__isAuthorizedWithdrawal_true:test_returnsTrueWhenMsgSenderIsApprovedForAll() (gas: 8923)
-Market__multicall_empty_bytes_reverts:test() (gas: 20979)
-Market__multicall_initialize_reverts:test() (gas: 36257)
+Market__multicall_empty_bytes_reverts:test() (gas: 20957)
+Market__multicall_initialize_reverts:test() (gas: 36213)
Market__setPriceMultiple:test() (gas: 29035)
-Market__setPriceMultiple:test_revertsWhenSetBelow100() (gas: 3549)
+Market__setPriceMultiple:test_revertsWhenSetBelow100() (gas: 3593)
Market__setPurchasingToken:test() (gas: 1021494)
Market__validatePrioritySupply:test_supplyAfterPurchaseIsLessThanPriorityRestrictedThreshold() (gas: 2499)
Market__validatePrioritySupply:test_supplyAfterPurchaseIsZero() (gas: 2566)
@@ -68,52 +70,52 @@ Market__validatePrioritySupply_buyerIsAllowlistedAndAmountExceedsPriorityRestric
Market__validatePrioritySupply_reverts_LowSupplyAllowlistRequired:test() (gas: 7692)
Market__validateSupply:test() (gas: 292)
Market__validateSupply:test_reverts_OutOfSupply() (gas: 3194)
-Market_calculates_prices_using_decimal:test() (gas: 66535)
-Market_convertPurchasingTokenDecimalsToRemovalDecimals:test() (gas: 26051)
+Market_calculates_prices_using_decimal:test() (gas: 66492)
+Market_convertPurchasingTokenDecimalsToRemovalDecimals:test() (gas: 26029)
Market_convertRemovalDecimalsToPurchasingTokenDecimals:test() (gas: 29773)
-Market_getActiveSuppliers:test_1_supplier() (gas: 610023)
-Market_getActiveSuppliers:test_3_suppliers() (gas: 1262053)
-Market_getActiveSuppliers:test_no_suppliers() (gas: 20903)
-Market_getPriceMultiple:test() (gas: 14895)
-Market_getRemovalIdsForSupplier:test_1_removal() (gas: 610382)
-Market_getRemovalIdsForSupplier:test_3_removals() (gas: 993815)
-Market_getRemovalIdsForSupplier:test_3_removals_different_vintages() (gas: 1039924)
+Market_getActiveSuppliers:test_1_supplier() (gas: 610132)
+Market_getActiveSuppliers:test_3_suppliers() (gas: 1262512)
+Market_getActiveSuppliers:test_no_suppliers() (gas: 20837)
+Market_getPriceMultiple:test() (gas: 14873)
+Market_getRemovalIdsForSupplier:test_1_removal() (gas: 610557)
+Market_getRemovalIdsForSupplier:test_3_removals() (gas: 993990)
+Market_getRemovalIdsForSupplier:test_3_removals_different_vintages() (gas: 1040099)
Market_getRemovalIdsForSupplier:test_no_removals() (gas: 26044)
-Market_onERC1155BatchReceived:test() (gas: 208058)
-Market_onERC1155BatchReceived_reverts_SenderNotRemovalContract:test() (gas: 355257)
+Market_onERC1155BatchReceived:test() (gas: 208168)
+Market_onERC1155BatchReceived_reverts_SenderNotRemovalContract:test() (gas: 355367)
Market_onERC1155Received:test() (gas: 206036)
Market_onERC1155Received_reverts_SenderNotRemovalContract:test() (gas: 159000)
-Market_purchasingTokenAddress:test() (gas: 17080)
-Market_replace:test() (gas: 344957)
-Market_replace_reverts_CertificateNotYetMinted:test() (gas: 49494)
-Market_replace_reverts_ReplacementAmountExceedsNrtDeficit:test() (gas: 52525)
-Market_replace_reverts_ReplacementAmountMismatch:test() (gas: 86288)
-Market_setNoriFeePercentage_revertsInvalidPercentage:test() (gas: 20298)
-Market_setPriorityRestrictedThreshold:test() (gas: 157447)
-Market_setPriorityRestrictedThreshold:test_zeroAvailable() (gas: 152422)
-Market_setPurchasingTokenAndPriceMultiple:test() (gas: 1026654)
-Market_setPurchasingTokenAndPriceMultiple_revertsIfNotAdmin:test() (gas: 50857)
-Market_supplierSelectionUsingUpSuppliersLastRemoval:test() (gas: 917974)
-Market_swapWithoutFeeSpecialOrder_emits_and_skips_transfer_when_transferring_wrong_erc20_to_rNori:test() (gas: 421037)
-Market_swap_emits_and_skips_transfer_when_transferring_wrong_erc20_to_rNori:test() (gas: 519499)
-Market_swap_emits_event_and_skips_mint_when_minting_rNori_to_nonERC1155Receiver:test() (gas: 596721)
-Market_swap_revertsWhenUnsafeERC20TransferFails:test() (gas: 216571)
-Market_validates_certificate_amount:test() (gas: 595965)
-Market_withdraw_1x3_center:test() (gas: 340924)
-Market_withdraw_2x1_back:test() (gas: 345584)
-Market_withdraw_2x1_front:test() (gas: 333941)
-Market_withdraw_2x1_front_relist:test() (gas: 381854)
-Market_withdraw_as_DEFAULT_ADMIN_ROLE:test() (gas: 276634)
-Market_withdraw_as_operator:test() (gas: 285801)
-Market_withdraw_as_supplier:test() (gas: 274775)
-Market_withdraw_reverts:test() (gas: 138819)
+Market_purchasingTokenAddress:test() (gas: 17191)
+Market_replace:test() (gas: 345000)
+Market_replace_reverts_CertificateNotYetMinted:test() (gas: 49559)
+Market_replace_reverts_ReplacementAmountExceedsNrtDeficit:test() (gas: 52590)
+Market_replace_reverts_ReplacementAmountMismatch:test() (gas: 86353)
+Market_setNoriFeePercentage_revertsInvalidPercentage:test() (gas: 20254)
+Market_setPriorityRestrictedThreshold:test() (gas: 157403)
+Market_setPriorityRestrictedThreshold:test_zeroAvailable() (gas: 152378)
+Market_setPurchasingTokenAndPriceMultiple:test() (gas: 1026588)
+Market_setPurchasingTokenAndPriceMultiple_revertsIfNotAdmin:test() (gas: 50791)
+Market_supplierSelectionUsingUpSuppliersLastRemoval:test() (gas: 920153)
+Market_swapWithoutFeeSpecialOrder_emits_and_skips_transfer_when_transferring_wrong_erc20_to_rNori:test() (gas: 421070)
+Market_swap_emits_and_skips_transfer_when_transferring_wrong_erc20_to_rNori:test() (gas: 521672)
+Market_swap_emits_event_and_skips_mint_when_minting_rNori_to_nonERC1155Receiver:test() (gas: 598957)
+Market_swap_revertsWhenUnsafeERC20TransferFails:test() (gas: 218819)
+Market_validates_certificate_amount:test() (gas: 596888)
+Market_withdraw_1x3_center:test() (gas: 340858)
+Market_withdraw_2x1_back:test() (gas: 345518)
+Market_withdraw_2x1_front:test() (gas: 333875)
+Market_withdraw_2x1_front_relist:test() (gas: 381810)
+Market_withdraw_as_DEFAULT_ADMIN_ROLE:test() (gas: 276568)
+Market_withdraw_as_operator:test() (gas: 285735)
+Market_withdraw_as_supplier:test() (gas: 274709)
+Market_withdraw_reverts:test() (gas: 138753)
NORI_name:test() (gas: 17205)
NORI_permit:test() (gas: 92382)
NoriUSDC_permit:test() (gas: 122061)
RemovalQueue_getTotalBalanceFromRemovalQueue:test() (gas: 23899)
RemovalQueue_getTotalBalanceFromRemovalQueue:test_100xRemovalsOfTheDifferentVintages() (gas: 895786)
RemovalQueue_getTotalBalanceFromRemovalQueue:test_100xRemovalsOfTheSameVintage() (gas: 620299)
-RemovalQueue_insertRemovalByVintage:test_insertRemovalOnce() (gas: 119590)
+RemovalQueue_insertRemovalByVintage:test_insertRemovalOnce() (gas: 119613)
RemovalQueue_insertRemovalByVintage:test_insertRemovalTwice() (gas: 121125)
Removal__beforeTokenTransfer:test() (gas: 18010)
Removal__beforeTokenTransfer:test_paused_reverts_Paused() (gas: 29432)
@@ -121,46 +123,46 @@ Removal__createRemovalData:test() (gas: 22593)
Removal__createRemovalData:test_reverts_InvalidData() (gas: 25711)
Removal__createRemovalDataBatch:test() (gas: 29572)
Removal__createRemovalDataBatch:test_reverts_InvalidData2() (gas: 36714)
-Removal__isValidTransfer:testFuzz_ReturnFalse_NonMultiplesOf1e14(uint256) (runs: 256, μ: 13892, ~: 13847)
-Removal__isValidTransfer:testFuzz_ReturnTrue_MultiplesOf1e14(uint256) (runs: 256, μ: 14383, ~: 14497)
-Removal__isValidTransfer:testFuzz_ReturnTrue_SmallestGranularity() (gas: 6832)
-Removal__isValidTransfer:test_ReturnFalse_AmountIsTooGranular() (gas: 6898)
-Removal__isValidTransfer:test_ReturnFalse_AmountIsTooGranularAndToIsTheCertificate() (gas: 4745)
-Removal__isValidTransfer:test_ReturnFalse_AmountIsTooGranularAndToIsTheMarket() (gas: 2608)
-Removal__isValidTransfer:test_ReturnFalse_AmountIsZeroAndToIsTheCertificate() (gas: 4705)
-Removal__isValidTransfer:test_ReturnFalse_AmountIsZeroAndToIsTheMarket() (gas: 2521)
-Removal__isValidTransfer:test_ReturnTrue_AmountIsZeroAndToIsNeitherTheMarketNorCertificate() (gas: 6852)
+Removal__isValidTransferAmount:testFuzz_ReturnFalse_NonMultiplesOf1e14(uint256) (runs: 256, μ: 13891, ~: 13847)
+Removal__isValidTransferAmount:testFuzz_ReturnTrue_MultiplesOf1e14(uint256) (runs: 256, μ: 14380, ~: 14497)
+Removal__isValidTransferAmount:testFuzz_ReturnTrue_SmallestGranularity() (gas: 6832)
+Removal__isValidTransferAmount:test_ReturnFalse_AmountIsTooGranular() (gas: 6898)
+Removal__isValidTransferAmount:test_ReturnFalse_AmountIsTooGranularAndToIsTheCertificate() (gas: 4745)
+Removal__isValidTransferAmount:test_ReturnFalse_AmountIsTooGranularAndToIsTheMarket() (gas: 2608)
+Removal__isValidTransferAmount:test_ReturnFalse_AmountIsZeroAndToIsTheCertificate() (gas: 4705)
+Removal__isValidTransferAmount:test_ReturnFalse_AmountIsZeroAndToIsTheMarket() (gas: 2521)
+Removal__isValidTransferAmount:test_ReturnTrue_AmountIsZeroAndToIsNeitherTheMarketNorCertificate() (gas: 6852)
Removal__validateRemoval:test() (gas: 2491)
Removal__validateRemoval:test_reverts_InvalidData() (gas: 5373)
Removal_addBalance:test() (gas: 60280)
Removal_addBalance_reverts_RemovalNotYetMinted:test() (gas: 31115)
Removal_batchGetHoldbackPercentages_multipleIds:test() (gas: 11098)
Removal_batchGetHoldbackPercentages_singleId:test() (gas: 10346)
-Removal_consign_revertsForSoldRemovals:test() (gas: 1090733)
-Removal_getMarketBalance:test() (gas: 1103137)
-Removal_getOwnedTokenIds:test_multiple_tokens_with_transfer() (gas: 1078578)
+Removal_consign_revertsForSoldRemovals:test() (gas: 1172740)
+Removal_getMarketBalance:test() (gas: 1185770)
+Removal_getOwnedTokenIds:test_multiple_tokens_with_transfer() (gas: 1078643)
Removal_getOwnedTokenIds:test_no_tokens() (gas: 18683)
Removal_getProjectId:test() (gas: 19307)
Removal_grantRole:test_reverts_when_paused() (gas: 26272)
Removal_migrate:test() (gas: 989116)
Removal_migrate_gasLimit:test() (gas: 15359829)
Removal_migrate_revertsIfRemovalBalanceSumDifferentFromCertificateAmount:test() (gas: 1002846)
-Removal_mintBatch:test() (gas: 348546)
-Removal_mintBatch_list:test() (gas: 557747)
-Removal_mintBatch_list_sequential:test() (gas: 748808)
-Removal_mintBatch_multiple:test_16() (gas: 3083367)
-Removal_mintBatch_multiple:test_2() (gas: 726074)
-Removal_mintBatch_multiple:test_32() (gas: 5778524)
-Removal_mintBatch_multiple:test_4() (gas: 1062791)
-Removal_mintBatch_multiple:test_8() (gas: 1736230)
+Removal_mintBatch:test() (gas: 348611)
+Removal_mintBatch_list:test() (gas: 557922)
+Removal_mintBatch_list_sequential:test() (gas: 749158)
+Removal_mintBatch_multiple:test_16() (gas: 3083542)
+Removal_mintBatch_multiple:test_2() (gas: 726249)
+Removal_mintBatch_multiple:test_32() (gas: 5778699)
+Removal_mintBatch_multiple:test_4() (gas: 1062966)
+Removal_mintBatch_multiple:test_8() (gas: 1736405)
Removal_mintBatch_revertsInvalidHoldbackPercentage:test() (gas: 56204)
Removal_mintBatch_reverts_mint_to_wrong_address:test() (gas: 89002)
-Removal_mintBatch_zero_amount_removal:test() (gas: 311120)
+Removal_mintBatch_zero_amount_removal:test() (gas: 311185)
Removal_mintBatch_zero_amount_removal_to_market_reverts:test() (gas: 85435)
-Removal_multicall:test_balanceOfBatch() (gas: 491940)
-Removal_release_listed:test() (gas: 486320)
-Removal_release_listed_isRemovedFromMarket:test() (gas: 486675)
-Removal_release_partial_listed:test() (gas: 79549)
+Removal_multicall:test_balanceOfBatch() (gas: 492005)
+Removal_release_listed:test() (gas: 486531)
+Removal_release_listed_isRemovedFromMarket:test() (gas: 486885)
+Removal_release_partial_listed:test() (gas: 79637)
Removal_release_retired:test() (gas: 92425)
Removal_release_retired_2x:test() (gas: 98489)
Removal_release_retired_burned:test() (gas: 94905)
@@ -168,7 +170,7 @@ Removal_release_retired_burned:testDecrementsCertificateDiscrepancy() (gas: 8894
Removal_release_retired_oneHundredCertificates:test() (gas: 89535)
Removal_release_reverts_AccessControl:test() (gas: 48757)
Removal_release_unlisted:test() (gas: 48617)
-Removal_release_unlisted_listed_and_retired:test() (gas: 237498)
+Removal_release_unlisted_listed_and_retired:test() (gas: 237568)
Removal_renounceRole:test_reverts_when_paused() (gas: 19688)
Removal_revokeRole:test_reverts_when_paused() (gas: 26840)
Removal_safeBatchTransferFrom_reverts_ForbiddenTransfer:test() (gas: 32073)
diff --git a/contracts.json b/contracts.json
index 80184cbf..035c4597 100644
--- a/contracts.json
+++ b/contracts.json
@@ -1,7 +1,7 @@
{
"hardhat": {
"NORI": {
- "proxyAddress": "0xEdf0bef28943aE9d2ee7d84cF1AEEC85588655b7"
+ "proxyAddress": "0xcb3a9Df3363315fb6E190A77C9421ecc6a92cfF0"
},
"BridgedPolygonNORI": {
"proxyAddress": "0xbF1134A069430ED15FC6AA4Ba7563ca340213eb6"
@@ -31,7 +31,7 @@
"proxyAddress": "0xb4c857d57b7130F033159958B5af4Fa3Bd0a04c6"
},
"NoriUSDC": {
- "proxyAddress": "0x2AE26a38Bc11CE4FdfCaf0431D5a708b17E0dD99"
+ "proxyAddress": "0xEdf0bef28943aE9d2ee7d84cF1AEEC85588655b7"
}
},
"localhost": {
diff --git a/contracts/Market.sol b/contracts/Market.sol
index 0dcfa6b7..4e58989d 100644
--- a/contracts/Market.sol
+++ b/contracts/Market.sol
@@ -29,7 +29,7 @@ import {UInt256ArrayLib, AddressArrayLib} from "./ArrayLib.sol";
* and token balances that comprise the specific certificate for the amount purchased.
*
* The market maintains a "priority restricted threshold", which is a configurable threshold of supply that is
- * always reserved to sell only to buyers who have the `ALLOWLIST_ROLE`. Purchases that would drop supply below
+ * always reserved to sell only to buyers who have the `PRIORITY_ALLOWLIST_ROLE`. Purchases that would drop supply below
* this threshold will revert without the correct role.
*
* ###### Additional behaviors and features
@@ -41,7 +41,8 @@ import {UInt256ArrayLib, AddressArrayLib} from "./ArrayLib.sol";
* - `MARKET_ADMIN_ROLE`: Can set the value of market configuration variables: fee percentage, fee wallet address,
* priority restricted threshold, purchasing token, and price multiple. Can execute replacement operations through
* the `replace` function. Can submit special orders through `swapWithoutFeeSpecialOrder`.
- * - `ALLOWLIST_ROLE`: Can purchase from priority restricted supply.
+ * - `PRIORITY_ALLOWLIST_ROLE`: Can purchase from priority restricted supply.
+ * - `SWAP_ALLOWLIST_ROLE`: Can purchase using the `swap` endpoint.
* - [Can receive ERC1155 tokens](https://docs.openzeppelin.com/contracts/4.x/api/token/erc1155#IERC1155Receiver)
*
* ##### Inherits:
@@ -192,7 +193,14 @@ contract Market is
/**
* @notice Role conferring the ability to purchase supply when inventory is below the priority restricted threshold.
*/
- bytes32 public constant ALLOWLIST_ROLE = keccak256("ALLOWLIST_ROLE");
+ bytes32 public constant PRIORITY_ALLOWLIST_ROLE =
+ keccak256("PRIORITY_ALLOWLIST_ROLE");
+
+ /**
+ * @notice Role conferring the ability to purchase using the `swap` endpoint.
+ */
+ bytes32 public constant SWAP_ALLOWLIST_ROLE =
+ keccak256("SWAP_ALLOWLIST_ROLE");
/**
* @notice The number of decimal places reserved for Nori fee calculations.
@@ -373,7 +381,8 @@ contract Market is
_setPurchasingToken({purchasingToken: purchasingToken});
_setPriceMultiple({priceMultiple: priceMultiple_});
_grantRole({role: DEFAULT_ADMIN_ROLE, account: _msgSender()});
- _grantRole({role: ALLOWLIST_ROLE, account: _msgSender()});
+ _grantRole({role: PRIORITY_ALLOWLIST_ROLE, account: _msgSender()});
+ _grantRole({role: SWAP_ALLOWLIST_ROLE, account: _msgSender()});
_grantRole({role: MARKET_ADMIN_ROLE, account: _msgSender()});
}
@@ -543,7 +552,7 @@ contract Market is
/**
* @notice Sets the current value of the priority restricted threshold, which is the amount of inventory
- * that will always be reserved to sell only to buyers with the `ALLOWLIST_ROLE` role.
+ * that will always be reserved to sell only to buyers with the `PRIORITY_ALLOWLIST_ROLE` role.
* @dev Emits a `SetPriorityRestrictedThreshold` event.
*
* ##### Requirements:
@@ -674,6 +683,7 @@ contract Market is
* ##### Requirements:
*
* - Can only be used when this contract is not paused.
+ * - Can only be used if the message sender has the `SWAP_ALLOWLIST_ROLE`.
* @param recipient The address to which the certificate will be issued.
* @param amount The total amount of Removals being purchased.
* @param deadline The EIP2612 permit deadline in Unix time.
@@ -688,7 +698,7 @@ contract Market is
uint8 v,
bytes32 r,
bytes32 s
- ) external whenNotPaused {
+ ) external whenNotPaused onlyRole(SWAP_ALLOWLIST_ROLE) {
_validateCertificateAmount({amount: amount});
SupplyAllocationData memory allocationData = _allocateRemovals({
certificateAmount: amount
@@ -728,10 +738,14 @@ contract Market is
*
* - Can only be used when this contract is not paused.
* - Can only be used if this contract has been granted approval to transfer the sender's ERC20 tokens.
+ * - Can only be used if the message sender has the `SWAP_ALLOWLIST_ROLE`.
* @param recipient The address to which the certificate will be issued.
* @param amount The total amount of Removals to purchase.
*/
- function swap(address recipient, uint256 amount) external whenNotPaused {
+ function swap(
+ address recipient,
+ uint256 amount
+ ) external whenNotPaused onlyRole(SWAP_ALLOWLIST_ROLE) {
_validateCertificateAmount({amount: amount});
SupplyAllocationData memory allocationData = _allocateRemovals({
certificateAmount: amount
@@ -847,7 +861,7 @@ contract Market is
/**
* @notice Returns the current value of the priority restricted threshold, which is the amount of inventory
- * that will always be reserved to sell only to buyers with the `ALLOWLIST_ROLE` role.
+ * that will always be reserved to sell only to buyers with the `PRIORITY_ALLOWLIST_ROLE` role.
* @return The threshold of supply allowed for priority customers only.
*/
function getPriorityRestrictedThreshold() external view returns (uint256) {
@@ -1435,7 +1449,7 @@ contract Market is
b: certificateAmount
});
if (supplyAfterPurchase < _priorityRestrictedThreshold) {
- if (!hasRole({role: ALLOWLIST_ROLE, account: _msgSender()})) {
+ if (!hasRole({role: PRIORITY_ALLOWLIST_ROLE, account: _msgSender()})) {
revert LowSupplyAllowlistRequired();
}
}
diff --git a/docs/Market.md b/docs/Market.md
index 2ddff9e7..7c754cd3 100644
--- a/docs/Market.md
+++ b/docs/Market.md
@@ -11,7 +11,7 @@ Each of these certificates is a non-transferrable, non-fungible token that owns
and token balances that comprise the specific certificate for the amount purchased.
The market maintains a "priority restricted threshold", which is a configurable threshold of supply that is
-always reserved to sell only to buyers who have the `ALLOWLIST_ROLE`. Purchases that would drop supply below
+always reserved to sell only to buyers who have the `PRIORITY_ALLOWLIST_ROLE`. Purchases that would drop supply below
this threshold will revert without the correct role.
###### Additional behaviors and features
@@ -23,7 +23,8 @@ state are pausable.
- `MARKET_ADMIN_ROLE`: Can set the value of market configuration variables: fee percentage, fee wallet address,
priority restricted threshold, purchasing token, and price multiple. Can execute replacement operations through
the `replace` function. Can submit special orders through `swapWithoutFeeSpecialOrder`.
-- `ALLOWLIST_ROLE`: Can purchase from priority restricted supply.
+- `PRIORITY_ALLOWLIST_ROLE`: Can purchase from priority restricted supply.
+- `SWAP_ALLOWLIST_ROLE`: Can purchase using the `swap` endpoint.
- [Can receive ERC1155 tokens](https://docs.openzeppelin.com/contracts/4.x/api/token/erc1155#IERC1155Receiver)
##### Inherits:
@@ -137,10 +138,10 @@ restricted threshold.
-### ALLOWLIST_ROLE
+### PRIORITY_ALLOWLIST_ROLE
```solidity
-bytes32 ALLOWLIST_ROLE
+bytes32 PRIORITY_ALLOWLIST_ROLE
```
Role conferring the ability to purchase supply when inventory is below the priority restricted threshold.
@@ -148,6 +149,17 @@ Role conferring the ability to purchase supply when inventory is below the prior
+### SWAP_ALLOWLIST_ROLE
+
+```solidity
+bytes32 SWAP_ALLOWLIST_ROLE
+```
+
+Role conferring the ability to purchase using the `swap` endpoint.
+
+
+
+
### FEE_DECIMALS
```solidity
@@ -485,7 +497,7 @@ function setPriorityRestrictedThreshold(uint256 threshold) external
```
Sets the current value of the priority restricted threshold, which is the amount of inventory
-that will always be reserved to sell only to buyers with the `ALLOWLIST_ROLE` role.
+that will always be reserved to sell only to buyers with the `PRIORITY_ALLOWLIST_ROLE` role.
Emits a `SetPriorityRestrictedThreshold` event.
@@ -606,27 +618,27 @@ https://docs.openzeppelin.com/contracts/4.x/api/token/erc1155#ERC1155Receiver) f
### swap
```solidity
-function swap(address recipient, address permitOwner, uint256 amount, uint256 deadline, uint8 v, bytes32 r, bytes32 s) external
+function swap(address recipient, uint256 amount, uint256 deadline, uint8 v, bytes32 r, bytes32 s) external
```
Exchange ERC20 tokens for an ERC721 certificate by transferring ownership of the removals to the
certificate. Relies on the EIP-2612 permit extension to facilitate ERC20 token transfer.
See [ERC20Permit](https://docs.openzeppelin.com/contracts/4.x/api/token/erc20#ERC20Permit) for more.
-The message sender must present a valid permit to this contract to temporarily authorize this market
-to transfer the permit owner's ERC20 to complete the purchase. A certificate is minted in the Certificate contract
+The message sender must sign and present a valid permit to this contract to temporarily authorize this market
+to transfer their ERC20 to complete the purchase. A certificate is minted in the Certificate contract
to the specified recipient and the ERC20 is distributed to the suppliers of the carbon removals,
to the RestrictedNORI contract that controls any restricted tokens owed to the suppliers, and finally
to Nori Inc. as a market operator fee.
##### Requirements:
-- Can only be used when this contract is not paused.
+- Can only be used when this contract is not paused.
+- Can only be used if the message sender has the `SWAP_ALLOWLIST_ROLE`.
| Name | Type | Description |
| ---- | ---- | ----------- |
| recipient | address | The address to which the certificate will be issued. |
-| permitOwner | address | The address that signed the EIP2612 permit and will pay for the removals. |
| amount | uint256 | The total amount of Removals being purchased. |
| deadline | uint256 | The EIP2612 permit deadline in Unix time. |
| v | uint8 | The recovery identifier for the permit's secp256k1 signature. |
@@ -654,7 +666,8 @@ to Nori Inc. as a market operator fee.
##### Requirements:
- Can only be used when this contract is not paused.
-- Can only be used if this contract has been granted approval to transfer the sender's ERC20 tokens.
+- Can only be used if this contract has been granted approval to transfer the sender's ERC20 tokens.
+- Can only be used if the message sender has the `SWAP_ALLOWLIST_ROLE`.
| Name | Type | Description |
| ---- | ---- | ----------- |
@@ -734,7 +747,7 @@ function getPriorityRestrictedThreshold() external view returns (uint256)
```
Returns the current value of the priority restricted threshold, which is the amount of inventory
-that will always be reserved to sell only to buyers with the `ALLOWLIST_ROLE` role.
+that will always be reserved to sell only to buyers with the `PRIORITY_ALLOWLIST_ROLE` role.
@@ -1180,9 +1193,9 @@ Validates the certificate purchase amount.
##### Requirements:
- Amount is not zero.
-- Amount is divisible by 10^(18 - `_purchasingToken.decimals()` + FEE_DECIMALS). This requirement means that the
-smallest purchase amount for a token with 18 decimals (e.g., NORI) and 2 FEE_DECIMALS is 100, whilst the smallest
-purchase amount for a token with 6 decimals (e.g., USDC) and 2 FEE_DECIMALS is 100,000,000,000,000.
+- Amount is divisible by 10^(18 - `_purchasingToken.decimals()` + `FEE_DECIMALS`). This requirement means that the
+smallest purchase amount for a token with 18 decimals (e.g., NORI) and 2 `FEE_DECIMALS` is 100, whilst the smallest
+purchase amount for a token with 6 decimals (e.g., USDC) and 2 `FEE_DECIMALS` is 100,000,000,000,000.
| Name | Type | Description |
| ---- | ---- | ----------- |
diff --git a/docs/README.md b/docs/README.md
index bb08645b..af12c328 100644
--- a/docs/README.md
+++ b/docs/README.md
@@ -1,6 +1,6 @@
# Nori Smart Contracts
-Nori's core product is the Nori _Removal_ Tonne (NRT). NRTs can be understood as carbon removal credits that are granted to a supplier for the CO2 they have removed from the atmosphere via [regenerative agriculture](https://www.weforum.org/agenda/2023/01/5-ways-to-scale-regenerative-agriculture-davos23/). For each tonne of carbon removed, a supplier is granted 1 NRT. Suppliers then consign their NRTs to Nori's marketplace, effectively listing them for sale at a fixed amount of USDC.
+Nori's core product is the Nori _Removal_ Tonne (NRT). NRTs can be understood as carbon removal credits that are granted to a supplier for the CO2 they have removed from the atmosphere via [regenerative agriculture](https://www.weforum.org/agenda/2023/01/5-ways-to-scale-regenerative-agriculture-davos23/). For each tonne of carbon removed, a supplier is granted 1 NRT. Suppliers then consign their NRTs to Nori's marketplace, effectively listing them for sale at a fixed amount of USDC.
If a supplier is found to have released the sequestered carbon the corresponding Removals will be burned and funds from the insurance reserve used to replace them making the _Certificate_ and buyer whole. Automating the replacement of those burned Removals is on the future roadmap but is not implemented here.
@@ -46,7 +46,8 @@ The core swap market contract of the Nori platform. Removals are listed for sale
##### Swap mechanism
-The `swap` function is the primary point of interaction with the market for buyers. Calls to the `swap` function include an amount of supported ERC20 tokens (_USDC_ or _BridgedPolygonNORI_) to spend and a recipient wallet address to which the _Certificate_ is minted. These calls also include a pre-signed authorization to transfer the corresponding amount of the supported ERC20 following the [ERC20Permit](https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/token/ERC20/extensions/draft-ERC20Permit.sol) pattern.
+The `swap` function is the primary point of interaction with the market for buyers. Calls to the `swap` function include an amount of NRTs to purchase and a recipient wallet address to which the _Certificate_ is minted. These calls also include a pre-signed authorization to transfer the corresponding amount (including fees) of the supported ERC20 (_USDC_ or _BridgedPolygonNORI_) following the [ERC20Permit](https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/token/ERC20/extensions/draft-ERC20Permit.sol) pattern. Alternatively, the buyer can pre-approve the Market contract as a spender of the corresponding ERC20 and use the version
+of `swap` that does not require permit arguments. Note that this previously public endpoint now requires buyers to have the `SWAP_ALLOWLIST_ROLE` to comply with sanctions laws and regulations.
The ERC20 tokens transferred from the buyer to this contract are distributed as follows:
@@ -60,7 +61,7 @@ An unsold _Removal_ can be withdrawn from the market (delisted for sale) by the
##### Priority Supply Mechanism
-The market may be configured with a priority supply threshold. When supply listed for sale drops below this threshold purchases are restricted to addresses having the `ALLOWLIST_ROLE` role. This mechanism gives Nori the ability to reserve supply for pre-committed partnerships or other off-chain arrangements.
+The market may be configured with a priority supply threshold. When supply listed for sale drops below this threshold purchases are restricted to addresses having the `PRIORITY_ALLOWLIST_ROLE` role. This mechanism gives Nori the ability to reserve supply for pre-committed partnerships or other off-chain arrangements.
### Support Libraries
diff --git a/docs/UInt256ArrayLib.md b/docs/UInt256ArrayLib.md
index 691ca1f2..8295c3db 100644
--- a/docs/UInt256ArrayLib.md
+++ b/docs/UInt256ArrayLib.md
@@ -106,5 +106,32 @@ new uint256[](100).fill(1).slice(0, 50); // returns: [:50]
| ---- | ---- | ----------- |
| sliced | uint256[] | The sliced array. |
+### shrink
+
+```solidity
+function shrink(uint256[] values, uint256 length) internal pure returns (uint256[])
+```
+
+Shorten an array to specified length.
+
+Shortens the specified array to the specified length by directly overwriting
+the length of the original array in storage.
+
+##### Example usage:
+
+```solidity
+new uint256[](100).fill(1).shrink(50); // resizes the original array to length 50
+```
+-
+
+| Name | Type | Description |
+| ---- | ---- | ----------- |
+| values | uint256[] | The array to shorten. |
+| length | uint256 | The desired length of the array. |
+
+| Name | Type | Description |
+| ---- | ---- | ----------- |
+| [0] | uint256[] | values The shortened array. |
+
diff --git a/test/Market.t.sol b/test/Market.t.sol
index a8283901..60fd7516 100644
--- a/test/Market.t.sol
+++ b/test/Market.t.sol
@@ -94,6 +94,7 @@ contract Market_swap_revertsWhenUnsafeERC20TransferFails is UpgradeableMarket {
1 days,
_unsafeErc20
);
+ _market.grantRole(_market.SWAP_ALLOWLIST_ROLE(), owner);
}
function test() external {
@@ -171,6 +172,7 @@ contract MarketReplaceTestHelper is UpgradeableMarket {
1 days,
_bpNori
);
+ _market.grantRole(_market.SWAP_ALLOWLIST_ROLE(), owner);
vm.prank(owner);
_market.swap(
owner,
@@ -402,6 +404,7 @@ contract Market_swap_emits_event_and_skips_mint_when_minting_rNori_to_nonERC1155
1 days,
_bpNori
);
+ _market.grantRole(_market.SWAP_ALLOWLIST_ROLE(), owner);
}
function test() external {
@@ -478,6 +481,7 @@ contract Market_swap_emits_and_skips_transfer_when_transferring_wrong_erc20_to_r
1 days,
_noriUSDC
);
+ _market.grantRole(_market.SWAP_ALLOWLIST_ROLE(), owner);
}
function test() external {
@@ -871,7 +875,16 @@ contract Market_withdraw_2x1_back is MarketBalanceTestHelper {
contract Market_ALLOWLIST_ROLE is UpgradeableMarket {
function test() external {
- assertEq(_market.ALLOWLIST_ROLE(), keccak256("ALLOWLIST_ROLE"));
+ assertEq(
+ _market.PRIORITY_ALLOWLIST_ROLE(),
+ keccak256("PRIORITY_ALLOWLIST_ROLE")
+ );
+ }
+}
+
+contract Market_SANCTION_ALLOWLIST_ROLE is UpgradeableMarket {
+ function test() external {
+ assertEq(_market.SWAP_ALLOWLIST_ROLE(), keccak256("SWAP_ALLOWLIST_ROLE"));
}
}
@@ -948,7 +961,10 @@ contract Market__validatePrioritySupply_buyerIsAllowlistedAndAmountExceedsPriori
_grantRole({role: MARKET_ADMIN_ROLE, account: _msgSender()});
vm.prank(_msgSender());
this.setPriorityRestrictedThreshold({threshold: 0.5 ether});
- _grantRole({role: ALLOWLIST_ROLE, account: _namedAccounts.deployer});
+ _grantRole({
+ role: PRIORITY_ALLOWLIST_ROLE,
+ account: _namedAccounts.deployer
+ });
}
function test() external view {
@@ -1436,6 +1452,7 @@ contract Market_supplierSelectionUsingUpSuppliersLastRemoval is
1 days,
_bpNori
);
+ _market.grantRole(_market.SWAP_ALLOWLIST_ROLE(), owner);
}
function test() external {
@@ -1508,7 +1525,7 @@ contract MarketSupplierSelectionNotUsingUpSuppliersLastRemoval is
checkoutTotal = _market.calculateCheckoutTotal(certificateAmount);
vm.prank(_namedAccounts.admin);
_bpNori.deposit(owner, abi.encode(checkoutTotal));
-
+ _market.grantRole(_market.SWAP_ALLOWLIST_ROLE(), owner);
signedPermit = _signatureUtils.generatePermit(
ownerPrivateKey,
address(_market),
@@ -1516,6 +1533,7 @@ contract MarketSupplierSelectionNotUsingUpSuppliersLastRemoval is
1 days,
_bpNori
);
+ _market.grantRole(_market.SWAP_ALLOWLIST_ROLE(), owner);
}
function test() external {
@@ -1667,6 +1685,7 @@ contract Market_USDC_swap_respects_decimal_mismatch is UpgradeableUSDCMarket {
1 days,
_purchasingToken
);
+ _market.grantRole(_market.SWAP_ALLOWLIST_ROLE(), owner);
vm.startPrank(owner);
vm.expectEmit(false, false, false, true);
@@ -1759,6 +1778,7 @@ contract Market_USDC_swap_withholds_restricted_nori is UpgradeableUSDCMarket {
1 days,
_purchasingToken
);
+ _market.grantRole(_market.SWAP_ALLOWLIST_ROLE(), owner);
vm.startPrank(owner);
vm.expectEmit(false, false, false, true);
@@ -1794,19 +1814,22 @@ contract Market_USDC_swap_withholds_restricted_nori is UpgradeableUSDCMarket {
contract Market_validates_certificate_amount is UpgradeableUSDCMarket {
address owner;
+ uint256 ownerPrivateKey = 0xA11CE;
uint256 checkoutTotal;
SignedPermit signedPermit;
function setUp() external {
+ owner = vm.addr(ownerPrivateKey);
vm.prank(_namedAccounts.admin);
_market.grantRole(_market.MARKET_ADMIN_ROLE(), _namedAccounts.admin);
+ vm.prank(_namedAccounts.admin);
+
+ _market.grantRole(_market.SWAP_ALLOWLIST_ROLE(), owner);
}
function test() external {
- uint256 ownerPrivateKey = 0xA11CE;
uint256 noriFeePercentage = _market.getNoriFeePercentage();
uint256 priceMultiple = _market.getPriceMultiple();
- owner = vm.addr(ownerPrivateKey);
uint256[] memory testValues = new uint256[](4);
testValues[0] = 0;
@@ -1835,6 +1858,7 @@ contract Market_validates_certificate_amount is UpgradeableUSDCMarket {
vm.expectRevert(revertData);
_market.swap(owner, numberOfNRTsToPurchase);
+ vm.prank(owner);
vm.expectRevert(revertData);
_market.swap(owner, numberOfNRTsToPurchase, 0, 0, 0, 0);
diff --git a/test/Market.test.ts b/test/Market.test.ts
index 3d826510..d8d6e0e9 100644
--- a/test/Market.test.ts
+++ b/test/Market.test.ts
@@ -14,7 +14,7 @@ describe('Market', () => {
describe('roles', () => {
for (const { role } of [
{ role: 'DEFAULT_ADMIN_ROLE' },
- { role: 'ALLOWLIST_ROLE' },
+ { role: 'PRIORITY_ALLOWLIST_ROLE' },
] as const) {
it(`will assign the role ${role} to the deployer and set the DEFAULT_ADMIN_ROLE as the role admin`, async () => {
const { market, hre } = await setupTest();
@@ -158,9 +158,9 @@ describe('Market', () => {
);
});
});
- describe('ALLOWLIST_ROLE', () => {
- it(`should allow allowlisted accounts to purchase supply when inventory is below threshold`, async () => {
- const role = 'ALLOWLIST_ROLE';
+ describe('PRIORITY_ALLOWLIST_ROLE', () => {
+ it(`should allow priority allowlisted accounts to purchase supply when inventory is below threshold`, async () => {
+ const role = 'PRIORITY_ALLOWLIST_ROLE';
const accountWithRole = 'admin';
const totalAvailableSupply = formatTokenAmount(50);
const { bpNori, market, hre } = await setupTest({
@@ -187,6 +187,10 @@ describe('Market', () => {
spender: market.address,
value,
});
+ await market.grantRole(
+ hre.ethers.utils.id('SWAP_ALLOWLIST_ROLE'),
+ hre.namedAccounts[accountWithRole]
+ );
await expect(
market
.connect(namedSigners[accountWithRole])
@@ -200,7 +204,7 @@ describe('Market', () => {
)
).not.to.be.reverted;
});
- it(`should revert when an account that is not on the allowlist tries purchase supply when inventory is below the threshold`, async () => {
+ it(`should revert when an account that does not have the priority allowlist role tries purchase supply when inventory is below the threshold`, async () => {
const { bpNori, market, hre, totalAmountOfSupply } = await setupTest({
userFixtures: {
supplier: {
@@ -215,7 +219,7 @@ describe('Market', () => {
await market.setPriorityRestrictedThreshold(
priorityRestrictedThreshold
);
- const roleId = await market.ALLOWLIST_ROLE();
+ const roleId = await market.PRIORITY_ALLOWLIST_ROLE();
expect(await market.hasRole(roleId, accountWithoutRole.address)).to.be
.false;
const value = await market.calculateCheckoutTotal(totalAmountOfSupply);
@@ -224,6 +228,10 @@ describe('Market', () => {
spender: market.address,
value,
});
+ await market.grantRole(
+ hre.ethers.utils.id('SWAP_ALLOWLIST_ROLE'),
+ accountWithoutRole.address
+ );
await expect(
market
.connect(accountWithoutRole)
@@ -376,6 +384,10 @@ describe('Market', () => {
spender: market.address,
value,
});
+ await market.grantRole(
+ hre.ethers.utils.id('SWAP_ALLOWLIST_ROLE'),
+ buyer.address
+ );
await market
.connect(buyer)
['swap(address,uint256,uint256,uint8,bytes32,bytes32)'](
@@ -1030,6 +1042,10 @@ describe('Market', () => {
spender: market.address,
value,
});
+ await market.grantRole(
+ hre.ethers.utils.id('SWAP_ALLOWLIST_ROLE'),
+ buyer.address
+ );
await market
.connect(buyer)
['swap(address,uint256,uint256,uint8,bytes32,bytes32)'](
@@ -1109,6 +1125,10 @@ describe('Market', () => {
userFixtures.buyer.bpBalance
);
// expect(await certificate.balanceOf(investor1.address, 0)).to.equal(0); // todo
+ await market.grantRole(
+ hre.ethers.utils.id('SWAP_ALLOWLIST_ROLE'),
+ buyer.address
+ );
await market
.connect(buyer)
['swap(address,uint256,uint256,uint8,bytes32,bytes32)'](
diff --git a/test/Removal.t.sol b/test/Removal.t.sol
index cc247ac6..cf2dd76b 100644
--- a/test/Removal.t.sol
+++ b/test/Removal.t.sol
@@ -105,6 +105,7 @@ contract Removal_consign_revertsForSoldRemovals is UpgradeableMarket {
1 days,
_bpNori
);
+ _market.grantRole(_market.SWAP_ALLOWLIST_ROLE(), owner);
vm.prank(owner);
_market.swap(
owner,
@@ -808,6 +809,7 @@ contract Removal_release_retired_burned is UpgradeableMarket {
1 days,
_bpNori
);
+ _market.grantRole(_market.SWAP_ALLOWLIST_ROLE(), owner);
vm.prank(owner);
_market.swap(
owner,
@@ -866,6 +868,7 @@ contract Removal_release_retired is UpgradeableMarket {
1 days,
_bpNori
);
+ _market.grantRole(_market.SWAP_ALLOWLIST_ROLE(), owner);
vm.prank(owner);
_market.swap(
owner,
@@ -918,6 +921,7 @@ contract Removal_release_retired_oneHundredCertificates is UpgradeableMarket {
1 days,
_bpNori
);
+ _market.grantRole(_market.SWAP_ALLOWLIST_ROLE(), owner);
vm.prank(owner);
_market.swap(
owner,
@@ -1050,6 +1054,7 @@ contract Removal_release_unlisted_listed_and_retired is UpgradeableMarket {
1 days,
_bpNori
);
+ _market.grantRole(_market.SWAP_ALLOWLIST_ROLE(), owner);
vm.prank(owner);
_market.swap(
owner,
@@ -1163,6 +1168,7 @@ contract Removal_release_retired_2x is UpgradeableMarket {
1 days,
_bpNori
);
+ _market.grantRole(_market.SWAP_ALLOWLIST_ROLE(), owner);
vm.prank(owner);
_market.swap(
owner,
@@ -1264,6 +1270,7 @@ contract Removal_getMarketBalance is UpgradeableMarket {
1 days,
_bpNori
);
+ _market.grantRole(_market.SWAP_ALLOWLIST_ROLE(), owner);
vm.prank(owner);
_market.swap(
owner,
diff --git a/test/RestrictedNORI.t.sol b/test/RestrictedNORI.t.sol
index 240e0208..d49bce00 100644
--- a/test/RestrictedNORI.t.sol
+++ b/test/RestrictedNORI.t.sol
@@ -140,6 +140,7 @@ contract RestrictedNORI_revokeUnreleasedTokens is UpgradeableMarket {
1 days,
_bpNori
);
+ _market.grantRole(_market.SWAP_ALLOWLIST_ROLE(), owner);
vm.prank(owner);
_market.swap(
owner,
diff --git a/test/checkout.int.t.sol b/test/checkout.int.t.sol
index f10d739b..52276ab9 100644
--- a/test/checkout.int.t.sol
+++ b/test/checkout.int.t.sol
@@ -68,6 +68,7 @@ contract Checkout_buyingFromOneRemoval is Checkout {
1 days,
_bpNori
);
+ _market.grantRole(_market.SWAP_ALLOWLIST_ROLE(), _owner);
}
function test() external {
@@ -106,6 +107,7 @@ contract Checkout_buyingFromOneRemoval_byApproval is Checkout {
_bpNori.deposit(_namedAccounts.buyer, abi.encode(_amount));
vm.prank(_namedAccounts.buyer);
_bpNori.approve(address(_market), MAX_INT);
+ _market.grantRole(_market.SWAP_ALLOWLIST_ROLE(), _namedAccounts.buyer);
assertEq(_removal.getMarketBalance(), 1 ether);
assertEq(_removal.numberOfTokensOwnedByAddress(address(_market)), 1);
_assertExpectedBalances(_namedAccounts.supplier, 0, false, 0);
@@ -129,7 +131,7 @@ contract Checkout_buyingFromOneRemoval_byApproval is Checkout {
}
}
-contract Checkout_swapWithDifferentPermitSignerAndMsgSender is Checkout {
+contract Checkout_swapRevertsWithDifferentPermitSignerAndMsgSender is Checkout {
uint256 private _ownerPrivateKey = 0xA11CE;
address private _owner = vm.addr(_ownerPrivateKey);
address private _msgSender = vm.addr(0x12345);
@@ -160,10 +162,11 @@ contract Checkout_swapWithDifferentPermitSignerAndMsgSender is Checkout {
1 days,
_bpNori
);
+ _market.grantRole(_market.SWAP_ALLOWLIST_ROLE(), _owner);
}
function test() external {
- vm.prank(_owner);
+ vm.expectRevert();
_market.swap(
_owner,
_certificateAmount,
@@ -172,14 +175,47 @@ contract Checkout_swapWithDifferentPermitSignerAndMsgSender is Checkout {
_signedPermit.r,
_signedPermit.s
);
- _assertExpectedBalances(address(_market), 0, false, 0);
- _assertExpectedBalances(_namedAccounts.supplier, 0, false, 0);
- _assertExpectedBalances(address(_certificate), _certificateAmount, true, 1);
- assertEq(
- _removal.balanceOf(address(_certificate), _removalIds[0]),
- _certificateAmount
+ }
+}
+
+contract Checkout_swapRevertsWhenBuyerIsMissingSANCTION_ALLOWLIST_ROLE is
+ Checkout
+{
+ uint256 private _ownerPrivateKey = 0xA11CE;
+ address private _owner = vm.addr(_ownerPrivateKey);
+ address private _msgSender = vm.addr(0x12345);
+ uint256 private _certificateAmount = 1 ether;
+ uint256 private _amount;
+ SignedPermit private _signedPermit;
+
+ function setUp() external {
+ _removalIds = _seedRemovals({
+ to: _namedAccounts.supplier,
+ count: 1,
+ list: true
+ });
+ _amount = _market.calculateCheckoutTotal(_certificateAmount);
+ vm.prank(_namedAccounts.admin);
+ _bpNori.deposit(_owner, abi.encode(_amount));
+ _signedPermit = _signatureUtils.generatePermit(
+ _ownerPrivateKey,
+ address(_market),
+ _amount,
+ 1 days,
+ _bpNori
+ );
+ }
+
+ function test() external {
+ vm.expectRevert();
+ _market.swap(
+ _owner,
+ _certificateAmount,
+ _signedPermit.permit.deadline,
+ _signedPermit.v,
+ _signedPermit.r,
+ _signedPermit.s
);
- assertEq(_certificate.ownerOf(_certificateTokenId), _owner);
}
}
@@ -224,6 +260,7 @@ contract Checkout_buyingFromTenRemovals is Checkout {
1 days,
_bpNori
);
+ _market.grantRole(_market.SWAP_ALLOWLIST_ROLE(), _owner);
_assertExpectedBalances(_namedAccounts.supplier, 0, false, 0);
_assertExpectedBalances(address(_certificate), 0, false, 0);
assertEq(_removal.balanceOf(address(_certificate), _removalIds[0]), 0);
@@ -591,6 +628,7 @@ contract Checkout_buyingFromTenSuppliers is Checkout {
1 days,
_bpNori
);
+ _market.grantRole(_market.SWAP_ALLOWLIST_ROLE(), _owner);
_assertExpectedBalances(_namedAccounts.supplier, 0, false, 0);
_assertExpectedBalances(address(_certificate), 0, false, 0);
assertEq(
@@ -674,6 +712,7 @@ contract Checkout_buyingWithAlternateERC20 is Checkout {
list: true,
holdbackPercentage: 0
});
+ _market.grantRole(_market.SWAP_ALLOWLIST_ROLE(), _owner);
}
function test() external {
@@ -798,6 +837,7 @@ contract Checkout_buyingWithAlternateERC20_floatingPointPriceMultiple is
1 days,
_erc20
);
+ _market.grantRole(_market.SWAP_ALLOWLIST_ROLE(), _owner);
}
function test() external {