Skip to content

Commit

Permalink
[NO-2653] Prototype replacement mechanism for released removals (#496)
Browse files Browse the repository at this point in the history
* almost the whole thing

* additional validations, tests

* add guarantee discrepancy accounting

* almost the whole thing

* additional validations, tests

* triage slither

* use discrepancy to avoid over-replacements, tests

* docs and snapshot

* renames

* review comments

* update event typings

* docgen, snapshot, fix event typing

Co-authored-by: amiecorso <amie@nori.com>
Co-authored-by: Richie Farman <rlfarman@gmail.com>
  • Loading branch information
3 people committed Jan 25, 2023
1 parent 65f3145 commit 075b30a
Show file tree
Hide file tree
Showing 11 changed files with 739 additions and 170 deletions.
128 changes: 66 additions & 62 deletions .gas-snapshot
Expand Up @@ -20,7 +20,7 @@ BridgedPolygonNORI_name:test() (gas: 17205)
BridgedPolygonNORI_permit:test() (gas: 92606)
Certificate__msgSenderERC721A:test() (gas: 422)
Certificate_approve:test() (gas: 17906)
Certificate_burn:test() (gas: 187904)
Certificate_burn:test() (gas: 188394)
Certificate_burn:test_reverts_when_paused() (gas: 54783)
Certificate_name:test() (gas: 17282)
Certificate_safeTransferFrom:test() (gas: 51377)
Expand All @@ -29,7 +29,7 @@ Certificate_safeTransferFrom:test_overload_reverts_when_paused() (gas: 38141)
Certificate_safeTransferFrom:test_reverts_when_paused() (gas: 38174)
Certificate_safeTransferFrom_reverts_ForbiddenTransferAfterMinting:test() (gas: 19043)
Certificate_safeTransferFrom_reverts_ForbiddenTransferAfterMinting:test_overload() (gas: 19056)
Certificate_setApprovalForAll:test() (gas: 17899)
Certificate_setApprovalForAll:test() (gas: 17929)
Certificate_supportsInterface:test() (gas: 5159)
Certificate_transferFrom:test() (gas: 48618)
Certificate_transferFrom:test_reverts_when_paused() (gas: 38088)
Expand All @@ -40,16 +40,16 @@ LockedNORITest:testReentryTokensReceived() (gas: 1267234)
LockedNORITest:testReentryTokensToSend() (gas: 1268801)
LockedNORITest:testTokensReceivedReverts() (gas: 69048)
LockedNORILib_availableAmount:test() (gas: 12371)
MarketSupplierSelectionNotUsingUpSuppliersLastRemoval:test() (gas: 889467)
Market_ALLOWLIST_ROLE:test() (gas: 12799)
MarketSupplierSelectionNotUsingUpSuppliersLastRemoval:test() (gas: 890618)
Market_ALLOWLIST_ROLE:test() (gas: 12777)
Market__addActiveRemoval:test() (gas: 183344)
Market__addActiveRemoval:test__lis2VintagesFor1SupplierFor2SubIdentifiers() (gas: 242879)
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: 443)
Market__isAuthorizedWithdrawal_true:test_returnsTrueWhenMsgSenderHasDefaultAdminRole() (gas: 96555)
Market__isAuthorizedWithdrawal_true:test_returnsTrueWhenMsgSenderEqualsOwner() (gas: 399)
Market__isAuthorizedWithdrawal_true:test_returnsTrueWhenMsgSenderHasDefaultAdminRole() (gas: 96533)
Market__isAuthorizedWithdrawal_true:test_returnsTrueWhenMsgSenderIsApprovedForAll() (gas: 9011)
Market__multicall_empty_bytes_reverts:test() (gas: 19362)
Market__multicall_initialize_reverts:test() (gas: 34619)
Expand All @@ -61,37 +61,41 @@ Market__validatePrioritySupply_buyerIsAllowlistedAndAmountExceedsPriorityRestric
Market__validatePrioritySupply_reverts_LowSupplyAllowlistRequired:test() (gas: 7710)
Market__validateSupply:test() (gas: 292)
Market__validateSupply:test_reverts_OutOfSupply() (gas: 3194)
Market_getActiveSuppliers:test_1_supplier() (gas: 609757)
Market_getActiveSuppliers:test_3_suppliers() (gas: 1261343)
Market_getActiveSuppliers:test_no_suppliers() (gas: 20925)
Market_getActiveSuppliers:test_1_supplier() (gas: 609713)
Market_getActiveSuppliers:test_3_suppliers() (gas: 1261255)
Market_getActiveSuppliers:test_no_suppliers() (gas: 20903)
Market_getPriceMultiple:test() (gas: 14873)
Market_getRemovalIdsForSupplier:test_1_removal() (gas: 610072)
Market_getRemovalIdsForSupplier:test_3_removals() (gas: 992973)
Market_getRemovalIdsForSupplier:test_3_removals_different_vintages() (gas: 1039082)
Market_getRemovalIdsForSupplier:test_1_removal() (gas: 610050)
Market_getRemovalIdsForSupplier:test_3_removals() (gas: 992951)
Market_getRemovalIdsForSupplier:test_3_removals_different_vintages() (gas: 1039060)
Market_getRemovalIdsForSupplier:test_no_removals() (gas: 26022)
Market_onERC1155BatchReceived:test() (gas: 208168)
Market_onERC1155BatchReceived_reverts_SenderNotRemovalContract:test() (gas: 353262)
Market_onERC1155Received:test() (gas: 206036)
Market_onERC1155Received_reverts_SenderNotRemovalContract:test() (gas: 158734)
Market_purchasingTokenAddress:test() (gas: 17169)
Market_setNoriFeePercentage_revertsInvalidPercentage:test() (gas: 20276)
Market_replace:test() (gas: 337349)
Market_replace_reverts_CertificateNotYetMinted:test() (gas: 49424)
Market_replace_reverts_ReplacementAmountExceedsNrtDeficit:test() (gas: 53081)
Market_replace_reverts_ReplacementAmountMismatch:test() (gas: 86481)
Market_setNoriFeePercentage_revertsInvalidPercentage:test() (gas: 20254)
Market_setPriorityRestrictedThreshold:test() (gas: 157403)
Market_setPriorityRestrictedThreshold:test_zeroAvailable() (gas: 152378)
Market_setPurchasingTokenAndPriceMultiple:test() (gas: 34557)
Market_setPurchasingTokenAndPriceMultiple_revertsIfNotAdmin:test() (gas: 50831)
Market_supplierSelectionUsingUpSuppliersLastRemoval:test() (gas: 886174)
Market_swapWithoutFee_emits_and_skips_transfer_when_transferring_wrong_erc20_to_rNori:test() (gas: 405371)
Market_swap_emits_and_skips_transfer_when_transferring_wrong_erc20_to_rNori:test() (gas: 487998)
Market_swap_emits_event_and_skips_mint_when_minting_rNori_to_nonERC1155Receiver:test() (gas: 543004)
Market_swap_revertsWhenUnsafeERC20TransferFails:test() (gas: 157740)
Market_withdraw_1x3_center:test() (gas: 340401)
Market_withdraw_2x1_back:test() (gas: 345061)
Market_withdraw_2x1_front:test() (gas: 333418)
Market_setPurchasingTokenAndPriceMultiple:test() (gas: 34535)
Market_setPurchasingTokenAndPriceMultiple_revertsIfNotAdmin:test() (gas: 50809)
Market_supplierSelectionUsingUpSuppliersLastRemoval:test() (gas: 887325)
Market_swapWithoutFee_emits_and_skips_transfer_when_transferring_wrong_erc20_to_rNori:test() (gas: 406717)
Market_swap_emits_and_skips_transfer_when_transferring_wrong_erc20_to_rNori:test() (gas: 489291)
Market_swap_emits_event_and_skips_mint_when_minting_rNori_to_nonERC1155Receiver:test() (gas: 544160)
Market_swap_revertsWhenUnsafeERC20TransferFails:test() (gas: 158582)
Market_withdraw_1x3_center:test() (gas: 340512)
Market_withdraw_2x1_back:test() (gas: 345172)
Market_withdraw_2x1_front:test() (gas: 333529)
Market_withdraw_2x1_front_relist:test() (gas: 381544)
Market_withdraw_as_DEFAULT_ADMIN_ROLE:test() (gas: 276111)
Market_withdraw_as_operator:test() (gas: 285278)
Market_withdraw_as_supplier:test() (gas: 274252)
Market_withdraw_reverts:test() (gas: 138753)
Market_withdraw_as_DEFAULT_ADMIN_ROLE:test() (gas: 276222)
Market_withdraw_as_operator:test() (gas: 285389)
Market_withdraw_as_supplier:test() (gas: 274363)
Market_withdraw_reverts:test() (gas: 138864)
NORI_name:test() (gas: 17205)
NORI_permit:test() (gas: 92606)
Removal__beforeTokenTransfer:test() (gas: 17504)
Expand All @@ -106,48 +110,48 @@ Removal_addBalance:test() (gas: 59774)
Removal_addBalance_reverts_RemovalNotYetMinted:test() (gas: 31115)
Removal_batchGetHoldbackPercentages_multipleIds:test() (gas: 11098)
Removal_batchGetHoldbackPercentages_singleId:test() (gas: 10346)
Removal_consign_revertsForSoldRemovals:test() (gas: 1065340)
Removal_getMarketBalance:test() (gas: 1075368)
Removal_getOwnedTokenIds:test_multiple_tokens_with_transfer() (gas: 1076130)
Removal_consign_revertsForSoldRemovals:test() (gas: 1066247)
Removal_getMarketBalance:test() (gas: 1076364)
Removal_getOwnedTokenIds:test_multiple_tokens_with_transfer() (gas: 1076108)
Removal_getOwnedTokenIds:test_no_tokens() (gas: 18683)
Removal_getProjectId:test() (gas: 19307)
Removal_grantRole:test_reverts_when_paused() (gas: 26272)
Removal_migrate:test() (gas: 980346)
Removal_migrate_gasLimit:test() (gas: 15251323)
Removal_migrate_revertsIfRemovalBalanceSumDifferentFromCertificateAmount:test() (gas: 994624)
Removal_mintBatch:test() (gas: 347908)
Removal_mintBatch_list:test() (gas: 557459)
Removal_mintBatch_list_sequential:test() (gas: 748298)
Removal_mintBatch_multiple:test_16() (gas: 3079089)
Removal_mintBatch_multiple:test_2() (gas: 725520)
Removal_mintBatch_multiple:test_32() (gas: 5769990)
Removal_mintBatch_multiple:test_4() (gas: 1061705)
Removal_mintBatch_multiple:test_8() (gas: 1734080)
Removal_migrate:test() (gas: 980847)
Removal_migrate_gasLimit:test() (gas: 15251847)
Removal_migrate_revertsIfRemovalBalanceSumDifferentFromCertificateAmount:test() (gas: 995130)
Removal_mintBatch:test() (gas: 347886)
Removal_mintBatch_list:test() (gas: 557437)
Removal_mintBatch_list_sequential:test() (gas: 748254)
Removal_mintBatch_multiple:test_16() (gas: 3079067)
Removal_mintBatch_multiple:test_2() (gas: 725498)
Removal_mintBatch_multiple:test_32() (gas: 5769968)
Removal_mintBatch_multiple:test_4() (gas: 1061683)
Removal_mintBatch_multiple:test_8() (gas: 1734058)
Removal_mintBatch_revertsInvalidHoldbackPercentage:test() (gas: 56204)
Removal_mintBatch_reverts_mint_to_wrong_address:test() (gas: 88501)
Removal_mintBatch_zero_amount_removal:test() (gas: 310482)
Removal_mintBatch_zero_amount_removal:test() (gas: 310460)
Removal_mintBatch_zero_amount_removal_to_market_reverts:test() (gas: 86745)
Removal_multicall:test_balanceOfBatch() (gas: 490796)
Removal_release_listed:test() (gas: 485812)
Removal_release_listed_isRemovedFromMarket:test() (gas: 486132)
Removal_release_partial_listed:test() (gas: 79202)
Removal_multicall:test_balanceOfBatch() (gas: 490774)
Removal_release_listed:test() (gas: 485777)
Removal_release_listed_isRemovedFromMarket:test() (gas: 486096)
Removal_release_partial_listed:test() (gas: 79180)
Removal_release_retired:test() (gas: 91919)
Removal_release_retired_2x:test() (gas: 97983)
Removal_release_retired_burned:test() (gas: 94399)
Removal_release_retired_burned:testDecrementsCertificateDiscrepancy() (gas: 88441)
Removal_release_retired_oneHundredCertificates:test() (gas: 89029)
Removal_release_reverts_AccessControl:test() (gas: 48728)
Removal_release_unlisted:test() (gas: 48212)
Removal_release_unlisted_listed_and_retired:test() (gas: 235856)
Removal_release_unlisted_listed_and_retired:test() (gas: 235838)
Removal_renounceRole:test_reverts_when_paused() (gas: 19688)
Removal_revokeRole:test_reverts_when_paused() (gas: 26840)
Removal_safeBatchTransferFrom_reverts_ForbiddenTransfer:test() (gas: 32073)
Removal_safeTransferFrom_reverts_ForbiddenTransfer:test() (gas: 27738)
Removal_setHoldbackPercentage:test() (gas: 37780)
Removal_setHoldbackPercentage:test_reverts_InvalidHoldbackPercentage() (gas: 29153)
RemovalQueue_getTotalBalanceFromRemovalQueue:test() (gas: 23808)
RemovalQueue_getTotalBalanceFromRemovalQueue:test_100xRemovalsOfTheDifferentVintages() (gas: 895673)
RemovalQueue_getTotalBalanceFromRemovalQueue:test_100xRemovalsOfTheSameVintage() (gas: 620208)
RemovalQueue_getTotalBalanceFromRemovalQueue:test() (gas: 23899)
RemovalQueue_getTotalBalanceFromRemovalQueue:test_100xRemovalsOfTheDifferentVintages() (gas: 895764)
RemovalQueue_getTotalBalanceFromRemovalQueue:test_100xRemovalsOfTheSameVintage() (gas: 620299)
RemovalQueue_insertRemovalByVintage:test_insertRemovalOnce() (gas: 119590)
RemovalQueue_insertRemovalByVintage:test_insertRemovalTwice() (gas: 121125)
RestrictedNORI__validateSchedule:test_startTimeNotZero() (gas: 291)
Expand All @@ -162,14 +166,14 @@ RestrictedNORI_scheduleExists:test() (gas: 15089)
RestrictedNORI_scheduleExists:test_doesntExist() (gas: 15068)
RestrictedNORI_transfers_revert:testSafeBatchTransferFromReverts() (gas: 29158)
RestrictedNORI_transfers_revert:testSafeTransferFromReverts() (gas: 23292)
Checkout_buyingFromOneRemoval:test() (gas: 696819)
Checkout_buyingFromOneRemoval_byApproval:test() (gas: 667530)
Checkout_buyingFromTenRemovals:test() (gas: 1869981)
Checkout_buyingFromTenRemovals_singleSupplier:test() (gas: 1859021)
Checkout_buyingFromTenRemovals_singleSupplier_byApproval:test() (gas: 1857021)
Checkout_buyingFromTenRemovals_singleSupplier_withoutFee:test() (gas: 1644186)
Checkout_buyingFromTenRemovals_withoutFee:test() (gas: 1655147)
Checkout_buyingFromTenSuppliers:test() (gas: 2748266)
Checkout_buyingWithAlternateERC20:test() (gas: 523342)
Checkout_buyingWithAlternateERC20_floatingPointPriceMultiple:test() (gas: 523342)
Checkout_swapWithDifferentPermitSignerAndMsgSender:test() (gas: 697272)
Checkout_buyingFromOneRemoval:test() (gas: 697975)
Checkout_buyingFromOneRemoval_byApproval:test() (gas: 668708)
Checkout_buyingFromTenRemovals:test() (gas: 1871307)
Checkout_buyingFromTenRemovals_singleSupplier:test() (gas: 1860479)
Checkout_buyingFromTenRemovals_singleSupplier_byApproval:test() (gas: 1858479)
Checkout_buyingFromTenRemovals_singleSupplier_withoutFee:test() (gas: 1645820)
Checkout_buyingFromTenRemovals_withoutFee:test() (gas: 1656803)
Checkout_buyingFromTenSuppliers:test() (gas: 2749593)
Checkout_buyingWithAlternateERC20:test() (gas: 524613)
Checkout_buyingWithAlternateERC20_floatingPointPriceMultiple:test() (gas: 524613)
Checkout_swapWithDifferentPermitSignerAndMsgSender:test() (gas: 698428)
65 changes: 46 additions & 19 deletions contracts/Certificate.sol
Expand Up @@ -61,6 +61,28 @@ contract Certificate is
{
using UInt256ArrayLib for uint256[];

/**
* @notice The data that is passed to the `onERC1155BatchReceived` function data parameter when creating a new
* certificate.
* @dev This struct is used to pass data to the `onERC1155BatchReceived` function when creating a new certificate.
*
* @param isReplacement A bool used to differentiate between a token batch being received to create a new
* certificate and a token batch being received as a replacement for previously released removals.
* @param recipient The address is the address that will receive the new certificate.
* @param certificateAmount The amount of the certificate that will be minted.
* @param purchasingTokenAddress The address is the address of the token that was used to purchase the certificate.
* @param priceMultiple The number of purchasing tokens required to purchase one NRT.
* @param noriFeePercentage The fee percentage charged by Nori at the time of this purchase.
*/
struct CertificateData {
bool isReplacement;
address recipient;
uint256 certificateAmount;
address purchasingTokenAddress;
uint256 priceMultiple;
uint256 noriFeePercentage;
}

/**
* @notice Role conferring operator permissions.
* @dev Assigned to operators which are the only addresses which can transfer certificates outside
Expand Down Expand Up @@ -194,7 +216,8 @@ contract Certificate is
* - The certificate recipient and amount must be encoded in the `data` parameter.
* @param removalIds The array of ERC1155 Removal IDs received.
* @param removalAmounts The removal amounts per each removal ID.
* @param data The bytes that encode the certificate's recipient address and total amount.
* @param data The bytes that encode information about either the new certificate to be minted, or replacement
* removals being sent to replace released removals.
* @return The selector of the function.
*/
function onERC1155BatchReceived(
Expand All @@ -204,25 +227,29 @@ contract Certificate is
uint256[] calldata removalAmounts,
bytes calldata data
) external returns (bytes4) {
if (_msgSender() != address(_removal)) {
revert SenderNotRemovalContract();
require(
_msgSender() == address(_removal),
"Certificate: Sender not removal contract"
);
bool isReplacement = abi.decode(data, (bool));
if (isReplacement) {
uint256 replacementAmount = removalAmounts.sum();
_nrtDeficit -= replacementAmount;
} else {
CertificateData memory certificateData = abi.decode(
data,
(CertificateData)
);
_receiveRemovalBatch({
recipient: certificateData.recipient,
certificateAmount: certificateData.certificateAmount,
removalIds: removalIds,
removalAmounts: removalAmounts,
purchasingTokenAddress: certificateData.purchasingTokenAddress,
priceMultiple: certificateData.priceMultiple,
noriFeePercentage: certificateData.noriFeePercentage
});
}
(
address recipient,
uint256 certificateAmount,
address purchasingTokenAddress,
uint256 priceMultiple,
uint256 noriFeePercentage
) = abi.decode(data, (address, uint256, address, uint256, uint256));
_receiveRemovalBatch({
recipient: recipient,
certificateAmount: certificateAmount,
removalIds: removalIds,
removalAmounts: removalAmounts,
purchasingTokenAddress: purchasingTokenAddress,
priceMultiple: priceMultiple,
noriFeePercentage: noriFeePercentage
});
return this.onERC1155BatchReceived.selector;
}

Expand Down
12 changes: 12 additions & 0 deletions contracts/Errors.sol
Expand Up @@ -129,6 +129,18 @@ error InvalidHoldbackPercentage(uint8 holdbackPercentage);
* contracts.
*/
error RemovalAlreadySoldOrConsigned(uint256 tokenId);
/**
* @notice Thrown when replacement removal amounts do not sum to the specified total amount being replaced.
*/
error ReplacementAmountMismatch();
/**
* @notice Thrown when attempting to replace more removals than the size of the deficit.
*/
error ReplacementAmountExceedsNrtDeficit();
/**
* @notice Thrown when attempting to replace removals on behalf of a certificate that has not been minted yet.
*/
error CertificateNotYetMinted(uint256 tokenId);
/**
* @notice Thrown when an ERC20 token transfer fails.
*/
Expand Down

0 comments on commit 075b30a

Please sign in to comment.